diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000..5f64bd27f3 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +DATABASE_URL=postgres://backend:looks-backend@localhost:5432/ordinals \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2f753fd1fc..3b727e06cd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -3,10 +3,10 @@ name: CI on: push: branches: - - master + - main + tags: + - '*' pull_request: - branches: - - master defaults: run: @@ -14,105 +14,42 @@ defaults: env: RUSTFLAGS: --deny warnings - LANGUAGES: de fr es pt ru zh ja ko fil ar hi it jobs: - docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Install Rust Toolchain Components - uses: actions-rs/toolchain@v1 - with: - override: true - toolchain: stable - - - uses: Swatinem/rust-cache@v2 - - - uses: peaceiris/actions-mdbook@v1 - with: - mdbook-version: latest - - - name: Install mdbook-i18n-helpers - run: cargo install mdbook-i18n-helpers - - - name: Install mdbook-linkcheck - run: | - mkdir -p mdbook-linkcheck - cd mdbook-linkcheck - wget https://github.com/Michael-F-Bryan/mdbook-linkcheck/releases/latest/download/mdbook-linkcheck.x86_64-unknown-linux-gnu.zip - unzip mdbook-linkcheck.x86_64-unknown-linux-gnu.zip - chmod +x mdbook-linkcheck - pwd >> $GITHUB_PATH - - - name: Build docs - run: mdbook build docs -d build - - - name: Build all translations for docs - run: | - for lang in ${{ env.LANGUAGES }}; do - echo "::group::Building $lang translation" - MDBOOK_BOOK__LANGUAGE=$lang \ - mdbook build docs -d build/$lang - mv docs/build/$lang/html docs/build/html/$lang - echo "::endgroup::" - done - - - name: Deploy Pages - uses: peaceiris/actions-gh-pages@v3 - if: github.ref == 'refs/heads/master' - with: - github_token: ${{secrets.GITHUB_TOKEN}} - publish_branch: gh-pages - publish_dir: docs/build/html - lint: + timeout-minutes: 10 runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 - - name: Install Rust Toolchain Components - uses: actions-rs/toolchain@v1 - with: - components: clippy, rustfmt - override: true - toolchain: stable + - name: Install Rust Toolchain Components + uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@v2 - - name: Clippy - run: cargo clippy --all --all-targets + - name: Clippy + run: cargo clippy --all --all-targets - - name: Format - run: cargo fmt --all -- --check - - - name: Check for Forbidden Words - run: | - sudo apt-get install ripgrep - ./bin/forbid + - name: Format + run: cargo fmt --all -- --check test: - strategy: - matrix: - os: - - macos-latest - - ubuntu-latest - - windows-latest - - runs-on: ${{matrix.os}} - + timeout-minutes: 10 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - + name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 - - name: Install Rust Toolchain Components - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable + - name: Install Rust Toolchain Components + uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@v2 - - name: Test - run: cargo test --all + - name: Test + run: cargo test --all diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml new file mode 100644 index 0000000000..0c295e6e90 --- /dev/null +++ b/.github/workflows/docker.yaml @@ -0,0 +1,83 @@ +name: docker + +on: + push: + branches: + - main + tags: + - '*' + pull_request: + +jobs: + ordinals-indexer: + timeout-minutes: 60 + runs-on: [self-hosted] + steps: + - + name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - + name: Login to ECR + uses: docker/login-action@v3 + with: + registry: 726418723176.dkr.ecr.us-east-1.amazonaws.com + username: ${{ secrets.LR_PRD_AWS_ACCESS_KEY_ID }} + password: ${{ secrets.LR_PRD_AWS_SECRET_ACCESS_KEY }} + - + name: Build SHA tag + id: vars + run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + - + name: Create Docker builder context + run: docker context create builders + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver: docker-container + endpoint: builders + platforms: linux/arm64 + version: latest + - + name: Bump version and push tag + uses: LooksRare/github-tag-action@1.0.0 + if: github.event_name != 'pull_request' + id: version + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERBOSE: false + WITH_V: true + - + name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + flavor: | + latest=auto + prefix= + suffix= + # list of Docker images to use as base name for tags + images: | + 726418723176.dkr.ecr.us-east-1.amazonaws.com/ord + # generate Docker tags based on the following events/attributes + tags: | + latest + type=schedule + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{raw}},value=${{ steps.version.outputs.tag }} + type=semver,pattern=v{{major}}.{{minor}},value=${{ steps.version.outputs.tag }},enable={{is_default_branch}} + type=semver,pattern=v{{major}},value=${{ steps.version.outputs.tag }},enable={{is_default_branch}} + type=sha,prefix=,format=short + - + name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + file: Dockerfile + platforms: linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index 374d8f0b1b..0000000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,81 +0,0 @@ -name: Release - -on: - push: - tags: - - '*' - -defaults: - run: - shell: bash - -jobs: - release: - strategy: - fail-fast: false - matrix: - target: - - aarch64-apple-darwin - - x86_64-apple-darwin - - x86_64-pc-windows-msvc - - x86_64-unknown-linux-gnu - include: - - target: aarch64-apple-darwin - os: macos-latest - target_rustflags: '' - - target: x86_64-apple-darwin - os: macos-latest - target_rustflags: '' - - target: x86_64-pc-windows-msvc - os: windows-latest - target_rustflags: '' - - target: x86_64-unknown-linux-gnu - os: ubuntu-latest - target_rustflags: '' - - runs-on: ${{matrix.os}} - - steps: - - uses: actions/checkout@v2 - - - name: Install Rust Toolchain Components - uses: actions-rs/toolchain@v1 - with: - override: true - target: ${{ matrix.target }} - toolchain: stable - - - name: Install Linux Dependencies - if: ${{ matrix.os == 'ubuntu-latest' }} - run: | - sudo apt-get update - sudo apt-get install musl-tools libssl-dev pkg-config - - - name: Release Type - id: release-type - run: | - if [[ ${{ github.ref }} =~ ^refs/tags/[0-9]+[.][0-9]+[.][0-9]+$ ]]; then - echo ::set-output name=value::release - else - echo ::set-output name=value::prerelease - fi - - - name: Package - id: package - env: - TARGET: ${{ matrix.target }} - REF: ${{ github.ref }} - OS: ${{ matrix.os }} - TARGET_RUSTFLAGS: ${{ matrix.target_rustflags }} - run: ./bin/package - shell: bash - - - name: Publish Archive - uses: softprops/action-gh-release@v0.1.15 - if: ${{ startsWith(github.ref, 'refs/tags/') }} - with: - draft: false - files: ${{ steps.package.outputs.archive }} - prerelease: ${{ steps.release-type.outputs.value == 'prerelease' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 632b51ef39..cffa3eb598 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.env /*.redb /.idea/ /.vagrant @@ -12,3 +13,4 @@ /target /test-times.txt /tmp +/index-data diff --git a/.sqlx/query-03b5007410af0c37827309e7e2439bc7809996d1e479898c906cfe39f3350acf.json b/.sqlx/query-03b5007410af0c37827309e7e2439bc7809996d1e479898c906cfe39f3350acf.json new file mode 100644 index 0000000000..01db865bce --- /dev/null +++ b/.sqlx/query-03b5007410af0c37827309e7e2439bc7809996d1e479898c906cfe39f3350acf.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO event (type_id, block_height, inscription_id, location)\n SELECT $1, $2, $3, $4\n WHERE NOT EXISTS (\n SELECT 1 FROM event\n WHERE type_id = $1 AND block_height = $2 AND inscription_id = $3 AND location = $4\n )\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int2", + "Int4", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "03b5007410af0c37827309e7e2439bc7809996d1e479898c906cfe39f3350acf" +} diff --git a/.sqlx/query-37a57ddabacea1d4357e4c906c37caacffb6de8323126e4529e5bcf875e6cc41.json b/.sqlx/query-37a57ddabacea1d4357e4c906c37caacffb6de8323126e4529e5bcf875e6cc41.json new file mode 100644 index 0000000000..c6bce2eed5 --- /dev/null +++ b/.sqlx/query-37a57ddabacea1d4357e4c906c37caacffb6de8323126e4529e5bcf875e6cc41.json @@ -0,0 +1,46 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT type_id, block_height, inscription_id, location, old_location\n FROM event WHERE block_height = $1\n ORDER BY type_id ASC, id ASC\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "type_id", + "type_info": "Int2" + }, + { + "ordinal": 1, + "name": "block_height", + "type_info": "Int4" + }, + { + "ordinal": 2, + "name": "inscription_id", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "location", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "old_location", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [ + false, + false, + false, + true, + true + ] + }, + "hash": "37a57ddabacea1d4357e4c906c37caacffb6de8323126e4529e5bcf875e6cc41" +} diff --git a/.sqlx/query-3d3201c09d01f1fe7776db4abb54d8b24981488c6fe23aa9d6b06b28e7d21ddb.json b/.sqlx/query-3d3201c09d01f1fe7776db4abb54d8b24981488c6fe23aa9d6b06b28e7d21ddb.json new file mode 100644 index 0000000000..46e5f38b5d --- /dev/null +++ b/.sqlx/query-3d3201c09d01f1fe7776db4abb54d8b24981488c6fe23aa9d6b06b28e7d21ddb.json @@ -0,0 +1,36 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO inscription (\n genesis_id\n , number\n , content_type\n , content_length\n , metadata\n , genesis_block_height\n , genesis_block_time\n , sat_number\n , sat_rarity\n , sat_block_height\n , sat_block_time\n , fee\n , charms\n , children\n , parents\n ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)\n ON CONFLICT (genesis_id) DO UPDATE SET\n number = EXCLUDED.number\n , content_type = EXCLUDED.content_type\n , content_length = COALESCE(EXCLUDED.content_length, inscription.content_length)\n , metadata = COALESCE(EXCLUDED.metadata, inscription.metadata)\n , genesis_block_height = EXCLUDED.genesis_block_height\n , genesis_block_time = EXCLUDED.genesis_block_time\n , sat_number = COALESCE(EXCLUDED.sat_number, inscription.sat_number)\n , sat_rarity = COALESCE(EXCLUDED.sat_rarity, inscription.sat_rarity)\n , sat_block_height = COALESCE(EXCLUDED.sat_block_height, inscription.sat_block_height)\n , sat_block_time = COALESCE(EXCLUDED.sat_block_time, inscription.sat_block_time)\n , fee = EXCLUDED.fee\n , charms = EXCLUDED.charms\n , children = COALESCE(EXCLUDED.children, inscription.children)\n , parents = COALESCE(EXCLUDED.parents, inscription.parents)\n RETURNING id\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text", + "Int4", + "Varchar", + "Int4", + "Text", + "Int4", + "Int8", + "Int8", + "Int4", + "Int4", + "Int8", + "Int8", + "Int2", + "Text", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "3d3201c09d01f1fe7776db4abb54d8b24981488c6fe23aa9d6b06b28e7d21ddb" +} diff --git a/.sqlx/query-46ed37001552b072aa3e8bf3e37bb7f90cd11f74847a8cadb41326032b580b23.json b/.sqlx/query-46ed37001552b072aa3e8bf3e37bb7f90cd11f74847a8cadb41326032b580b23.json new file mode 100644 index 0000000000..4a27327a6a --- /dev/null +++ b/.sqlx/query-46ed37001552b072aa3e8bf3e37bb7f90cd11f74847a8cadb41326032b580b23.json @@ -0,0 +1,24 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO location (\n inscription_id\n , block_height\n , block_time\n , tx_id\n , to_address\n , cur_output\n , cur_offset\n , from_address\n , prev_output\n , prev_offset\n , value\n )\n SELECT\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11\n WHERE NOT EXISTS (\n SELECT 1 FROM location\n WHERE inscription_id = $1\n AND block_height = $2\n AND block_time = $3\n AND tx_id = $4\n AND to_address = $5\n AND cur_output = $6\n AND cur_offset = $7\n AND from_address = $8\n AND prev_output = $9\n AND prev_offset = $10\n AND value = $11\n )\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4", + "Int4", + "Int8", + "Text", + "Text", + "Text", + "Int8", + "Text", + "Text", + "Int8", + "Int8" + ] + }, + "nullable": [] + }, + "hash": "46ed37001552b072aa3e8bf3e37bb7f90cd11f74847a8cadb41326032b580b23" +} diff --git a/.sqlx/query-4fbd66a8177f12a3291abc8506287408dd4c93132eeb1f1af111079bbe0d8d49.json b/.sqlx/query-4fbd66a8177f12a3291abc8506287408dd4c93132eeb1f1af111079bbe0d8d49.json new file mode 100644 index 0000000000..81b601f334 --- /dev/null +++ b/.sqlx/query-4fbd66a8177f12a3291abc8506287408dd4c93132eeb1f1af111079bbe0d8d49.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id FROM inscription WHERE genesis_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "4fbd66a8177f12a3291abc8506287408dd4c93132eeb1f1af111079bbe0d8d49" +} diff --git a/.sqlx/query-7d3e8f006c4f66cff514bb7bcf5314d0d235da28ead0b89fdaa090cf2f16a151.json b/.sqlx/query-7d3e8f006c4f66cff514bb7bcf5314d0d235da28ead0b89fdaa090cf2f16a151.json new file mode 100644 index 0000000000..2e8656483f --- /dev/null +++ b/.sqlx/query-7d3e8f006c4f66cff514bb7bcf5314d0d235da28ead0b89fdaa090cf2f16a151.json @@ -0,0 +1,18 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO event (type_id, block_height, inscription_id, location, old_location)\n SELECT $1, $2, $3, $4, $5\n WHERE NOT EXISTS (\n SELECT 1 FROM event\n WHERE type_id = $1 AND block_height = $2 AND inscription_id = $3 AND location = $4 AND old_location = $5\n )\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int2", + "Int4", + "Text", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "7d3e8f006c4f66cff514bb7bcf5314d0d235da28ead0b89fdaa090cf2f16a151" +} diff --git a/Cargo.lock b/Cargo.lock index 1461e36bc0..7802a30431 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,30 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if 1.0.0", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -41,6 +65,60 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "amq-protocol" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051d4d77904272e9be7e292607378dc9900d15b8d314bfd3ed4b82fdd84f125" +dependencies = [ + "amq-protocol-tcp", + "amq-protocol-types", + "amq-protocol-uri", + "cookie-factory", + "nom", + "serde", +] + +[[package]] +name = "amq-protocol-tcp" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3d51dd36e67d757c9ba80a7b2a2a2a69254c1dbe4d8c631824ec7f5b69f60e" +dependencies = [ + "amq-protocol-uri", + "tcp-stream", + "tracing", +] + +[[package]] +name = "amq-protocol-types" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0acdd47054ced8b9bc89ee0dbb42ccc8028de48d8658b24de4c255a226c9bfec" +dependencies = [ + "cookie-factory", + "nom", + "serde", + "serde_json", +] + +[[package]] +name = "amq-protocol-uri" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17881b7575dab3e71403f28a3e50b71f0d1bd026829abca3c48664522ce0df0" +dependencies = [ + "amq-protocol-types", + "percent-encoding", + "url", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -141,8 +219,24 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30ff05a702273012438132f449575dbc804e27b2f3cbe3069aa237d26c98fa33" dependencies = [ - "asn1-rs-derive", - "asn1-rs-impl", + "asn1-rs-derive 0.1.0", + "asn1-rs-impl 0.1.0", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ad1373757efa0f70ec53939aabc7152e1591cb485208052993070ac8d2429d" +dependencies = [ + "asn1-rs-derive 0.5.0", + "asn1-rs-impl 0.2.0", "displaydoc", "nom", "num-traits", @@ -160,7 +254,19 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "synstructure", + "synstructure 0.12.6", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", + "synstructure 0.13.1", ] [[package]] @@ -174,6 +280,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "async-channel" version = "2.2.1" @@ -201,6 +318,45 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-executor" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b10202063978b3351199d68f8b22c4e47e4b1b822f8d43fd862d5ea8c006b29a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.1.0", + "futures-lite 2.3.0", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel", + "async-executor", + "async-io 2.3.2", + "async-lock 3.3.0", + "blocking", + "futures-lite 2.3.0", + "once_cell", +] + +[[package]] +name = "async-global-executor-trait" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33dd14c5a15affd2abcff50d84efd4009ada28a860f01c14f9d654f3e81b3f75" +dependencies = [ + "async-global-executor", + "async-trait", + "executor-trait", +] + [[package]] name = "async-http-codec" version = "0.8.0" @@ -227,13 +383,32 @@ dependencies = [ "futures-lite 1.13.0", "log", "parking", - "polling", + "polling 2.8.0", "rustix 0.37.27", "slab", "socket2 0.4.10", "waker-fn", ] +[[package]] +name = "async-io" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" +dependencies = [ + "async-lock 3.3.0", + "cfg-if 1.0.0", + "concurrent-queue", + "futures-io", + "futures-lite 2.3.0", + "parking", + "polling 3.7.0", + "rustix 0.38.34", + "slab", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "async-lock" version = "2.8.0" @@ -260,11 +435,23 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0434b1ed18ce1cf5769b8ac540e33f01fa9471058b5e89da9e06f3c882a8c12f" dependencies = [ - "async-io", + "async-io 1.13.0", "blocking", "futures-lite 1.13.0", ] +[[package]] +name = "async-reactor-trait" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6012d170ad00de56c9ee354aef2e358359deb1ec504254e0e5a3774771de0e" +dependencies = [ + "async-io 1.13.0", + "async-trait", + "futures-core", + "reactor-trait", +] + [[package]] name = "async-task" version = "4.7.1" @@ -313,7 +500,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70b425fc1a1d5a3988aa2e1c94c4823740416406f3416322c9dcb542fb997d37" dependencies = [ - "async-io", + "async-io 1.13.0", "base64 0.13.1", "futures", "futures-lite 1.13.0", @@ -327,6 +514,15 @@ dependencies = [ "utf-8", ] +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "atom_syndication" version = "0.12.2" @@ -368,6 +564,33 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +[[package]] +name = "aws-lc-rs" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5509d663b2c00ee421bda8d6a24d6c42e15970957de1701b8df9f6fbe5707df1" +dependencies = [ + "aws-lc-sys", + "mirai-annotations", + "paste", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5d317212c2a78d86ba6622e969413c38847b62f48111f8b763af3dac2f9840" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libc", + "paste", +] + [[package]] name = "axum" version = "0.6.20" @@ -431,7 +654,7 @@ dependencies = [ "hyper", "pin-project-lite", "rustls 0.21.12", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "tokio", "tokio-rustls", "tower-service", @@ -470,6 +693,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bech32" version = "0.9.1" @@ -482,6 +711,29 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" +[[package]] +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags 2.5.0", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.60", + "which", +] + [[package]] name = "bip39" version = "2.0.0" @@ -540,6 +792,9 @@ name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +dependencies = [ + "serde", +] [[package]] name = "block-buffer" @@ -550,6 +805,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "blocking" version = "1.6.0" @@ -633,11 +897,34 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] [[package]] name = "cfg-if" @@ -699,6 +986,27 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clang-sys" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.4" @@ -739,6 +1047,27 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + +[[package]] +name = "cms" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b77c319abfd5219629c45c34c89ba945ed3c5e49fcde9d16b6c3885f118a730" +dependencies = [ + "const-oid", + "der", + "spki", + "x509-cert", +] + [[package]] name = "colorchoice" version = "1.0.1" @@ -764,6 +1093,26 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "config" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" +dependencies = [ + "async-trait", + "convert_case 0.6.0", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml 0.8.12", + "yaml-rust", +] + [[package]] name = "console" version = "0.15.8" @@ -778,39 +1127,95 @@ dependencies = [ ] [[package]] -name = "convert_case" -version = "0.4.0" +name = "const-oid" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] -name = "core-foundation" -version = "0.9.4" +name = "const-random" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" dependencies = [ - "core-foundation-sys", - "libc", + "const-random-macro", ] [[package]] -name = "core-foundation-sys" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" - -[[package]] -name = "cpufeatures" -version = "0.2.12" +name = "const-random-macro" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "libc", + "getrandom", + "once_cell", + "tiny-keccak", ] [[package]] -name = "crc32fast" -version = "1.4.0" +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie-factory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ @@ -829,7 +1234,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools", + "itertools 0.10.5", "num-traits", "once_cell", "oorandom", @@ -850,7 +1255,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] @@ -872,6 +1277,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.19" @@ -980,13 +1394,40 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "der_derive", + "flagset", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "der-parser" version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe398ac75057914d7d07307bf67dc7f3f574a26783b4fc7805a20ffa9f506e82" dependencies = [ - "asn1-rs", + "asn1-rs 0.3.1", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs 0.6.1", "displaydoc", "nom", "num-bigint", @@ -994,6 +1435,17 @@ dependencies = [ "rusticata-macros", ] +[[package]] +name = "der_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fe87ce4529967e0ba1dcf8450bab64d97dfd5010a6256187ffe2e43e6f0e049" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "deranged" version = "0.3.11" @@ -1041,13 +1493,22 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "convert_case", + "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version", "syn 1.0.109", ] +[[package]] +name = "des" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +dependencies = [ + "cipher", +] + [[package]] name = "diff" version = "0.1.13" @@ -1061,7 +1522,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -1105,11 +1568,41 @@ dependencies = [ "syn 2.0.60", ] +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + [[package]] name = "either" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +dependencies = [ + "serde", +] [[package]] name = "encode_unicode" @@ -1165,6 +1658,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if 1.0.0", + "home", + "windows-sys 0.48.0", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -1219,6 +1723,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ebc5a6d89e3c90b84e8f33c8737933dda8f1c106b5415900b38b9d433841478" +[[package]] +name = "executor-trait" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a1052dd43212a7777ec6a69b117da52f5e52f07aec47d00c1a2b33b85d06b08" +dependencies = [ + "async-trait", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -1234,6 +1747,18 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" + +[[package]] +name = "flagset" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb3aa5e95cf9aabc17f060cfa0ced7b83f042390760ca53bf09df9968acaa1" + [[package]] name = "flate2" version = "1.0.30" @@ -1244,6 +1769,17 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1274,6 +1810,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.30" @@ -1316,6 +1858,17 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot 0.12.2", +] + [[package]] name = "futures-io" version = "0.3.30" @@ -1343,7 +1896,10 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ + "fastrand 2.1.0", "futures-core", + "futures-io", + "parking", "pin-project-lite", ] @@ -1444,6 +2000,12 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "globset" version = "0.4.14" @@ -1517,17 +2079,39 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" + [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "heck" @@ -1553,6 +2137,33 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "html-escaper" version = "0.2.0" @@ -1727,6 +2338,16 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -1779,12 +2400,30 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.69" @@ -1794,6 +2433,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + [[package]] name = "jsonrpc" version = "0.14.1" @@ -1844,7 +2494,7 @@ dependencies = [ "jsonrpc-server-utils", "log", "net2", - "parking_lot", + "parking_lot 0.11.2", "unicase", ] @@ -1875,11 +2525,42 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "lapin" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fae02c316a8a5922ce7518afa6b6c00e9a099f8e59587567e3331efdd11b8ceb" +dependencies = [ + "amq-protocol", + "async-global-executor-trait", + "async-reactor-trait", + "async-trait", + "executor-trait", + "flume", + "futures-core", + "futures-io", + "parking_lot 0.12.2", + "pinky-swear", + "reactor-trait", + "serde", + "tracing", + "waker-fn", +] + [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" @@ -1887,6 +2568,22 @@ version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if 1.0.0", + "windows-targets 0.52.5", +] + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "libredox" version = "0.1.3" @@ -1898,13 +2595,30 @@ dependencies = [ ] [[package]] -name = "linux-raw-sys" -version = "0.3.8" +name = "libsqlite3-sys" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] [[package]] -name = "linux-raw-sys" +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" @@ -1937,6 +2651,16 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if 1.0.0", + "digest", +] + [[package]] name = "memchr" version = "2.7.2" @@ -1995,6 +2719,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mirai-annotations" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" + [[package]] name = "mockcore" version = "0.0.1" @@ -2119,6 +2849,23 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec 1.13.2", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -2134,6 +2881,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -2154,6 +2912,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -2187,7 +2946,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38e20717fa0541f39bd146692035c37bedfa532b3e5071b35761082407546b2a" dependencies = [ - "asn1-rs", + "asn1-rs 0.3.1", +] + +[[package]] +name = "oid-registry" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c958dd45046245b9c3c2547369bb634eb461670b2e7e0de552905801a648d1d" +dependencies = [ + "asn1-rs 0.6.1", ] [[package]] @@ -2270,6 +3038,7 @@ dependencies = [ "ciborium", "clap", "colored", + "config", "criterion", "ctrlc", "dirs", @@ -2282,6 +3051,7 @@ dependencies = [ "humantime", "hyper", "indicatif", + "lapin", "lazy_static", "log", "mime", @@ -2294,6 +3064,7 @@ dependencies = [ "ordinals", "pretty_assertions", "pulldown-cmark", + "rand", "redb", "regex", "reqwest", @@ -2307,6 +3078,7 @@ dependencies = [ "serde_with", "serde_yaml", "sha3", + "sqlx", "sysinfo", "tempfile", "tokio", @@ -2343,6 +3115,16 @@ dependencies = [ "serde_json", ] +[[package]] +name = "ordered-multimap" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e" +dependencies = [ + "dlv-list", + "hashbrown 0.13.2", +] + [[package]] name = "ordinals" version = "0.0.8" @@ -2356,6 +3138,28 @@ dependencies = [ "thiserror", ] +[[package]] +name = "p12-keystore" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbd7792ed56836118732faffa19b8c2bb20d5f3ff8b403002cd817d6c4ffc96c" +dependencies = [ + "cbc", + "cms", + "der", + "des", + "hex", + "hmac", + "pkcs12", + "pkcs5", + "rand", + "rc2", + "sha1", + "sha2", + "thiserror", + "x509-parser 0.16.0", +] + [[package]] name = "parking" version = "2.2.0" @@ -2370,7 +3174,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.10", ] [[package]] @@ -2382,11 +3196,46 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec 1.13.2", "winapi", ] +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall 0.5.1", + "smallvec 1.13.2", + "windows-targets 0.52.5", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "pem" version = "1.1.1" @@ -2396,12 +3245,66 @@ dependencies = [ "base64 0.13.1", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pest" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "pest_meta" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "pin-project" version = "1.1.5" @@ -2434,6 +3337,18 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pinky-swear" +version = "6.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cfae3ead413ca051a681152bd266438d3bfa301c9bdf836939a14c721bb2a21" +dependencies = [ + "doc-comment", + "flume", + "parking_lot 0.12.2", + "tracing", +] + [[package]] name = "piper" version = "0.2.1" @@ -2445,6 +3360,57 @@ dependencies = [ "futures-io", ] +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs12" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "695b3df3d3cc1015f12d70235e35b6b79befc5fa7a9b95b951eab1dd07c9efc2" +dependencies = [ + "cms", + "const-oid", + "der", + "digest", + "spki", + "x509-cert", + "zeroize", +] + +[[package]] +name = "pkcs5" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" +dependencies = [ + "aes", + "cbc", + "der", + "pbkdf2", + "scrypt", + "sha2", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.30" @@ -2495,6 +3461,21 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "polling" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" +dependencies = [ + "cfg-if 1.0.0", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 0.38.34", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "portable-atomic" version = "1.6.0" @@ -2523,13 +3504,23 @@ dependencies = [ "yansi", ] +[[package]] +name = "prettyplease" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac2cf0f2e4f42b49f5ffd07dae8d746508ef7526c13940e5f524012ae6c6550" +dependencies = [ + "proc-macro2", + "syn 2.0.60", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" dependencies = [ - "toml", + "toml 0.5.11", ] [[package]] @@ -2629,6 +3620,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rc2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62c64daa8e9438b84aaae55010a93f396f8e60e3911590fcba770d04643fc1dd" +dependencies = [ + "cipher", +] + [[package]] name = "rcgen" version = "0.10.0" @@ -2641,6 +3641,17 @@ dependencies = [ "yasna", ] +[[package]] +name = "reactor-trait" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "438a4293e4d097556730f4711998189416232f009c137389e0f961d2bc0ddc58" +dependencies = [ + "async-trait", + "futures-core", + "futures-io", +] + [[package]] name = "redb" version = "2.1.0" @@ -2659,6 +3670,24 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "redox_users" version = "0.4.5" @@ -2724,7 +3753,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", @@ -2771,6 +3800,38 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64 0.21.7", + "bitflags 2.5.0", + "serde", + "serde_derive", +] + +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rss" version = "2.0.7" @@ -2817,12 +3878,28 @@ dependencies = [ "walkdir", ] +[[package]] +name = "rust-ini" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091" +dependencies = [ + "cfg-if 1.0.0", + "ordered-multimap", +] + [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[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" @@ -2894,13 +3971,28 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afabcee0551bd1aa3e18e5adbf2c0544722014b899adb31bd186ec638d3da97e" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.102.3", + "subtle", + "zeroize", +] + [[package]] name = "rustls-acme" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0c132c57c13d708da4320d515289db08754f0eb539b9481ea8e35476a01d290" dependencies = [ - "async-io", + "async-io 1.13.0", "async-trait", "async-web-client", "axum-server", @@ -2920,7 +4012,33 @@ dependencies = [ "tokio", "tokio-util 0.7.10", "webpki-roots", - "x509-parser", + "x509-parser 0.13.2", +] + +[[package]] +name = "rustls-connector" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727a826801254b6cfcd2508a0508c01b7c1bca21d3673e84d86da084781b83d5" +dependencies = [ + "log", + "rustls 0.23.5", + "rustls-native-certs", + "rustls-pki-types", + "rustls-webpki 0.102.3", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.1.2", + "rustls-pki-types", + "schannel", + "security-framework", ] [[package]] @@ -2932,6 +4050,16 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.5.0" @@ -2954,6 +4082,7 @@ version = "0.102.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" dependencies = [ + "aws-lc-rs", "ring 0.17.8", "rustls-pki-types", "untrusted 0.9.0", @@ -2971,6 +4100,15 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -2995,6 +4133,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2", + "salsa20", + "sha2", +] + [[package]] name = "sct" version = "0.7.1" @@ -3108,6 +4257,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3163,6 +4321,17 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.8" @@ -3178,67 +4347,324 @@ dependencies = [ name = "sha3" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" +dependencies = [ + "maybe-uninit", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" +dependencies = [ + "itertools 0.12.1", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" dependencies = [ - "digest", - "keccak", + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", ] [[package]] -name = "slab" -version = "0.4.9" +name = "sqlx-core" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" dependencies = [ - "autocfg", + "ahash", + "atoi", + "byteorder", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener 2.5.3", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap 2.2.6", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "sha2", + "smallvec 1.13.2", + "sqlformat", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", + "webpki-roots", ] [[package]] -name = "smallvec" -version = "0.6.14" +name = "sqlx-macros" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" dependencies = [ - "maybe-uninit", + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", ] [[package]] -name = "smallvec" -version = "1.13.2" +name = "sqlx-macros-core" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" +dependencies = [ + "dotenvy", + "either", + "heck 0.4.1", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio", + "url", +] [[package]] -name = "socket2" -version = "0.4.10" +name = "sqlx-mysql" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" dependencies = [ - "libc", - "winapi", + "atoi", + "base64 0.21.7", + "bitflags 2.5.0", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec 1.13.2", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", ] [[package]] -name = "socket2" -version = "0.5.7" +name = "sqlx-postgres" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" dependencies = [ - "libc", - "windows-sys 0.52.0", + "atoi", + "base64 0.21.7", + "bitflags 2.5.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha2", + "smallvec 1.13.2", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", ] [[package]] -name = "spin" -version = "0.5.2" +name = "sqlx-sqlite" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "tracing", + "url", + "urlencoding", +] [[package]] -name = "spin" -version = "0.9.8" +name = "stringprep" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +dependencies = [ + "finl_unicode", + "unicode-bidi", + "unicode-normalization", +] [[package]] name = "strsim" @@ -3320,6 +4746,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "sysinfo" version = "0.30.11" @@ -3356,6 +4793,19 @@ dependencies = [ "libc", ] +[[package]] +name = "tcp-stream" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "495b0abdce3dc1f8fd27240651c9e68890c14e9d9c61527b1ce44d8a5a7bd3d5" +dependencies = [ + "cfg-if 1.0.0", + "native-tls", + "p12-keystore", + "rustls-connector", + "rustls-pemfile 2.1.2", +] + [[package]] name = "tempfile" version = "3.10.1" @@ -3370,18 +4820,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" dependencies = [ "proc-macro2", "quote", @@ -3419,6 +4869,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -3456,6 +4915,7 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", + "signal-hook-registry", "socket2 0.5.7", "tokio-macros", "windows-sys 0.48.0", @@ -3541,6 +5001,40 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" +dependencies = [ + "indexmap 2.2.6", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower" version = "0.4.13" @@ -3600,9 +5094,21 @@ checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "tracing-core" version = "0.1.32" @@ -3624,6 +5130,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "unicase" version = "2.7.0" @@ -3654,6 +5166,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + [[package]] name = "unicode-width" version = "0.1.12" @@ -3666,6 +5184,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "unindent" version = "0.2.3" @@ -3769,6 +5293,12 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.92" @@ -3851,6 +5381,28 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.34", +] + +[[package]] +name = "whoami" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +dependencies = [ + "redox_syscall 0.4.1", + "wasite", +] + [[package]] name = "winapi" version = "0.3.9" @@ -4040,6 +5592,15 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +[[package]] +name = "winnow" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" @@ -4050,24 +5611,61 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "x509-cert" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" +dependencies = [ + "const-oid", + "der", + "spki", +] + [[package]] name = "x509-parser" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb9bace5b5589ffead1afb76e43e34cff39cd0f3ce7e170ae0c29e53b88eb1c" dependencies = [ - "asn1-rs", + "asn1-rs 0.3.1", "base64 0.13.1", "data-encoding", - "der-parser", + "der-parser 7.0.0", + "lazy_static", + "nom", + "oid-registry 0.4.0", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs 0.6.1", + "data-encoding", + "der-parser 9.0.0", "lazy_static", "nom", - "oid-registry", + "oid-registry 0.7.0", "rusticata-macros", "thiserror", "time", ] +[[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" @@ -4083,6 +5681,26 @@ dependencies = [ "time", ] +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "zeroize" version = "1.7.0" diff --git a/Cargo.toml b/Cargo.toml index 696b6c65b2..a87ddf0041 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ http = "0.2.6" humantime = "2.1.0" hyper = { version = "0.14.24", features = ["client", "http2"] } indicatif = "0.17.1" +lapin = { version = "2.3.3", features = ["native-tls"] } lazy_static = "1.4.0" log = "0.4.14" mime = "0.3.16" @@ -63,13 +64,16 @@ serde_json = { version = "1.0.81", features = ["preserve_order"] } serde_with = "3.7.0" serde_yaml = "0.9.17" sha3 = "0.10.8" +sqlx = { version = "0.7", features = ["runtime-tokio", "tls-rustls", "postgres"] } sysinfo = "0.30.3" tempfile = "3.2.0" -tokio = { version = "1.17.0", features = ["rt-multi-thread"] } +tokio = { version = "1.17.0", features = ["rt-multi-thread", "signal"] } tokio-stream = "0.1.9" tokio-util = {version = "0.7.3", features = ["compat"] } tower-http = { version = "0.4.0", features = ["auth", "compression-br", "compression-gzip", "cors", "set-header"] } urlencoding = "2.1.3" +rand = "0.8.5" +config = "0.14.0" [dev-dependencies] criterion = "0.5.1" diff --git a/README.md b/README.md index 3f4465ef7a..49483ab853 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,40 @@ currently prioritized issues. Join [the Discord server](https://discord.gg/87cjuz4FYg) to chat with fellow ordinal degenerates. +LooksRare Ordinals +------------------ + +**Database & Sqlx Setup** + +1. Setup the local `ordinals` database +`$ psql -h localhost -d postgres -q -f db/config.sql` + +2. Run the database migrations. +`$ cargo sqlx migrate run --database-url $DATABASE_URL --source db/migrations/` + +3. Clone `.env.example` into `.env` for compile time checked sql queries + +4. After changing queries you can run +`$ cargo sqlx prepare --database-url $DATABASE_URL` + +Note `$DATABASE_URL` is likely to be _"postgres://backend:looks-backend@localhost:5432/ordinals"_. + +**Running via command line:** + +To run ord event server (signet): +``` +RUST_LOG=info cargo run --package ord --bin ord -- --bitcoin-rpc-url "http://localhost:38332" --bitcoin-rpc-username user --bitcoin-rpc-password password --commit-interval 10 --rabbitmq-url amqp://indexer:s3cr3t@localhost --rabbitmq-exchange ord-tx --chain signet --data-dir ./index-data event-server --http-port 8080 +``` + +To run ord event-consumer "inscription" indexer: +``` +RUST_LOG=info cargo run --package ord --bin ord -- --rabbitmq-url amqp://indexer:s3cr3t@localhost event-consumer --inscriptions-queue btc-inscription-q --database-url postgresql://backend:looks-backend@localhost:5432/ordinals +``` + +``` +RUST_LOG=info cargo run --package ord --bin ord -- --rabbitmq-url amqp://indexer:s3cr3t@localhost block-consumer --blocks-queue btc-blocks-q --database-url postgresql://backend:looks-backend@localhost:5432/ordinals --ord-api-url http://localhost:8080 +``` + Donate ------ @@ -42,48 +76,6 @@ as well as hosting costs for [ordinals.com](https://ordinals.com). Thank you for donating! -Wallet ------- - -`ord` relies on Bitcoin Core for private key management and transaction signing. -This has a number of implications that you must understand in order to use -`ord` wallet commands safely: - -- Bitcoin Core is not aware of inscriptions and does not perform sat - control. Using `bitcoin-cli` commands and RPC calls with `ord` wallets may - lead to loss of inscriptions. - -- `ord wallet` commands automatically load the `ord` wallet given by the - `--name` option, which defaults to 'ord'. Keep in mind that after running - an `ord wallet` command, an `ord` wallet may be loaded. - -- Because `ord` has access to your Bitcoin Core wallets, `ord` should not be - used with wallets that contain a material amount of funds. Keep ordinal and - cardinal wallets segregated. - -### Pre-alpha wallet migration - -Alpha `ord` wallets are not compatible with wallets created by previous -versions of `ord`. To migrate, use `ord wallet send` from the old wallet to -send sats and inscriptions to addresses generated by the new wallet with `ord -wallet receive`. - -Installation ------------- - -`ord` is written in Rust and can be built from -[source](https://github.com/ordinals/ord). Pre-built binaries are available on the -[releases page](https://github.com/ordinals/ord/releases). - -You can install the latest pre-built binary from the command line with: - -```sh -curl --proto '=https' --tlsv1.2 -fsLS https://ordinals.com/install.sh | bash -s -``` - -Once `ord` is installed, you should be able to run `ord --version` on the -command line. - Building -------- @@ -140,23 +132,6 @@ A Docker image can be built with: docker build -t ordinals/ord . ``` -### Homebrew - -`ord` is available in [Homebrew](https://brew.sh/): - -``` -brew install ord -``` - -### Debian Package - -To build a `.deb` package: - -``` -cargo install cargo-deb -cargo deb -``` - Contributing ------------ @@ -265,75 +240,3 @@ the server and show `info`-level log messages and above: ``` $ RUST_LOG=info cargo run server ``` - -New Releases ------------- - -Release commit messages use the following template: - -``` -Release x.y.z - -- Bump version: x.y.z → x.y.z -- Update changelog -- Update changelog contributor credits -- Update dependencies -``` - -Translations ------------- - -To translate [the docs](https://docs.ordinals.com) we use -[mdBook i18n helper](https://github.com/google/mdbook-i18n-helpers). - -See -[mdbook-i18n-helpers usage guide](https://github.com/google/mdbook-i18n-helpers/blob/main/i18n-helpers/USAGE.md) -for help. - -Adding a new translations is somewhat involved, so feel free to start -translation and open a pull request, even if your translation is incomplete. - -Take a look at -[this commit](https://github.com/ordinals/ord/commit/329f31bf6dac207dad001507dd6f18c87fdef355) -for an example of adding a new translation. A maintainer will help you integrate it -into our build system. - -To start a new translation: - -1. Install `mdbook`, `mdbook-i18n-helpers`, and `mdbook-linkcheck`: - - ``` - cargo install mdbook mdbook-i18n-helpers mdbook-linkcheck - ``` - -2. Generate a new `pot` file named `messages.pot`: - - ``` - MDBOOK_OUTPUT='{"xgettext": {"pot-file": "messages.pot"}}' - mdbook build -d po - ``` - -3. Run `msgmerge` on `XX.po` where `XX` is the two-letter - [ISO-639](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) code for - the language you are translating into. This will update the `po` file with - the text of the most recent English version: - - ``` - msgmerge --update po/XX.po po/messages.pot - ``` - -4. Untranslated sections are marked with `#, fuzzy` in `XX.po`. Edit the - `msgstr` string with the translated text. - -5. Execute the `mdbook` command to rebuild the docs. For Chinese, whose - two-letter ISO-639 code is `zh`: - - ``` - mdbook build docs -d build - MDBOOK_BOOK__LANGUAGE=zh mdbook build docs -d build/zh - mv docs/build/zh/html docs/build/html/zh - python3 -m http.server --directory docs/build/html --bind 127.0.0.1 8080 - ``` - -6. If everything looks good, commit `XX.po` and open a pull request on GitHub. - Other changed files should be omitted from the pull request. diff --git a/db/config.sql b/db/config.sql new file mode 100644 index 0000000000..e7908988e6 --- /dev/null +++ b/db/config.sql @@ -0,0 +1,16 @@ +/* Create database */ +DROP DATABASE IF EXISTS ordinals; + +DO $$ +BEGIN + IF NOT EXISTS(SELECT FROM pg_catalog.pg_roles WHERE rolname = 'backend') THEN + -- Create role with password and grant the appropriate permissions to that role +CREATE ROLE backend WITH ENCRYPTED PASSWORD 'looks-backend' LOGIN; +ALTER ROLE backend CREATEDB; +END IF; +END +$$; + +CREATE DATABASE ordinals WITH OWNER backend; + +GRANT ALL PRIVILEGES ON DATABASE ordinals to backend; \ No newline at end of file diff --git a/db/migrations/20240431000000_ordinals.sql b/db/migrations/20240431000000_ordinals.sql new file mode 100644 index 0000000000..57779b10ff --- /dev/null +++ b/db/migrations/20240431000000_ordinals.sql @@ -0,0 +1,48 @@ +CREATE TABLE event ( + id SERIAL PRIMARY KEY + , type_id SMALLINT NOT NULL --1=InscriptionCreated; 2=InscriptionTransferred + , block_height INTEGER NOT NULL + , inscription_id TEXT NOT NULL + , location TEXT -- Will hold either 'location' or 'new_location' based on type + , old_location TEXT -- Only used for InscriptionTransferred + , created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_events_block_height ON event (block_height); +CREATE INDEX idx_events_inscription_id ON event (inscription_id); + +CREATE TABLE inscription ( + id SERIAL PRIMARY KEY + , genesis_id TEXT NOT NULL + , number INTEGER NOT NULL + , content_type VARCHAR + , content_length INTEGER + , metadata TEXT + , genesis_block_height INTEGER NOT NULL + , genesis_block_time BIGINT NOT NULL + , sat_number BIGINT + , sat_rarity INTEGER + , sat_block_height INTEGER + , sat_block_time BIGINT + , fee BIGINT NOT NULL + , charms SMALLINT NOT NULL + , children TEXT + , parents TEXT +); + +CREATE UNIQUE INDEX idx_unique_inscription_genesis_id ON inscription (genesis_id); + +CREATE TABLE location ( + id SERIAL PRIMARY KEY + , inscription_id INTEGER REFERENCES inscription (id) + , block_height INTEGER NOT NULL + , block_time BIGINT NOT NULL + , tx_id TEXT NOT NULL + , to_address TEXT NOT NULL + , cur_output TEXT NOT NULL + , cur_offset BIGINT NOT NULL + , from_address TEXT + , prev_output TEXT + , prev_offset BIGINT + , value BIGINT +); diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000000..1f9ace4ab1 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,40 @@ +version: '3.8' +services: + + db: + image: "postgres:15.3" + volumes: + - ./db/volume:/var/lib/postgresql/data + ports: + - "55432:5432" + environment: + POSTGRES_USER: backend + POSTGRES_PASSWORD: looks-backend + POSTGRES_DB: ordinals + POSTGRES_PORT: 5432 + + db-setup: + image: postgres:15.3 + depends_on: + - db + command: > + bash -c ' + until pg_isready -h db -U $$POSTGRES_USER; do + echo "Waiting for PostgreSQL to start..."; + sleep 1; + done; + echo "Applying config.sql to set up the database..."; + psql -h db -U $$POSTGRES_USER -d postgres -q -f /db/config.sql; + echo "Applying migrations..."; + for file in /db/migrations/*/*.sql; do + echo "Applying $$file"; + psql -h db -U $$POSTGRES_USER -d $$POSTGRES_DB -q -f $$file; + done; + ' + environment: + PGOPTIONS: '--client-min-messages=error' + POSTGRES_USER: backend + POSTGRES_DB: ordinals + PGPASSWORD: looks-backend + volumes: + - "./db:/db" diff --git a/rmqconfig.json b/rmqconfig.json new file mode 100644 index 0000000000..de454be89a --- /dev/null +++ b/rmqconfig.json @@ -0,0 +1,55 @@ +{ + "queues": [ + { + "name": "btc-inscription-q", + "vhost": "/", + "durable": true, + "auto_delete": false, + "arguments": {} + }, + { + "name": "btc-blocks-q", + "vhost": "/", + "durable": true, + "auto_delete": false, + "arguments": {} + } + ], + "exchanges": [ + { + "name": "ord-tx", + "vhost": "/", + "type": "topic", + "durable": true, + "auto_delete": false, + "internal": false, + "arguments": {} + } + ], + "bindings": [ + { + "source": "ord-tx", + "vhost": "/", + "destination": "btc-blocks-q", + "destination_type": "queue", + "routing_key": "BlockCommitted", + "arguments": {} + }, + { + "source": "ord-tx", + "vhost": "/", + "destination": "btc-inscription-q", + "destination_type": "queue", + "routing_key": "InscriptionCreated", + "arguments": {} + }, + { + "source": "ord-tx", + "vhost": "/", + "destination": "btc-inscription-q", + "destination_type": "queue", + "routing_key": "InscriptionTransferred", + "arguments": {} + } + ] +} diff --git a/src/api.rs b/src/api.rs index 0cd03d7e1a..89ad1f827d 100644 --- a/src/api.rs +++ b/src/api.rs @@ -76,6 +76,33 @@ pub struct Children { pub page: usize, } +#[derive(Debug, Serialize, Deserialize)] +pub struct InscriptionDetails { + pub id: InscriptionId, + pub number: i32, + pub owner: Option, + pub content_type: Option, + pub content_length: Option, + pub metadata: Option>, + + pub genesis_block_height: u32, + pub genesis_block_time: i64, + + pub sat_number: Option, + pub sat_name: Option, + pub sat_rarity: Option, + pub sat_block_height: Option, + pub sat_block_time: Option, + + pub satpoint: SatPoint, + pub value: Option, + pub fee: u64, + pub charms: u16, + + pub children: Vec, + pub parents: Vec, +} + #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] pub struct Inscription { pub address: Option, diff --git a/src/index.rs b/src/index.rs index b3e26e02d0..ca96397d49 100644 --- a/src/index.rs +++ b/src/index.rs @@ -6316,25 +6316,29 @@ mod tests { txid: create_txid, index: 0, }; - let create_event = event_receiver.blocking_recv().unwrap(); let expected_charms = if context.index.index_sats { 513 } else { 0 }; - assert_eq!( - create_event, - Event::InscriptionCreated { - inscription_id, - location: Some(SatPoint { - outpoint: OutPoint { - txid: create_txid, - vout: 0 - }, - offset: 0 - }), - sequence_number: 0, - block_height: 2, - charms: expected_charms, - parent_inscription_ids: Vec::new(), + + let expected_event = Event::InscriptionCreated { + inscription_id, + location: Some(SatPoint { + outpoint: OutPoint { + txid: create_txid, + vout: 0, + }, + offset: 0, + }), + sequence_number: 0, + block_height: 2, + charms: expected_charms, + parent_inscription_ids: Vec::new(), + }; + loop { + let received_event = event_receiver.blocking_recv().unwrap(); + if received_event == expected_event { + assert_eq!(received_event, expected_event); + break; } - ); + } // Transfer inscription let transfer_txid = context.core.broadcast_tx(TransactionTemplate { @@ -6346,29 +6350,32 @@ mod tests { context.mine_blocks(1); - let transfer_event = event_receiver.blocking_recv().unwrap(); - assert_eq!( - transfer_event, - Event::InscriptionTransferred { - block_height: 3, - inscription_id, - new_location: SatPoint { - outpoint: OutPoint { - txid: transfer_txid, - vout: 0 - }, - offset: 0 + let expected_event = Event::InscriptionTransferred { + block_height: 3, + inscription_id, + new_location: SatPoint { + outpoint: OutPoint { + txid: transfer_txid, + vout: 0, }, - old_location: SatPoint { - outpoint: OutPoint { - txid: create_txid, - vout: 0 - }, - offset: 0 + offset: 0, + }, + old_location: SatPoint { + outpoint: OutPoint { + txid: create_txid, + vout: 0, }, - sequence_number: 0, + offset: 0, + }, + sequence_number: 0, + }; + loop { + let received_event = event_receiver.blocking_recv().unwrap(); + if received_event == expected_event { + assert_eq!(received_event, expected_event); + break; } - ); + } } #[test] @@ -6420,14 +6427,18 @@ mod tests { [], ); - assert_eq!( - event_receiver.blocking_recv().unwrap(), - Event::RuneEtched { - block_height: 8, - txid: txid0, - rune_id: id, + let expected_event = Event::RuneEtched { + block_height: 8, + txid: txid0, + rune_id: id, + }; + loop { + let received_event = event_receiver.blocking_recv().unwrap(); + if received_event == expected_event { + pretty_assert_eq!(received_event, expected_event); + break; } - ); + } let txid1 = context.core.broadcast_tx(TransactionTemplate { inputs: &[(2, 0, 0, Witness::new())], @@ -6473,15 +6484,19 @@ mod tests { )], ); - assert_eq!( - event_receiver.blocking_recv().unwrap(), - Event::RuneMinted { - block_height: 9, - txid: txid1, - rune_id: id, - amount: 1000, + let expected_event = Event::RuneMinted { + block_height: 9, + txid: txid1, + rune_id: id, + amount: 1000, + }; + loop { + let received_event = event_receiver.blocking_recv().unwrap(); + if received_event == expected_event { + assert_eq!(received_event, expected_event); + break; } - ); + } let txid2 = context.core.broadcast_tx(TransactionTemplate { inputs: &[(9, 1, 0, Witness::new())], @@ -6532,19 +6547,23 @@ mod tests { event_receiver.blocking_recv().unwrap(); - pretty_assert_eq!( - event_receiver.blocking_recv().unwrap(), - Event::RuneTransferred { - block_height: 10, + let expected_event = Event::RuneTransferred { + block_height: 10, + txid: txid2, + rune_id: id, + amount: 1000, + outpoint: OutPoint { txid: txid2, - rune_id: id, - amount: 1000, - outpoint: OutPoint { - txid: txid2, - vout: 0, - }, + vout: 0, + }, + }; + loop { + let received_event = event_receiver.blocking_recv().unwrap(); + if received_event == expected_event { + pretty_assert_eq!(received_event, expected_event); + break; } - ); + } let txid3 = context.core.broadcast_tx(TransactionTemplate { inputs: &[(10, 1, 0, Witness::new())], @@ -6597,14 +6616,18 @@ mod tests { event_receiver.blocking_recv().unwrap(); - pretty_assert_eq!( - event_receiver.blocking_recv().unwrap(), - Event::RuneBurned { - block_height: 11, - txid: txid3, - amount: 111, - rune_id: id, + let expected_event = Event::RuneBurned { + block_height: 11, + txid: txid3, + amount: 111, + rune_id: id, + }; + loop { + let received_event = event_receiver.blocking_recv().unwrap(); + if received_event == expected_event { + pretty_assert_eq!(received_event, expected_event); + break; } - ); + } } } diff --git a/src/index/event.rs b/src/index/event.rs index 96e1b41f99..6e9d91cbce 100644 --- a/src/index/event.rs +++ b/src/index/event.rs @@ -1,6 +1,6 @@ use super::*; -#[derive(Debug, Clone, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub enum Event { InscriptionCreated { block_height: u32, @@ -41,4 +41,7 @@ pub enum Event { rune_id: RuneId, txid: Txid, }, + BlockCommitted { + height: u32, + }, } diff --git a/src/index/updater.rs b/src/index/updater.rs index a511f89607..b0b984a597 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -101,7 +101,7 @@ impl<'index> Updater<'index> { uncommitted += 1; if uncommitted == self.index.settings.commit_interval() { - self.commit(wtx, value_cache)?; + self.commit(wtx, value_cache, uncommitted)?; value_cache = HashMap::new(); uncommitted = 0; wtx = self.index.begin_write()?; @@ -140,7 +140,7 @@ impl<'index> Updater<'index> { } if uncommitted > 0 { - self.commit(wtx, value_cache)?; + self.commit(wtx, value_cache, uncommitted)?; } if let Some(progress_bar) = &mut progress_bar { @@ -696,7 +696,12 @@ impl<'index> Updater<'index> { Ok(()) } - fn commit(&mut self, wtx: WriteTransaction, value_cache: HashMap) -> Result { + fn commit( + &mut self, + wtx: WriteTransaction, + value_cache: HashMap, + uncommitted: usize, + ) -> Result { log::info!( "Committing at block height {}, {} outputs traversed, {} in map, {} cached", self.height, @@ -739,6 +744,24 @@ impl<'index> Updater<'index> { Reorg::update_savepoints(self.index, self.height)?; + if self.height >= self.index.first_inscription_height { + if let Some(sender) = self.index.event_sender.as_ref() { + if let Ok(uncommitted) = u32::try_from(uncommitted) { + for current_height in (self.height - uncommitted)..self.height { + sender.blocking_send(Event::BlockCommitted { + height: current_height, + })?; + } + } else { + log::error!( + "Failed to publish block range from_height: {}, uncommitted: {}", + self.height, + uncommitted + ); + } + } + } + Ok(()) } } diff --git a/src/indexer/api_client.rs b/src/indexer/api_client.rs new file mode 100644 index 0000000000..9ffa895d18 --- /dev/null +++ b/src/indexer/api_client.rs @@ -0,0 +1,120 @@ +use std::time::Duration; + +use anyhow::anyhow; +use bitcoin::Txid; +use reqwest::{Client, RequestBuilder}; +use tokio::time::sleep; + +use crate::api::{BlockInfo, InscriptionDetails, Transaction}; + +pub struct ApiClient { + ord_api_url: String, + client: Client, +} + +impl ApiClient { + pub fn new(ord_api_url: String) -> anyhow::Result { + let client = Client::builder().timeout(Duration::from_secs(30)).build()?; + + Ok(ApiClient { + ord_api_url, + client, + }) + } + + async fn execute_with_retries( + &self, + request_builder: RequestBuilder, + max_attempts: u32, + ) -> Result + where + T: for<'de> serde::Deserialize<'de> + 'static, + { + let mut attempts = 0; + let mut delay = Duration::from_secs(1); + + let mut last_error: Option = None; + + while attempts < max_attempts { + let request = request_builder + .try_clone() + .ok_or_else(|| anyhow!("Failed to clone request"))?; + + let response = request.send().await; + + match response { + Ok(resp) => match resp.error_for_status() { + Ok(valid_response) => { + return valid_response + .json::() + .await + .map_err(anyhow::Error::from); + } + Err(e) + if e.status().map_or_else( + || false, + |status_code| status_code.is_server_error() || status_code.is_client_error(), + ) => + { + last_error = Some(format!( + "{}: {}", + e.status() + .map_or("No Status Code".to_string(), |s| s.as_str().to_string()), + e + )); + attempts += 1; + sleep(delay).await; + delay *= 2; + } + Err(e) => return Err(anyhow!(e)), + }, + Err(e) => { + last_error = Some(e.to_string()); + attempts += 1; + sleep(delay).await; + delay *= 2; + } + } + } + + Err(anyhow!( + "Exceeded maximum retry attempts after {} tries. Last error: {}. Attempted endpoint: {}", + max_attempts, + last_error.unwrap_or_else(|| "No error captured".to_string()), + request_builder.build().unwrap().url().to_string() + )) + } + + pub async fn fetch_inscription_details( + &self, + inscription_id: &String, + ) -> Result { + let request_builder = self + .client + .get(format!( + "{}/inscription/{}/details", + self.ord_api_url, inscription_id + )) + .header("Accept", "application/json"); + + self.execute_with_retries(request_builder, 3).await + } + + pub async fn fetch_tx(&self, tx_id: Txid) -> Result { + let request_builder = self + .client + .get(format!("{}/tx/{}", self.ord_api_url, tx_id)) + .header("Accept", "application/json"); + + self.execute_with_retries(request_builder, 3).await + } + + pub async fn fetch_block_info(&self, block_height: &u32) -> Result { + let request_builder = self + .client + .get(format!("{}/r/blockinfo/{}", self.ord_api_url, block_height)) + .header("Accept", "application/json"); + + self.execute_with_retries(request_builder, 3).await + } +} diff --git a/src/indexer/block_consumer.rs b/src/indexer/block_consumer.rs new file mode 100644 index 0000000000..31a4567440 --- /dev/null +++ b/src/indexer/block_consumer.rs @@ -0,0 +1,117 @@ +use anyhow::Context; +use clap::Parser; +use futures::StreamExt; +use lapin::{message::Delivery, options::*, types::FieldTable, Channel}; +use tokio::runtime::Runtime; + +use crate::index::event::Event; +use crate::indexer::api_client::ApiClient; +use crate::indexer::db_client::DbClient; +use crate::indexer::inscription_indexation::InscriptionIndexation; +use crate::indexer::rmq_con::{ + generate_consumer_tag, republish_to_queue, setup_rabbitmq_connection, +}; +use crate::settings::Settings; +use crate::subcommand::SubcommandResult; + +#[derive(Debug, Parser)] +pub struct BlockConsumer { + #[arg(long, help = "RMQ queue to consume blocks.")] + pub(crate) blocks_queue: Option, + + #[arg(long, help = "DB url to persist inscriptions.")] + pub(crate) database_url: Option, + + #[arg(long, help = "Ord api url to fetch inscriptions.")] + pub(crate) ord_api_url: Option, +} + +impl BlockConsumer { + pub fn run(self, settings: &Settings) -> SubcommandResult { + Runtime::new()?.block_on(async { + let database_url = self.database_url.context("db url is required")?; + let db = DbClient::new(database_url, 2).await?; + + let api_url = self.ord_api_url.context("api url must be defined")?; + let api_client = ApiClient::new(api_url.clone()).context("Failed to create API client")?; + + let tag = generate_consumer_tag("lr-ord-evts"); + let addr = settings.rabbitmq_addr().context("rmq url is required")?; + let queue_name = self.blocks_queue.context("rmq queue is required")?; + let channel = setup_rabbitmq_connection(addr).await?; + channel + .basic_qos(2, BasicQosOptions::default()) + .await + .context("Failed to set basic_qos")?; + + let inscription_indexer = InscriptionIndexation::new(settings, db, api_client); + + let mut consumer = channel + .basic_consume( + &queue_name, + tag.as_str(), + BasicConsumeOptions::default(), + FieldTable::default(), + ) + .await?; + + log::info!("started block consumer {tag} for {queue_name}"); + + loop { + if let Some(msg) = consumer.next().await { + match msg { + Ok(d) => BlockConsumer::handle_delivery(&channel, d, &inscription_indexer).await?, + Err(err) => log::error!("error consuming message: {err}"), + } + } + } + }) + } + + /// Handle the persistence of incoming queue "block" messages. + /// + /// Re-enqueues the message up to `max_delivery` times for processing failures. + /// Bubbles up the `lapin::Error` only if the ack/reject itself fails. + async fn handle_delivery( + channel: &Channel, + delivery: Delivery, + indexer: &InscriptionIndexation, + ) -> Result<(), lapin::Error> { + let max_delivery = 3; + let reject = BasicRejectOptions { requeue: false }; + + let delivery_count = delivery + .properties + .headers() + .as_ref() + .and_then(|h| h.inner().get("x-delivery-count")?.as_short_uint()) + .unwrap_or(0); + + let event = serde_json::from_slice::(&delivery.data).context("should deserialize evt"); + + if delivery_count > max_delivery { + log::error!("failed event dropped {:?}", event); + return delivery.reject(reject).await; + } + + if let Ok(ref e) = event { + if BlockConsumer::process_event(e, indexer).await.is_ok() { + return delivery.ack(BasicAckOptions::default()).await; + }; + }; + + log::warn!("failed event requeued {:?}", event); + republish_to_queue(channel, &delivery, &delivery_count).await?; + delivery.reject(reject).await + } + + async fn process_event( + event: &Event, + indexer: &InscriptionIndexation, + ) -> Result<(), anyhow::Error> { + match event { + Event::BlockCommitted { height } => indexer.sync_blocks(height).await, + _ => Ok(log::warn!("skipped unhandled event type {:?}", event)), + } + } +} diff --git a/src/indexer/db_client.rs b/src/indexer/db_client.rs new file mode 100644 index 0000000000..2ac1bb2475 --- /dev/null +++ b/src/indexer/db_client.rs @@ -0,0 +1,276 @@ +use bitcoin::{OutPoint, Txid}; +use ordinals::SatPoint; +use sqlx::postgres::PgPoolOptions; +use sqlx::types::Json; +use sqlx::PgPool; +use sqlx::Result; +use std::str::FromStr; + +use crate::api::InscriptionDetails; +use crate::InscriptionId; + +pub enum EventType { + InscriptionCreated, + InscriptionTransferred, +} + +#[derive(Debug, Clone)] +pub struct Event { + pub type_id: i16, + pub block_height: i32, + pub inscription_id: String, + pub location: Option, + pub old_location: Option, +} + +impl Event { + pub fn get_type(&self) -> EventType { + match self.type_id { + 1 => EventType::InscriptionCreated, + 2 => EventType::InscriptionTransferred, + _ => unreachable!(), + } + } +} + +pub struct DbClient { + pool: PgPool, +} + +impl DbClient { + pub async fn new(database_url: String, max_conn: u32) -> Result { + let pool = PgPoolOptions::new() + .max_connections(max_conn) + .connect(database_url.as_str()) + .await?; + + Ok(Self { pool }) + } + + pub async fn close(&self) { + self.pool.close().await; + } + + pub async fn fetch_events_by_block_height( + &self, + block_height: &u32, + ) -> Result, sqlx::Error> { + sqlx::query!( + r#" + SELECT type_id, block_height, inscription_id, location, old_location + FROM event WHERE block_height = $1 + ORDER BY type_id ASC, id ASC + "#, + i32::try_from(block_height.to_owned()).expect("block_height should fit in pg integer"), + ) + .map(|r| Event { + type_id: r.type_id, + block_height: r.block_height, + inscription_id: r.inscription_id, + location: r.location.and_then(|s| SatPoint::from_str(&s).ok()), + old_location: r.old_location.and_then(|s| SatPoint::from_str(&s).ok()), + }) + .fetch_all(&self.pool) + .await + } + + pub async fn save_inscription_created( + &self, + block_height: &u32, + inscription_id: &InscriptionId, + location: &Option, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + INSERT INTO event (type_id, block_height, inscription_id, location) + SELECT $1, $2, $3, $4 + WHERE NOT EXISTS ( + SELECT 1 FROM event + WHERE type_id = $1 AND block_height = $2 AND inscription_id = $3 AND location = $4 + ) + "#, + 1, // Type ID for `InscriptionCreated` + block_height.to_owned() as i64, + inscription_id.to_string(), + location.map(|loc| loc.to_string()) + ) + .execute(&self.pool) + .await?; + + Ok(()) + } + + pub async fn save_inscription_transferred( + &self, + block_height: &u32, + inscription_id: &InscriptionId, + new_location: &SatPoint, + old_location: &SatPoint, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + INSERT INTO event (type_id, block_height, inscription_id, location, old_location) + SELECT $1, $2, $3, $4, $5 + WHERE NOT EXISTS ( + SELECT 1 FROM event + WHERE type_id = $1 AND block_height = $2 AND inscription_id = $3 AND location = $4 AND old_location = $5 + ) + "#, + 2, // Type ID for `InscriptionCreated` + block_height.to_owned() as i64, + inscription_id.to_string(), + new_location.to_string(), + old_location.to_string() + ) + .execute(&self.pool) + .await?; + + Ok(()) + } + + pub async fn fetch_inscription_id_by_genesis_id( + &self, + genesis_id: &String, + ) -> Result, sqlx::Error> { + sqlx::query!( + r#"SELECT id FROM inscription WHERE genesis_id = $1"#, + genesis_id + ) + .map(|r| r.id) + .fetch_optional(&self.pool) + .await + } + + pub async fn save_inscription( + &self, + inscription_details: &InscriptionDetails, + metadata: Option, + ) -> Result { + sqlx::query!( + r#" + INSERT INTO inscription ( + genesis_id + , number + , content_type + , content_length + , metadata + , genesis_block_height + , genesis_block_time + , sat_number + , sat_rarity + , sat_block_height + , sat_block_time + , fee + , charms + , children + , parents + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) + ON CONFLICT (genesis_id) DO UPDATE SET + number = EXCLUDED.number + , content_type = EXCLUDED.content_type + , content_length = COALESCE(EXCLUDED.content_length, inscription.content_length) + , metadata = COALESCE(EXCLUDED.metadata, inscription.metadata) + , genesis_block_height = EXCLUDED.genesis_block_height + , genesis_block_time = EXCLUDED.genesis_block_time + , sat_number = COALESCE(EXCLUDED.sat_number, inscription.sat_number) + , sat_rarity = COALESCE(EXCLUDED.sat_rarity, inscription.sat_rarity) + , sat_block_height = COALESCE(EXCLUDED.sat_block_height, inscription.sat_block_height) + , sat_block_time = COALESCE(EXCLUDED.sat_block_time, inscription.sat_block_time) + , fee = EXCLUDED.fee + , charms = EXCLUDED.charms + , children = COALESCE(EXCLUDED.children, inscription.children) + , parents = COALESCE(EXCLUDED.parents, inscription.parents) + RETURNING id + "#, + inscription_details.id.to_string(), + inscription_details.number, + inscription_details.content_type.as_deref(), + inscription_details + .content_length + .map(|n| i32::try_from(n).expect("content_length should fit in pg integer")), + metadata, + i32::try_from(inscription_details.genesis_block_height) + .expect("genesis_block_height should fit in pg integer"), + inscription_details.genesis_block_time, + inscription_details + .sat_number + .map(|n| i64::try_from(n).expect("sat_number should fit in pg bigint")), + inscription_details.sat_rarity.map(|r| r as i32), + inscription_details + .sat_block_height + .map(|n| i32::try_from(n).expect("sat_block_height should fit in pg integer")), + inscription_details.sat_block_time, + i64::try_from(inscription_details.fee).expect("fee should fit in pg bigint"), + i16::try_from(inscription_details.charms).expect("charts should fit in pg smallint"), + Json(&inscription_details.children).encode_to_string(), + Json(&inscription_details.parents).encode_to_string() + ) + .map(|r| r.id) + .fetch_one(&self.pool) + .await + } + + pub async fn save_location( + &self, + id: i32, + block_height: i32, + block_time: u64, + tx_id: Option, + to_address: Option, + to_outpoint: Option, + to_offset: Option, + from_address: Option, + from_outpoint: Option, + from_offset: Option, + value: Option, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + INSERT INTO location ( + inscription_id + , block_height + , block_time + , tx_id + , to_address + , cur_output + , cur_offset + , from_address + , prev_output + , prev_offset + , value + ) + SELECT + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11 + WHERE NOT EXISTS ( + SELECT 1 FROM location + WHERE inscription_id = $1 + AND block_height = $2 + AND block_time = $3 + AND tx_id = $4 + AND to_address = $5 + AND cur_output = $6 + AND cur_offset = $7 + AND from_address = $8 + AND prev_output = $9 + AND prev_offset = $10 + AND value = $11 + ) + "#, + id, + block_height, + i64::try_from(block_time).expect("block_time should fit in pg bigint"), + tx_id.map(|n| n.to_string()), + to_address, + to_outpoint.map(|n| n.to_string()), + to_offset.map(|n| i64::try_from(n).expect("to_offset should fit in pg bigint")), + from_address, + from_outpoint.map(|n| n.to_string()), + from_offset.map(|n| i64::try_from(n).expect("from_offset should fit in pg bigint")), + value.map(|n| i64::try_from(n).expect("value should fit in pg bigint")), + ) + .execute(&self.pool) + .await?; + + Ok(()) + } +} diff --git a/src/indexer/event_consumer.rs b/src/indexer/event_consumer.rs new file mode 100644 index 0000000000..b3380f7f41 --- /dev/null +++ b/src/indexer/event_consumer.rs @@ -0,0 +1,134 @@ +use anyhow::Context; +use clap::Parser; +use futures::StreamExt; +use lapin::{message::Delivery, options::*, types::FieldTable, Channel}; +use tokio::runtime::Runtime; + +use crate::index::event::Event; +use crate::indexer::db_client::DbClient; +use crate::indexer::rmq_con::{ + generate_consumer_tag, republish_to_queue, setup_rabbitmq_connection, +}; +use crate::settings::Settings; +use crate::subcommand::SubcommandResult; + +#[derive(Debug, Parser)] +pub struct EventConsumer { + #[arg(long, help = "RMQ queue to consume inscription events.")] + pub(crate) inscriptions_queue: Option, + #[arg(long, help = "DB url to persist inscriptions.")] + pub(crate) database_url: Option, +} + +impl EventConsumer { + pub fn run(self, settings: &Settings) -> SubcommandResult { + Runtime::new()?.block_on(async { + let database_url = self.database_url.context("db url is required")?; + let db = DbClient::new(database_url, 2).await?; + + let tag = generate_consumer_tag("lr-ord-evts"); + let addr = settings.rabbitmq_addr().context("rmq url is required")?; + let queue_name = self.inscriptions_queue.context("rmq queue is required")?; + let channel = setup_rabbitmq_connection(addr).await?; + channel + .basic_qos(2, BasicQosOptions::default()) + .await + .context("Failed to set basic_qos")?; + + let mut consumer = channel + .basic_consume( + &queue_name, + tag.as_str(), + BasicConsumeOptions::default(), + FieldTable::default(), + ) + .await?; + + log::info!("started event consumer {tag} for {queue_name}"); + + loop { + if let Some(msg) = consumer.next().await { + match msg { + Ok(d) => EventConsumer::handle_delivery(&channel, d, &db).await?, + Err(e) => log::error!("error consuming message: {}", e), + } + } + } + }) + } + + /// Handle the persistence of incoming queue "event" messages. + /// + /// Re-enqueues the message up to `max_delivery` times for processing failures. + /// Bubbles up the `lapin::Error` only if the ack/reject itself fails. + async fn handle_delivery( + channel: &Channel, + delivery: Delivery, + db: &DbClient, + ) -> Result<(), lapin::Error> { + let max_delivery = 3; + let reject = BasicRejectOptions { requeue: false }; + + let delivery_count = delivery + .properties + .headers() + .as_ref() + .and_then(|h| h.inner().get("x-delivery-count")?.as_short_uint()) + .unwrap_or(0); + + let event = serde_json::from_slice::(&delivery.data).context("should deserialize evt"); + + if delivery_count > max_delivery { + log::error!("failed event dropped {:?}", event); + return delivery.reject(reject).await; + } + + if let Ok(ref e) = event { + if EventConsumer::process_event(e, db).await.is_ok() { + return delivery.ack(BasicAckOptions::default()).await; + }; + }; + + log::warn!("failed event requeued {:?}", event); + republish_to_queue(channel, &delivery, &delivery_count).await?; + delivery.reject(reject).await + } + + async fn process_event(event: &Event, db: &DbClient) -> Result<(), sqlx::Error> { + match event { + Event::InscriptionCreated { + block_height, + inscription_id, + location, + .. + } => { + db.save_inscription_created( + // + block_height, + inscription_id, + location, + ) + .await + } + + Event::InscriptionTransferred { + block_height, + inscription_id, + new_location, + old_location, + .. + } => { + db.save_inscription_transferred( + // + block_height, + inscription_id, + new_location, + old_location, + ) + .await + } + + _ => Ok(log::warn!("skipped unhandled event type {:?}", event)), + } + } +} diff --git a/src/indexer/event_publisher.rs b/src/indexer/event_publisher.rs new file mode 100644 index 0000000000..b41cc3a01e --- /dev/null +++ b/src/indexer/event_publisher.rs @@ -0,0 +1,146 @@ +use anyhow::{anyhow, Context, Result}; +use lapin::{ + options::{BasicPublishOptions, BasicQosOptions}, + BasicProperties, Channel, +}; +use std::time::Duration; +use tokio::runtime::Runtime; +use tokio::sync::mpsc; +use tokio::time::sleep; + +use crate::index::event::Event; +use crate::indexer::rmq_con::setup_rabbitmq_connection; +use crate::settings::Settings; +use crate::shutdown_process; + +async fn rabbit_qos_setup(channel: Channel) -> Result { + channel + .basic_qos(2, BasicQosOptions::default()) + .await + .context("failed to set basic qos")?; + + Ok(channel) +} + +pub struct EventPublisher { + pub(crate) sender: mpsc::Sender, +} + +impl EventPublisher { + pub fn run(settings: &Settings) -> Result { + let addr = settings + .rabbitmq_addr() + .context("rabbitmq amqp credentials and url must be defined")? + .to_owned(); + + let exchange = settings + .rabbitmq_exchange() + .context("rabbitmq exchange path must be defined")? + .to_owned(); + + let (tx, rx) = mpsc::channel::(1); + + std::thread::spawn(move || { + Runtime::new().expect("runtime is setup").block_on(async { + match EventPublisher::consume_channel(addr, exchange, rx).await { + Ok(_) => log::info!("Channel closed."), + Err(e) => { + log::error!("Fatal error publishing to RMQ, exiting {}", e); + shutdown_process(); + } + } + }) + }); + + Ok(EventPublisher { sender: tx }) + } + + /// Publishes an event to the message exchange. + /// On failures it will retry up to `n` times with exponential backoff. + /// After `n` attempts, the retry loop will break and the main loop continues. + /// + /// TODO: Consider how to handle failure without skipping. + async fn consume_channel( + addr: String, + exchange: String, + mut rx: mpsc::Receiver, + ) -> Result<()> { + let channel = setup_rabbitmq_connection(&addr).await?; + let mut channel = rabbit_qos_setup(channel).await?; + + while let Some(event) = rx.recv().await { + let message = serde_json::to_vec(&event)?; + let routing_key = EventPublisher::type_name(&event); + + let mut backoff_delay = Duration::from_secs(1); + let mut max_attempts = 8; + + let publish_retried = loop { + match EventPublisher::publish_message(&channel, &exchange, routing_key, &message).await { + Ok(value) => break Ok(value), + Err(e) => { + if max_attempts == 0 { + break Err(e); + } + + max_attempts -= 1; + + log::error!( + "Error: {e}. Retries left: {max_attempts}. Retrying in {}s.", + backoff_delay.as_secs() + ); + + sleep(backoff_delay).await; + + channel = rabbit_qos_setup(setup_rabbitmq_connection(&addr).await?) + .await + .inspect_err(|e| log::error!("error reconnecting rmq: {e}")) + .unwrap_or(channel); + + backoff_delay *= 2; + } + } + }; + + publish_retried? + } + + Ok(()) + } + + async fn publish_message( + channel: &Channel, + exchange: &str, + routing_key: &str, + message: &[u8], + ) -> Result<()> { + let publish = channel + .basic_publish( + exchange, + routing_key, + BasicPublishOptions::default(), + message, + BasicProperties::default(), + ) + .await? + .await?; + + if !publish.is_ack() { + return Err(anyhow!("message was not acknowledged")); + } + + Ok(()) + } + + fn type_name(event: &Event) -> &'static str { + match event { + Event::InscriptionCreated { .. } => "InscriptionCreated", + Event::InscriptionTransferred { .. } => "InscriptionTransferred", + Event::RuneBurned { .. } => "RuneBurned", + Event::RuneEtched { .. } => "RuneEtched", + Event::RuneMinted { .. } => "RuneMinted", + Event::RuneTransferred { .. } => "RuneTransferred", + Event::BlockCommitted { .. } => "BlockCommitted", + } + } +} diff --git a/src/indexer/inscription_indexation.rs b/src/indexer/inscription_indexation.rs new file mode 100644 index 0000000000..da008b2bae --- /dev/null +++ b/src/indexer/inscription_indexation.rs @@ -0,0 +1,175 @@ +use anyhow::anyhow; +use ciborium::from_reader; +use ordinals::SatPoint; +use serde_json::Value; +use std::io::Cursor; + +use crate::api::BlockInfo; +use crate::indexer::api_client::ApiClient; +use crate::indexer::db_client::{DbClient, Event, EventType}; +use crate::settings::Settings; + +pub struct InscriptionIndexation { + settings: Settings, + db: DbClient, + api: ApiClient, +} + +impl InscriptionIndexation { + pub fn new(settings: &Settings, db: DbClient, api: ApiClient) -> Self { + Self { + settings: settings.clone(), + db, + api, + } + } + + pub async fn sync_blocks(&self, block_height: &u32) -> Result<(), anyhow::Error> { + let events = self.db.fetch_events_by_block_height(block_height).await?; + + if !events.is_empty() { + let block_info = self.api.fetch_block_info(block_height).await?; + + for event in events { + match event.get_type() { + EventType::InscriptionCreated => self + .process_inscription_created(&event, &block_info) + .await + .inspect_err(|e| log::error!("error with inscription_created {:?}: {e}", event)), + + EventType::InscriptionTransferred => self + .process_inscription_transferred(&event, &block_info) + .await + .inspect_err(|e| log::error!("error with inscription_transferred {:?}: {e}", event)), + }?; + } + } + + log::info!("Blocks committed event for={block_height}"); + + Ok(()) + } + + async fn process_inscription_created( + &self, + event: &Event, + block_info: &BlockInfo, + ) -> anyhow::Result<()> { + let inscription = self + .api + .fetch_inscription_details(&event.inscription_id) + .await?; + let metadata = self.try_and_extract_metadata(&inscription.metadata); + let location = event.location.as_ref(); + let to_location = match location { + Some(loc) => Some(self.process_location(loc).await?), + None => None, + }; + + let id = self.db.save_inscription(&inscription, metadata).await?; + + self + .db + .save_location( + id, + event.block_height, + block_info.timestamp, + location.map(|loc| loc.outpoint.txid), + to_location.as_ref().map(|d| d.0.clone()), + location.map(|loc| loc.outpoint), + location.map(|loc| loc.offset), + None, + None, + None, + to_location.as_ref().map(|d| d.1), + ) + .await + .map_err(|err| anyhow!("error saving inscription_created location: {err}")) + } + + fn try_and_extract_metadata(&self, metadata: &Option>) -> Option { + metadata.as_ref().and_then(|bytes| { + let cursor = Cursor::new(bytes); + let result = from_reader(cursor); + match result { + Ok(value) => match &value { + Value::Object(obj) if obj.is_empty() => None, + Value::Object(_) | Value::Array(_) => serde_json::to_string(&value).ok(), + _ => Some(hex::encode(bytes)), + }, + Err(_) => Some(hex::encode(bytes)), + } + }) + } + + async fn process_inscription_transferred( + &self, + event: &Event, + block_info: &BlockInfo, + ) -> Result<(), anyhow::Error> { + let inscription_id = match self + .db + .fetch_inscription_id_by_genesis_id(&event.inscription_id) + .await? + { + Some(id) => id, + None => { + let inscription = self + .api + .fetch_inscription_details(&event.inscription_id) + .await?; + let metadata = self.try_and_extract_metadata(&inscription.metadata); + self.db.save_inscription(&inscription, metadata).await? + } + }; + + let location = event.location.as_ref(); + let old_location = event.location.as_ref(); + + let to_location = match &event.location { + Some(location) => Some(self.process_location(location).await?), + None => None, + }; + let from_location = match &event.old_location { + Some(location) => Some(self.process_location(location).await?), + None => None, + }; + + self + .db + .save_location( + inscription_id, + event.block_height, + block_info.timestamp, + location.map(|loc| loc.outpoint.txid), + to_location.as_ref().map(|d| d.0.clone()), + location.map(|loc| loc.outpoint), + location.map(|loc| loc.offset), + from_location.as_ref().map(|d| d.0.clone()), + old_location.map(|loc| loc.outpoint), + old_location.map(|loc| loc.offset), + to_location.as_ref().map(|d| d.1), + ) + .await + .map_err(|err| anyhow!("error saving inscription_transferred location: {err}")) + } + + async fn process_location(&self, location: &SatPoint) -> Result<(String, u64), anyhow::Error> { + let tx_details = self.api.fetch_tx(location.outpoint.txid).await?; + + let output = tx_details + .transaction + .output + .into_iter() + .nth(location.outpoint.vout.try_into()?) + .ok_or_else(|| anyhow!("output not found in tx: {}", location.outpoint.txid))?; + + let address = self + .settings + .chain() + .address_from_script(&output.script_pubkey)? + .to_string(); + + Ok((address, output.value)) + } +} diff --git a/src/indexer/rmq_con.rs b/src/indexer/rmq_con.rs new file mode 100644 index 0000000000..6dde40403c --- /dev/null +++ b/src/indexer/rmq_con.rs @@ -0,0 +1,91 @@ +use anyhow::{Context, Error}; +use chrono::Utc; +use lapin::message::Delivery; +use lapin::options::{BasicPublishOptions, ConfirmSelectOptions}; +use lapin::publisher_confirm::Confirmation; +use lapin::tcp::{AMQPUriTcpExt, NativeTlsConnector}; +use lapin::types::{FieldTable, ShortUInt}; +use lapin::uri::AMQPUri; +use lapin::{BasicProperties, Channel, Connection, ConnectionProperties}; +use rand::distributions::{Alphanumeric, DistString}; + +pub async fn setup_rabbitmq_connection(addr: &str) -> Result { + let conn = connect_to_rabbitmq(addr).await?; + let channel = conn + .create_channel() + .await + .context("creates rmq connection channel")?; + channel + .confirm_select(ConfirmSelectOptions::default()) + .await + .context("enable msg confirms")?; + Ok(channel) +} + +async fn connect_to_rabbitmq(addr: &str) -> Result { + let opt = ConnectionProperties::default(); + let uri = addr + .parse::() + .map_err(Error::msg) + .context("failed to parse ampq uri")?; + + match uri.authority.host.as_str() { + "localhost" => Connection::connect(addr, opt) + .await + .context("failed to establish an unsecure ampq connection"), + + _ => { + let connect = move |uri: &AMQPUri| { + uri.connect().and_then(|stream| { + let mut tls_builder = NativeTlsConnector::builder(); + tls_builder.danger_accept_invalid_certs(true); + let connector = &tls_builder.build().expect("tls configuration failed"); + stream.into_native_tls(connector, &uri.authority.host) + }) + }; + + Connection::connector(uri, Box::new(connect), opt) + .await + .context("failed to establish a secure ampq connection") + } + } +} + +// TODO get pod name from k8s? +pub fn generate_consumer_tag(prefix: &str) -> String { + let timestamp = Utc::now().format("%Y%m%d%H%M%S"); + format!( + "{}-{}-{}", + prefix, + timestamp, + Alphanumeric.sample_string(&mut rand::thread_rng(), 16) + ) +} + +pub async fn republish_to_queue( + channel: &Channel, + delivery: &Delivery, + delivery_count: &ShortUInt, +) -> lapin::Result { + let mut new_headers = delivery + .properties + .headers() + .as_ref() + .cloned() + .unwrap_or_else(FieldTable::default); + new_headers.insert( + "x-delivery-count".into(), + ShortUInt::from(delivery_count + 1).into(), + ); + + channel + .basic_publish( + "", + delivery.routing_key.as_str(), + BasicPublishOptions::default(), + &delivery.data, + BasicProperties::default().with_headers(new_headers), + ) + .await? + .await +} diff --git a/src/lib.rs b/src/lib.rs index 929af95c15..e10a25f1cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,10 +2,10 @@ clippy::large_enum_variant, clippy::result_large_err, clippy::too_many_arguments, + clippy::cast_lossless, clippy::type_complexity )] #![deny( - clippy::cast_lossless, clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::cast_sign_loss @@ -120,6 +120,15 @@ pub mod subcommand; mod tally; pub mod templates; pub mod wallet; +pub mod indexer { + pub mod api_client; + pub mod block_consumer; + pub mod db_client; + pub mod event_consumer; + pub mod event_publisher; + pub mod inscription_indexation; + pub mod rmq_con; +} type Result = std::result::Result; @@ -231,24 +240,25 @@ fn gracefully_shutdown_indexer() { } } -pub fn main() { - env_logger::init(); - ctrlc::set_handler(move || { - if SHUTTING_DOWN.fetch_or(true, atomic::Ordering::Relaxed) { - process::exit(1); - } +fn shutdown_process() { + if SHUTTING_DOWN.fetch_or(true, atomic::Ordering::Relaxed) { + process::exit(1); + } + + eprintln!("Shutting down gracefully. Press again to shutdown immediately."); - eprintln!("Shutting down gracefully. Press again to shutdown immediately."); + LISTENERS + .lock() + .unwrap() + .iter() + .for_each(|handle| handle.graceful_shutdown(Some(Duration::from_millis(100)))); - LISTENERS - .lock() - .unwrap() - .iter() - .for_each(|handle| handle.graceful_shutdown(Some(Duration::from_millis(100)))); + gracefully_shutdown_indexer(); +} - gracefully_shutdown_indexer(); - }) - .expect("Error setting handler"); +pub fn main() { + env_logger::init(); + ctrlc::set_handler(shutdown_process).expect("Error setting handler"); let args = Arguments::parse(); diff --git a/src/options.rs b/src/options.rs index 6c3c7f2826..fcd132441c 100644 --- a/src/options.rs +++ b/src/options.rs @@ -84,6 +84,10 @@ pub struct Options { help = "Require basic HTTP authentication with . Credentials are sent in cleartext. Consider using authentication in conjunction with HTTPS." )] pub(crate) server_username: Option, + #[arg(long, help = "RMQ url.")] + pub(crate) rabbitmq_url: Option, + #[arg(long, help = "RMQ exchange to publish index events.")] + pub(crate) rabbitmq_exchange: Option, #[arg(long, short, help = "Use regtest. Equivalent to `--chain regtest`.")] pub(crate) regtest: bool, #[arg(long, short, help = "Use signet. Equivalent to `--chain signet`.")] diff --git a/src/settings.rs b/src/settings.rs index 295827200a..6d40a9f565 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -28,6 +28,8 @@ pub struct Settings { server_password: Option, server_url: Option, server_username: Option, + rabbitmq_url: Option, + rabbitmq_exchange: Option, } impl Settings { @@ -143,6 +145,8 @@ impl Settings { server_password: self.server_password.or(source.server_password), server_url: self.server_url.or(source.server_url), server_username: self.server_username.or(source.server_username), + rabbitmq_url: self.rabbitmq_url.or(source.rabbitmq_url), + rabbitmq_exchange: self.rabbitmq_exchange.or(source.rabbitmq_exchange), } } @@ -178,6 +182,8 @@ impl Settings { server_password: options.server_password, server_url: None, server_username: options.server_username, + rabbitmq_url: options.rabbitmq_url, + rabbitmq_exchange: options.rabbitmq_exchange, } } @@ -257,6 +263,8 @@ impl Settings { server_password: get_string("SERVER_PASSWORD"), server_url: get_string("SERVER_URL"), server_username: get_string("SERVER_USERNAME"), + rabbitmq_url: get_string("RMQ_URL"), + rabbitmq_exchange: get_string("RMQ_EXCHANGE"), }) } @@ -287,6 +295,8 @@ impl Settings { server_password: None, server_url: Some(server_url.into()), server_username: None, + rabbitmq_url: None, + rabbitmq_exchange: None, } } @@ -367,6 +377,8 @@ impl Settings { server_password: self.server_password, server_url: self.server_url, server_username: self.server_username, + rabbitmq_url: self.rabbitmq_url, + rabbitmq_exchange: self.rabbitmq_exchange, }) } @@ -426,7 +438,7 @@ impl Settings { "regtest" => Chain::Regtest, "signet" => Chain::Signet, other => bail!("Bitcoin RPC server on unknown chain: {other}"), - } + }; } Err(bitcoincore_rpc::Error::JsonRpc(bitcoincore_rpc::jsonrpc::Error::Rpc(err))) if err.code == -28 => {} @@ -563,6 +575,14 @@ impl Settings { pub(crate) fn server_url(&self) -> Option<&str> { self.server_url.as_deref() } + + pub fn rabbitmq_exchange(&self) -> Option<&str> { + self.rabbitmq_exchange.as_deref() + } + + pub fn rabbitmq_addr(&self) -> Option<&String> { + self.rabbitmq_url.as_ref() + } } #[cfg(test)] @@ -1012,10 +1032,14 @@ mod tests { ("SERVER_PASSWORD", "server password"), ("SERVER_URL", "server url"), ("SERVER_USERNAME", "server username"), + ("RMQ_URL", "http://127.0.0.1"), + ("RMQ_USERNAME", "rmq username"), + ("RMQ_PASSWORD", "rmq password"), + ("RMQ_EXCHANGE", "rmq exchange"), ] - .into_iter() - .map(|(key, value)| (key.into(), value.into())) - .collect::>(); + .into_iter() + .map(|(key, value)| (key.into(), value.into())) + .collect::>(); pretty_assert_eq!( Settings::from_env(env).unwrap(), @@ -1056,6 +1080,8 @@ mod tests { server_password: Some("server password".into()), server_url: Some("server url".into()), server_username: Some("server username".into()), + rabbitmq_url: Some("http://127.0.0.1".into()), + rabbitmq_exchange: Some("rmq exchange".into()), } ); } @@ -1089,6 +1115,8 @@ mod tests { "--no-index-inscriptions", "--server-password=server password", "--server-username=server username", + "--rabbitmq-url=http://127.0.0.1", + "--rabbitmq-exchange=rmq exchange", ]) .unwrap() ), @@ -1118,6 +1146,8 @@ mod tests { server_password: Some("server password".into()), server_url: None, server_username: Some("server username".into()), + rabbitmq_url: Some("http://127.0.0.1".into()), + rabbitmq_exchange: Some("rmq exchange".into()), } ); } diff --git a/src/subcommand.rs b/src/subcommand.rs index fd90a9fd0d..e6ef5b87c0 100644 --- a/src/subcommand.rs +++ b/src/subcommand.rs @@ -1,4 +1,6 @@ use super::*; +use crate::indexer::event_publisher::EventPublisher; +use crate::indexer::{block_consumer, event_consumer}; pub mod balances; pub mod decode; @@ -39,6 +41,12 @@ pub(crate) enum Subcommand { Runes, #[command(about = "Run the explorer server")] Server(server::Server), + #[command(about = "Run the explorer server in event emit mode")] + EventServer(server::Server), + #[command(about = "Run inscription event consumer")] + EventConsumer(event_consumer::EventConsumer), + #[command(about = "Run block event consumer")] + BlockConsumer(block_consumer::BlockConsumer), #[command(about = "Display settings")] Settings, #[command(about = "Display information about a block's subsidy")] @@ -71,6 +79,19 @@ impl Subcommand { LISTENERS.lock().unwrap().push(handle.clone()); server.run(settings, index, handle) } + Self::EventServer(server) => { + let publisher = EventPublisher::run(&settings)?; + let handle = axum_server::Handle::new(); + let index = Arc::new(Index::open_with_event_sender( + &settings, + Some(publisher.sender.clone()), + )?); + + LISTENERS.lock().unwrap().push(handle.clone()); + server.run(settings, index, handle) + } + Self::EventConsumer(event_consumer) => event_consumer.run(&settings), + Self::BlockConsumer(block_consumer) => block_consumer.run(&settings), Self::Settings => settings::run(settings), Self::Subsidy(subsidy) => subsidy.run(), Self::Supply => supply::run(), diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 954264a79b..0b63629cb5 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -1,3 +1,4 @@ +pub(crate) use server_config::ServerConfig; use { self::{ accept_encoding::AcceptEncoding, @@ -39,7 +40,7 @@ use { }, }; -pub(crate) use server_config::ServerConfig; +use crate::api::InscriptionDetails; mod accept_encoding; mod accept_json; @@ -202,6 +203,10 @@ impl Server { .route("/feed.xml", get(Self::feed)) .route("/input/:block/:transaction/:input", get(Self::input)) .route("/inscription/:inscription_query", get(Self::inscription)) + .route( + "/inscription/:inscription_query/details", + get(Self::inscription_details), + ) .route("/inscriptions", get(Self::inscriptions)) .route("/inscriptions", post(Self::inscriptions_json)) .route("/inscriptions/:page", get(Self::inscriptions_paginated)) @@ -1567,6 +1572,82 @@ impl Server { }) } + async fn inscription_details( + Extension(server_config): Extension>, + Extension(index): Extension>, + Path(DeserializeFromStr(query)): Path>, + AcceptJson(accept_json): AcceptJson, + ) -> ServerResult { + task::block_in_place(|| { + if let query::Inscription::Sat(_) = query { + if !index.has_sat_index() { + return Err(ServerError::NotFound("sat index required".into())); + } + } + + let (info, txout, inscription) = index + .inscription_info(query)? + .ok_or_not_found(|| format!("inscription {query}"))?; + + Ok(if accept_json { + let sat_block_time = info + .sat + .and_then(|sat| index.block_time(sat.height()).ok()) + .map(|block_time| block_time.timestamp().timestamp()); + + let response = InscriptionDetails { + id: info.id, + number: info.number, + owner: info.address, //not used + content_type: info.content_type, + content_length: info.content_length, + metadata: inscription.metadata, + genesis_block_height: info.height, + genesis_block_time: info.timestamp, + sat_number: info.sat.map(|sat| sat.0), + sat_name: info.sat.map(|sat| sat.name()), //not used + sat_rarity: info.sat.map(|sat| sat.rarity()), + sat_block_height: info.sat.map(|sat| sat.height().0), + sat_block_time, + satpoint: info.satpoint, //not used + value: info.value, //not used + fee: info.fee, + charms: Charm::Vindicated.unset(info.charms.iter().fold(0, |mut acc, charm| { + charm.set(&mut acc); + acc + })), + children: info.children, + parents: info.parents, + }; + Json(response).into_response() + } else { + InscriptionHtml { + chain: server_config.chain, + charms: Charm::Vindicated.unset(info.charms.iter().fold(0, |mut acc, charm| { + charm.set(&mut acc); + acc + })), + children: info.children, + fee: info.fee, + height: info.height, + inscription, + id: info.id, + number: info.number, + next: info.next, + output: txout, + parents: info.parents, + previous: info.previous, + rune: info.rune, + sat: info.sat, + satpoint: info.satpoint, + timestamp: Utc.timestamp_opt(info.timestamp, 0).unwrap(), + } + .page(server_config) + .into_response() + }) + }) + } + async fn inscriptions_json( Extension(index): Extension>, AcceptJson(accept_json): AcceptJson, diff --git a/tests/settings.rs b/tests/settings.rs index 0244259be4..bb0b7128cf 100644 --- a/tests/settings.rs +++ b/tests/settings.rs @@ -30,7 +30,9 @@ fn default() { "no_index_inscriptions": false, "server_password": null, "server_url": null, - "server_username": null + "server_username": null, + "rabbitmq_url": null, + "rabbitmq_exchange": null \} "#, )