├── .env.example ├── .gitattributes ├── .github └── workflows │ ├── apiwrapper.yml │ ├── build.yml │ ├── license.yml │ ├── prerelease.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .golangci.yml ├── .run ├── API.run.xml ├── Clear.run.xml ├── Indexer.run.xml ├── Test.run.xml ├── package.json └── requests │ ├── address.http │ ├── block.http │ ├── namespace.http │ └── stats.http ├── .vscode └── launch.json ├── HEADER ├── LICENSE ├── Makefile ├── README.md ├── adr └── adr-template.md ├── build ├── api │ └── Dockerfile ├── celestials │ └── Dockerfile └── indexer │ └── Dockerfile ├── cmd ├── api │ ├── admin_middleware.go │ ├── bus │ │ ├── dispatcher.go │ │ └── observer.go │ ├── cache │ │ ├── interface.go │ │ ├── middleware.go │ │ ├── response.go │ │ ├── response_test.go │ │ └── valkey.go │ ├── config.go │ ├── docs │ │ └── swagger.json │ ├── gas │ │ ├── data.go │ │ ├── data_test.go │ │ ├── interface.go │ │ ├── mock.go │ │ ├── tracker.go │ │ └── tracker_test.go │ ├── handler │ │ ├── address.go │ │ ├── address_test.go │ │ ├── block.go │ │ ├── block_test.go │ │ ├── constant.go │ │ ├── constant_test.go │ │ ├── error.go │ │ ├── gas.go │ │ ├── gas_test.go │ │ ├── ibc.go │ │ ├── ibc_test.go │ │ ├── namespace.go │ │ ├── namespace_test.go │ │ ├── proposals.go │ │ ├── proposals_test.go │ │ ├── requests.go │ │ ├── responses.go │ │ ├── responses │ │ │ ├── address.go │ │ │ ├── blob.go │ │ │ ├── blobProof.go │ │ │ ├── block.go │ │ │ ├── block_test.go │ │ │ ├── constants.go │ │ │ ├── constants_test.go │ │ │ ├── delegation.go │ │ │ ├── event.go │ │ │ ├── gas.go │ │ │ ├── grant.go │ │ │ ├── histogram.go │ │ │ ├── ibc.go │ │ │ ├── message.go │ │ │ ├── namespace.go │ │ │ ├── namespace_message.go │ │ │ ├── namespace_test.go │ │ │ ├── ods.go │ │ │ ├── proposal.go │ │ │ ├── rollup.go │ │ │ ├── search.go │ │ │ ├── state.go │ │ │ ├── stats.go │ │ │ ├── stats_test.go │ │ │ ├── tx.go │ │ │ ├── validator.go │ │ │ ├── validator_test.go │ │ │ ├── vesting.go │ │ │ └── vote.go │ │ ├── rollup.go │ │ ├── rollup_auth.go │ │ ├── rollup_test.go │ │ ├── search.go │ │ ├── search_test.go │ │ ├── state.go │ │ ├── state_test.go │ │ ├── stats.go │ │ ├── stats_test.go │ │ ├── tx.go │ │ ├── tx_test.go │ │ ├── validator.go │ │ ├── validator_test.go │ │ ├── validators.go │ │ ├── validators_test.go │ │ ├── vesting.go │ │ ├── vesting_test.go │ │ ├── websocket │ │ │ ├── channel.go │ │ │ ├── channel_test.go │ │ │ ├── client.go │ │ │ ├── client_test.go │ │ │ ├── errors.go │ │ │ ├── filters.go │ │ │ ├── manager.go │ │ │ ├── messages.go │ │ │ └── processors.go │ │ └── ws_test.go │ ├── init.go │ ├── init_test.go │ ├── main.go │ ├── markdown │ │ ├── histogram.md │ │ ├── search.md │ │ ├── summary.md │ │ └── websocket.md │ ├── routes_test.go │ ├── sentry_middleware.go │ └── timeout.go ├── celestials │ ├── config.go │ ├── init.go │ ├── main.go │ └── main_test.go └── indexer │ ├── init.go │ └── main.go ├── configs └── dipdup.yml ├── database ├── functions │ ├── 00_add_view_refresh_job.sql │ ├── 01_refresh_materialized_view.sql │ └── 02_add_job_refresh_materialized_view.sql └── views │ ├── 00_block_stats_by_minute.sql │ ├── 01_block_stats_by_hour.sql │ ├── 02_block_stats_by_day.sql │ ├── 03_block_stats_by_year.sql │ ├── 04_block_stats_by_month.sql │ ├── 05_block_stats_by_week.sql │ ├── 06_namespace_stats_by_hour.sql │ ├── 07_namespace_stats_by_day.sql │ ├── 08_namespace_stats_by_year.sql │ ├── 09_namespace_stats_by_month.sql │ ├── 10_namespace_stats_by_week.sql │ ├── 11_rollup_stats_by_hour.sql │ ├── 12_rollup_stats_by_day.sql │ ├── 13_rollup_stats_by_month.sql │ ├── 16_staking_by_hour.sql │ ├── 17_staking_by_day.sql │ ├── 18_staking_by_month.sql │ ├── 19_accounts_tx_by_hour.sql │ ├── 20_accounts_tx_by_day.sql │ ├── 21_accounts_tx_by_month.sql │ ├── 22_leaderboard.sql │ ├── 23_square_size.sql │ ├── 24_leaderboard_day.sql │ ├── 25_da_change.sql │ ├── 26_ibc_transfers_by_hour.sql │ ├── 27_ibc_transfers_by_day.sql │ └── 28_ibc_transfers_by_month.sql ├── docker-compose.yml ├── go.mod ├── go.sum ├── init.dev.sh ├── internal ├── blob │ ├── mock.go │ ├── r2.go │ ├── storage.go │ └── storage_test.go ├── currency │ ├── currency.go │ └── currency_test.go ├── math │ ├── min.go │ └── time.go ├── profiler │ └── profiler.go ├── stats │ ├── tags.go │ └── tags_test.go ├── storage │ ├── address.go │ ├── api_key.go │ ├── balance.go │ ├── blob_log.go │ ├── block.go │ ├── block_signature.go │ ├── block_signature_test.go │ ├── block_stats.go │ ├── constant.go │ ├── constant_test.go │ ├── delegation.go │ ├── denom_metadata.go │ ├── denom_metadata_test.go │ ├── errors.go │ ├── event.go │ ├── generic.go │ ├── grant.go │ ├── ibc_channel.go │ ├── ibc_client.go │ ├── ibc_connection.go │ ├── ibc_transfer.go │ ├── jail.go │ ├── message.go │ ├── message_address.go │ ├── mock │ │ ├── address.go │ │ ├── api_key.go │ │ ├── balance.go │ │ ├── blob_log.go │ │ ├── block.go │ │ ├── block_signature.go │ │ ├── block_stats.go │ │ ├── constant.go │ │ ├── core.go │ │ ├── delegation.go │ │ ├── denom_metadata.go │ │ ├── event.go │ │ ├── generic.go │ │ ├── grant.go │ │ ├── ibc_channel.go │ │ ├── ibc_client.go │ │ ├── ibc_connection.go │ │ ├── ibc_transfer.go │ │ ├── jail.go │ │ ├── message.go │ │ ├── namespace.go │ │ ├── proposal.go │ │ ├── redelegation.go │ │ ├── rollup.go │ │ ├── rollup_provider.go │ │ ├── staking_log.go │ │ ├── state.go │ │ ├── stats.go │ │ ├── tx.go │ │ ├── undelegation.go │ │ ├── validator.go │ │ ├── vesting_account.go │ │ ├── vesting_period.go │ │ └── vote.go │ ├── namespace.go │ ├── namespace_message.go │ ├── namespace_test.go │ ├── postgres │ │ ├── address.go │ │ ├── address_test.go │ │ ├── apikey.go │ │ ├── apikey_test.go │ │ ├── blob_log.go │ │ ├── blob_log_test.go │ │ ├── block.go │ │ ├── block_signature.go │ │ ├── block_signature_test.go │ │ ├── block_stats.go │ │ ├── block_stats_test.go │ │ ├── block_test.go │ │ ├── constant.go │ │ ├── core.go │ │ ├── core_test.go │ │ ├── custom_types.go │ │ ├── delegation.go │ │ ├── delegation_test.go │ │ ├── denom_metadata.go │ │ ├── event.go │ │ ├── event_test.go │ │ ├── export.go │ │ ├── export_test.go │ │ ├── grant.go │ │ ├── grant_test.go │ │ ├── ibc_channel.go │ │ ├── ibc_channel_test.go │ │ ├── ibc_client.go │ │ ├── ibc_client_test.go │ │ ├── ibc_connection.go │ │ ├── ibc_connection_test.go │ │ ├── ibc_transfer.go │ │ ├── ibc_transfer_test.go │ │ ├── index.go │ │ ├── init_scripts.go │ │ ├── jail.go │ │ ├── jail_test.go │ │ ├── message.go │ │ ├── migrations │ │ │ ├── 20250102_rollup_tags.up.sql │ │ │ ├── 20250113_rollup_verified.up.sql │ │ │ ├── 20250412_add_module.go │ │ │ ├── 20250607_constants_format.go │ │ │ ├── 20250608_proposal_columns.go │ │ │ ├── 20250609_add_event_update_client_proposal.go │ │ │ └── migrations.go │ │ ├── namespace.go │ │ ├── notificator.go │ │ ├── proposal.go │ │ ├── proposal_test.go │ │ ├── redelegation.go │ │ ├── redelegation_test.go │ │ ├── rollup.go │ │ ├── rollup_test.go │ │ ├── scopes.go │ │ ├── search.go │ │ ├── search_test.go │ │ ├── sentry_hook.go │ │ ├── staking_log.go │ │ ├── state.go │ │ ├── stats.go │ │ ├── stats_test.go │ │ ├── storage_test.go │ │ ├── transaction.go │ │ ├── transaction_test.go │ │ ├── tx.go │ │ ├── tx_test.go │ │ ├── undelegation.go │ │ ├── undelegation_test.go │ │ ├── validator.go │ │ ├── validator_test.go │ │ ├── vesting_account.go │ │ ├── vesting_account_test.go │ │ ├── vesting_period.go │ │ ├── vote.go │ │ └── vote_test.go │ ├── proposal.go │ ├── redelegation.go │ ├── rollup.go │ ├── rollup_provider.go │ ├── signers.go │ ├── staking_log.go │ ├── state.go │ ├── stats.go │ ├── tx.go │ ├── types │ │ ├── bitmask.go │ │ ├── bitmask_test.go │ │ ├── event_type.go │ │ ├── event_type_enum.go │ │ ├── ibc.go │ │ ├── ibc_enum.go │ │ ├── module.go │ │ ├── module_enum.go │ │ ├── msg_address_type.go │ │ ├── msg_address_type_enum.go │ │ ├── msg_type.go │ │ ├── msg_type_bitmask.go │ │ ├── msg_type_bitmask_test.go │ │ ├── msg_type_enum.go │ │ ├── packed_bytes.go │ │ ├── proposal.go │ │ ├── proposal_enum.go │ │ ├── rolllup.go │ │ ├── rolllup_enum.go │ │ ├── staking_log_type.go │ │ ├── staking_log_type_enum.go │ │ ├── status.go │ │ ├── status_enum.go │ │ ├── vested_type.go │ │ └── vested_type_enum.go │ ├── undelegation.go │ ├── validator.go │ ├── vesting_account.go │ ├── vesting_period.go │ ├── views.go │ └── vote.go └── test_suite │ ├── block.go │ └── helpers.go ├── pkg ├── indexer │ ├── config │ │ └── config.go │ ├── decode │ │ ├── context │ │ │ └── context.go │ │ ├── decoder │ │ │ ├── map.go │ │ │ └── map_test.go │ │ ├── event.go │ │ ├── event_test.go │ │ ├── handle │ │ │ ├── authz.go │ │ │ ├── bank.go │ │ │ ├── blob.go │ │ │ ├── coreChannel.go │ │ │ ├── coreClient.go │ │ │ ├── coreConnection.go │ │ │ ├── createAddresses.go │ │ │ ├── createAddresses_test.go │ │ │ ├── crisis.go │ │ │ ├── distribution.go │ │ │ ├── evidence.go │ │ │ ├── fee.go │ │ │ ├── feegrant.go │ │ │ ├── gov.go │ │ │ ├── group.go │ │ │ ├── hyperlane.go │ │ │ ├── ibc.go │ │ │ ├── interchainAccounts.go │ │ │ ├── nft.go │ │ │ ├── qgb.go │ │ │ ├── signal.go │ │ │ ├── slashing.go │ │ │ ├── staking.go │ │ │ ├── test │ │ │ │ ├── authz_test.go │ │ │ │ ├── bank_test.go │ │ │ │ ├── blob_test.go │ │ │ │ ├── coreChannel_test.go │ │ │ │ ├── coreClient_test.go │ │ │ │ ├── coreConnection_test.go │ │ │ │ ├── crisis_test.go │ │ │ │ ├── distribution_test.go │ │ │ │ ├── evidence_test.go │ │ │ │ ├── fee_test.go │ │ │ │ ├── feegrant_test.go │ │ │ │ ├── gov_test.go │ │ │ │ ├── group_test.go │ │ │ │ ├── hyperlane_test.go │ │ │ │ ├── ibc_test.go │ │ │ │ ├── interchainAccounts_test.go │ │ │ │ ├── nft_test.go │ │ │ │ ├── qgb_test.go │ │ │ │ ├── signal_test.go │ │ │ │ ├── slashing_test.go │ │ │ │ ├── staking_test.go │ │ │ │ ├── upgrade_test.go │ │ │ │ └── vesting_test.go │ │ │ ├── upgrade.go │ │ │ └── vesting.go │ │ ├── legacy │ │ │ └── blobstream.go │ │ ├── message.go │ │ ├── message_test.go │ │ ├── tx.go │ │ └── tx_test.go │ ├── genesis │ │ ├── constant.go │ │ ├── denom_metadata.go │ │ ├── genesis.go │ │ ├── genesis_test.go │ │ ├── parse.go │ │ └── save.go │ ├── indexer.go │ ├── parser │ │ ├── blob.go │ │ ├── blob_test.go │ │ ├── event_handlers.go │ │ ├── event_handlers_test.go │ │ ├── events │ │ │ ├── acknowledgement.go │ │ │ ├── acknowledgement_test.go │ │ │ ├── cancel_unbonding.go │ │ │ ├── cancel_unbonding_test.go │ │ │ ├── channel_close.go │ │ │ ├── channel_open_confirm.go │ │ │ ├── channel_open_confirm_test.go │ │ │ ├── channel_open_init.go │ │ │ ├── connection_open_confirm.go │ │ │ ├── connection_open_init.go │ │ │ ├── connection_open_init_test.go │ │ │ ├── create_client.go │ │ │ ├── delegate.go │ │ │ ├── delegate_test.go │ │ │ ├── deposit.go │ │ │ ├── deposit_test.go │ │ │ ├── exec.go │ │ │ ├── exec_test.go │ │ │ ├── handlers.go │ │ │ ├── recv_packet.go │ │ │ ├── recv_packet_test.go │ │ │ ├── redelegate.go │ │ │ ├── redelegate_test.go │ │ │ ├── submit_proposal.go │ │ │ ├── submit_proposal_test.go │ │ │ ├── undelegation.go │ │ │ ├── undelegation_test.go │ │ │ ├── unjail.go │ │ │ ├── unjail_test.go │ │ │ ├── update_client.go │ │ │ ├── vote.go │ │ │ ├── vote_test.go │ │ │ ├── withdraw_rewards.go │ │ │ ├── withdraw_validator_commission.go │ │ │ └── withdraw_validator_commission_test.go │ │ ├── listen.go │ │ ├── parse.go │ │ ├── parseEvents.go │ │ ├── parseEvents_test.go │ │ ├── parseTxs.go │ │ ├── parseTxs_test.go │ │ ├── parser.go │ │ └── parser_test.go │ ├── receiver │ │ ├── genesis.go │ │ ├── receiver.go │ │ ├── receiver_test.go │ │ ├── sequencer.go │ │ ├── sequencer_test.go │ │ ├── sync.go │ │ ├── sync_test.go │ │ └── worker.go │ ├── rollback │ │ ├── balances.go │ │ ├── balances_test.go │ │ ├── message.go │ │ ├── namespace.go │ │ ├── namespace_test.go │ │ ├── rollback.go │ │ ├── rollback_test.go │ │ ├── tx.go │ │ └── validators.go │ └── storage │ │ ├── address.go │ │ ├── address_test.go │ │ ├── block_signature.go │ │ ├── delegations.go │ │ ├── functions.go │ │ ├── functions_test.go │ │ ├── message.go │ │ ├── message_test.go │ │ ├── namespace.go │ │ ├── namespace_test.go │ │ ├── proposals.go │ │ ├── proposals_test.go │ │ ├── state.go │ │ ├── state_test.go │ │ ├── storage.go │ │ ├── storage_test.go │ │ └── validators.go ├── node │ ├── api.go │ ├── api │ │ ├── api.go │ │ └── module_accounts.go │ ├── dal │ │ ├── blob.go │ │ └── node.go │ ├── mock │ │ └── api.go │ ├── rpc │ │ ├── api.go │ │ ├── block.go │ │ ├── genesis.go │ │ ├── getBlockResults.go │ │ ├── head.go │ │ └── status.go │ └── types │ │ ├── blob.go │ │ ├── errors.go │ │ ├── genesis.go │ │ ├── jsonrpc.go │ │ └── status.go └── types │ ├── address.go │ ├── address_test.go │ ├── block.go │ ├── blockData.go │ ├── block_results.go │ ├── hex.go │ ├── hex_test.go │ └── level.go └── test ├── data ├── address.yml ├── apikey.yml ├── balance.yml ├── blob_log.yml ├── block.yml ├── block_signature.yml ├── block_stats.yml ├── celestial.yml ├── celestial_state.yml ├── constant.yml ├── delegation.yml ├── denom_metadata.yml ├── event.yml ├── grant.yml ├── ibc_channel.yml ├── ibc_client.yml ├── ibc_connection.yml ├── ibc_transfer.yml ├── jail.yml ├── message.yml ├── msg_address.yml ├── namespace.yml ├── namespace_message.yml ├── proposal.yml ├── redelegation.yml ├── rollback │ ├── address.yml │ ├── balance.yml │ ├── blob_log.yml │ ├── block.yml │ ├── block_signature.yml │ ├── block_stats.yml │ ├── constant.yml │ ├── delegation.yml │ ├── denom_metadata.yml │ ├── event.yml │ ├── grant.yml │ ├── jail.yml │ ├── message.yml │ ├── msg_address.yml │ ├── namespace.yml │ ├── namespace_message.yml │ ├── redelegation.yml │ ├── rollup.yml │ ├── rollup_provider.yml │ ├── signer.yml │ ├── state.yml │ ├── tx.yml │ ├── undelegation.yml │ ├── validator.yml │ └── vesting_account.yml ├── rollup.yml ├── rollup_provider.yml ├── signer.yml ├── state.yml ├── tx.yml ├── undelegation.yml ├── validator.yml ├── vesting_account.yml └── vote.yml ├── json ├── block_1768659.json ├── genesis.json └── results_1768659.json └── newman ├── env.json └── tests.json /.env.example: -------------------------------------------------------------------------------- 1 | LOG_LEVEL=debug 2 | INDEXER_NAME=celestia_indexer 3 | INDEXER_START_LEVEL=1 4 | INDEXER_BLOCK_PERIOD=15 # seconds 5 | INDEXER_SCRIPTS_DIR= # ONLY FOR LOCAL DEVELOPMENT. DO NOT SET IT IN PRODUCTION 6 | INDEXER_REQUEST_BULK_SIZE=10 7 | CELESTIA_DAL_API_URL= # REQUIRED 8 | CELESTIA_DAL_API_TIMEOUT=30 # seconds 9 | CELESTIA_DAL_API_RPS=10 10 | CELESTIA_NODE_AUTH_TOKEN= # REQUIRED FOR DAL API 11 | CELESTIA_NODE_URL= # REQUIRED 12 | CELESTIA_NODE_RPS=5 13 | CELESTIA_NODE_TIMEOUT=10 # seconds 14 | CELESTIA_NODE_WS_URL= 15 | CELESTIA_NODE_API_URL= # REQUIRED 16 | CELESTIA_NODE_API_TIMEOUT=30 # seconds 17 | CELESTIA_NODE_API_RPS=10 18 | POSTGRES_HOST=db 19 | POSTGRES_PORT=5432 20 | POSTGRES_USER= # REQUIRED 21 | POSTGRES_PASSWORD= # REQUIRED 22 | POSTGRES_DB=celestia 23 | API_RATE_LIMIT=20 24 | API_PROMETHEUS_ENABLED=false 25 | API_REQUEST_TIMEOUT=10 26 | API_WEBSOCKET_ENABLED=true 27 | SENTRY_DSN= 28 | CELENIUM_ENV=production 29 | CELESTIALS_API_URL= 30 | CELESTIALS_API_RPS=3 31 | CELESTIALS_API_TIMEOUT=10 32 | CELESTIALS_CHAIN_ID=celestia-1 33 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | go.sum merge=union 2 | cmd/api/docs/* linguist-generated=true 3 | **/mock/* linguist-generated=true 4 | -------------------------------------------------------------------------------- /.github/workflows/apiwrapper.yml: -------------------------------------------------------------------------------- 1 | name: Generate API 2 | permissions: 3 | contents: read 4 | pull-requests: read 5 | on: 6 | push: 7 | tags: 8 | - 'v*.*.*' 9 | 10 | jobs: 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repo 16 | uses: actions/checkout@v4 17 | 18 | - name: Get latest tag 19 | id: get_tag 20 | run: echo "LATEST_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV 21 | 22 | - name: Set output 23 | id: set_output 24 | run: echo "::set-output name=tag::${{ env.LATEST_TAG }}" 25 | 26 | - name: Request to API wrapper 27 | run: | 28 | curl -X POST \ 29 | -d '{ "event_type": "tag_created", "client_payload": { "tag": "${{ steps.set_output.outputs.tag }}"}}' \ 30 | -H "Content-Type: application/json" \ 31 | -H "Authorization: token ${{ secrets.PAT }}" \ 32 | https://api.github.com/repos/celenium-io/celenium-api-go/dispatches 33 | -------------------------------------------------------------------------------- /.github/workflows/license.yml: -------------------------------------------------------------------------------- 1 | #on: pull_request 2 | on: workflow_dispatch 3 | permissions: 4 | contents: read 5 | pull-requests: read 6 | 7 | jobs: 8 | license-check: 9 | runs-on: ubuntu-latest 10 | name: License Check 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: LavyshAlexander/go-licenses-action@46ff6dea75572b9eb60f6ccb7b600e959b79047c 14 | with: 15 | allowed-licenses: MIT,Apache-1.0,Apache-1.1,Apache-2.0,BSD-2-Clause-FreeBSD,BSD-2-Clause-NetBSD,BSD-2-Clause,BSD-3-Clause-Attribution,BSD-3-Clause-Clear,BSD-3-Clause-LBNL,BSD-3-Clause,BSD-4-Clause,BSD-4-Clause-UC,BSD-Protection,ISC,LGPL-2.0,LGPL-2.1,LGPL-3.0,LGPLLR,MPL-1.0,MPL-1.1,MPL-2.0,Unlicense 16 | ignore: github.com/ethereum/go-ethereum,github.com/regen-network/cosmos-proto,github.com/modern-go/reflect2,golang.org/x/sys/unix,mellium.im/sasl,github.com/klauspost/compress/zstd/internal/xxhash,github.com/mattn/go-sqlite3,github.com/cespare/xxhash/v2,github.com/klauspost/reedsolomon,github.com/klauspost/cpuid/v2,filippo.io/edwards25519/field,github.com/golang/snappy,golang.org/x/crypto/chacha20 17 | -------------------------------------------------------------------------------- /.github/workflows/prerelease.yml: -------------------------------------------------------------------------------- 1 | name: Pre-release 2 | permissions: 3 | contents: write 4 | pull-requests: read 5 | on: 6 | push: 7 | tags: 8 | - 'v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+' 9 | 10 | jobs: 11 | pre-release: 12 | name: Pre-release 13 | runs-on: ubuntu-latest 14 | env: 15 | ACTIONS_ALLOW_UNSECURE_COMMANDS: true 16 | steps: 17 | - name: Check out the repo 18 | uses: actions/checkout@v4 19 | 20 | - name: Set up environment 21 | run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF#refs/*/} 22 | 23 | - name: Publish GitHub release 24 | uses: "marvinpinto/action-automatic-releases@v1.2.1" 25 | with: 26 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 27 | prerelease: true 28 | 29 | sentry: 30 | name: Sentry release 31 | runs-on: ubuntu-latest 32 | env: 33 | ACTIONS_ALLOW_UNSECURE_COMMANDS: true 34 | steps: 35 | - name: Check out the repo 36 | uses: actions/checkout@v4 37 | 38 | - name: Set up environment 39 | run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF#refs/*/} 40 | 41 | - name: Create Sentry release 42 | uses: getsentry/action-release@v1 43 | env: 44 | SENTRY_AUTH_TOKEN: "${{ secrets.SENTRY_AUTH_TOKEN }}" 45 | SENTRY_ORG: "${{ secrets.SENTRY_ORG }}" 46 | SENTRY_PROJECT: "${{ secrets.SENTRY_PROJECT }}" 47 | SENTRY_URL: "${{ secrets.SENTRY_URL }}" 48 | with: 49 | environment: production 50 | version: "${{ env.RELEASE_VERSION }}" 51 | ignore_empty: true 52 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | permissions: 3 | contents: write 4 | pull-requests: read 5 | on: 6 | push: 7 | tags: 8 | - 'v[0-9]+.[0-9]+.[0-9]+' 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | env: 15 | ACTIONS_ALLOW_UNSECURE_COMMANDS: true 16 | steps: 17 | - name: Check out the repo 18 | uses: actions/checkout@v4 19 | 20 | - name: Set up environment 21 | run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF#refs/*/} 22 | 23 | - name: Publish GitHub release 24 | uses: "marvinpinto/action-automatic-releases@v1.2.1" 25 | with: 26 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 27 | prerelease: false 28 | 29 | sentry: 30 | name: Sentry release 31 | runs-on: ubuntu-latest 32 | env: 33 | ACTIONS_ALLOW_UNSECURE_COMMANDS: true 34 | steps: 35 | - name: Check out the repo 36 | uses: actions/checkout@v4 37 | 38 | - name: Set up environment 39 | run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF#refs/*/} 40 | 41 | - name: Create Sentry release 42 | uses: getsentry/action-release@v1 43 | env: 44 | SENTRY_AUTH_TOKEN: "${{ secrets.SENTRY_AUTH_TOKEN }}" 45 | SENTRY_ORG: "${{ secrets.SENTRY_ORG }}" 46 | SENTRY_PROJECT: "${{ secrets.SENTRY_PROJECT }}" 47 | SENTRY_URL: "${{ secrets.SENTRY_URL }}" 48 | with: 49 | environment: production 50 | version: "${{ env.RELEASE_VERSION }}" 51 | ignore_empty: true 52 | 53 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | 4 | name: Tests 5 | permissions: 6 | contents: read 7 | pull-requests: read 8 | jobs: 9 | golangci: 10 | name: Linter 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | - name: Set up Go 16 | uses: actions/setup-go@v5 17 | with: 18 | go-version: '1.24' 19 | cache: false 20 | - name: golangci-lint 21 | uses: golangci/golangci-lint-action@v6 22 | with: 23 | version: v1.64.6 24 | args: --timeout=5m 25 | test: 26 | name: Test 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout code 30 | uses: actions/checkout@v4 31 | 32 | - name: Install Go 33 | uses: actions/setup-go@v5 34 | with: 35 | go-version: 1.24.x 36 | 37 | - name: Golang tests 38 | env: 39 | GO111MODULE: on 40 | run: | 41 | go mod download 42 | go test -p 8 ./... 43 | 44 | - name: Update coverage report 45 | uses: ncruces/go-coverage-report@v0.2.5 46 | with: 47 | report: 'true' 48 | chart: 'true' 49 | amend: 'true' 50 | reuse-go: 'true' 51 | continue-on-error: true 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | bin 11 | .DS_Store 12 | 13 | # Test binary, built with `go test -c` 14 | *.test 15 | 16 | # Output of the go coverage tool, specifically when used with LiteIDE 17 | *.out 18 | 19 | # Dependency directories (remove the comment below to include it) 20 | # vendor/ 21 | 22 | # Go workspace file 23 | go.work 24 | 25 | *.env 26 | 27 | # IDE and other 3rd party tools 28 | .idea 29 | *.private.* 30 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable: 3 | - goconst 4 | - gocritic 5 | - gofmt 6 | - govet 7 | - prealloc 8 | - unconvert 9 | - unused 10 | - errcheck 11 | - ineffassign 12 | - containedctx 13 | - usetesting 14 | - musttag 15 | - mirror 16 | - tagalign 17 | - zerologlint 18 | - copyloopvar 19 | - gosec 20 | - misspell 21 | - noctx 22 | - prealloc 23 | - protogetter 24 | - makezero 25 | linters-settings: 26 | gosec: 27 | excludes: 28 | - G115 -------------------------------------------------------------------------------- /.run/Clear.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.run/Test.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.run/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "docker compose up -d db", 4 | "stop": "docker compose down -v" 5 | } 6 | } -------------------------------------------------------------------------------- /.run/requests/address.http: -------------------------------------------------------------------------------- 1 | ################################################ Local 2 | 3 | ### GET All indexed addresses 4 | http://{{host}}/v1/address 5 | 6 | ### Get count of all indexed addresses 7 | http://{{host}}/v1/address/count 8 | 9 | ### Get specific address data 10 | http://{{host}}/v1/address/celestia1z4909eqzfzngegw43tr2vle7d69fhww4hmmusw 11 | 12 | ### Get specific address transactions 13 | http://{{host}}/v1/address/celestia1z4909eqzfzngegw43tr2vle7d69fhww4hmmusw/txs 14 | 15 | ### Get specific address messages 16 | http://{{host}}/v1/address/celestia1z4909eqzfzngegw43tr2vle7d69fhww4hmmusw/messages 17 | -------------------------------------------------------------------------------- /.run/requests/block.http: -------------------------------------------------------------------------------- 1 | ### Get blocks list 2 | http://{{host}}/v1/block 3 | 4 | ### Get blocks count 5 | http://{{host}}/v1/block/count 6 | 7 | ### Get genesis block 8 | http://{{host}}/v1/block/0 9 | 10 | ### Get block by height 11 | http://{{host}}/v1/block/12 12 | 13 | ### Get block events 14 | http://{{host}}/v1/block/20345/events 15 | 16 | ### Get block stats 17 | http://{{host}}/v1/block/20345/stats 18 | 19 | ### Get block namespaces 20 | http://{{host}}/v1/block/20345/namespace 21 | 22 | ### Get block namespaces count 23 | http://{{host}}/v1/block/20345/namespace/count 24 | 25 | -------------------------------------------------------------------------------- /.run/requests/namespace.http: -------------------------------------------------------------------------------- 1 | ### GET namespace messages 2 | http://{{host}}/v1/namespace/00000000000000000000000000000000000042690c204d39600fddd3/0/messages?limit=2 3 | 4 | ### GET namespace messages 5 | http://{{host}}/v1/namespace/00000000000000000000000000000000000042690c204d39600fddd3/0/messages?limit=2 6 | 7 | -------------------------------------------------------------------------------- /.run/requests/stats.http: -------------------------------------------------------------------------------- 1 | ### Get validators 2 | http://{{host}}/v1/summary/validator/count -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch API", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "debug", 12 | "program": "${workspaceFolder}/cmd/api", 13 | "args": [ 14 | "-c", 15 | "../../configs/dipdup.yml", 16 | ], 17 | "envFile": "${workspaceFolder}/.env" 18 | }, { 19 | "name": "Launch Indexer", 20 | "type": "go", 21 | "request": "launch", 22 | "mode": "debug", 23 | "program": "${workspaceFolder}/cmd/indexer", 24 | "args": [ 25 | "-c", 26 | "../../configs/dipdup.yml", 27 | ], 28 | "envFile": "${workspaceFolder}/.env" 29 | }, { 30 | "name": "Launch Celestials", 31 | "type": "go", 32 | "request": "launch", 33 | "mode": "debug", 34 | "program": "${workspaceFolder}/cmd/celestials", 35 | "args": [ 36 | "-c", 37 | "../../configs/dipdup.yml", 38 | ], 39 | "envFile": "${workspaceFolder}/.env" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /HEADER: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2024 PK Lab AG 2 | SPDX-License-Identifier: MIT -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 DipDup 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build/api/Dockerfile: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------- 2 | # The first stage container, for building the application 3 | # --------------------------------------------------------------------- 4 | FROM golang:1.24.3-alpine AS builder 5 | 6 | ENV CGO_ENABLED=0 7 | ENV GO111MODULE=on 8 | ENV GOOS=linux 9 | 10 | RUN apk --no-cache add ca-certificates 11 | 12 | RUN mkdir -p $GOPATH/src/github.com/celenium-io/celestia-indexer/ 13 | 14 | COPY ./go.* $GOPATH/src/github.com/celenium-io/celestia-indexer/ 15 | WORKDIR $GOPATH/src/github.com/celenium-io/celestia-indexer 16 | RUN go mod download 17 | 18 | COPY cmd/api cmd/api 19 | COPY internal internal 20 | COPY pkg pkg 21 | 22 | WORKDIR $GOPATH/src/github.com/celenium-io/celestia-indexer/cmd/api/ 23 | RUN go build -a -installsuffix cgo -o /go/bin/api . 24 | 25 | # --------------------------------------------------------------------- 26 | # The second stage container, for running the application 27 | # --------------------------------------------------------------------- 28 | FROM scratch 29 | 30 | WORKDIR /app/api 31 | 32 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 33 | COPY --from=builder /go/bin/api /go/bin/api 34 | COPY ./configs/dipdup.yml ./ 35 | COPY database database 36 | COPY cmd/api/docs docs 37 | 38 | ENTRYPOINT ["/go/bin/api", "-c", "dipdup.yml"] -------------------------------------------------------------------------------- /build/celestials/Dockerfile: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------- 2 | # The first stage container, for building the application 3 | # --------------------------------------------------------------------- 4 | FROM golang:1.24.3-alpine AS builder 5 | 6 | ENV CGO_ENABLED=0 7 | ENV GO111MODULE=on 8 | ENV GOOS=linux 9 | 10 | RUN apk --no-cache add ca-certificates 11 | 12 | RUN mkdir -p $GOPATH/src/github.com/celenium-io/celestia-indexer/ 13 | 14 | COPY ./go.* $GOPATH/src/github.com/celenium-io/celestia-indexer/ 15 | WORKDIR $GOPATH/src/github.com/celenium-io/celestia-indexer 16 | RUN go mod download 17 | 18 | COPY cmd/celestials cmd/celestials 19 | COPY internal internal 20 | COPY pkg pkg 21 | 22 | WORKDIR $GOPATH/src/github.com/celenium-io/celestia-indexer/cmd/celestials/ 23 | RUN go build -a -installsuffix cgo -o /go/bin/celestials . 24 | 25 | # --------------------------------------------------------------------- 26 | # The second stage container, for running the application 27 | # --------------------------------------------------------------------- 28 | FROM scratch 29 | 30 | WORKDIR /app/celestials 31 | 32 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 33 | COPY --from=builder /go/bin/celestials /go/bin/celestials 34 | COPY ./configs/dipdup.yml ./ 35 | COPY database database 36 | 37 | ENTRYPOINT ["/go/bin/celestials", "-c", "dipdup.yml"] -------------------------------------------------------------------------------- /build/indexer/Dockerfile: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------- 2 | # The first stage container, for building the application 3 | # --------------------------------------------------------------------- 4 | FROM golang:1.24.3-alpine AS builder 5 | 6 | ENV CGO_ENABLED=0 7 | ENV GO111MODULE=on 8 | ENV GOOS=linux 9 | 10 | RUN apk --no-cache add ca-certificates 11 | 12 | RUN mkdir -p $GOPATH/src/github.com/celenium-io/celestia-indexer/ 13 | 14 | COPY ./go.* $GOPATH/src/github.com/celenium-io/celestia-indexer/ 15 | WORKDIR $GOPATH/src/github.com/celenium-io/celestia-indexer 16 | RUN go mod download 17 | 18 | COPY cmd/indexer cmd/indexer 19 | COPY internal internal 20 | COPY pkg pkg 21 | 22 | WORKDIR $GOPATH/src/github.com/celenium-io/celestia-indexer/cmd/indexer/ 23 | RUN go build -a -o /go/bin/indexer . 24 | 25 | # --------------------------------------------------------------------- 26 | # The second stage container, for running the application 27 | # --------------------------------------------------------------------- 28 | FROM scratch 29 | 30 | WORKDIR /app/celestia-indexer/ 31 | 32 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 33 | COPY --from=builder /go/bin/indexer /go/bin/indexer 34 | COPY configs/dipdup.yml ./ 35 | COPY database database 36 | 37 | ENTRYPOINT ["/go/bin/indexer", "-c", "dipdup.yml"] -------------------------------------------------------------------------------- /cmd/api/admin_middleware.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package main 5 | 6 | import ( 7 | "net/http" 8 | 9 | "github.com/celenium-io/celestia-indexer/cmd/api/handler" 10 | "github.com/celenium-io/celestia-indexer/internal/storage" 11 | "github.com/labstack/echo/v4" 12 | ) 13 | 14 | var accessDeniedErr = echo.Map{ 15 | "error": "access denied", 16 | } 17 | 18 | func AdminMiddleware() echo.MiddlewareFunc { 19 | return checkOnAdminPermission 20 | } 21 | 22 | func checkOnAdminPermission(next echo.HandlerFunc) echo.HandlerFunc { 23 | return func(ctx echo.Context) error { 24 | val := ctx.Get(handler.ApiKeyName) 25 | apiKey, ok := val.(storage.ApiKey) 26 | if !ok { 27 | return ctx.JSON(http.StatusForbidden, accessDeniedErr) 28 | } 29 | if !apiKey.Admin { 30 | return ctx.JSON(http.StatusForbidden, accessDeniedErr) 31 | } 32 | return next(ctx) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /cmd/api/bus/observer.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package bus 5 | 6 | import ( 7 | "github.com/celenium-io/celestia-indexer/internal/storage" 8 | "github.com/dipdup-io/workerpool" 9 | ) 10 | 11 | type Observer struct { 12 | blocks chan *storage.Block 13 | state chan *storage.State 14 | 15 | listenBlocks bool 16 | listenHead bool 17 | 18 | g workerpool.Group 19 | } 20 | 21 | func NewObserver(channels ...string) *Observer { 22 | if len(channels) == 0 { 23 | return nil 24 | } 25 | 26 | observer := &Observer{ 27 | blocks: make(chan *storage.Block, 1024), 28 | state: make(chan *storage.State, 1024), 29 | g: workerpool.NewGroup(), 30 | } 31 | 32 | for i := range channels { 33 | switch channels[i] { 34 | case storage.ChannelBlock: 35 | observer.listenBlocks = true 36 | case storage.ChannelHead: 37 | observer.listenHead = true 38 | } 39 | } 40 | 41 | return observer 42 | } 43 | 44 | func (observer Observer) Close() error { 45 | observer.g.Wait() 46 | close(observer.blocks) 47 | close(observer.state) 48 | return nil 49 | } 50 | 51 | func (observer Observer) notifyBlocks(block *storage.Block) { 52 | if observer.listenBlocks { 53 | observer.blocks <- block 54 | } 55 | } 56 | 57 | func (observer Observer) notifyState(state *storage.State) { 58 | if observer.listenHead { 59 | observer.state <- state 60 | } 61 | } 62 | 63 | func (observer Observer) Blocks() <-chan *storage.Block { 64 | return observer.blocks 65 | } 66 | 67 | func (observer Observer) Head() <-chan *storage.State { 68 | return observer.state 69 | } 70 | -------------------------------------------------------------------------------- /cmd/api/cache/interface.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package cache 5 | 6 | import ( 7 | "context" 8 | "io" 9 | "time" 10 | ) 11 | 12 | type ICache interface { 13 | io.Closer 14 | 15 | Get(ctx context.Context, key string) (string, bool) 16 | Set(ctx context.Context, key string, data string, f ExpirationFunc) error 17 | } 18 | 19 | type ExpirationFunc func() time.Duration 20 | -------------------------------------------------------------------------------- /cmd/api/cache/valkey.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package cache 5 | 6 | import ( 7 | "context" 8 | "time" 9 | 10 | "github.com/pkg/errors" 11 | valkey "github.com/valkey-io/valkey-go" 12 | ) 13 | 14 | var _ ICache = (*ValKey)(nil) 15 | 16 | type ValKey struct { 17 | client valkey.Client 18 | ttlSeconds int64 19 | } 20 | 21 | func NewValKey(url string, ttl time.Duration) (*ValKey, error) { 22 | opts, err := valkey.ParseURL(url) 23 | if err != nil { 24 | return nil, errors.Wrap(err, "parse valkey url") 25 | } 26 | client, err := valkey.NewClient(opts) 27 | if err != nil { 28 | return nil, errors.Wrap(err, "create valkey client") 29 | } 30 | 31 | return &ValKey{ 32 | client: client, 33 | ttlSeconds: int64(ttl.Seconds()), 34 | }, nil 35 | } 36 | 37 | func (c *ValKey) Get(ctx context.Context, key string) (data string, found bool) { 38 | val, err := c.client.Do( 39 | ctx, c.client.B().Get().Key(key).Build(), 40 | ).ToString() 41 | return val, err == nil 42 | } 43 | 44 | func (c *ValKey) Set(ctx context.Context, key string, data string, expirationFunc ExpirationFunc) error { 45 | expiredAt := c.ttlSeconds 46 | if expirationFunc != nil { 47 | expiredAt = int64(expirationFunc().Seconds()) 48 | } 49 | 50 | return c.client.Do( 51 | ctx, 52 | c.client.B().Set().Key(key).Value(data).ExSeconds(expiredAt).Build(), 53 | ).Error() 54 | } 55 | 56 | func (c *ValKey) Close() error { 57 | c.client.Close() 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /cmd/api/config.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package main 5 | 6 | import ( 7 | "github.com/celenium-io/celestia-indexer/internal/profiler" 8 | indexerConfig "github.com/celenium-io/celestia-indexer/pkg/indexer/config" 9 | "github.com/dipdup-net/go-lib/config" 10 | ) 11 | 12 | type Config struct { 13 | *config.Config `yaml:",inline"` 14 | LogLevel string `validate:"omitempty,oneof=debug trace info warn error fatal panic" yaml:"log_level"` 15 | ApiConfig ApiConfig `validate:"required" yaml:"api"` 16 | Profiler *profiler.Config `validate:"omitempty" yaml:"profiler"` 17 | Indexer indexerConfig.Indexer `validate:"required" yaml:"indexer"` 18 | Environment string `validate:"omitempty,oneof=development production" yaml:"environment"` 19 | } 20 | 21 | type ApiConfig struct { 22 | Bind string `validate:"required,hostname_port" yaml:"bind"` 23 | RateLimit float64 `validate:"omitempty,min=0" yaml:"rate_limit"` 24 | Prometheus bool `validate:"omitempty" yaml:"prometheus"` 25 | RequestTimeout int `validate:"omitempty,min=1" yaml:"request_timeout"` 26 | BlobReceiver string `validate:"required" yaml:"blob_receiver"` 27 | SentryDsn string `validate:"omitempty" yaml:"sentry_dsn"` 28 | Websocket bool `validate:"omitempty" yaml:"websocket"` 29 | Cache string `validate:"omitempty,url" yaml:"cache"` 30 | } 31 | -------------------------------------------------------------------------------- /cmd/api/gas/data.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package gas 5 | 6 | import ( 7 | "sync" 8 | 9 | "github.com/shopspring/decimal" 10 | ) 11 | 12 | type GasPrice struct { 13 | Slow string 14 | Median string 15 | Fast string 16 | } 17 | 18 | type info struct { 19 | Height uint64 20 | Percentiles []decimal.Decimal 21 | TxCount int64 22 | GasUsed int64 23 | GasWanted int64 24 | Fee decimal.Decimal 25 | GasUsedRatio decimal.Decimal 26 | BlockOccupancy float64 27 | } 28 | 29 | type queue struct { 30 | data []info 31 | capacity int 32 | mx *sync.RWMutex 33 | } 34 | 35 | func newQueue(capacity int) *queue { 36 | return &queue{ 37 | data: make([]info, 0), 38 | capacity: capacity, 39 | mx: new(sync.RWMutex), 40 | } 41 | } 42 | 43 | func (q *queue) Push(item info) { 44 | q.mx.Lock() 45 | if len(q.data) == q.capacity { 46 | q.data = q.data[:len(q.data)-2] 47 | } 48 | q.data = append([]info{item}, q.data...) 49 | q.mx.Unlock() 50 | } 51 | 52 | func (q *queue) Range(handler func(item info) (bool, error)) error { 53 | if handler == nil { 54 | return nil 55 | } 56 | 57 | q.mx.RLock() 58 | defer q.mx.RUnlock() 59 | 60 | for i := range q.data { 61 | br, err := handler(q.data[i]) 62 | if err != nil { 63 | return err 64 | } 65 | if br { 66 | return nil 67 | } 68 | } 69 | return nil 70 | } 71 | 72 | func (q *queue) Size() int { 73 | q.mx.RLock() 74 | defer q.mx.RUnlock() 75 | 76 | return len(q.data) 77 | } 78 | -------------------------------------------------------------------------------- /cmd/api/gas/data_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package gas 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestQueue(t *testing.T) { 13 | q := newQueue(10) 14 | 15 | for i := 0; i < 10000; i++ { 16 | q.Push(info{ 17 | Height: uint64(i), 18 | TxCount: 2, 19 | }) 20 | } 21 | 22 | var totalTx int64 23 | err := q.Range(func(item info) (bool, error) { 24 | totalTx += item.TxCount 25 | return false, nil 26 | }) 27 | require.NoError(t, err) 28 | require.EqualValues(t, 20, totalTx) 29 | } 30 | -------------------------------------------------------------------------------- /cmd/api/gas/interface.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package gas 5 | 6 | import ( 7 | "context" 8 | "io" 9 | ) 10 | 11 | //go:generate mockgen -source=$GOFILE -destination=mock.go -package=gas -typed 12 | type ITracker interface { 13 | io.Closer 14 | 15 | Start(ctx context.Context) 16 | Init(ctx context.Context) error 17 | SubscribeOnCompute(handler ComputeHandler) 18 | State() GasPrice 19 | } 20 | -------------------------------------------------------------------------------- /cmd/api/handler/responses.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package handler 5 | 6 | import ( 7 | "net/http" 8 | 9 | "github.com/labstack/echo/v4" 10 | ) 11 | 12 | func returnArray[T any](c echo.Context, arr []T) error { 13 | if arr == nil { 14 | return c.JSON(http.StatusOK, []any{}) 15 | } 16 | 17 | return c.JSON(http.StatusOK, arr) 18 | } 19 | -------------------------------------------------------------------------------- /cmd/api/handler/responses/blobProof.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package responses 5 | 6 | import ( 7 | "github.com/celestiaorg/celestia-app/v4/pkg/proof" 8 | ) 9 | 10 | type BlobProof struct { 11 | Start int32 `example:"0" format:"integer" json:"start" swaggertype:"integer"` 12 | End int32 `example:"16" format:"integer" json:"end" swaggertype:"integer"` 13 | Nodes [][]byte `json:"nodes"` 14 | } 15 | 16 | func NewProofs(proofs []*proof.NMTProof) []BlobProof { 17 | result := make([]BlobProof, len(proofs)) 18 | for i := range proofs { 19 | result[i] = BlobProof{ 20 | Start: proofs[i].Start, 21 | End: proofs[i].End, 22 | Nodes: proofs[i].Nodes, 23 | } 24 | } 25 | return result 26 | } 27 | -------------------------------------------------------------------------------- /cmd/api/handler/responses/constants_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package responses 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func Test_roundCounstant(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | val string 16 | want string 17 | }{ 18 | { 19 | name: "test 1", 20 | val: "0.000000000000000000", 21 | want: "0", 22 | }, { 23 | name: "test 2", 24 | val: "0.334000000000000000", 25 | want: "0.334", 26 | }, { 27 | name: "test 3", 28 | val: "utia", 29 | want: "utia", 30 | }, { 31 | name: "test 4", 32 | val: "604800s", 33 | want: "604800s", 34 | }, { 35 | name: "test 5", 36 | val: "10000", 37 | want: "10000", 38 | }, 39 | } 40 | for _, tt := range tests { 41 | t.Run(tt.name, func(t *testing.T) { 42 | got := roundCounstant(tt.val) 43 | require.Equal(t, tt.want, got) 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /cmd/api/handler/responses/event.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package responses 5 | 6 | import ( 7 | pkgTypes "github.com/celenium-io/celestia-indexer/pkg/types" 8 | "time" 9 | 10 | "github.com/celenium-io/celestia-indexer/internal/storage" 11 | "github.com/celenium-io/celestia-indexer/internal/storage/types" 12 | ) 13 | 14 | type Event struct { 15 | Id uint64 `example:"321" format:"int64" json:"id" swaggertype:"integer"` 16 | Height pkgTypes.Level `example:"100" format:"int64" json:"height" swaggertype:"integer"` 17 | Time time.Time `example:"2023-07-04T03:10:57+00:00" format:"date-time" json:"time" swaggertype:"string"` 18 | Position int64 `example:"1" format:"int64" json:"position" swaggertype:"integer"` 19 | TxId uint64 `example:"11" format:"int64" json:"tx_id,omitempty" swaggertype:"integer"` 20 | 21 | Type types.EventType `example:"commission" json:"type"` 22 | 23 | Data map[string]any `json:"data"` 24 | } 25 | 26 | func NewEvent(event storage.Event) Event { 27 | result := Event{ 28 | Id: event.Id, 29 | Height: event.Height, 30 | Time: event.Time, 31 | Position: event.Position, 32 | Type: event.Type, 33 | Data: event.Data, 34 | } 35 | 36 | if event.TxId != nil { 37 | result.TxId = *event.TxId 38 | } 39 | 40 | return result 41 | } 42 | -------------------------------------------------------------------------------- /cmd/api/handler/responses/gas.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package responses 5 | 6 | type GasPrice struct { 7 | Slow string `example:"0.1234" format:"string" json:"slow" swaggertype:"string"` 8 | Median string `example:"0.1234" format:"string" json:"median" swaggertype:"string"` 9 | Fast string `example:"0.1234" format:"string" json:"fast" swaggertype:"string"` 10 | } 11 | -------------------------------------------------------------------------------- /cmd/api/handler/responses/histogram.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package responses 5 | 6 | import ( 7 | "time" 8 | 9 | "github.com/celenium-io/celestia-indexer/internal/storage" 10 | ) 11 | 12 | type HistogramItem struct { 13 | Time time.Time `example:"2023-07-04T03:10:57+00:00" format:"date-time" json:"time" swaggertype:"string"` 14 | Value string `example:"2223424" format:"string" json:"value" swaggertype:"string"` 15 | } 16 | 17 | func NewHistogramItem(item storage.HistogramItem) HistogramItem { 18 | return HistogramItem{ 19 | Time: item.Time, 20 | Value: item.Value, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /cmd/api/handler/responses/namespace_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package responses 5 | 6 | import ( 7 | "encoding/hex" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func Test_decodeName(t *testing.T) { 14 | tests := []struct { 15 | name string 16 | nsId string 17 | want string 18 | }{ 19 | { 20 | name: "test 1", 21 | nsId: "6d656d6573", 22 | want: "memes", 23 | }, { 24 | name: "test 2", 25 | nsId: "0000000000000000000000000000006d656d6573", 26 | want: "memes", 27 | }, { 28 | name: "test 3", 29 | nsId: "0000000000000000000000000000000000000000e6edd3ffbef8c7d8", 30 | want: "e6edd3ffbef8c7d8", 31 | }, { 32 | name: "test 4", 33 | nsId: "00000000000000000000000000000000000000e6edd3ffbef8c700d8", 34 | want: "e6edd3ffbef8c700d8", 35 | }, { 36 | name: "test 5", 37 | nsId: "00000000000000000000000000000000006d656d657300", 38 | want: "6d656d657300", 39 | }, 40 | } 41 | for _, tt := range tests { 42 | t.Run(tt.name, func(t *testing.T) { 43 | decoded, err := hex.DecodeString(tt.nsId) 44 | require.NoError(t, err) 45 | 46 | got := decodeName(decoded) 47 | require.Equal(t, tt.want, got) 48 | }) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /cmd/api/handler/responses/search.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package responses 5 | 6 | type SearchItem struct { 7 | // Result type which is in the result. Can be 'block', 'address', 'namespace', 'tx', 'validator', 'rollup' 8 | Type string `json:"type"` 9 | 10 | // Search result. Can be one of folowwing types: Block, Address, Namespace, Tx, Validator, Rollup 11 | Result any `json:"result" swaggertype:"object"` 12 | } 13 | -------------------------------------------------------------------------------- /cmd/api/handler/state.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package handler 5 | 6 | import ( 7 | "net/http" 8 | 9 | "github.com/celenium-io/celestia-indexer/cmd/api/handler/responses" 10 | "github.com/celenium-io/celestia-indexer/internal/storage" 11 | "github.com/labstack/echo/v4" 12 | ) 13 | 14 | type StateHandler struct { 15 | state storage.IState 16 | validator storage.IValidator 17 | indexerName string 18 | } 19 | 20 | func NewStateHandler(state storage.IState, validator storage.IValidator, indexerName string) *StateHandler { 21 | return &StateHandler{ 22 | state: state, 23 | validator: validator, 24 | indexerName: indexerName, 25 | } 26 | } 27 | 28 | // Head godoc 29 | // 30 | // @Summary Get current indexer head 31 | // @Description Get current indexer head 32 | // @Tags general 33 | // @ID head 34 | // @Produce json 35 | // @Success 200 {object} responses.State 36 | // @Success 204 37 | // @Failure 400 {object} Error 38 | // @Failure 500 {object} Error 39 | // @Router /head [get] 40 | func (sh *StateHandler) Head(c echo.Context) error { 41 | state, err := sh.state.ByName(c.Request().Context(), sh.indexerName) 42 | if err != nil { 43 | return handleError(c, err, sh.state) 44 | } 45 | 46 | votingPower, err := sh.validator.TotalVotingPower(c.Request().Context()) 47 | if err != nil { 48 | return handleError(c, err, sh.state) 49 | } 50 | state.TotalVotingPower = votingPower 51 | 52 | return c.JSON(http.StatusOK, responses.NewState(state)) 53 | } 54 | -------------------------------------------------------------------------------- /cmd/api/handler/websocket/channel.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package websocket 5 | 6 | import ( 7 | sdkSync "github.com/dipdup-net/indexer-sdk/pkg/sync" 8 | 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | type processor[I any, M INotification] func(data I) Notification[M] 13 | 14 | type Channel[I any, M INotification] struct { 15 | clients *sdkSync.Map[uint64, client] 16 | processor processor[I, M] 17 | filters Filterable[M] 18 | } 19 | 20 | func NewChannel[I any, M INotification](processor processor[I, M], filters Filterable[M]) *Channel[I, M] { 21 | return &Channel[I, M]{ 22 | clients: sdkSync.NewMap[uint64, client](), 23 | processor: processor, 24 | filters: filters, 25 | } 26 | } 27 | 28 | func (channel *Channel[I, M]) AddClient(c client) { 29 | channel.clients.Set(c.Id(), c) 30 | } 31 | 32 | func (channel *Channel[I, M]) RemoveClient(id uint64) { 33 | channel.clients.Delete(id) 34 | } 35 | 36 | func (channel *Channel[I, M]) processMessage(msg I) error { 37 | if channel.clients.Len() == 0 { 38 | return nil 39 | } 40 | 41 | data := channel.processor(msg) 42 | 43 | if err := channel.clients.Range(func(_ uint64, value client) (error, bool) { 44 | if channel.filters.Filter(value, data) { 45 | value.Notify(data) 46 | } 47 | return nil, false 48 | }); err != nil { 49 | return errors.Wrap(err, "write message to client") 50 | } 51 | 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /cmd/api/handler/websocket/client_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package websocket 5 | 6 | import ( 7 | "log" 8 | "net/http" 9 | "net/http/httptest" 10 | "runtime" 11 | "testing" 12 | 13 | "github.com/labstack/echo/v4" 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | func TestNotifyClosedClient(t *testing.T) { 18 | client := newClient(10, nil, nil) 19 | err := client.Close() 20 | require.NoError(t, err, "closing client") 21 | client.Notify("test") 22 | } 23 | 24 | func BenchmarkHandle(b *testing.B) { 25 | e := echo.New() 26 | manager := NewManager(nil) 27 | for i := 0; i < b.N; i++ { 28 | req := httptest.NewRequest(http.MethodGet, "/", nil) 29 | rec := httptest.NewRecorder() 30 | c := e.NewContext(req, rec) 31 | 32 | _ = manager.Handle(c) 33 | } 34 | 35 | var rtm runtime.MemStats 36 | runtime.ReadMemStats(&rtm) 37 | log.Println("Alloc", rtm.Alloc) 38 | log.Println("Frees", rtm.Frees) 39 | log.Println("Heap alloc", rtm.HeapAlloc) 40 | log.Println("Heap in use", rtm.HeapInuse) 41 | log.Println("last GC", rtm.LastGC) 42 | } 43 | -------------------------------------------------------------------------------- /cmd/api/handler/websocket/errors.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package websocket 5 | 6 | import "errors" 7 | 8 | var ( 9 | ErrUnknownMethod = errors.New("unknown method") 10 | ErrUnknownChannel = errors.New("unknown channel") 11 | ErrUnavailableFilter = errors.New("unknown filter value") 12 | ) 13 | -------------------------------------------------------------------------------- /cmd/api/handler/websocket/filters.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package websocket 5 | 6 | import ( 7 | "github.com/celenium-io/celestia-indexer/cmd/api/handler/responses" 8 | ) 9 | 10 | type Filterable[M INotification] interface { 11 | Filter(c client, msg Notification[M]) bool 12 | } 13 | 14 | type BlockFilter struct{} 15 | 16 | func (f BlockFilter) Filter(c client, msg Notification[*responses.Block]) bool { 17 | if msg.Body == nil { 18 | return false 19 | } 20 | fltrs := c.Filters() 21 | if fltrs == nil { 22 | return false 23 | } 24 | return fltrs.blocks 25 | } 26 | 27 | type HeadFilter struct{} 28 | 29 | func (f HeadFilter) Filter(c client, msg Notification[*responses.State]) bool { 30 | if msg.Body == nil { 31 | return false 32 | } 33 | fltrs := c.Filters() 34 | if fltrs == nil { 35 | return false 36 | } 37 | return fltrs.head 38 | } 39 | 40 | type GasPriceFilter struct{} 41 | 42 | func (f GasPriceFilter) Filter(c client, msg Notification[*responses.GasPrice]) bool { 43 | if msg.Body == nil { 44 | return false 45 | } 46 | fltrs := c.Filters() 47 | if fltrs == nil { 48 | return false 49 | } 50 | return fltrs.gasPrice 51 | } 52 | 53 | type Filters struct { 54 | head bool 55 | blocks bool 56 | gasPrice bool 57 | } 58 | -------------------------------------------------------------------------------- /cmd/api/handler/websocket/processors.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package websocket 5 | 6 | import ( 7 | "github.com/celenium-io/celestia-indexer/cmd/api/gas" 8 | "github.com/celenium-io/celestia-indexer/cmd/api/handler/responses" 9 | "github.com/celenium-io/celestia-indexer/internal/storage" 10 | ) 11 | 12 | func blockProcessor(block storage.Block) Notification[*responses.Block] { 13 | response := responses.NewBlock(block, true) 14 | return NewBlockNotification(response) 15 | } 16 | 17 | func headProcessor(state storage.State) Notification[*responses.State] { 18 | response := responses.NewState(state) 19 | return NewStateNotification(response) 20 | } 21 | 22 | func gasPriceProcessor(data gas.GasPrice) Notification[*responses.GasPrice] { 23 | return NewGasPriceNotification(responses.GasPrice{ 24 | Slow: data.Slow, 25 | Median: data.Median, 26 | Fast: data.Fast, 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /cmd/api/init_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package main 5 | 6 | import ( 7 | "net/http" 8 | "net/http/httptest" 9 | "testing" 10 | 11 | "github.com/labstack/echo/v4" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func Test_observableCacheSkipper(t *testing.T) { 16 | e := echo.New() 17 | 18 | tests := []struct { 19 | name string 20 | path string 21 | method string 22 | want bool 23 | }{ 24 | { 25 | name: "test 1", 26 | path: "/v1/ws", 27 | method: http.MethodGet, 28 | want: true, 29 | }, { 30 | name: "test 2", 31 | path: "/metrics", 32 | method: http.MethodGet, 33 | want: true, 34 | }, { 35 | // name: "test 3", 36 | // path: "/v1/block/:height", 37 | // method: http.MethodGet, 38 | // want: true, 39 | // }, { 40 | // name: "test 4", 41 | // path: "/v1/tx/:hash", 42 | // method: http.MethodGet, 43 | // want: true, 44 | // }, { 45 | name: "test 5", 46 | path: "/v1/some_post", 47 | method: http.MethodPost, 48 | want: true, 49 | }, { 50 | name: "test 6", 51 | path: "/v1/valid", 52 | method: http.MethodGet, 53 | want: false, 54 | }, 55 | } 56 | for _, tt := range tests { 57 | t.Run(tt.name, func(t *testing.T) { 58 | req := httptest.NewRequest(tt.method, tt.path, nil) 59 | rec := httptest.NewRecorder() 60 | c := e.NewContext(req, rec) 61 | c.SetPath(tt.path) 62 | 63 | got := observableCacheSkipper(c) 64 | require.Equal(t, tt.want, got) 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /cmd/api/markdown/histogram.md: -------------------------------------------------------------------------------- 1 | Returns histogram by table, function and timeframe 2 | 3 | ### Parameters 4 | 5 | `table`, `function` and `column` parameters are the same as summary endpoint. 6 | 7 | 8 | ### Timeframe 9 | 10 | * `hour` 11 | * `day` 12 | * `week` 13 | * `month` 14 | * `year` -------------------------------------------------------------------------------- /cmd/api/markdown/summary.md: -------------------------------------------------------------------------------- 1 | Returns string value by passed table and function. 2 | 3 | ### Availiable tables 4 | * `block` 5 | * `block_stats` 6 | * `tx` 7 | * `message` 8 | * `event` 9 | 10 | 11 | ### Availiable functions 12 | * `sum` 13 | * `min` 14 | * `max` 15 | * `avg` 16 | * `count` 17 | 18 | 19 | `Column` query parameter is required for functions `sum`, `min`, `max` and `avg` and should not pass for `count`. 20 | 21 | 22 | ### Availiable columns and functions for tables: 23 | 24 | #### Block 25 | * `height` -- min max 26 | * `time` -- min max 27 | 28 | #### Block stats 29 | * `height` -- min max 30 | * `time` -- min max 31 | * `tx_count` -- min max sum avg 32 | * `events_count` -- min max sum avg 33 | * `blobs_size` -- min max sum avg 34 | * `block_time` -- min max sum avg 35 | * `supply_chnge` -- min max sum avg 36 | * `inflation_rate` -- min max avg 37 | * `fee` -- min max sum avg 38 | * `gas_used` -- min max sum avg 39 | * `gas_limit` -- min max sum avg 40 | 41 | #### Tx 42 | * `height` -- min max 43 | * `time` -- min max 44 | * `gas_wanted` -- min max sum avg 45 | * `gas_used` -- min max sum avg 46 | * `timeout_height` -- min max avg 47 | * `events_count` -- min max sum avg 48 | * `messages_count` -- min max sum avg 49 | * `fee` -- min max sum avg 50 | 51 | #### Event 52 | * `height` -- min max 53 | * `time` -- min max 54 | 55 | #### Message 56 | * `height` -- min max 57 | * `time` -- min max -------------------------------------------------------------------------------- /cmd/api/timeout.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "time" 9 | 10 | "github.com/labstack/echo/v4" 11 | ) 12 | 13 | func RequestTimeout(timeout time.Duration, skipper func(echo.Context) bool) echo.MiddlewareFunc { 14 | return func(next echo.HandlerFunc) echo.HandlerFunc { 15 | return func(c echo.Context) error { 16 | if skipper != nil { 17 | if skipper(c) { 18 | return next(c) 19 | } 20 | } 21 | 22 | timeoutCtx, cancel := context.WithTimeout(c.Request().Context(), timeout) 23 | c.SetRequest(c.Request().WithContext(timeoutCtx)) 24 | defer cancel() 25 | return next(c) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cmd/celestials/config.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package main 5 | 6 | import "github.com/celenium-io/celestia-indexer/pkg/indexer/config" 7 | 8 | type CelestialsConfig struct { 9 | ChainId string `validate:"required" yaml:"chain_id"` 10 | } 11 | 12 | type Config struct { 13 | *config.Config `yaml:",inline"` 14 | 15 | Celestials CelestialsConfig `validate:"required" yaml:"celestials"` 16 | } 17 | -------------------------------------------------------------------------------- /cmd/celestials/main_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package main 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/celenium-io/celestia-indexer/internal/storage/mock" 10 | "github.com/stretchr/testify/require" 11 | "go.uber.org/mock/gomock" 12 | ) 13 | 14 | func TestAddressHandler(t *testing.T) { 15 | tests := []struct { 16 | address string 17 | wantErr bool 18 | }{ 19 | { 20 | address: "celestia1a9qy9fuxyteksjhtv2mxvs7z29nt5s3cf6d6ln", 21 | wantErr: false, 22 | }, { 23 | address: "celestia1kw6mw70wafdxgp2n8s4lscx04du8ka6dvul6jy", 24 | wantErr: false, 25 | }, { 26 | address: "osmo13ge29x4e2s63a8ytz2px8gurtyznmue4a69n5275692v3qn3ks8q7cwck7", 27 | wantErr: true, 28 | }, { 29 | address: "0x79FF9170499b0691c3878D6f95519dB05c53C9a1", 30 | wantErr: true, 31 | }, { 32 | address: "celestia1kw6mw70wafdxgp2n8s4lscx04du8ka6dvul6jy", 33 | wantErr: true, 34 | }, 35 | } 36 | 37 | ctrl := gomock.NewController(t) 38 | defer ctrl.Finish() 39 | 40 | address := mock.NewMockIAddress(ctrl) 41 | 42 | for _, tt := range tests { 43 | t.Run(tt.address, func(t *testing.T) { 44 | if tt.wantErr { 45 | address.EXPECT(). 46 | IdByHash(gomock.Any(), gomock.Any()). 47 | Return([]uint64{}, nil). 48 | AnyTimes() 49 | } else { 50 | address.EXPECT(). 51 | IdByHash(gomock.Any(), gomock.Any()). 52 | Return([]uint64{1}, nil). 53 | Times(1) 54 | } 55 | 56 | id, err := addressHandler(t.Context(), address, tt.address) 57 | require.Equal(t, err != nil, tt.wantErr) 58 | if err == nil { 59 | require.Equal(t, uint64(1), id) 60 | } 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /cmd/indexer/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | 12 | "github.com/dipdup-net/indexer-sdk/pkg/modules/stopper" 13 | 14 | "github.com/celenium-io/celestia-indexer/pkg/indexer" 15 | 16 | "github.com/rs/zerolog/log" 17 | "github.com/spf13/cobra" 18 | ) 19 | 20 | var rootCmd = &cobra.Command{ 21 | Use: "indexer", 22 | Short: "DipDup Verticals | Celenium Indexer", 23 | } 24 | 25 | func main() { 26 | cfg, err := initConfig() 27 | if err != nil { 28 | return 29 | } 30 | 31 | if err = initLogger(cfg.LogLevel); err != nil { 32 | return 33 | } 34 | if err = initProflier(cfg.Profiler); err != nil { 35 | return 36 | } 37 | 38 | ctx, cancel := context.WithCancel(context.Background()) 39 | 40 | notifyCtx, notifyCancel := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) 41 | defer notifyCancel() 42 | 43 | stopperModule := stopper.NewModule(cancel) 44 | indexerModule, err := indexer.New(ctx, *cfg, stopperModule) 45 | if err != nil { 46 | log.Panic().Err(err).Msg("error during indexer module creation") 47 | return 48 | } 49 | 50 | stopperModule.Start(ctx) 51 | indexerModule.Start(ctx) 52 | 53 | <-notifyCtx.Done() 54 | cancel() 55 | 56 | if err := indexerModule.Close(); err != nil { 57 | log.Panic().Err(err).Msg("stopping indexer") 58 | } 59 | 60 | if prscp != nil { 61 | if err := prscp.Stop(); err != nil { 62 | log.Panic().Err(err).Msg("stopping pyroscope") 63 | } 64 | } 65 | 66 | log.Info().Msg("stopped") 67 | } 68 | -------------------------------------------------------------------------------- /database/functions/00_add_view_refresh_job.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE PROCEDURE add_view_refresh_job(view_name text, end_offset interval, schedule interval) AS 2 | $$ 3 | declare 4 | mat_id text; 5 | id bigint; 6 | begin 7 | select mat_hypertable_id::text into mat_id from "_timescaledb_catalog".continuous_agg where user_view_name = view_name; 8 | 9 | if not exists (select from timescaledb_information.jobs where hypertable_name = '_materialized_hypertable_' || mat_id) 10 | then 11 | SELECT add_continuous_aggregate_policy(view_name, 12 | start_offset => NULL, 13 | end_offset => end_offset, 14 | schedule_interval => schedule, 15 | if_not_exists => true) INTO id; 16 | end if; 17 | end; 18 | $$ LANGUAGE plpgsql; -------------------------------------------------------------------------------- /database/functions/01_refresh_materialized_view.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE PROCEDURE refresh_materialized_view(job_id INT, config JSONB) 2 | LANGUAGE PLPGSQL AS 3 | $$ 4 | BEGIN 5 | SET enable_seqscan TO false; 6 | REFRESH MATERIALIZED VIEW leaderboard; 7 | SET enable_seqscan TO true; 8 | END 9 | $$; 10 | 11 | 12 | CREATE OR REPLACE PROCEDURE refresh_short_materialized_view(job_id INT, config JSONB) 13 | LANGUAGE PLPGSQL AS 14 | $$ 15 | BEGIN 16 | REFRESH MATERIALIZED VIEW leaderboard_day; 17 | END 18 | $$; 19 | 20 | 21 | CREATE OR REPLACE PROCEDURE refresh_da_change_materialized_view(job_id INT, config JSONB) 22 | LANGUAGE PLPGSQL AS 23 | $$ 24 | BEGIN 25 | REFRESH MATERIALIZED VIEW da_change; 26 | END 27 | $$; 28 | 29 | -------------------------------------------------------------------------------- /database/functions/02_add_job_refresh_materialized_view.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE PROCEDURE add_job_refresh_materialized_view() 2 | LANGUAGE PLPGSQL AS 3 | $$ 4 | BEGIN 5 | if not exists (select from timescaledb_information.jobs where proc_name = 'refresh_materialized_view') 6 | then 7 | PERFORM add_job('refresh_materialized_view', '1h', config => NULL); 8 | end if; 9 | 10 | if not exists (select from timescaledb_information.jobs where proc_name = 'refresh_short_materialized_view') 11 | then 12 | PERFORM add_job('refresh_short_materialized_view', '15m', config => NULL); 13 | end if; 14 | 15 | if not exists (select from timescaledb_information.jobs where proc_name = 'refresh_da_change_materialized_view') 16 | then 17 | PERFORM add_job('refresh_da_change_materialized_view', '1h', config => NULL); 18 | end if; 19 | END 20 | $$; -------------------------------------------------------------------------------- /database/views/00_block_stats_by_minute.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS block_stats_by_minute 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 minute'::interval, time) AS ts, 5 | sum(bytes_in_block) as bytes_in_block, 6 | (sum(blobs_size)/60.0) as bps, 7 | max(case when block_time > 0 then blobs_size::float/(block_time/1000.0) else 0 end) as bps_max, 8 | min(case when block_time > 0 then blobs_size::float/(block_time/1000.0) else 0 end) as bps_min, 9 | (sum(tx_count)/60.0) as tps, 10 | max(case when block_time > 0 then tx_count::float/(block_time/1000.0) else 0 end) as tps_max, 11 | min(case when block_time > 0 then tx_count::float/(block_time/1000.0) else 0 end) as tps_min, 12 | avg(block_time) as block_time, 13 | percentile_agg(block_time) as block_time_pct, 14 | sum(blobs_size) as blobs_size, 15 | sum(blobs_count) as blobs_count, 16 | sum(tx_count) as tx_count, 17 | sum(events_count) as events_count, 18 | sum(fee) as fee, 19 | sum(supply_change) as supply_change, 20 | sum(rewards) as rewards, 21 | sum(commissions) as commissions, 22 | sum(gas_limit) as gas_limit, 23 | sum(gas_used) as gas_used, 24 | (case when sum(gas_limit) > 0 then sum(fee) / sum(gas_limit) else 0 end) as gas_price, 25 | (case when sum(gas_limit) > 0 then sum(gas_used) / sum(gas_limit) else 0 end) as gas_efficiency 26 | from block_stats 27 | group by 1 28 | order by 1 desc 29 | with no data; 30 | 31 | CALL add_view_refresh_job('block_stats_by_minute', NULL, INTERVAL '1 minute'); 32 | -------------------------------------------------------------------------------- /database/views/01_block_stats_by_hour.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS block_stats_by_hour 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 hour'::interval, bbm.ts) AS ts, 5 | sum(bytes_in_block) as bytes_in_block, 6 | sum(blobs_size)/3600.0 as bps, 7 | max(bps_max) as bps_max, 8 | min(bps_min) as bps_min, 9 | sum(tx_count)/3600.0 as tps, 10 | max(tps_max) as tps_max, 11 | min(tps_min) as tps_min, 12 | mean(rollup(block_time_pct)) as block_time, 13 | rollup(block_time_pct) as block_time_pct, 14 | sum(blobs_size) as blobs_size, 15 | sum(blobs_count) as blobs_count, 16 | sum(tx_count) as tx_count, 17 | sum(events_count) as events_count, 18 | sum(fee) as fee, 19 | sum(supply_change) as supply_change, 20 | sum(rewards) as rewards, 21 | sum(commissions) as commissions, 22 | sum(gas_limit) as gas_limit, 23 | sum(gas_used) as gas_used, 24 | (case when sum(gas_limit) > 0 then sum(fee) / sum(gas_limit) else 0 end) as gas_price, 25 | (case when sum(gas_limit) > 0 then sum(gas_used) / sum(gas_limit) else 0 end) as gas_efficiency 26 | from block_stats_by_minute as bbm 27 | group by 1 28 | order by 1 desc 29 | with no data; 30 | 31 | CALL add_view_refresh_job('block_stats_by_hour', NULL, INTERVAL '1 minute'); 32 | -------------------------------------------------------------------------------- /database/views/02_block_stats_by_day.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS block_stats_by_day 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 day'::interval, hour.ts) AS ts, 5 | sum(bytes_in_block) as bytes_in_block, 6 | sum(blobs_size)/86400.0 as bps, 7 | max(bps_max) as bps_max, 8 | min(bps_min) as bps_min, 9 | sum(tx_count)/86400.0 as tps, 10 | max(tps_max) as tps_max, 11 | min(tps_min) as tps_min, 12 | mean(rollup(block_time_pct)) as block_time, 13 | rollup(block_time_pct) as block_time_pct, 14 | sum(blobs_size) as blobs_size, 15 | sum(blobs_count) as blobs_count, 16 | sum(tx_count) as tx_count, 17 | sum(events_count) as events_count, 18 | sum(fee) as fee, 19 | sum(supply_change) as supply_change, 20 | sum(rewards) as rewards, 21 | sum(commissions) as commissions, 22 | sum(gas_limit) as gas_limit, 23 | sum(gas_used) as gas_used, 24 | (case when sum(gas_limit) > 0 then sum(fee) / sum(gas_limit) else 0 end) as gas_price, 25 | (case when sum(gas_limit) > 0 then sum(gas_used) / sum(gas_limit) else 0 end) as gas_efficiency 26 | from block_stats_by_hour as hour 27 | group by 1 28 | order by 1 desc 29 | with no data; 30 | 31 | CALL add_view_refresh_job('block_stats_by_day', NULL, INTERVAL '5 minute'); 32 | -------------------------------------------------------------------------------- /database/views/03_block_stats_by_year.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS block_stats_by_year 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 year', day.ts) AS ts, 5 | sum(bytes_in_block) as bytes_in_block, 6 | sum(blobs_size)/(count(*) * 86400.0) as bps, 7 | max(bps_max) as bps_max, 8 | min(bps_min) as bps_min, 9 | sum(tx_count)/(count(*) * 86400.0) as tps, 10 | max(tps_max) as tps_max, 11 | min(tps_min) as tps_min, 12 | mean(rollup(block_time_pct)) as block_time, 13 | rollup(block_time_pct) as block_time_pct, 14 | sum(blobs_size) as blobs_size, 15 | sum(blobs_count) as blobs_count, 16 | sum(tx_count) as tx_count, 17 | sum(events_count) as events_count, 18 | sum(fee) as fee, 19 | sum(supply_change) as supply_change, 20 | sum(rewards) as rewards, 21 | sum(commissions) as commissions, 22 | sum(gas_limit) as gas_limit, 23 | sum(gas_used) as gas_used, 24 | (case when sum(gas_limit) > 0 then sum(fee) / sum(gas_limit) else 0 end) as gas_price, 25 | (case when sum(gas_limit) > 0 then sum(gas_used) / sum(gas_limit) else 0 end) as gas_efficiency 26 | from block_stats_by_day as day 27 | group by 1 28 | order by 1 desc 29 | with no data; 30 | 31 | CALL add_view_refresh_job('block_stats_by_year', NULL, INTERVAL '1 hour'); -------------------------------------------------------------------------------- /database/views/04_block_stats_by_month.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS block_stats_by_month 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 month', day.ts) AS ts, 5 | sum(bytes_in_block) as bytes_in_block, 6 | sum(blobs_size)/(count(*) * 86400.0) as bps, 7 | max(bps_max) as bps_max, 8 | min(bps_min) as bps_min, 9 | sum(tx_count)/(count(*) * 86400.0) as tps, 10 | max(tps_max) as tps_max, 11 | min(tps_min) as tps_min, 12 | mean(rollup(block_time_pct)) as block_time, 13 | rollup(block_time_pct) as block_time_pct, 14 | sum(blobs_size) as blobs_size, 15 | sum(blobs_count) as blobs_count, 16 | sum(tx_count) as tx_count, 17 | sum(events_count) as events_count, 18 | sum(fee) as fee, 19 | sum(supply_change) as supply_change, 20 | sum(rewards) as rewards, 21 | sum(commissions) as commissions, 22 | sum(gas_limit) as gas_limit, 23 | sum(gas_used) as gas_used, 24 | (case when sum(gas_limit) > 0 then sum(fee) / sum(gas_limit) else 0 end) as gas_price, 25 | (case when sum(gas_limit) > 0 then sum(gas_used) / sum(gas_limit) else 0 end) as gas_efficiency 26 | from block_stats_by_day as day 27 | group by 1 28 | order by 1 desc 29 | with no data; 30 | 31 | CALL add_view_refresh_job('block_stats_by_month', NULL, INTERVAL '1 hour'); -------------------------------------------------------------------------------- /database/views/05_block_stats_by_week.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS block_stats_by_week 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 week'::interval, day.ts) AS ts, 5 | sum(bytes_in_block) as bytes_in_block, 6 | sum(blobs_size)/(7 * 86400.0) as bps, 7 | max(bps_max) as bps_max, 8 | min(bps_min) as bps_min, 9 | sum(tx_count)/(7 * 86400.0) as tps, 10 | max(tps_max) as tps_max, 11 | min(tps_min) as tps_min, 12 | mean(rollup(block_time_pct)) as block_time, 13 | rollup(block_time_pct) as block_time_pct, 14 | sum(blobs_size) as blobs_size, 15 | sum(blobs_count) as blobs_count, 16 | sum(tx_count) as tx_count, 17 | sum(events_count) as events_count, 18 | sum(fee) as fee, 19 | sum(supply_change) as supply_change, 20 | sum(rewards) as rewards, 21 | sum(commissions) as commissions, 22 | sum(gas_limit) as gas_limit, 23 | sum(gas_used) as gas_used, 24 | (case when sum(gas_limit) > 0 then sum(fee) / sum(gas_limit) else 0 end) as gas_price, 25 | (case when sum(gas_limit) > 0 then sum(gas_used) / sum(gas_limit) else 0 end) as gas_efficiency 26 | from block_stats_by_day as day 27 | group by 1 28 | order by 1 desc 29 | with no data; 30 | 31 | CALL add_view_refresh_job('block_stats_by_week', NULL, INTERVAL '1 hour'); 32 | -------------------------------------------------------------------------------- /database/views/06_namespace_stats_by_hour.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS namespace_stats_by_hour 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 hour'::interval, nm.time) AS ts, 5 | nm.namespace_id, 6 | count(*) as pfb_count, 7 | sum(size) as size 8 | from namespace_message as nm 9 | group by 1, 2 10 | order by 1 desc 11 | with no data; 12 | 13 | CALL add_view_refresh_job('namespace_stats_by_hour', NULL, INTERVAL '1 minutes'); 14 | -------------------------------------------------------------------------------- /database/views/07_namespace_stats_by_day.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS namespace_stats_by_day 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 day'::interval, nm.ts) AS ts, 5 | nm.namespace_id, 6 | sum(pfb_count) as pfb_count, 7 | sum(size) as size 8 | from namespace_stats_by_hour as nm 9 | group by 1, 2 10 | order by 1 desc 11 | with no data; 12 | 13 | CALL add_view_refresh_job('namespace_stats_by_day', NULL, INTERVAL '5 minute'); 14 | -------------------------------------------------------------------------------- /database/views/08_namespace_stats_by_year.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS namespace_stats_by_year 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 year', nm.ts) AS ts, 5 | nm.namespace_id, 6 | sum(pfb_count) as pfb_count, 7 | sum(size) as size 8 | from namespace_stats_by_day as nm 9 | group by 1, 2 10 | order by 1 desc 11 | with no data; 12 | 13 | CALL add_view_refresh_job('namespace_stats_by_year', NULL, INTERVAL '1 hour'); 14 | -------------------------------------------------------------------------------- /database/views/09_namespace_stats_by_month.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS namespace_stats_by_month 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 month', nm.ts) AS ts, 5 | nm.namespace_id, 6 | sum(pfb_count) as pfb_count, 7 | sum(size) as size 8 | from namespace_stats_by_day as nm 9 | group by 1, 2 10 | order by 1 desc 11 | with no data; 12 | 13 | CALL add_view_refresh_job('namespace_stats_by_month', NULL, INTERVAL '1 hour'); 14 | -------------------------------------------------------------------------------- /database/views/10_namespace_stats_by_week.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS namespace_stats_by_week 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 week'::interval, nm.ts) AS ts, 5 | nm.namespace_id, 6 | sum(pfb_count) as pfb_count, 7 | sum(size) as size 8 | from namespace_stats_by_day as nm 9 | group by 1, 2 10 | order by 1 desc 11 | with no data; 12 | 13 | CALL add_view_refresh_job('namespace_stats_by_week', NULL, INTERVAL '1 hour'); -------------------------------------------------------------------------------- /database/views/11_rollup_stats_by_hour.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS rollup_stats_by_hour 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 hour'::interval, logs.time) AS time, 5 | logs.namespace_id, 6 | logs.signer_id, 7 | sum(logs.size) as size, 8 | count(*) as blobs_count, 9 | max(logs.time) as last_time, 10 | min(logs.time) as first_time, 11 | sum(logs.fee) as fee 12 | from blob_log as logs 13 | group by 1, 2, 3 14 | with no data; 15 | 16 | CALL add_view_refresh_job('rollup_stats_by_hour', NULL, INTERVAL '1 minute'); 17 | -------------------------------------------------------------------------------- /database/views/12_rollup_stats_by_day.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS rollup_stats_by_day 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 day'::interval, logs.time) AS time, 5 | logs.namespace_id, 6 | logs.signer_id, 7 | sum(logs.size) as size, 8 | sum(logs.blobs_count) as blobs_count, 9 | max(logs.last_time) as last_time, 10 | min(logs.first_time) as first_time, 11 | sum(fee) as fee 12 | from rollup_stats_by_hour as logs 13 | group by 1, 2, 3 14 | with no data; 15 | 16 | CALL add_view_refresh_job('rollup_stats_by_day', NULL, INTERVAL '5 minute'); 17 | -------------------------------------------------------------------------------- /database/views/13_rollup_stats_by_month.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS rollup_stats_by_month 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 month'::interval, logs.time) AS time, 5 | logs.namespace_id, 6 | logs.signer_id, 7 | sum(logs.size) as size, 8 | sum(logs.blobs_count) as blobs_count, 9 | max(logs.last_time) as last_time, 10 | min(logs.first_time) as first_time, 11 | sum(fee) as fee 12 | from rollup_stats_by_day as logs 13 | group by 1, 2, 3 14 | with no data; 15 | 16 | CALL add_view_refresh_job('rollup_stats_by_month', NULL, INTERVAL '1 hour'); 17 | -------------------------------------------------------------------------------- /database/views/16_staking_by_hour.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS staking_by_hour 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 hour'::interval, time) AS time, 5 | logs.validator_id, 6 | sum(case when type = 'delegation' then change else 0 end) as flow, 7 | sum(case when type = 'rewards' and change > 0 then change else 0 end) as rewards, 8 | sum(case when type = 'commissions' and change > 0 then change else 0 end) as commissions 9 | from staking_log as logs 10 | group by 1, 2 11 | with no data; 12 | 13 | CALL add_view_refresh_job('staking_by_hour', NULL, INTERVAL '1 minute'); 14 | -------------------------------------------------------------------------------- /database/views/17_staking_by_day.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS staking_by_day 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 day'::interval, time) AS time, 5 | logs.validator_id, 6 | sum(flow) as flow, 7 | sum(rewards) as rewards, 8 | sum(commissions) as commissions 9 | from staking_by_hour as logs 10 | group by 1, 2 11 | with no data; 12 | 13 | CALL add_view_refresh_job('staking_by_day', NULL, INTERVAL '5 minute'); 14 | -------------------------------------------------------------------------------- /database/views/18_staking_by_month.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS staking_by_month 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 month'::interval, time) AS time, 5 | logs.validator_id, 6 | sum(flow) as flow, 7 | sum(rewards) as rewards, 8 | sum(commissions) as commissions 9 | from staking_by_day as logs 10 | group by 1, 2 11 | with no data; 12 | 13 | CALL add_view_refresh_job('staking_by_month', NULL, INTERVAL '1 hour'); 14 | -------------------------------------------------------------------------------- /database/views/19_accounts_tx_by_hour.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS accounts_tx_by_hour 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 hour'::interval, time) AS time, 5 | signer.address_id, 6 | sum(fee) as fee, 7 | sum(gas_wanted) as gas_wanted, 8 | sum(gas_used) as gas_used, 9 | count(tx.id) as count 10 | from tx 11 | inner join signer on signer.tx_id = tx.id 12 | group by 1, 2 13 | with no data; 14 | 15 | CALL add_view_refresh_job('accounts_tx_by_hour', NULL, INTERVAL '1 minute'); 16 | -------------------------------------------------------------------------------- /database/views/20_accounts_tx_by_day.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS accounts_tx_by_day 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 day'::interval, time) AS time, 5 | tx.address_id, 6 | sum(fee) as fee, 7 | sum(gas_wanted) as gas_wanted, 8 | sum(gas_used) as gas_used, 9 | sum(count) as count 10 | from accounts_tx_by_hour as tx 11 | group by 1, 2 12 | with no data; 13 | 14 | CALL add_view_refresh_job('accounts_tx_by_day', NULL, INTERVAL '5 minute'); 15 | -------------------------------------------------------------------------------- /database/views/21_accounts_tx_by_month.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS accounts_tx_by_month 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 month'::interval, time) AS time, 5 | tx.address_id, 6 | sum(fee) as fee, 7 | sum(gas_wanted) as gas_wanted, 8 | sum(gas_used) as gas_used, 9 | sum(count) as count 10 | from accounts_tx_by_day as tx 11 | group by 1, 2 12 | with no data; 13 | 14 | CALL add_view_refresh_job('accounts_tx_by_month', NULL, INTERVAL '1 hour'); 15 | -------------------------------------------------------------------------------- /database/views/22_leaderboard.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS leaderboard AS 2 | with board as ( 3 | select 4 | rollup_id, 5 | sum(size) as size, 6 | sum(blobs_count) as blobs_count, 7 | max(last_time) as last_time, 8 | min(first_time) as first_time, 9 | sum(fee) as fee 10 | from ( 11 | select 12 | namespace_id, 13 | signer_id, 14 | sum(size) as size, 15 | sum(blobs_count) as blobs_count, 16 | max(last_time) as last_time, 17 | min(first_time) as first_time, 18 | sum(fee) as fee 19 | from rollup_stats_by_month 20 | group by 1, 2 21 | ) as agg 22 | inner join rollup_provider as rp on (rp.address_id = agg.signer_id OR rp.address_id = 0) AND (rp.namespace_id = agg.namespace_id OR rp.namespace_id = 0) 23 | inner join rollup on rollup.id = rp.rollup_id 24 | where rollup.verified = TRUE 25 | group by 1 26 | ) 27 | select 28 | board.size, 29 | board.blobs_count, 30 | board.last_time, 31 | board.first_time, 32 | board.fee, 33 | board.size / (select sum(size) from board) as size_pct, 34 | board.fee / (select sum(fee) from board)as fee_pct, 35 | board.blobs_count / (select sum(blobs_count) from board)as blobs_count_pct, 36 | (now() - board.last_time < INTERVAL '1 month') as is_active, 37 | rollup.* 38 | from board 39 | inner join rollup on rollup.id = board.rollup_id; 40 | 41 | CALL add_job_refresh_materialized_view(); -------------------------------------------------------------------------------- /database/views/23_square_size.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS square_size 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 day'::interval, time) AS ts, 5 | square_size, 6 | count(*) as count_blocks 7 | from block_stats 8 | where square_size > 0 9 | group by 1, 2 10 | order by 1 desc, 2 desc 11 | with no data; 12 | 13 | CALL add_view_refresh_job('square_size', NULL, INTERVAL '1 hour'); 14 | -------------------------------------------------------------------------------- /database/views/24_leaderboard_day.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS leaderboard_day AS 2 | with 3 | data as ( 4 | select * from blob_log where time > now() - '1 day'::interval 5 | ), 6 | rollup_data as ( 7 | select data.*, rp.rollup_id from data 8 | inner join rollup_provider rp on (rp.namespace_id = 0 or rp.namespace_id = data.namespace_id) and (rp.address_id = data.signer_id OR rp.address_id = 0) 9 | ) 10 | select 11 | avg(size) as avg_size, 12 | count(*) as blobs_count, 13 | sum(size) as total_size, 14 | sum(rollup_data.fee) as total_fee, 15 | ceil(sum(size) / (60*60*24)) as throughput, 16 | count(DISTINCT rollup_data.namespace_id) as namespace_count, 17 | count(DISTINCT rollup_data.msg_id) as pfb_count, 18 | (case when sum(size) > 0 then ceil(sum(rollup_data.fee) * 1024 * 1024 / sum(size)) else 0 end) as mb_price, 19 | rollup_id 20 | from rollup_data 21 | group by rollup_id; 22 | 23 | CALL add_job_refresh_materialized_view(); -------------------------------------------------------------------------------- /database/views/26_ibc_transfers_by_hour.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS ibc_transfers_by_hour 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 hour'::interval, time) AS time, 5 | channel_id, 6 | sum(amount) as amount, 7 | count(ibc_transfer.id) as count 8 | from ibc_transfer 9 | group by 1, 2 10 | with no data; 11 | 12 | CALL add_view_refresh_job('ibc_transfers_by_hour', NULL, INTERVAL '1 minute'); 13 | -------------------------------------------------------------------------------- /database/views/27_ibc_transfers_by_day.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS ibc_transfers_by_day 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 day'::interval, time) AS time, 5 | channel_id, 6 | sum(amount) as amount, 7 | sum(count) as count 8 | from ibc_transfers_by_hour 9 | group by 1, 2 10 | with no data; 11 | 12 | CALL add_view_refresh_job('ibc_transfers_by_day', NULL, INTERVAL '5 minute'); 13 | -------------------------------------------------------------------------------- /database/views/28_ibc_transfers_by_month.sql: -------------------------------------------------------------------------------- 1 | CREATE MATERIALIZED VIEW IF NOT EXISTS ibc_transfers_by_month 2 | WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS 3 | select 4 | time_bucket('1 day'::interval, time) AS time, 5 | channel_id, 6 | sum(amount) as amount, 7 | sum(count) as count 8 | from ibc_transfers_by_day 9 | group by 1, 2 10 | with no data; 11 | 12 | CALL add_view_refresh_job('ibc_transfers_by_month', NULL, INTERVAL '1 hour'); 13 | -------------------------------------------------------------------------------- /init.dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #################################################### VCS config 4 | git config --local core.attributesfile ./.gitattributes 5 | 6 | ########################################### Install third tools 7 | 8 | # linter 9 | go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.1 # as long we use go@1.20 10 | 11 | # for generating swagger specification 12 | go install github.com/swaggo/swag/cmd/swag@latest 13 | 14 | # for generating enum types 15 | curl -fsSL "https://github.com/abice/go-enum/releases/download/v0.5.7/go-enum_$(uname -s)_$(uname -m)" -o ${GOPATH}/bin/go-enum 16 | chmod +x ${GOPATH}/bin/go-enum 17 | go install go.uber.org/mock/mockgen@main 18 | 19 | # for checking licenses used by project and deps 20 | go install github.com/google/go-licenses@latest 21 | 22 | # for api test, should have npm installed 23 | npm install -g newman 24 | 25 | # for setting up license header in each source code file 26 | go install github.com/vvuwei/update-license@v0.0.1 -------------------------------------------------------------------------------- /internal/blob/storage.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package blob 5 | 6 | import ( 7 | "context" 8 | "encoding/base64" 9 | "fmt" 10 | 11 | blobTypes "github.com/cometbft/cometbft/proto/tendermint/types" 12 | "github.com/gabriel-vasile/mimetype" 13 | ) 14 | 15 | type Blob struct { 16 | *blobTypes.Blob 17 | Commitment []byte 18 | Height uint64 19 | } 20 | 21 | func (blob Blob) String() string { 22 | hash := []byte{byte(blob.ShareVersion)} 23 | ns := base64.URLEncoding.EncodeToString(append(hash, blob.NamespaceId...)) 24 | cm := base64.URLEncoding.EncodeToString(blob.Commitment) 25 | return fmt.Sprintf("%s/%d/%s", ns, blob.Height, cm) 26 | } 27 | 28 | func (blob Blob) ContentType() string { 29 | contentType := mimetype.Detect(blob.Data) 30 | return contentType.String() 31 | } 32 | 33 | //go:generate mockgen -source=$GOFILE -destination=mock.go -package=blob -typed 34 | type Storage interface { 35 | Save(ctx context.Context, blob Blob) error 36 | SaveBulk(ctx context.Context, blobs []Blob) error 37 | Head(ctx context.Context) (uint64, error) 38 | UpdateHead(ctx context.Context, head uint64) error 39 | } 40 | 41 | func Base64ToUrl(s string) (string, error) { 42 | b, err := base64.StdEncoding.DecodeString(s) 43 | if err != nil { 44 | return "", err 45 | } 46 | return base64.URLEncoding.EncodeToString(b), nil 47 | } 48 | -------------------------------------------------------------------------------- /internal/blob/storage_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package blob 5 | 6 | import ( 7 | "testing" 8 | 9 | blobTypes "github.com/cometbft/cometbft/proto/tendermint/types" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestBlob_String(t *testing.T) { 14 | tests := []struct { 15 | name string 16 | blob *blobTypes.Blob 17 | commitment []byte 18 | height uint64 19 | want string 20 | }{ 21 | { 22 | name: "test 1", 23 | blob: &blobTypes.Blob{ 24 | Data: []byte{0x01}, 25 | NamespaceId: []byte{0x1}, 26 | }, 27 | commitment: []byte{0x02}, 28 | height: 100, 29 | want: "AAE=/100/Ag==", 30 | }, 31 | } 32 | for _, tt := range tests { 33 | t.Run(tt.name, func(t *testing.T) { 34 | blob := Blob{ 35 | Blob: tt.blob, 36 | Commitment: tt.commitment, 37 | Height: tt.height, 38 | } 39 | require.Equal(t, tt.want, blob.String()) 40 | }) 41 | } 42 | } 43 | 44 | func TestBase64ToUrl(t *testing.T) { 45 | tests := []struct { 46 | name string 47 | s string 48 | want string 49 | wantErr bool 50 | }{ 51 | { 52 | name: "test 1", 53 | s: "zvwwM2k3fmfU8t6i1Mprs34+VQUIn2bdvz6IO2thcAU=", 54 | want: "zvwwM2k3fmfU8t6i1Mprs34-VQUIn2bdvz6IO2thcAU=", 55 | }, 56 | } 57 | for _, tt := range tests { 58 | t.Run(tt.name, func(t *testing.T) { 59 | got, err := Base64ToUrl(tt.s) 60 | require.NoError(t, err) 61 | require.Equal(t, tt.want, got) 62 | }) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /internal/currency/currency.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package currency 5 | 6 | import "github.com/shopspring/decimal" 7 | 8 | const ( 9 | Utia string = "utia" 10 | Tia string = "tia" 11 | ) 12 | 13 | const ( 14 | DefaultCurrency = "utia" 15 | ) 16 | 17 | func StringTia(val decimal.Decimal) string { 18 | return val.StringFixed(6) 19 | } 20 | 21 | func StringUtia(val decimal.Decimal) string { 22 | return val.StringFixed(0) 23 | } 24 | 25 | var divider = decimal.NewFromInt(10).Pow(decimal.NewFromInt(-6)) 26 | 27 | func StringTiaFromUtia(val decimal.Decimal) string { 28 | return val.Mul(divider).StringFixed(6) 29 | } 30 | -------------------------------------------------------------------------------- /internal/currency/currency_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package currency 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/shopspring/decimal" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestStringTia(t *testing.T) { 14 | tests := []struct { 15 | name string 16 | val decimal.Decimal 17 | want string 18 | }{ 19 | { 20 | name: "test 1", 21 | val: decimal.RequireFromString("0.123456789"), 22 | want: "0.123457", 23 | }, { 24 | name: "test 2", 25 | val: decimal.RequireFromString("10000.123456789"), 26 | want: "10000.123457", 27 | }, { 28 | name: "test 3", 29 | val: decimal.RequireFromString("10000"), 30 | want: "10000.000000", 31 | }, { 32 | name: "test 4", 33 | val: decimal.RequireFromString("2"), 34 | want: "2.000000", 35 | }, 36 | } 37 | for _, tt := range tests { 38 | t.Run(tt.name, func(t *testing.T) { 39 | got := StringTia(tt.val) 40 | require.Equal(t, tt.want, got) 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /internal/math/min.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package math 5 | 6 | import "golang.org/x/exp/constraints" 7 | 8 | func Min[T constraints.Ordered](x, y T) T { 9 | if x < y { 10 | return x 11 | } 12 | return y 13 | } 14 | -------------------------------------------------------------------------------- /internal/math/time.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package math 5 | 6 | import "time" 7 | 8 | func TimeFromNano(ts uint64) time.Time { 9 | return time.Unix(0, int64(ts)).UTC() 10 | } 11 | -------------------------------------------------------------------------------- /internal/profiler/profiler.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package profiler 5 | 6 | import ( 7 | "fmt" 8 | "runtime" 9 | 10 | "github.com/grafana/pyroscope-go" 11 | ) 12 | 13 | type Config struct { 14 | Server string `validate:"omitempty,http_url" yaml:"server"` 15 | Project string `validate:"omitempty" yaml:"project"` 16 | } 17 | 18 | func New(cfg *Config, service string) (*pyroscope.Profiler, error) { 19 | if cfg == nil || cfg.Server == "" { 20 | return nil, nil 21 | } 22 | 23 | runtime.SetMutexProfileFraction(5) 24 | runtime.SetBlockProfileRate(5) 25 | 26 | return pyroscope.Start(pyroscope.Config{ 27 | ApplicationName: fmt.Sprintf("%s-%s", cfg.Project, service), 28 | ServerAddress: cfg.Server, 29 | Tags: map[string]string{ 30 | "project": cfg.Project, 31 | "service": service, 32 | }, 33 | 34 | ProfileTypes: []pyroscope.ProfileType{ 35 | pyroscope.ProfileCPU, 36 | pyroscope.ProfileAllocObjects, 37 | pyroscope.ProfileAllocSpace, 38 | pyroscope.ProfileInuseObjects, 39 | pyroscope.ProfileInuseSpace, 40 | pyroscope.ProfileGoroutines, 41 | pyroscope.ProfileMutexCount, 42 | pyroscope.ProfileMutexDuration, 43 | pyroscope.ProfileBlockCount, 44 | pyroscope.ProfileBlockDuration, 45 | }, 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /internal/storage/api_key.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/uptrace/bun" 10 | ) 11 | 12 | //go:generate mockgen -source=$GOFILE -destination=mock/$GOFILE -package=mock -typed 13 | type IApiKey interface { 14 | Get(ctx context.Context, key string) (ApiKey, error) 15 | } 16 | 17 | type ApiKey struct { 18 | bun.BaseModel `bun:"apikey" comment:"Table with private api keys"` 19 | 20 | Key string `bun:"key,pk,notnull" comment:"Key"` 21 | Description string `bun:"description" comment:"Additional info about issuer and user"` 22 | Admin bool `bun:"admin" comment:"Verified user"` 23 | } 24 | 25 | func (ApiKey) TableName() string { 26 | return "apikey" 27 | } 28 | -------------------------------------------------------------------------------- /internal/storage/balance.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import ( 7 | "github.com/celenium-io/celestia-indexer/internal/currency" 8 | "github.com/dipdup-net/indexer-sdk/pkg/storage" 9 | "github.com/shopspring/decimal" 10 | "github.com/uptrace/bun" 11 | ) 12 | 13 | //go:generate mockgen -source=$GOFILE -destination=mock/$GOFILE -package=mock -typed 14 | type IBalance interface { 15 | storage.Table[*Balance] 16 | } 17 | 18 | type Balance struct { 19 | bun.BaseModel `bun:"balance" comment:"Table with account balances."` 20 | 21 | Id uint64 `bun:"id,pk,notnull,autoincrement" comment:"Unique internal identity"` 22 | Currency string `bun:"currency,pk,notnull" comment:"Balance currency"` 23 | Spendable decimal.Decimal `bun:"spendable,type:numeric" comment:"Spendable balance"` 24 | Delegated decimal.Decimal `bun:"delegated,type:numeric" comment:"Delegated balance"` 25 | Unbonding decimal.Decimal `bun:"unbonding,type:numeric" comment:"Unbonding balance"` 26 | } 27 | 28 | func (Balance) TableName() string { 29 | return "balance" 30 | } 31 | 32 | func (b Balance) IsEmpty() bool { 33 | return b.Currency == "" && b.Spendable.IsZero() 34 | } 35 | 36 | func EmptyBalance() Balance { 37 | return Balance{ 38 | Currency: currency.DefaultCurrency, 39 | Spendable: decimal.Zero, 40 | Delegated: decimal.Zero, 41 | Unbonding: decimal.Zero, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /internal/storage/block_signature.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import ( 7 | "context" 8 | "time" 9 | 10 | "github.com/celenium-io/celestia-indexer/pkg/types" 11 | "github.com/dipdup-net/indexer-sdk/pkg/storage" 12 | "github.com/uptrace/bun" 13 | ) 14 | 15 | //go:generate mockgen -source=$GOFILE -destination=mock/$GOFILE -package=mock -typed 16 | type IBlockSignature interface { 17 | storage.Table[*BlockSignature] 18 | 19 | LevelsByValidator(ctx context.Context, validatorId uint64, startHeight types.Level) ([]types.Level, error) 20 | } 21 | 22 | type BlockSignature struct { 23 | bun.BaseModel `bun:"block_signature" comment:"Table with block signatures"` 24 | 25 | Id uint64 `bun:"id,pk,notnull,autoincrement" comment:"Unique internal id"` 26 | Height types.Level `bun:",notnull" comment:"The number (height) of this block"` 27 | Time time.Time `bun:"time,pk,notnull" comment:"The time of block"` 28 | ValidatorId uint64 `bun:"validator_id" comment:"Validator's internal identity"` 29 | 30 | Validator *Validator `bun:"rel:belongs-to"` 31 | } 32 | 33 | func (BlockSignature) TableName() string { 34 | return "block_signature" 35 | } 36 | -------------------------------------------------------------------------------- /internal/storage/block_signature_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import ( 7 | "github.com/stretchr/testify/assert" 8 | "testing" 9 | ) 10 | 11 | func TestTableName(t *testing.T) { 12 | blockSignature := BlockSignature{} 13 | assert.Equal(t, "block_signature", blockSignature.TableName()) 14 | } 15 | -------------------------------------------------------------------------------- /internal/storage/constant.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import ( 7 | "context" 8 | "strconv" 9 | 10 | "github.com/celenium-io/celestia-indexer/internal/storage/types" 11 | "github.com/uptrace/bun" 12 | ) 13 | 14 | //go:generate mockgen -source=$GOFILE -destination=mock/$GOFILE -package=mock -typed 15 | type IConstant interface { 16 | Get(ctx context.Context, module types.ModuleName, name string) (Constant, error) 17 | ByModule(ctx context.Context, module types.ModuleName) ([]Constant, error) 18 | All(ctx context.Context) ([]Constant, error) 19 | } 20 | 21 | type Constant struct { 22 | bun.BaseModel `bun:"table:constant" comment:"Table with celestia constants."` 23 | 24 | Module types.ModuleName `bun:"module,pk,type:module_name" comment:"Module name which declares constant"` 25 | Name string `bun:"name,pk,type:text" comment:"Constant name"` 26 | Value string `bun:"value,type:text" comment:"Constant value"` 27 | } 28 | 29 | func (Constant) TableName() string { 30 | return "constant" 31 | } 32 | 33 | func (c Constant) MustUint64() uint64 { 34 | i, err := strconv.ParseUint(c.Value, 10, 64) 35 | if err != nil { 36 | panic(err) 37 | } 38 | return i 39 | } 40 | 41 | func (c Constant) MustUint32() uint32 { 42 | i, err := strconv.ParseUint(c.Value, 10, 32) 43 | if err != nil { 44 | panic(err) 45 | } 46 | //nolint:gosec 47 | return uint32(i) 48 | } 49 | -------------------------------------------------------------------------------- /internal/storage/constant_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import ( 7 | "github.com/stretchr/testify/assert" 8 | "testing" 9 | ) 10 | 11 | func TestConstant_TableName(t *testing.T) { 12 | constant := Constant{} 13 | assert.Equal(t, "constant", constant.TableName()) 14 | } 15 | -------------------------------------------------------------------------------- /internal/storage/denom_metadata.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/uptrace/bun" 10 | ) 11 | 12 | //go:generate mockgen -source=$GOFILE -destination=mock/$GOFILE -package=mock -typed 13 | type IDenomMetadata interface { 14 | All(ctx context.Context) ([]DenomMetadata, error) 15 | } 16 | 17 | type DenomMetadata struct { 18 | bun.BaseModel `bun:"table:denom_metadata" comment:"Table with celestia coins metadata."` 19 | 20 | Id uint64 `bun:"id,pk,notnull,autoincrement" comment:"Internal unique identity"` 21 | Description string `bun:"description,type:text" comment:"Denom description"` 22 | Base string `bun:"base,type:text" comment:"Denom base"` 23 | Display string `bun:"display,type:text" comment:"Denom display"` 24 | Name string `bun:"name,type:text" comment:"Denom name"` 25 | Symbol string `bun:"symbol,type:text" comment:"Denom symbol"` 26 | Uri string `bun:"uri,type:text" comment:"Denom uri"` 27 | 28 | Units []byte `bun:"units,type:bytea" comment:"Denom units information"` 29 | } 30 | 31 | func (DenomMetadata) TableName() string { 32 | return "denom_metadata" 33 | } 34 | -------------------------------------------------------------------------------- /internal/storage/denom_metadata_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import ( 7 | "github.com/stretchr/testify/assert" 8 | "testing" 9 | ) 10 | 11 | func TestDenomMetadata_TableName(t *testing.T) { 12 | denom_metadata := DenomMetadata{} 13 | assert.Equal(t, "denom_metadata", denom_metadata.TableName()) 14 | } 15 | -------------------------------------------------------------------------------- /internal/storage/errors.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import "errors" 7 | 8 | var ( 9 | ErrValidation = errors.New("validation error") 10 | ) 11 | -------------------------------------------------------------------------------- /internal/storage/jail.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import ( 7 | "context" 8 | "time" 9 | 10 | pkgTypes "github.com/celenium-io/celestia-indexer/pkg/types" 11 | "github.com/dipdup-net/indexer-sdk/pkg/storage" 12 | "github.com/shopspring/decimal" 13 | "github.com/uptrace/bun" 14 | ) 15 | 16 | //go:generate mockgen -source=$GOFILE -destination=mock/$GOFILE -package=mock -typed 17 | type IJail interface { 18 | storage.Table[*Jail] 19 | 20 | ByValidator(ctx context.Context, id uint64, limit, offset int) ([]Jail, error) 21 | } 22 | 23 | // Jail - 24 | type Jail struct { 25 | bun.BaseModel `bun:"jail" comment:"Table with all jailed events."` 26 | 27 | Id uint64 `bun:"id,pk,notnull,autoincrement" comment:"Unique internal id"` 28 | Time time.Time `bun:"time,pk,notnull" comment:"The time of block"` 29 | Height pkgTypes.Level `bun:"height,notnull" comment:"The number (height) of this block"` 30 | ValidatorId uint64 `bun:"validator_id,notnull" comment:"Internal validator id"` 31 | Reason string `bun:"reason" comment:"Reason"` 32 | Burned decimal.Decimal `bun:"burned,type:numeric" comment:"Burned coins"` 33 | 34 | Validator *Validator `bun:"rel:belongs-to,join:validator_id=id"` 35 | } 36 | 37 | // TableName - 38 | func (Jail) TableName() string { 39 | return "jail" 40 | } 41 | -------------------------------------------------------------------------------- /internal/storage/message_address.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/celenium-io/celestia-indexer/internal/storage/types" 10 | "github.com/uptrace/bun" 11 | ) 12 | 13 | type MsgAddress struct { 14 | bun.BaseModel `bun:"msg_address" comment:"Table with relation tx to address"` 15 | 16 | AddressId uint64 `bun:"address_id,pk" comment:"Address internal id"` 17 | MsgId uint64 `bun:"msg_id,pk" comment:"Message internal id"` 18 | Type types.MsgAddressType `bun:",pk,type:msg_address_type" comment:"The reason why address link to transaction"` 19 | 20 | Address *Address `bun:"rel:belongs-to,join:address_id=id"` 21 | Msg *Message `bun:"rel:belongs-to,join:msg_id=id"` 22 | } 23 | 24 | func (MsgAddress) TableName() string { 25 | return "msg_address" 26 | } 27 | 28 | func (m MsgAddress) String() string { 29 | return fmt.Sprintf("%d_%d_%s", m.AddressId, m.MsgId, m.Type) 30 | } 31 | 32 | type AddressWithType struct { 33 | Address 34 | 35 | Type types.MsgAddressType 36 | } 37 | -------------------------------------------------------------------------------- /internal/storage/mock/core.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package mock 5 | 6 | import ( 7 | models "github.com/celenium-io/celestia-indexer/internal/storage" 8 | gomock "go.uber.org/mock/gomock" 9 | ) 10 | 11 | type Storage struct { 12 | Blocks models.IBlock 13 | Tx models.ITx 14 | Message models.IMessage 15 | Event models.IEvent 16 | Address models.IAddress 17 | Namespace models.INamespace 18 | State models.IState 19 | 20 | ctrl *gomock.Controller 21 | } 22 | 23 | func Create(t gomock.TestReporter) Storage { 24 | ctrl := gomock.NewController(t) 25 | return Storage{ 26 | Blocks: NewMockIBlock(ctrl), 27 | Tx: NewMockITx(ctrl), 28 | Message: NewMockIMessage(ctrl), 29 | Event: NewMockIEvent(ctrl), 30 | Address: NewMockIAddress(ctrl), 31 | Namespace: NewMockINamespace(ctrl), 32 | State: NewMockIState(ctrl), 33 | ctrl: ctrl, 34 | } 35 | } 36 | 37 | func (s Storage) Close() error { 38 | s.ctrl.Finish() 39 | return nil 40 | } 41 | 42 | func (s Storage) Ctrl() *gomock.Controller { 43 | return s.ctrl 44 | } 45 | -------------------------------------------------------------------------------- /internal/storage/namespace_message.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import ( 7 | "time" 8 | 9 | "github.com/celenium-io/celestia-indexer/pkg/types" 10 | 11 | "github.com/uptrace/bun" 12 | ) 13 | 14 | type NamespaceMessage struct { 15 | bun.BaseModel `bun:"namespace_message" comment:"Table with relation messages to namespace."` 16 | 17 | NamespaceId uint64 `bun:"namespace_id,pk" comment:"Namespace internal id"` 18 | MsgId uint64 `bun:"msg_id,pk" comment:"Message id"` 19 | TxId uint64 `bun:"tx_id" comment:"Transaction id"` 20 | 21 | Time time.Time `bun:"time,notnull,pk" comment:"Message time"` 22 | Height types.Level `bun:"height" comment:"Message block height"` 23 | Size uint64 `bun:"size" comment:"Total namespace size change due to message"` 24 | 25 | Message *Message `bun:"rel:belongs-to,join:msg_id=id"` 26 | Namespace *Namespace `bun:"rel:belongs-to,join:namespace_id=id"` 27 | Tx *Tx `bun:"rel:belongs-to,join:tx_id=id"` 28 | } 29 | 30 | func (NamespaceMessage) TableName() string { 31 | return "namespace_message" 32 | } 33 | -------------------------------------------------------------------------------- /internal/storage/namespace_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import ( 7 | "testing" 8 | 9 | testsuite "github.com/celenium-io/celestia-indexer/internal/test_suite" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestNamespace_Hash(t *testing.T) { 14 | tests := []struct { 15 | name string 16 | ns Namespace 17 | want string 18 | }{ 19 | { 20 | name: "test 1", 21 | ns: Namespace{ 22 | NamespaceID: testsuite.MustHexDecode("0000000000000000000000000000000000006e5bbbdfcea081b366b1"), 23 | Version: 0, 24 | }, 25 | want: "AAAAAAAAAAAAAAAAAAAAAAAAAG5bu9/OoIGzZrE=", 26 | }, { 27 | name: "test 2", 28 | ns: Namespace{ 29 | NamespaceID: testsuite.MustHexDecode("000000000000000000000000000000000000a476c00deb8796b16999"), 30 | Version: 0, 31 | }, 32 | want: "AAAAAAAAAAAAAAAAAAAAAAAAAKR2wA3rh5axaZk=", 33 | }, 34 | } 35 | for _, tt := range tests { 36 | t.Run(tt.name, func(t *testing.T) { 37 | got := tt.ns.Hash() 38 | require.Equal(t, tt.want, got) 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /internal/storage/postgres/apikey.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/celenium-io/celestia-indexer/internal/storage" 10 | "github.com/dipdup-net/go-lib/database" 11 | ) 12 | 13 | // ApiKey - 14 | type ApiKey struct { 15 | db *database.Bun 16 | } 17 | 18 | // NewApiKey - 19 | func NewApiKey(db *database.Bun) *ApiKey { 20 | return &ApiKey{ 21 | db: db, 22 | } 23 | } 24 | 25 | func (ak *ApiKey) Get(ctx context.Context, key string) (apikey storage.ApiKey, err error) { 26 | apikey.Key = key 27 | err = ak.db.DB().NewSelect().Model(&apikey).WherePK().Scan(ctx) 28 | return 29 | } 30 | -------------------------------------------------------------------------------- /internal/storage/postgres/apikey_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | "time" 9 | ) 10 | 11 | func (s *StorageTestSuite) TestApiKeyValid() { 12 | ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) 13 | defer ctxCancel() 14 | 15 | key, err := s.storage.ApiKeys.Get(ctx, "test_key") 16 | s.Require().NoError(err) 17 | s.Require().EqualValues("test_key", key.Key) 18 | s.Require().EqualValues("valid key", key.Description) 19 | } 20 | 21 | func (s *StorageTestSuite) TestApiKeyInvalid() { 22 | ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) 23 | defer ctxCancel() 24 | 25 | _, err := s.storage.ApiKeys.Get(ctx, "invalid") 26 | s.Require().Error(err) 27 | } 28 | -------------------------------------------------------------------------------- /internal/storage/postgres/block_signature.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/celenium-io/celestia-indexer/internal/storage" 10 | "github.com/celenium-io/celestia-indexer/pkg/types" 11 | "github.com/dipdup-net/go-lib/database" 12 | "github.com/dipdup-net/indexer-sdk/pkg/storage/postgres" 13 | ) 14 | 15 | // BlockSignature - 16 | type BlockSignature struct { 17 | *postgres.Table[*storage.BlockSignature] 18 | } 19 | 20 | // NewBlockSignature - 21 | func NewBlockSignature(db *database.Bun) *BlockSignature { 22 | return &BlockSignature{ 23 | Table: postgres.NewTable[*storage.BlockSignature](db), 24 | } 25 | } 26 | 27 | func (bs *BlockSignature) LevelsByValidator(ctx context.Context, validatorId uint64, startHeight types.Level) (levels []types.Level, err error) { 28 | err = bs.DB().NewSelect(). 29 | Model((*storage.BlockSignature)(nil)). 30 | Column("height"). 31 | Where("validator_id = ?", validatorId). 32 | Where("height > ?", startHeight). 33 | Order("id desc"). 34 | Scan(ctx, &levels) 35 | return 36 | } 37 | -------------------------------------------------------------------------------- /internal/storage/postgres/block_signature_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | "time" 9 | 10 | "github.com/celenium-io/celestia-indexer/pkg/types" 11 | ) 12 | 13 | func (s *StorageTestSuite) TestBlockSignatureLevels() { 14 | ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) 15 | defer ctxCancel() 16 | 17 | levels, err := s.storage.BlockSignatures.LevelsByValidator(ctx, 1, 998) 18 | s.Require().NoError(err) 19 | s.Require().Len(levels, 2) 20 | 21 | s.Require().Equal([]types.Level{1000, 999}, levels) 22 | } 23 | -------------------------------------------------------------------------------- /internal/storage/postgres/block_stats.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | 9 | pkgTypes "github.com/celenium-io/celestia-indexer/pkg/types" 10 | 11 | "github.com/celenium-io/celestia-indexer/internal/storage" 12 | "github.com/dipdup-net/go-lib/database" 13 | ) 14 | 15 | // BlockStats - 16 | type BlockStats struct { 17 | db *database.Bun 18 | } 19 | 20 | // NewBlockStats - 21 | func NewBlockStats(db *database.Bun) *BlockStats { 22 | return &BlockStats{ 23 | db: db, 24 | } 25 | } 26 | 27 | // ByHeight - 28 | func (b *BlockStats) ByHeight(ctx context.Context, height pkgTypes.Level) (stats storage.BlockStats, err error) { 29 | err = b.db.DB().NewSelect().Model(&stats). 30 | Where("height = ?", height). 31 | Limit(1). 32 | Scan(ctx) 33 | 34 | return 35 | } 36 | 37 | func (b *BlockStats) LastFrom(ctx context.Context, head pkgTypes.Level, limit int) (stats []storage.BlockStats, err error) { 38 | err = b.db.DB().NewSelect().Model(&stats). 39 | Where("height <= ?", head). 40 | Limit(limit). 41 | Order("id desc"). 42 | Scan(ctx) 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /internal/storage/postgres/constant.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/celenium-io/celestia-indexer/internal/storage" 10 | "github.com/celenium-io/celestia-indexer/internal/storage/types" 11 | "github.com/dipdup-net/go-lib/database" 12 | ) 13 | 14 | // Constant - 15 | type Constant struct { 16 | db *database.Bun 17 | } 18 | 19 | // NewConstant - 20 | func NewConstant(db *database.Bun) *Constant { 21 | return &Constant{ 22 | db: db, 23 | } 24 | } 25 | 26 | func (constant *Constant) Get(ctx context.Context, module types.ModuleName, name string) (c storage.Constant, err error) { 27 | err = constant.db.DB().NewSelect().Model(&c). 28 | Where("module = ?", module). 29 | Where("name = ?", name). 30 | Scan(ctx) 31 | return 32 | } 33 | 34 | func (constant *Constant) ByModule(ctx context.Context, module types.ModuleName) (c []storage.Constant, err error) { 35 | err = constant.db.DB().NewSelect().Model(&c). 36 | Where("module = ?", module). 37 | Scan(ctx) 38 | return 39 | } 40 | 41 | func (constant *Constant) All(ctx context.Context) (c []storage.Constant, err error) { 42 | err = constant.db.DB().NewSelect().Model(&c).Scan(ctx) 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /internal/storage/postgres/delegation_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | "time" 9 | ) 10 | 11 | func (s *StorageTestSuite) TestDelegationByAddress() { 12 | ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) 13 | defer ctxCancel() 14 | 15 | delegations, err := s.storage.Delegation.ByAddress(ctx, 1, 10, 0, false) 16 | s.Require().NoError(err) 17 | s.Require().Len(delegations, 1) 18 | 19 | d := delegations[0] 20 | s.Require().EqualValues(1, d.Id) 21 | s.Require().EqualValues(1, d.AddressId) 22 | s.Require().EqualValues(1, d.ValidatorId) 23 | s.Require().EqualValues("10000", d.Amount.String()) 24 | s.Require().NotNil(d.Validator) 25 | s.Require().Equal("Conqueror", d.Validator.Moniker) 26 | s.Require().Nil(d.Address) 27 | } 28 | 29 | func (s *StorageTestSuite) TestDelegationByValidator() { 30 | ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) 31 | defer ctxCancel() 32 | 33 | delegations, err := s.storage.Delegation.ByValidator(ctx, 1, 10, 0, true) 34 | s.Require().NoError(err) 35 | s.Require().Len(delegations, 2) 36 | 37 | d := delegations[0] 38 | s.Require().EqualValues(1, d.Id) 39 | s.Require().EqualValues(1, d.AddressId) 40 | s.Require().EqualValues(1, d.ValidatorId) 41 | s.Require().EqualValues("10000", d.Amount.String()) 42 | s.Require().Nil(d.Validator) 43 | s.Require().NotNil(d.Address) 44 | s.Require().NotNil(d.Address.Celestials) 45 | } 46 | -------------------------------------------------------------------------------- /internal/storage/postgres/denom_metadata.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/celenium-io/celestia-indexer/internal/storage" 10 | "github.com/dipdup-net/go-lib/database" 11 | ) 12 | 13 | // DenomMetadata - 14 | type DenomMetadata struct { 15 | db *database.Bun 16 | } 17 | 18 | // NewDenomMetadata - 19 | func NewDenomMetadata(db *database.Bun) *DenomMetadata { 20 | return &DenomMetadata{ 21 | db: db, 22 | } 23 | } 24 | 25 | func (dm *DenomMetadata) All(ctx context.Context) (metadata []storage.DenomMetadata, err error) { 26 | err = dm.db.DB().NewSelect().Model(&metadata).Scan(ctx) 27 | return 28 | } 29 | -------------------------------------------------------------------------------- /internal/storage/postgres/event_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | "time" 9 | 10 | "github.com/celenium-io/celestia-indexer/internal/storage" 11 | "github.com/celenium-io/celestia-indexer/internal/storage/types" 12 | ) 13 | 14 | func (s *StorageTestSuite) TestEventByTxId() { 15 | ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) 16 | defer ctxCancel() 17 | 18 | events, err := s.storage.Event.ByTxId(ctx, 1, storage.EventFilter{ 19 | Limit: 10, 20 | }) 21 | s.Require().NoError(err) 22 | s.Require().Len(events, 1) 23 | s.Require().EqualValues(2, events[0].Id) 24 | s.Require().EqualValues(1000, events[0].Height) 25 | s.Require().EqualValues(1, events[0].Position) 26 | s.Require().Equal(types.EventTypeMint, events[0].Type) 27 | } 28 | 29 | func (s *StorageTestSuite) TestEventByBlock() { 30 | ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) 31 | defer ctxCancel() 32 | 33 | events, err := s.storage.Event.ByBlock(ctx, 1000, storage.EventFilter{ 34 | Limit: 2, 35 | }) 36 | s.Require().NoError(err) 37 | s.Require().Len(events, 1) 38 | s.Require().EqualValues(1, events[0].Id) 39 | s.Require().EqualValues(1000, events[0].Height) 40 | s.Require().EqualValues(0, events[0].Position) 41 | s.Require().Equal(types.EventTypeBurn, events[0].Type) 42 | } 43 | -------------------------------------------------------------------------------- /internal/storage/postgres/export.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | "database/sql" 9 | "fmt" 10 | "io" 11 | 12 | "github.com/dipdup-net/go-lib/config" 13 | "github.com/uptrace/bun" 14 | "github.com/uptrace/bun/dialect/pgdialect" 15 | "github.com/uptrace/bun/driver/pgdriver" 16 | ) 17 | 18 | type Export struct { 19 | *bun.DB 20 | } 21 | 22 | func NewExport(cfg config.Database) *Export { 23 | dsn := fmt.Sprintf( 24 | "postgres://%s:%s@%s:%d/%s?sslmode=disable&application_name=export", 25 | cfg.User, 26 | cfg.Password, 27 | cfg.Host, 28 | cfg.Port, 29 | cfg.Database, 30 | ) 31 | sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn))) 32 | db := bun.NewDB(sqldb, pgdialect.New()) 33 | return &Export{db} 34 | } 35 | 36 | func (e *Export) ToCsv(ctx context.Context, writer io.Writer, query string) error { 37 | conn, err := e.Conn(ctx) 38 | if err != nil { 39 | return err 40 | } 41 | defer conn.Close() 42 | 43 | rawQuery := fmt.Sprintf("COPY (%s) TO STDOUT WITH CSV HEADER", bun.Safe(query)) 44 | _, err = pgdriver.CopyTo(ctx, conn, writer, rawQuery) 45 | return err 46 | } 47 | 48 | func (e *Export) Close() error { 49 | if err := e.DB.Close(); err != nil { 50 | return err 51 | } 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /internal/storage/postgres/export_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "bytes" 8 | "context" 9 | "encoding/csv" 10 | "time" 11 | ) 12 | 13 | func (s *StorageTestSuite) TestExportToCsv() { 14 | ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) 15 | defer ctxCancel() 16 | 17 | var buf bytes.Buffer 18 | err := s.storage.export.ToCsv(ctx, &buf, "select * from address") 19 | s.Require().NoError(err) 20 | 21 | reader := csv.NewReader(bytes.NewReader(buf.Bytes())) 22 | rows, err := reader.ReadAll() 23 | s.Require().NoError(err) 24 | s.Require().Len(rows, 5) 25 | } 26 | -------------------------------------------------------------------------------- /internal/storage/postgres/grant_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | "time" 9 | ) 10 | 11 | func (s *StorageTestSuite) TestGrantByGrantee() { 12 | ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) 13 | defer ctxCancel() 14 | 15 | grants, err := s.storage.Grants.ByGrantee(ctx, 1, 10, 0) 16 | s.Require().NoError(err) 17 | s.Require().Len(grants, 1) 18 | 19 | grant := grants[0] 20 | s.Require().EqualValues(1, grant.Id) 21 | s.Require().EqualValues(1000, grant.Height) 22 | s.Require().EqualValues("/cosmos.staking.v1beta1.MsgDelegate", grant.Authorization) 23 | s.Require().NotNil(grant.Params) 24 | 25 | s.Require().NotNil(grant.Granter) 26 | s.Require().EqualValues("celestia1jc92qdnty48pafummfr8ava2tjtuhfdw774w60", grant.Granter.Address) 27 | s.Require().NotNil(grant.Granter.Celestials) 28 | } 29 | 30 | func (s *StorageTestSuite) TestGrantByGranter() { 31 | ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) 32 | defer ctxCancel() 33 | 34 | grants, err := s.storage.Grants.ByGranter(ctx, 2, 10, 0) 35 | s.Require().NoError(err) 36 | s.Require().Len(grants, 1) 37 | 38 | grant := grants[0] 39 | s.Require().EqualValues(1, grant.Id) 40 | s.Require().EqualValues(1000, grant.Height) 41 | s.Require().EqualValues("/cosmos.staking.v1beta1.MsgDelegate", grant.Authorization) 42 | s.Require().NotNil(grant.Params) 43 | 44 | s.Require().NotNil(grant.Grantee) 45 | s.Require().EqualValues("celestia1mm8yykm46ec3t0dgwls70g0jvtm055wk9ayal8", grant.Grantee.Address) 46 | s.Require().NotNil(grant.Grantee.Celestials) 47 | } 48 | -------------------------------------------------------------------------------- /internal/storage/postgres/init_scripts.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "bytes" 8 | "context" 9 | "os" 10 | "path/filepath" 11 | 12 | "github.com/pkg/errors" 13 | ) 14 | 15 | func (s Storage) createScripts(ctx context.Context, subFolder string, split bool) error { 16 | scriptsDir := filepath.Join(s.scriptsDir, subFolder) 17 | 18 | files, err := os.ReadDir(scriptsDir) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | for i := range files { 24 | if files[i].IsDir() { 25 | continue 26 | } 27 | 28 | path := filepath.Join(scriptsDir, files[i].Name()) 29 | raw, err := os.ReadFile(path) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | if split { 35 | queries := bytes.Split(raw, []byte{';'}) 36 | if len(queries) == 0 { 37 | continue 38 | } 39 | 40 | for _, query := range queries { 41 | query = bytes.TrimLeft(query, "\n ") 42 | if len(query) == 0 { 43 | continue 44 | } 45 | if _, err := s.Connection().DB().NewRaw(string(query)).Exec(ctx); err != nil { 46 | return errors.Wrapf(err, "creating %s '%s'", subFolder, files[i].Name()) 47 | } 48 | } 49 | } else { 50 | if _, err := s.Connection().DB().NewRaw(string(raw)).Exec(ctx); err != nil { 51 | return errors.Wrapf(err, "creating %s '%s'", subFolder, files[i].Name()) 52 | } 53 | } 54 | } 55 | 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /internal/storage/postgres/jail.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/celenium-io/celestia-indexer/internal/storage" 10 | "github.com/dipdup-net/go-lib/database" 11 | "github.com/dipdup-net/indexer-sdk/pkg/storage/postgres" 12 | ) 13 | 14 | // Jail - 15 | type Jail struct { 16 | *postgres.Table[*storage.Jail] 17 | } 18 | 19 | // NewJail - 20 | func NewJail(db *database.Bun) *Jail { 21 | return &Jail{ 22 | Table: postgres.NewTable[*storage.Jail](db), 23 | } 24 | } 25 | 26 | func (j *Jail) ByValidator(ctx context.Context, id uint64, limit, offset int) (jails []storage.Jail, err error) { 27 | query := j.DB().NewSelect().Model(&jails). 28 | Where("validator_id = ?", id). 29 | Order("time desc") 30 | 31 | query = limitScope(query, limit) 32 | if offset > 0 { 33 | query = query.Offset(offset) 34 | } 35 | err = query.Scan(ctx) 36 | return 37 | } 38 | -------------------------------------------------------------------------------- /internal/storage/postgres/jail_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | "time" 9 | ) 10 | 11 | func (s *StorageTestSuite) TestJailByValidator() { 12 | ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) 13 | defer ctxCancel() 14 | 15 | jails, err := s.storage.Jails.ByValidator(ctx, 1, 10, 0) 16 | s.Require().NoError(err) 17 | s.Require().Len(jails, 1) 18 | 19 | j := jails[0] 20 | s.Require().EqualValues(1, j.Id) 21 | s.Require().EqualValues(1, j.ValidatorId) 22 | s.Require().EqualValues("double_sign", j.Reason) 23 | s.Require().EqualValues("10000", j.Burned.String()) 24 | } 25 | -------------------------------------------------------------------------------- /internal/storage/postgres/migrations/20250102_rollup_tags.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE public."rollup" ADD COLUMN IF NOT EXISTS tags _varchar NULL; 2 | 3 | --bun:split 4 | 5 | COMMENT ON COLUMN public."rollup".tags IS 'Rollup tags'; 6 | 7 | --bun:split 8 | 9 | REFRESH MATERIALIZED VIEW leaderboard; 10 | 11 | --bun:split 12 | 13 | REFRESH MATERIALIZED VIEW leaderboard_day; -------------------------------------------------------------------------------- /internal/storage/postgres/migrations/20250113_rollup_verified.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE public."rollup" ADD COLUMN IF NOT EXISTS verified bool NULL; 2 | 3 | --bun:split 4 | 5 | COMMENT ON COLUMN public."rollup".verified IS 'Flag is set when rollup was approved'; 6 | 7 | --bun:split 8 | 9 | UPDATE rollup set verified = TRUE where id > 0; 10 | 11 | --bun:split 12 | 13 | REFRESH MATERIALIZED VIEW leaderboard; 14 | 15 | --bun:split 16 | 17 | REFRESH MATERIALIZED VIEW leaderboard_day; -------------------------------------------------------------------------------- /internal/storage/postgres/migrations/20250412_add_module.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package migrations 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/celenium-io/celestia-indexer/internal/storage/types" 10 | "github.com/uptrace/bun" 11 | ) 12 | 13 | func init() { 14 | Migrations.MustRegister(upAddPriceFeed, downPriceFeed) 15 | } 16 | 17 | func upAddPriceFeed(ctx context.Context, db *bun.DB) error { 18 | _, err := db.ExecContext(ctx, `ALTER TYPE module_name ADD VALUE IF NOT EXISTS ? AFTER ?`, types.ModuleNameBaseapp.String(), types.ModuleNameConsensus.String()) 19 | if err != nil { 20 | return err 21 | } 22 | _, err = db.ExecContext(ctx, `ALTER TYPE module_name ADD VALUE IF NOT EXISTS ? AFTER ?`, types.ModuleNameIcahost.String(), types.ModuleNameBaseapp.String()) 23 | return err 24 | } 25 | func downPriceFeed(ctx context.Context, db *bun.DB) error { 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /internal/storage/postgres/migrations/20250609_add_event_update_client_proposal.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package migrations 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/celenium-io/celestia-indexer/internal/storage/types" 10 | "github.com/uptrace/bun" 11 | ) 12 | 13 | func init() { 14 | Migrations.MustRegister(upAddEventUpdateClientProposal, downAddEventUpdateClientProposal) 15 | } 16 | 17 | func upAddEventUpdateClientProposal(ctx context.Context, db *bun.DB) error { 18 | _, err := db.ExecContext(ctx, `ALTER TYPE event_type ADD VALUE IF NOT EXISTS ? AFTER ?`, types.EventTypeUpdateClientProposal.String(), types.EventTypeChannelCloseConfirm.String()) 19 | return err 20 | } 21 | func downAddEventUpdateClientProposal(ctx context.Context, db *bun.DB) error { 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /internal/storage/postgres/migrations/migrations.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package migrations 5 | 6 | import ( 7 | "embed" 8 | 9 | "github.com/uptrace/bun/migrate" 10 | ) 11 | 12 | var Migrations = migrate.NewMigrations() 13 | 14 | //go:embed *.sql 15 | var sqlMigrations embed.FS 16 | 17 | func init() { 18 | if err := Migrations.Discover(sqlMigrations); err != nil { 19 | panic(err) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /internal/storage/postgres/notificator.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "time" 10 | 11 | "github.com/dipdup-net/go-lib/config" 12 | "github.com/lib/pq" 13 | "github.com/uptrace/bun" 14 | ) 15 | 16 | const ( 17 | connectionName = "celestia_notifications" 18 | minReconnectInterval = 10 * time.Second 19 | maxReconnectInterval = time.Minute 20 | ) 21 | 22 | type Notificator struct { 23 | db *bun.DB 24 | l *pq.Listener 25 | } 26 | 27 | func NewNotificator(cfg config.Database, db *bun.DB) *Notificator { 28 | connStr := fmt.Sprintf( 29 | "postgres://%s:%s@%s:%d/%s?sslmode=disable", 30 | cfg.User, 31 | cfg.Password, 32 | cfg.Host, 33 | cfg.Port, 34 | cfg.Database, 35 | ) 36 | return &Notificator{ 37 | l: pq.NewListener( 38 | connStr, 39 | minReconnectInterval, 40 | maxReconnectInterval, 41 | nil, 42 | ), 43 | db: db, 44 | } 45 | } 46 | 47 | func (n *Notificator) Notify(ctx context.Context, channel string, payload string) error { 48 | _, err := n.db.ExecContext(ctx, "NOTIFY ?, ?", bun.Ident(channel), payload) 49 | return err 50 | } 51 | 52 | func (n *Notificator) Listen() chan *pq.Notification { 53 | return n.l.Notify 54 | } 55 | 56 | func (n *Notificator) Subscribe(ctx context.Context, channels ...string) error { 57 | for i := range channels { 58 | if err := n.l.Listen(channels[i]); err != nil { 59 | return err 60 | } 61 | } 62 | return nil 63 | } 64 | 65 | func (n *Notificator) Close() error { 66 | return n.l.Close() 67 | } 68 | -------------------------------------------------------------------------------- /internal/storage/postgres/redelegation.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/celenium-io/celestia-indexer/internal/storage" 10 | "github.com/dipdup-net/go-lib/database" 11 | "github.com/dipdup-net/indexer-sdk/pkg/storage/postgres" 12 | ) 13 | 14 | // Redelegation - 15 | type Redelegation struct { 16 | *postgres.Table[*storage.Redelegation] 17 | } 18 | 19 | // NewRedelegation - 20 | func NewRedelegation(db *database.Bun) *Redelegation { 21 | return &Redelegation{ 22 | Table: postgres.NewTable[*storage.Redelegation](db), 23 | } 24 | } 25 | 26 | func (d *Redelegation) ByAddress(ctx context.Context, addressId uint64, limit, offset int) (redelegations []storage.Redelegation, err error) { 27 | subQuery := d.DB().NewSelect().Model((*storage.Redelegation)(nil)). 28 | Where("address_id = ?", addressId). 29 | Order("amount desc") 30 | 31 | subQuery = limitScope(subQuery, limit) 32 | if offset > 0 { 33 | subQuery = subQuery.Offset(offset) 34 | } 35 | 36 | err = d.DB().NewSelect(). 37 | TableExpr("(?) as redelegation", subQuery). 38 | ColumnExpr("redelegation.*"). 39 | ColumnExpr("source.id as source__id, source.moniker as source__moniker, source.cons_address as source__cons_address"). 40 | ColumnExpr("dest.id as destination__id, dest.moniker as destination__moniker, dest.cons_address as destination__cons_address"). 41 | Join("left join validator as source on source.id = src_id"). 42 | Join("left join validator as dest on dest.id = dest_id"). 43 | Scan(ctx, &redelegations) 44 | 45 | return 46 | } 47 | -------------------------------------------------------------------------------- /internal/storage/postgres/redelegation_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | "time" 9 | ) 10 | 11 | func (s *StorageTestSuite) TestRedelegationByAddress() { 12 | ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) 13 | defer ctxCancel() 14 | 15 | redelegations, err := s.storage.Redelegation.ByAddress(ctx, 1, 10, 0) 16 | s.Require().NoError(err) 17 | s.Require().Len(redelegations, 1) 18 | 19 | d := redelegations[0] 20 | s.Require().EqualValues(1, d.Id) 21 | s.Require().EqualValues(1, d.AddressId) 22 | s.Require().EqualValues(1, d.SrcId) 23 | s.Require().EqualValues(2, d.DestId) 24 | s.Require().EqualValues("1000", d.Amount.String()) 25 | s.Require().NotNil(d.Source) 26 | s.Require().Equal("Conqueror", d.Source.Moniker) 27 | s.Require().NotNil(d.Destination) 28 | s.Require().Equal("Witval", d.Destination.Moniker) 29 | s.Require().Nil(d.Address) 30 | } 31 | -------------------------------------------------------------------------------- /internal/storage/postgres/staking_log.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "github.com/celenium-io/celestia-indexer/internal/storage" 8 | "github.com/dipdup-net/go-lib/database" 9 | "github.com/dipdup-net/indexer-sdk/pkg/storage/postgres" 10 | ) 11 | 12 | // StakingLog - 13 | type StakingLog struct { 14 | *postgres.Table[*storage.StakingLog] 15 | } 16 | 17 | // NewStakingLog - 18 | func NewStakingLog(db *database.Bun) *StakingLog { 19 | return &StakingLog{ 20 | Table: postgres.NewTable[*storage.StakingLog](db), 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /internal/storage/postgres/state.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/celenium-io/celestia-indexer/internal/storage" 10 | "github.com/dipdup-net/go-lib/database" 11 | "github.com/dipdup-net/indexer-sdk/pkg/storage/postgres" 12 | ) 13 | 14 | // State - 15 | type State struct { 16 | *postgres.Table[*storage.State] 17 | } 18 | 19 | // NewState - 20 | func NewState(db *database.Bun) *State { 21 | return &State{ 22 | Table: postgres.NewTable[*storage.State](db), 23 | } 24 | } 25 | 26 | // ByName - 27 | func (s *State) ByName(ctx context.Context, name string) (state storage.State, err error) { 28 | err = s.DB().NewSelect().Model(&state).Where("name = ?", name).Scan(ctx) 29 | return 30 | } 31 | -------------------------------------------------------------------------------- /internal/storage/postgres/undelegation.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/celenium-io/celestia-indexer/internal/storage" 10 | "github.com/dipdup-net/go-lib/database" 11 | "github.com/dipdup-net/indexer-sdk/pkg/storage/postgres" 12 | ) 13 | 14 | // Undelegation - 15 | type Undelegation struct { 16 | *postgres.Table[*storage.Undelegation] 17 | } 18 | 19 | // NewUndelegation - 20 | func NewUndelegation(db *database.Bun) *Undelegation { 21 | return &Undelegation{ 22 | Table: postgres.NewTable[*storage.Undelegation](db), 23 | } 24 | } 25 | 26 | func (d *Undelegation) ByAddress(ctx context.Context, addressId uint64, limit, offset int) (undelegations []storage.Undelegation, err error) { 27 | subQuery := d.DB().NewSelect().Model((*storage.Undelegation)(nil)). 28 | Where("address_id = ?", addressId). 29 | Order("amount desc") 30 | 31 | subQuery = limitScope(subQuery, limit) 32 | if offset > 0 { 33 | subQuery = subQuery.Offset(offset) 34 | } 35 | 36 | err = d.DB().NewSelect(). 37 | TableExpr("(?) as undelegation", subQuery). 38 | ColumnExpr("undelegation.*"). 39 | ColumnExpr("validator.id as validator__id, validator.moniker as validator__moniker, validator.cons_address as validator__cons_address"). 40 | Join("left join validator on validator.id = validator_id"). 41 | Scan(ctx, &undelegations) 42 | 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /internal/storage/postgres/undelegation_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | "time" 9 | ) 10 | 11 | func (s *StorageTestSuite) TestUndelegationByAddress() { 12 | ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) 13 | defer ctxCancel() 14 | 15 | undelegations, err := s.storage.Undelegation.ByAddress(ctx, 1, 10, 0) 16 | s.Require().NoError(err) 17 | s.Require().Len(undelegations, 1) 18 | 19 | d := undelegations[0] 20 | s.Require().EqualValues(1, d.Id) 21 | s.Require().EqualValues(1, d.AddressId) 22 | s.Require().EqualValues(1, d.ValidatorId) 23 | s.Require().EqualValues("1000", d.Amount.String()) 24 | s.Require().NotNil(d.Validator) 25 | s.Require().Equal("Conqueror", d.Validator.Moniker) 26 | s.Require().Nil(d.Address) 27 | } 28 | -------------------------------------------------------------------------------- /internal/storage/postgres/vesting_account.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | "time" 9 | 10 | "github.com/celenium-io/celestia-indexer/internal/storage" 11 | "github.com/dipdup-net/go-lib/database" 12 | "github.com/dipdup-net/indexer-sdk/pkg/storage/postgres" 13 | ) 14 | 15 | // VestingAccount - 16 | type VestingAccount struct { 17 | *postgres.Table[*storage.VestingAccount] 18 | } 19 | 20 | // NewVestingAccount - 21 | func NewVestingAccount(db *database.Bun) *VestingAccount { 22 | return &VestingAccount{ 23 | Table: postgres.NewTable[*storage.VestingAccount](db), 24 | } 25 | } 26 | 27 | func (v *VestingAccount) ByAddress(ctx context.Context, addressId uint64, limit, offset int, showEnded bool) (accs []storage.VestingAccount, err error) { 28 | query := v.DB().NewSelect(). 29 | Model((*storage.VestingAccount)(nil)). 30 | Where("address_id = ?", addressId). 31 | Order("end_time desc") 32 | 33 | query = limitScope(query, limit) 34 | if offset > 0 { 35 | query = query.Offset(offset) 36 | } 37 | if !showEnded { 38 | query = query.Where("end_time >= ?", time.Now().UTC()) 39 | } 40 | 41 | err = v.DB().NewSelect(). 42 | TableExpr("(?) as vesting_account", query). 43 | ColumnExpr("vesting_account.*"). 44 | ColumnExpr("tx.hash as tx__hash"). 45 | Join("left join tx on tx.id = tx_id"). 46 | Scan(ctx, &accs) 47 | return 48 | } 49 | -------------------------------------------------------------------------------- /internal/storage/postgres/vesting_account_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | "time" 9 | ) 10 | 11 | func (s *StorageTestSuite) TestVestingAccountByAddress() { 12 | ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) 13 | defer ctxCancel() 14 | 15 | vestings, err := s.storage.VestingAccounts.ByAddress(ctx, 1, 1, 0, true) 16 | s.Require().NoError(err) 17 | s.Require().Len(vestings, 1) 18 | 19 | vesting := vestings[0] 20 | s.Require().Equal("100000", vesting.Amount.String()) 21 | s.Require().Equal("delayed", vesting.Type.String()) 22 | } 23 | -------------------------------------------------------------------------------- /internal/storage/postgres/vesting_period.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package postgres 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/celenium-io/celestia-indexer/internal/storage" 10 | "github.com/dipdup-net/go-lib/database" 11 | "github.com/dipdup-net/indexer-sdk/pkg/storage/postgres" 12 | ) 13 | 14 | // VestingPeriod - 15 | type VestingPeriod struct { 16 | *postgres.Table[*storage.VestingPeriod] 17 | } 18 | 19 | // NewVestingPeriod - 20 | func NewVestingPeriod(db *database.Bun) *VestingPeriod { 21 | return &VestingPeriod{ 22 | Table: postgres.NewTable[*storage.VestingPeriod](db), 23 | } 24 | } 25 | 26 | func (v *VestingPeriod) ByVesting(ctx context.Context, id uint64, limit, offset int) (periods []storage.VestingPeriod, err error) { 27 | query := v.DB().NewSelect(). 28 | Model(&periods). 29 | Where("vesting_account_id = ?", id). 30 | Order("id desc") 31 | 32 | query = limitScope(query, limit) 33 | if offset > 0 { 34 | query = query.Offset(offset) 35 | } 36 | err = query.Scan(ctx) 37 | return 38 | } 39 | -------------------------------------------------------------------------------- /internal/storage/rollup_provider.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import ( 7 | "github.com/dipdup-net/indexer-sdk/pkg/storage" 8 | "github.com/uptrace/bun" 9 | ) 10 | 11 | //go:generate mockgen -source=$GOFILE -destination=mock/$GOFILE -package=mock -typed 12 | type IRollupProvider interface { 13 | storage.Table[*RollupProvider] 14 | } 15 | 16 | // RollupProvider - 17 | type RollupProvider struct { 18 | bun.BaseModel `bun:"rollup_provider" comment:"Table with data providers for rollups."` 19 | 20 | RollupId uint64 `bun:"rollup_id,pk" comment:"Unique internal rollup identity"` 21 | NamespaceId uint64 `bun:"namespace_id,pk" comment:"Namespace identity. May be NULL"` 22 | AddressId uint64 `bun:"address_id,pk" comment:"Celestia address of data provider"` 23 | } 24 | 25 | // TableName - 26 | func (RollupProvider) TableName() string { 27 | return "rollup_provider" 28 | } 29 | -------------------------------------------------------------------------------- /internal/storage/signers.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import ( 7 | "github.com/uptrace/bun" 8 | ) 9 | 10 | type Signer struct { 11 | bun.BaseModel `bun:"signer" comment:"Table with signers tx"` 12 | 13 | AddressId uint64 `bun:"address_id,pk" comment:"Address internal id"` 14 | TxId uint64 `bun:"tx_id,pk" comment:"Transaction internal id"` 15 | 16 | Address *Address `bun:"rel:belongs-to,join:address_id=id"` 17 | Tx *Tx `bun:"rel:belongs-to,join:tx_id=id"` 18 | } 19 | 20 | func (Signer) TableName() string { 21 | return "signer" 22 | } 23 | -------------------------------------------------------------------------------- /internal/storage/types/ibc.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package types 5 | 6 | // swagger:enum ModuleName 7 | /* 8 | ENUM( 9 | initialization, 10 | opened, 11 | closed 12 | ) 13 | */ 14 | //go:generate go-enum --marshal --sql --values --names 15 | type IbcChannelStatus string 16 | -------------------------------------------------------------------------------- /internal/storage/types/module.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package types 5 | 6 | // swagger:enum ModuleName 7 | /* 8 | ENUM( 9 | auth, 10 | blob, 11 | crisis, 12 | distribution, 13 | indexer, 14 | gov, 15 | slashing, 16 | staking, 17 | consensus, 18 | baseapp, 19 | icahost 20 | ) 21 | */ 22 | //go:generate go-enum --marshal --sql --values 23 | type ModuleName string 24 | -------------------------------------------------------------------------------- /internal/storage/types/msg_address_type.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package types 5 | 6 | // swagger:enum MsgAddressType 7 | /* 8 | ENUM( 9 | validator, 10 | delegator, 11 | depositor, 12 | 13 | validatorSrc, 14 | validatorDst, 15 | 16 | fromAddress, 17 | toAddress, 18 | input, 19 | output, 20 | 21 | grantee, 22 | granter, 23 | signer, 24 | withdraw, 25 | 26 | voter, 27 | proposer, 28 | authority, 29 | 30 | sender, 31 | receiver, 32 | 33 | submitter, 34 | 35 | admin, 36 | newAdmin, 37 | groupPolicyAddress, 38 | executor, 39 | groupMember, 40 | 41 | owner, 42 | 43 | relayer, 44 | payee, 45 | ) 46 | */ 47 | //go:generate go-enum --marshal --sql --values 48 | type MsgAddressType string 49 | -------------------------------------------------------------------------------- /internal/storage/types/packed_bytes.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package types 5 | 6 | import ( 7 | "bytes" 8 | "database/sql" 9 | "database/sql/driver" 10 | 11 | "github.com/andybalholm/brotli" 12 | jsoniter "github.com/json-iterator/go" 13 | "github.com/pkg/errors" 14 | ) 15 | 16 | var json = jsoniter.ConfigCompatibleWithStandardLibrary 17 | 18 | type PackedBytes map[string]any 19 | 20 | var _ sql.Scanner = (*PackedBytes)(nil) 21 | 22 | func (pb *PackedBytes) Scan(src interface{}) error { 23 | if src == nil { 24 | return nil 25 | } 26 | b, ok := src.([]byte) 27 | if !ok { 28 | return errors.Errorf("invalid packed bytes type: %T", src) 29 | } 30 | 31 | result := bytes.NewBuffer(b) 32 | return json.NewDecoder(brotli.NewReader(result)).Decode(pb) 33 | } 34 | 35 | var _ driver.Valuer = (*PackedBytes)(nil) 36 | 37 | func (pb PackedBytes) Value() (driver.Value, error) { 38 | return pb.ToBytes() 39 | } 40 | 41 | func (pb PackedBytes) ToBytes() ([]byte, error) { 42 | b, err := json.Marshal(pb) 43 | if err != nil { 44 | return nil, err 45 | } 46 | result := bytes.NewBuffer(nil) 47 | writer := brotli.NewWriterLevel(result, brotli.BestSpeed) 48 | 49 | if _, err := writer.Write(b); err != nil { 50 | return nil, err 51 | } 52 | if err := writer.Close(); err != nil { 53 | return nil, err 54 | } 55 | return result.Bytes(), nil 56 | } 57 | -------------------------------------------------------------------------------- /internal/storage/types/proposal.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package types 5 | 6 | // swagger:enum ProposalStatus 7 | /* 8 | ENUM( 9 | inactive, 10 | active, 11 | removed, 12 | applied, 13 | rejected 14 | ) 15 | */ 16 | //go:generate go-enum --marshal --sql --values --names 17 | type ProposalStatus string 18 | 19 | func (p ProposalStatus) GreaterThan(status ProposalStatus) bool { 20 | switch status { 21 | case ProposalStatusInactive: 22 | return false 23 | case ProposalStatusRemoved: 24 | return true 25 | case ProposalStatusRejected: 26 | return true 27 | case ProposalStatusActive: 28 | return p == ProposalStatusInactive 29 | case ProposalStatusApplied: 30 | return true 31 | } 32 | return false 33 | } 34 | 35 | // swagger:enum ProposalType 36 | /* 37 | ENUM( 38 | param_changed, 39 | text, 40 | client_update, 41 | community_pool_spend 42 | ) 43 | */ 44 | //go:generate go-enum --marshal --sql --values --names 45 | type ProposalType string 46 | 47 | // swagger:enum VoteOption 48 | /* 49 | ENUM( 50 | yes, 51 | no, 52 | no_with_veto, 53 | abstain 54 | ) 55 | */ 56 | //go:generate go-enum --marshal --sql --values --names 57 | type VoteOption string 58 | 59 | // swagger:enum VoterType 60 | /* 61 | ENUM( 62 | address, 63 | validator 64 | ) 65 | */ 66 | //go:generate go-enum --marshal --sql --values --names 67 | type VoterType string 68 | -------------------------------------------------------------------------------- /internal/storage/types/rolllup.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package types 5 | 6 | /* 7 | ENUM( 8 | uncategorized, 9 | finance, 10 | gaming, 11 | nft, 12 | social 13 | ) 14 | */ 15 | //go:generate go-enum --marshal --sql --values --names 16 | type RollupCategory string 17 | 18 | // swagger:enum RollupType 19 | /* 20 | ENUM( 21 | sovereign, 22 | settled 23 | ) 24 | */ 25 | //go:generate go-enum --marshal --sql --values --names 26 | type RollupType string 27 | -------------------------------------------------------------------------------- /internal/storage/types/staking_log_type.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package types 5 | 6 | // swagger:enum StakingLogType 7 | /* 8 | ENUM( 9 | delegation, 10 | unbonding, 11 | rewards, 12 | commissions 13 | ) 14 | */ 15 | //go:generate go-enum --marshal --sql --values --names 16 | type StakingLogType string 17 | -------------------------------------------------------------------------------- /internal/storage/types/status.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package types 5 | 6 | // swagger:enum Status 7 | /* 8 | ENUM( 9 | success, 10 | failed 11 | ) 12 | */ 13 | //go:generate go-enum --marshal --sql --values --names 14 | type Status string 15 | -------------------------------------------------------------------------------- /internal/storage/types/vested_type.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package types 5 | 6 | // swagger:enum VestingType 7 | /* 8 | ENUM( 9 | delayed, 10 | periodic, 11 | permanent, 12 | continuous 13 | ) 14 | */ 15 | //go:generate go-enum --marshal --sql --values 16 | type VestingType string 17 | -------------------------------------------------------------------------------- /internal/storage/vesting_period.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import ( 7 | "context" 8 | "time" 9 | 10 | pkgTypes "github.com/celenium-io/celestia-indexer/pkg/types" 11 | "github.com/dipdup-net/indexer-sdk/pkg/storage" 12 | "github.com/shopspring/decimal" 13 | "github.com/uptrace/bun" 14 | ) 15 | 16 | //go:generate mockgen -source=$GOFILE -destination=mock/$GOFILE -package=mock -typed 17 | type IVestingPeriod interface { 18 | storage.Table[*VestingPeriod] 19 | 20 | ByVesting(ctx context.Context, id uint64, limit, offset int) ([]VestingPeriod, error) 21 | } 22 | 23 | type VestingPeriod struct { 24 | bun.BaseModel `bun:"vesting_period" comment:"Table with vesting periods"` 25 | 26 | Id uint64 `bun:"id,pk,notnull,autoincrement" comment:"Unique internal identity"` 27 | Height pkgTypes.Level `bun:"height,notnull" comment:"The number (height) of this block"` 28 | VestingAccountId uint64 `bun:"vesting_account_id" comment:"Vesting account internal identity"` 29 | Time time.Time `bun:"time,notnull" comment:"The time of periodic vesting"` 30 | Amount decimal.Decimal `bun:"amount,type:numeric" comment:"Vested amount"` 31 | } 32 | 33 | func (VestingPeriod) TableName() string { 34 | return "vesting_period" 35 | } 36 | -------------------------------------------------------------------------------- /internal/storage/views.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | const ( 7 | ViewBlockStatsByMinute = "block_stats_by_minute" 8 | ViewBlockStatsByHour = "block_stats_by_hour" 9 | ViewBlockStatsByDay = "block_stats_by_day" 10 | ViewBlockStatsByWeek = "block_stats_by_week" 11 | ViewBlockStatsByMonth = "block_stats_by_month" 12 | ViewBlockStatsByYear = "block_stats_by_year" 13 | ViewNamespaceStatsByHour = "namespace_stats_by_hour" 14 | ViewNamespaceStatsByDay = "namespace_stats_by_day" 15 | ViewNamespaceStatsByWeek = "namespace_stats_by_week" 16 | ViewNamespaceStatsByMonth = "namespace_stats_by_month" 17 | ViewNamespaceStatsByYear = "namespace_stats_by_year" 18 | ViewStakingByHour = "staking_by_hour" 19 | ViewStakingByDay = "staking_by_day" 20 | ViewStakingByMonth = "staking_by_month" 21 | ViewLeaderboard = "leaderboard" 22 | ViewLeaderboardDay = "leaderboard_day" 23 | ViewSquareSize = "square_size" 24 | ViewRollupStatsByHour = "rollup_stats_by_hour" 25 | ViewRollupStatsByDay = "rollup_stats_by_day" 26 | ViewRollupStatsByMonth = "rollup_stats_by_month" 27 | ViewDAChange = "da_change" 28 | ViewIbcTransfersByHour = "ibc_transfers_by_hour" 29 | ViewIbcTransfersByDay = "ibc_transfers_by_day" 30 | ViewIbcTransfersByMonth = "ibc_transfers_by_month" 31 | ) 32 | -------------------------------------------------------------------------------- /internal/test_suite/helpers.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package testsuite 5 | 6 | import ( 7 | "crypto/rand" 8 | "encoding/hex" 9 | "math/big" 10 | 11 | "github.com/shopspring/decimal" 12 | ) 13 | 14 | // Ptr - returns pointer of value for testing purpose 15 | // 16 | // one := Ptr(1) // one is pointer to int 17 | func Ptr[T any](t T) *T { 18 | return &t 19 | } 20 | 21 | // MustHexDecode - returns decoded hex string, if it can't decode throws panic 22 | // 23 | // data := MustHexDecode("deadbeaf") 24 | func MustHexDecode(s string) []byte { 25 | data, err := hex.DecodeString(s) 26 | if err != nil { 27 | panic(err) 28 | } 29 | return data 30 | } 31 | 32 | // RandomDecimal - returns random decimal value 33 | // 34 | // data := RandomDecimal() 35 | func RandomDecimal() decimal.Decimal { 36 | val, _ := rand.Int(rand.Reader, big.NewInt(1000)) 37 | return decimal.NewFromBigInt(val, 1) 38 | } 39 | 40 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 41 | 42 | // RandomText - generates random string with fixed size 43 | // 44 | // data := RandomText(10) 45 | func RandomText(n int) string { 46 | b := make([]rune, n) 47 | for i := range b { 48 | ids, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letterRunes)))) 49 | b[i] = letterRunes[ids.Int64()] 50 | } 51 | return string(b) 52 | } 53 | -------------------------------------------------------------------------------- /pkg/indexer/config/config.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/celenium-io/celestia-indexer/internal/profiler" 8 | "github.com/dipdup-net/go-lib/config" 9 | ) 10 | 11 | type Config struct { 12 | config.Config `yaml:",inline"` 13 | LogLevel string `validate:"omitempty,oneof=debug trace info warn error fatal panic" yaml:"log_level"` 14 | Indexer Indexer `yaml:"indexer"` 15 | Profiler *profiler.Config `validate:"omitempty" yaml:"profiler"` 16 | } 17 | 18 | type Indexer struct { 19 | Name string `validate:"omitempty" yaml:"name"` 20 | StartLevel int64 `validate:"omitempty" yaml:"start_level"` 21 | BlockPeriod int64 `validate:"omitempty" yaml:"block_period"` 22 | ScriptsDir string `validate:"omitempty,dir" yaml:"scripts_dir"` 23 | RequestBulkSize int `validate:"omitempty,min=1" yaml:"request_bulk_size"` 24 | } 25 | 26 | // Substitute - 27 | func (c *Config) Substitute() error { 28 | if err := c.Config.Substitute(); err != nil { 29 | return err 30 | } 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /pkg/indexer/decode/handle/createAddresses.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package handle 5 | 6 | import ( 7 | "github.com/celenium-io/celestia-indexer/internal/storage" 8 | storageTypes "github.com/celenium-io/celestia-indexer/internal/storage/types" 9 | "github.com/celenium-io/celestia-indexer/pkg/indexer/decode/context" 10 | "github.com/celenium-io/celestia-indexer/pkg/types" 11 | ) 12 | 13 | type addressData struct { 14 | t storageTypes.MsgAddressType 15 | address string 16 | } 17 | 18 | type addressesData []addressData 19 | 20 | func createAddresses(ctx *context.Context, data addressesData, level types.Level) ([]storage.AddressWithType, error) { 21 | addresses := make([]storage.AddressWithType, len(data)) 22 | for i, d := range data { 23 | _, hash, err := types.Address(d.address).Decode() 24 | if err != nil { 25 | return nil, err 26 | } 27 | address := storage.Address{ 28 | Hash: hash, 29 | Height: level, 30 | LastHeight: level, 31 | Address: d.address, 32 | Balance: storage.EmptyBalance(), 33 | } 34 | if err := ctx.AddAddress(&address); err != nil { 35 | return addresses, nil 36 | } 37 | 38 | addresses[i] = storage.AddressWithType{ 39 | Type: d.t, 40 | Address: address, 41 | } 42 | } 43 | return addresses, nil 44 | } 45 | -------------------------------------------------------------------------------- /pkg/indexer/decode/handle/crisis.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package handle 5 | 6 | import ( 7 | "github.com/celenium-io/celestia-indexer/internal/storage" 8 | storageTypes "github.com/celenium-io/celestia-indexer/internal/storage/types" 9 | "github.com/celenium-io/celestia-indexer/pkg/indexer/decode/context" 10 | crisisTypes "github.com/cosmos/cosmos-sdk/x/crisis/types" 11 | ) 12 | 13 | // MsgVerifyInvariant represents a message to verify a particular invariance. 14 | func MsgVerifyInvariant(ctx *context.Context, m *crisisTypes.MsgVerifyInvariant) (storageTypes.MsgType, []storage.AddressWithType, error) { 15 | msgType := storageTypes.MsgVerifyInvariant 16 | addresses, err := createAddresses(ctx, addressesData{ 17 | {t: storageTypes.MsgAddressTypeSender, address: m.Sender}, 18 | }, ctx.Block.Height) 19 | return msgType, addresses, err 20 | } 21 | -------------------------------------------------------------------------------- /pkg/indexer/decode/handle/evidence.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package handle 5 | 6 | import ( 7 | evidenceTypes "cosmossdk.io/x/evidence/types" 8 | "github.com/celenium-io/celestia-indexer/internal/storage" 9 | storageTypes "github.com/celenium-io/celestia-indexer/internal/storage/types" 10 | "github.com/celenium-io/celestia-indexer/pkg/indexer/decode/context" 11 | ) 12 | 13 | // MsgSubmitEvidence represents a message that supports submitting arbitrary 14 | // Evidence of misbehavior such as equivocation or counterfactual signing. 15 | func MsgSubmitEvidence(ctx *context.Context, m *evidenceTypes.MsgSubmitEvidence) (storageTypes.MsgType, []storage.AddressWithType, error) { 16 | msgType := storageTypes.MsgSubmitEvidence 17 | addresses, err := createAddresses(ctx, addressesData{ 18 | {t: storageTypes.MsgAddressTypeSubmitter, address: m.Submitter}, 19 | }, ctx.Block.Height) 20 | return msgType, addresses, err 21 | } 22 | -------------------------------------------------------------------------------- /pkg/indexer/decode/handle/ibc.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package handle 5 | 6 | import ( 7 | "github.com/celenium-io/celestia-indexer/internal/storage" 8 | storageTypes "github.com/celenium-io/celestia-indexer/internal/storage/types" 9 | "github.com/celenium-io/celestia-indexer/pkg/indexer/decode/context" 10 | ibcTypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" 11 | ) 12 | 13 | // IBCTransfer defines a msg to transfer fungible tokens (i.e., Coins) between 14 | // ICS20 enabled chains. See ICS Spec here: 15 | // https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#data-structures 16 | func IBCTransfer(ctx *context.Context, m *ibcTypes.MsgTransfer) (storageTypes.MsgType, []storage.AddressWithType, error) { 17 | msgType := storageTypes.IBCTransfer 18 | addresses, err := createAddresses(ctx, addressesData{ 19 | {t: storageTypes.MsgAddressTypeSender, address: m.Sender}, 20 | // {t: storageTypes.MsgAddressTypeReceiver, 21 | // address: m.Receiver}, // TODO: is it data to do IBC Transfer on cosmos network? 22 | }, ctx.Block.Height) 23 | return msgType, addresses, err 24 | } 25 | -------------------------------------------------------------------------------- /pkg/indexer/decode/handle/interchainAccounts.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package handle 5 | 6 | import ( 7 | "github.com/celenium-io/celestia-indexer/internal/storage" 8 | storageTypes "github.com/celenium-io/celestia-indexer/internal/storage/types" 9 | "github.com/celenium-io/celestia-indexer/pkg/indexer/decode/context" 10 | interchainAccounts "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/controller/types" 11 | ) 12 | 13 | // MsgRegisterInterchainAccount defines the payload for Msg/MsgRegisterInterchainAccount 14 | func MsgRegisterInterchainAccount(ctx *context.Context, m *interchainAccounts.MsgRegisterInterchainAccount) (storageTypes.MsgType, []storage.AddressWithType, error) { 15 | msgType := storageTypes.MsgRegisterInterchainAccount 16 | addresses, err := createAddresses(ctx, addressesData{ 17 | {t: storageTypes.MsgAddressTypeOwner, address: m.Owner}, 18 | }, ctx.Block.Height) 19 | return msgType, addresses, err 20 | } 21 | 22 | // MsgSendTx defines the payload for Msg/SendTx 23 | func MsgSendTx(ctx *context.Context, m *interchainAccounts.MsgSendTx) (storageTypes.MsgType, []storage.AddressWithType, error) { 24 | msgType := storageTypes.MsgSendTx 25 | addresses, err := createAddresses(ctx, addressesData{ 26 | {t: storageTypes.MsgAddressTypeOwner, address: m.Owner}, 27 | }, ctx.Block.Height) 28 | return msgType, addresses, err 29 | } 30 | -------------------------------------------------------------------------------- /pkg/indexer/decode/handle/nft.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package handle 5 | 6 | import ( 7 | "cosmossdk.io/x/nft" 8 | "github.com/celenium-io/celestia-indexer/internal/storage" 9 | storageTypes "github.com/celenium-io/celestia-indexer/internal/storage/types" 10 | "github.com/celenium-io/celestia-indexer/pkg/indexer/decode/context" 11 | ) 12 | 13 | // MsgSendNFT represents a message to send a nft from one account to another account. 14 | func MsgSendNFT(ctx *context.Context, m *nft.MsgSend) (storageTypes.MsgType, []storage.AddressWithType, error) { 15 | msgType := storageTypes.MsgSendNFT 16 | addresses, err := createAddresses(ctx, addressesData{ 17 | {t: storageTypes.MsgAddressTypeSender, address: m.Sender}, 18 | {t: storageTypes.MsgAddressTypeReceiver, address: m.Receiver}, 19 | }, ctx.Block.Height) 20 | return msgType, addresses, err 21 | } 22 | -------------------------------------------------------------------------------- /pkg/indexer/decode/handle/qgb.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package handle 5 | 6 | import ( 7 | "github.com/celenium-io/celestia-indexer/internal/storage" 8 | storageTypes "github.com/celenium-io/celestia-indexer/internal/storage/types" 9 | "github.com/celenium-io/celestia-indexer/pkg/indexer/decode/context" 10 | "github.com/celenium-io/celestia-indexer/pkg/indexer/decode/legacy" 11 | ) 12 | 13 | // MsgRegisterEVMAddress registers an evm address to a validator. 14 | func MsgRegisterEVMAddress(ctx *context.Context, m *legacy.MsgRegisterEVMAddress) (storageTypes.MsgType, []storage.AddressWithType, error) { 15 | msgType := storageTypes.MsgRegisterEVMAddress 16 | addresses, err := createAddresses(ctx, addressesData{ 17 | {t: storageTypes.MsgAddressTypeValidator, address: m.ValidatorAddress}, 18 | // TODO: think about EVM addresses 19 | }, ctx.Block.Height) 20 | return msgType, addresses, err 21 | } 22 | -------------------------------------------------------------------------------- /pkg/indexer/decode/handle/signal.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package handle 5 | 6 | import ( 7 | "github.com/celenium-io/celestia-indexer/internal/storage" 8 | storageTypes "github.com/celenium-io/celestia-indexer/internal/storage/types" 9 | "github.com/celenium-io/celestia-indexer/pkg/indexer/decode/context" 10 | ) 11 | 12 | // MsgSignalVersion - 13 | func MsgSignalVersion(ctx *context.Context, validatorAddress string) (storageTypes.MsgType, []storage.AddressWithType, error) { 14 | msgType := storageTypes.MsgSignalVersion 15 | addresses, err := createAddresses(ctx, addressesData{ 16 | {t: storageTypes.MsgAddressTypeValidator, address: validatorAddress}, 17 | }, ctx.Block.Height) 18 | return msgType, addresses, err 19 | } 20 | 21 | // MsgTryUpgrade - 22 | func MsgTryUpgrade(ctx *context.Context, signer string) (storageTypes.MsgType, []storage.AddressWithType, error) { 23 | msgType := storageTypes.MsgTryUpgrade 24 | addresses, err := createAddresses(ctx, addressesData{ 25 | {t: storageTypes.MsgAddressTypeSigner, address: signer}, 26 | }, ctx.Block.Height) 27 | return msgType, addresses, err 28 | } 29 | -------------------------------------------------------------------------------- /pkg/indexer/decode/handle/slashing.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package handle 5 | 6 | import ( 7 | "github.com/celenium-io/celestia-indexer/internal/storage" 8 | storageTypes "github.com/celenium-io/celestia-indexer/internal/storage/types" 9 | "github.com/celenium-io/celestia-indexer/pkg/indexer/decode/context" 10 | cosmosSlashingTypes "github.com/cosmos/cosmos-sdk/x/slashing/types" 11 | ) 12 | 13 | // MsgUnjail defines the Msg/Unjail request type 14 | func MsgUnjail(ctx *context.Context, m *cosmosSlashingTypes.MsgUnjail) (storageTypes.MsgType, []storage.AddressWithType, error) { 15 | msgType := storageTypes.MsgUnjail 16 | addresses, err := createAddresses(ctx, addressesData{ 17 | {t: storageTypes.MsgAddressTypeValidator, address: m.ValidatorAddr}, 18 | }, ctx.Block.Height) 19 | return msgType, addresses, err 20 | } 21 | -------------------------------------------------------------------------------- /pkg/indexer/decode/handle/upgrade.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package handle 5 | 6 | import ( 7 | upgrade "cosmossdk.io/x/upgrade/types" 8 | "github.com/celenium-io/celestia-indexer/internal/storage" 9 | storageTypes "github.com/celenium-io/celestia-indexer/internal/storage/types" 10 | "github.com/celenium-io/celestia-indexer/pkg/indexer/decode/context" 11 | ) 12 | 13 | // MsgSoftwareUpgrade is the Msg/SoftwareUpgrade request type. 14 | func MsgSoftwareUpgrade(ctx *context.Context, m *upgrade.MsgSoftwareUpgrade) (storageTypes.MsgType, []storage.AddressWithType, error) { 15 | msgType := storageTypes.MsgSoftwareUpgrade 16 | addresses, err := createAddresses(ctx, addressesData{ 17 | {t: storageTypes.MsgAddressTypeAuthority, address: m.Authority}, 18 | }, ctx.Block.Height) 19 | return msgType, addresses, err 20 | } 21 | 22 | // MsgSoftwareUpgrade is the Msg/SoftwareUpgrade request type. 23 | func MsgCancelUpgrade(ctx *context.Context, m *upgrade.MsgCancelUpgrade) (storageTypes.MsgType, []storage.AddressWithType, error) { 24 | msgType := storageTypes.MsgCancelUpgrade 25 | addresses, err := createAddresses(ctx, addressesData{ 26 | {t: storageTypes.MsgAddressTypeAuthority, address: m.Authority}, 27 | }, ctx.Block.Height) 28 | return msgType, addresses, err 29 | } 30 | -------------------------------------------------------------------------------- /pkg/indexer/genesis/denom_metadata.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package genesis 5 | 6 | import ( 7 | "github.com/celenium-io/celestia-indexer/internal/storage" 8 | "github.com/celenium-io/celestia-indexer/pkg/node/types" 9 | ) 10 | 11 | func (module *Module) parseDenomMetadata(raw []types.DenomMetadata, data *parsedData) { 12 | for i := range raw { 13 | dm := storage.DenomMetadata{ 14 | Description: raw[i].Description, 15 | Base: raw[i].Base, 16 | Display: raw[i].Display, 17 | Name: raw[i].Name, 18 | Symbol: raw[i].Symbol, 19 | Uri: raw[i].URI, 20 | Units: raw[i].DenomUnits, 21 | } 22 | data.denomMetadata = append(data.denomMetadata, dm) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pkg/indexer/parser/events/unjail_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package events 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/celenium-io/celestia-indexer/internal/storage" 10 | "github.com/celenium-io/celestia-indexer/internal/storage/types" 11 | testsuite "github.com/celenium-io/celestia-indexer/internal/test_suite" 12 | "github.com/celenium-io/celestia-indexer/pkg/indexer/decode/context" 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | func Test_handleUnjail(t *testing.T) { 17 | tests := []struct { 18 | name string 19 | ctx *context.Context 20 | events []storage.Event 21 | msg *storage.Message 22 | idx *int 23 | }{ 24 | { 25 | name: "test 1", 26 | ctx: context.NewContext(), 27 | events: []storage.Event{ 28 | { 29 | Height: 844287, 30 | Type: "message", 31 | Data: map[string]any{ 32 | "action": "/cosmos.slashing.v1beta1.MsgUnjail", 33 | }, 34 | }, { 35 | Height: 844287, 36 | Type: "message", 37 | Data: map[string]any{ 38 | "module": "slashing", 39 | "sender": "celestiavaloper1qe8uuf5x69c526h4nzxwv4ltftr73v7q5qhs58", 40 | }, 41 | }, 42 | }, 43 | msg: &storage.Message{ 44 | Type: types.MsgUnjail, 45 | Height: 844287, 46 | }, 47 | idx: testsuite.Ptr(0), 48 | }, 49 | } 50 | for _, tt := range tests { 51 | t.Run(tt.name, func(t *testing.T) { 52 | err := handleUnjail(tt.ctx, tt.events, tt.msg, tt.idx) 53 | require.NoError(t, err) 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /pkg/indexer/parser/listen.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package parser 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/celenium-io/celestia-indexer/pkg/types" 10 | ) 11 | 12 | func (p *Module) listen(ctx context.Context) { 13 | p.Log.Info().Msg("module started") 14 | 15 | input := p.MustInput(InputName) 16 | 17 | for { 18 | select { 19 | case <-ctx.Done(): 20 | return 21 | case msg, ok := <-input.Listen(): 22 | if !ok { 23 | p.Log.Warn().Msg("can't read message from input, it was drained and closed") 24 | p.MustOutput(StopOutput).Push(struct{}{}) 25 | return 26 | } 27 | 28 | block, ok := msg.(types.BlockData) 29 | if !ok { 30 | p.Log.Warn().Msgf("invalid message type: %T", msg) 31 | continue 32 | } 33 | 34 | if err := p.parse(block); err != nil { 35 | p.Log.Err(err). 36 | Uint64("height", uint64(block.Height)). 37 | Msg("block parsing error") 38 | p.MustOutput(StopOutput).Push(struct{}{}) 39 | continue 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pkg/indexer/parser/parser.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package parser 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/celenium-io/celestia-indexer/pkg/indexer/config" 10 | "github.com/dipdup-net/indexer-sdk/pkg/modules" 11 | ) 12 | 13 | type Module struct { 14 | modules.BaseModule 15 | 16 | cfg config.Indexer 17 | } 18 | 19 | var _ modules.Module = (*Module)(nil) 20 | 21 | const ( 22 | InputName = "blocks" 23 | OutputName = "data" 24 | OutputBlobsName = "blobs" 25 | StopOutput = "stop" 26 | ) 27 | 28 | func NewModule(cfg config.Indexer) Module { 29 | m := Module{ 30 | BaseModule: modules.New("parser"), 31 | cfg: cfg, 32 | } 33 | m.CreateInputWithCapacity(InputName, 128) 34 | m.CreateOutput(OutputName) 35 | m.CreateOutput(OutputBlobsName) 36 | m.CreateOutput(StopOutput) 37 | 38 | return m 39 | } 40 | 41 | func (p *Module) Start(ctx context.Context) { 42 | p.Log.Info().Msg("starting parser module...") 43 | p.G.GoCtx(ctx, p.listen) 44 | } 45 | 46 | func (p *Module) Close() error { 47 | p.Log.Info().Msg("closing...") 48 | p.G.Wait() 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /pkg/indexer/receiver/genesis.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package receiver 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/celenium-io/celestia-indexer/pkg/node/types" 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | func (r *Module) receiveGenesis(ctx context.Context) error { 14 | r.Log.Info().Msg("receiving genesis block") 15 | genesis, err := r.api.Genesis(ctx) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | moduleAccounts, err := r.cosmosApi.ModuleAccounts(ctx) 21 | if err != nil { 22 | return errors.Wrap(err, "module account") 23 | } 24 | 25 | r.Log.Info().Msgf("got initial height of genesis block: %d", genesis.InitialHeight) 26 | r.MustOutput(GenesisOutput).Push(types.GenesisOutput{ 27 | Genesis: genesis, 28 | ModuleAccs: moduleAccounts, 29 | }) 30 | genesisDoneInput := r.MustInput(GenesisDoneInput) 31 | 32 | // Wait until the genesis block will be saved 33 | for { 34 | select { 35 | case <-ctx.Done(): 36 | return nil 37 | case <-genesisDoneInput.Listen(): 38 | return nil 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pkg/indexer/rollback/message.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package rollback 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/celenium-io/celestia-indexer/internal/storage" 10 | "github.com/celenium-io/celestia-indexer/pkg/types" 11 | "github.com/pkg/errors" 12 | ) 13 | 14 | func (module *Module) rollbackMessages(ctx context.Context, tx storage.Transaction, height types.Level) (int64, error) { 15 | msgs, err := tx.RollbackMessages(ctx, height) 16 | if err != nil { 17 | return 0, err 18 | } 19 | 20 | if len(msgs) == 0 { 21 | return 0, nil 22 | } 23 | 24 | ids := make([]uint64, len(msgs)) 25 | for i := range msgs { 26 | ids[i] = msgs[i].Id 27 | } 28 | 29 | if err := tx.RollbackMessageAddresses(ctx, ids); err != nil { 30 | return 0, err 31 | } 32 | 33 | nsMsgs, err := tx.RollbackNamespaceMessages(ctx, height) 34 | if err != nil { 35 | return 0, err 36 | } 37 | ns, err := tx.RollbackNamespaces(ctx, height) 38 | if err != nil { 39 | return 0, err 40 | } 41 | 42 | if err := module.rollbackNamespaces(ctx, tx, nsMsgs, ns, msgs); err != nil { 43 | return 0, errors.Wrap(err, "namespace rollback") 44 | } 45 | 46 | return int64(len(ns)), nil 47 | } 48 | -------------------------------------------------------------------------------- /pkg/indexer/rollback/tx.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package rollback 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/celenium-io/celestia-indexer/internal/storage" 10 | "github.com/celenium-io/celestia-indexer/pkg/types" 11 | ) 12 | 13 | func (module *Module) rollbackTransactions(ctx context.Context, tx storage.Transaction, height types.Level) error { 14 | txs, err := tx.RollbackTxs(ctx, height) 15 | if err != nil { 16 | return nil 17 | } 18 | 19 | if len(txs) == 0 { 20 | return nil 21 | } 22 | 23 | ids := make([]uint64, len(txs)) 24 | for i := range txs { 25 | ids[i] = txs[i].Id 26 | } 27 | 28 | if err := tx.RollbackSigners(ctx, ids); err != nil { 29 | return err 30 | } 31 | 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /pkg/indexer/storage/address.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/celenium-io/celestia-indexer/internal/storage" 10 | ) 11 | 12 | func saveAddresses( 13 | ctx context.Context, 14 | tx storage.Transaction, 15 | addresses []*storage.Address, 16 | ) (map[string]uint64, int64, error) { 17 | if len(addresses) == 0 { 18 | return nil, 0, nil 19 | } 20 | 21 | totalAccounts, err := tx.SaveAddresses(ctx, addresses...) 22 | if err != nil { 23 | return nil, 0, err 24 | } 25 | 26 | addToId := make(map[string]uint64) 27 | balances := make([]storage.Balance, len(addresses)) 28 | for i := range addresses { 29 | addToId[addresses[i].Address] = addresses[i].Id 30 | addresses[i].Balance.Id = addresses[i].Id 31 | balances[i] = addresses[i].Balance 32 | } 33 | err = tx.SaveBalances(ctx, balances...) 34 | return addToId, totalAccounts, err 35 | } 36 | 37 | func saveSigners( 38 | ctx context.Context, 39 | tx storage.Transaction, 40 | addrToId map[string]uint64, 41 | txs []storage.Tx, 42 | ) error { 43 | if len(txs) == 0 || len(addrToId) == 0 { 44 | return nil 45 | } 46 | 47 | var txAddresses []storage.Signer 48 | for i := range txs { 49 | for j := range txs[i].Signers { 50 | if addrId, ok := addrToId[txs[i].Signers[j].Address]; ok { 51 | txAddresses = append(txAddresses, storage.Signer{ 52 | TxId: txs[i].Id, 53 | AddressId: addrId, 54 | }) 55 | } 56 | } 57 | } 58 | return tx.SaveSigners(ctx, txAddresses...) 59 | } 60 | -------------------------------------------------------------------------------- /pkg/indexer/storage/block_signature.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/celenium-io/celestia-indexer/internal/storage" 10 | "github.com/celenium-io/celestia-indexer/pkg/types" 11 | "github.com/pkg/errors" 12 | ) 13 | 14 | const ( 15 | countOfStoringSignsInLevels = 1_000 16 | ) 17 | 18 | func (module *Module) saveBlockSignatures( 19 | ctx context.Context, 20 | tx storage.Transaction, 21 | signs []storage.BlockSignature, 22 | height types.Level, 23 | ) error { 24 | retentionLevel := height - countOfStoringSignsInLevels 25 | if retentionLevel > 0 && height%10 == 0 { // make retention on every ten block 26 | if err := tx.RetentionBlockSignatures(ctx, retentionLevel); err != nil { 27 | return err 28 | } 29 | } 30 | 31 | if len(signs) == 0 { 32 | return nil 33 | } 34 | 35 | for i := range signs { 36 | if signs[i].Validator == nil { 37 | return errors.New("nil validator of block signature") 38 | } 39 | 40 | if id, ok := module.validatorsByConsAddress[signs[i].Validator.ConsAddress]; ok { 41 | signs[i].ValidatorId = id 42 | } else { 43 | return errors.Errorf("unknown validator: %s", signs[i].Validator.ConsAddress) 44 | } 45 | } 46 | 47 | return tx.SaveBlockSignatures(ctx, signs...) 48 | } 49 | -------------------------------------------------------------------------------- /pkg/indexer/storage/functions.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import "github.com/celenium-io/celestia-indexer/internal/storage" 7 | 8 | func setNamespacesFromMessage(msg storage.Message, namespaces map[string]*storage.Namespace) { 9 | for i := range msg.Namespace { 10 | key := msg.Namespace[i].String() 11 | if ns, ok := namespaces[key]; !ok { 12 | msg.Namespace[i].PfbCount = 1 13 | namespaces[key] = &msg.Namespace[i] 14 | } else { 15 | ns.PfbCount += 1 16 | ns.Size += msg.Namespace[i].Size 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /pkg/indexer/storage/namespace.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/celenium-io/celestia-indexer/internal/storage" 10 | ) 11 | 12 | func saveNamespaces( 13 | ctx context.Context, 14 | tx storage.Transaction, 15 | namespaces map[string]*storage.Namespace, 16 | ) (int64, error) { 17 | if len(namespaces) == 0 { 18 | return 0, nil 19 | } 20 | 21 | data := make([]*storage.Namespace, 0, len(namespaces)) 22 | for key := range namespaces { 23 | data = append(data, namespaces[key]) 24 | } 25 | 26 | totalNamespaces, err := tx.SaveNamespaces(ctx, data...) 27 | if err != nil { 28 | return 0, err 29 | } 30 | 31 | return totalNamespaces, nil 32 | } 33 | -------------------------------------------------------------------------------- /pkg/indexer/storage/namespace_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/celenium-io/celestia-indexer/internal/storage" 11 | "github.com/celenium-io/celestia-indexer/internal/storage/mock" 12 | "github.com/stretchr/testify/require" 13 | "go.uber.org/mock/gomock" 14 | ) 15 | 16 | func Test_saveNamespaces(t *testing.T) { 17 | tests := []struct { 18 | name string 19 | namespaces map[string]*storage.Namespace 20 | want int64 21 | wantErr bool 22 | }{ 23 | { 24 | name: "test 1", 25 | namespaces: map[string]*storage.Namespace{ 26 | "000010203040506070809000102030405060708090001020304050607": { 27 | FirstHeight: 100, 28 | Version: 0, 29 | NamespaceID: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7}, 30 | Size: 10, 31 | PfbCount: 1, 32 | Reserved: false, 33 | }, 34 | }, 35 | want: 1, 36 | }, 37 | } 38 | 39 | ctrl := gomock.NewController(t) 40 | defer ctrl.Finish() 41 | 42 | for _, tt := range tests { 43 | t.Run(tt.name, func(t *testing.T) { 44 | tx := mock.NewMockTransaction(ctrl) 45 | 46 | tx.EXPECT(). 47 | SaveNamespaces(gomock.Any(), gomock.Any()). 48 | DoAndReturn(func(_ context.Context, ns ...*storage.Namespace) (int64, error) { 49 | require.Equal(t, len(tt.namespaces), len(ns)) 50 | return int64(len(ns)), nil 51 | }) 52 | 53 | got, err := saveNamespaces(t.Context(), tx, tt.namespaces) 54 | require.Equal(t, tt.wantErr, err != nil) 55 | require.Equal(t, tt.want, got) 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /pkg/indexer/storage/state.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package storage 5 | 6 | import ( 7 | "github.com/celenium-io/celestia-indexer/internal/storage" 8 | "github.com/shopspring/decimal" 9 | ) 10 | 11 | func updateState(block *storage.Block, totalAccounts, totalNamespaces, totalProposals, ibcClientsCount int64, totalValidators int, totalVotingPower decimal.Decimal, state *storage.State) { 12 | if block.Id <= uint64(state.LastHeight) { 13 | return 14 | } 15 | 16 | state.LastHeight = block.Height 17 | state.LastHash = block.Hash 18 | state.LastTime = block.Time 19 | state.TotalTx += block.Stats.TxCount 20 | state.TotalAccounts += totalAccounts 21 | state.TotalNamespaces += totalNamespaces 22 | state.TotalProposals += totalProposals 23 | state.TotalBlobsSize += block.Stats.BlobsSize 24 | state.TotalValidators += totalValidators 25 | state.TotalFee = state.TotalFee.Add(block.Stats.Fee) 26 | state.TotalSupply = state.TotalSupply.Add(block.Stats.SupplyChange) 27 | state.TotalStake = state.TotalStake.Add(totalVotingPower) 28 | state.TotalIbcClients += ibcClientsCount 29 | state.ChainId = block.ChainId 30 | } 31 | -------------------------------------------------------------------------------- /pkg/node/api.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package node 5 | 6 | import ( 7 | "context" 8 | 9 | pkgTypes "github.com/celenium-io/celestia-indexer/pkg/types" 10 | 11 | "github.com/celenium-io/celestia-indexer/pkg/node/types" 12 | ) 13 | 14 | //go:generate mockgen -source=$GOFILE -destination=mock/$GOFILE -package=mock -typed 15 | type Api interface { 16 | Status(ctx context.Context) (types.Status, error) 17 | Head(ctx context.Context) (pkgTypes.ResultBlock, error) 18 | Block(ctx context.Context, level pkgTypes.Level) (pkgTypes.ResultBlock, error) 19 | BlockResults(ctx context.Context, level pkgTypes.Level) (pkgTypes.ResultBlockResults, error) 20 | Genesis(ctx context.Context) (types.Genesis, error) 21 | BlockData(ctx context.Context, level pkgTypes.Level) (pkgTypes.BlockData, error) 22 | BlockDataGet(ctx context.Context, level pkgTypes.Level) (pkgTypes.BlockData, error) 23 | BlockBulkData(ctx context.Context, levels ...pkgTypes.Level) ([]pkgTypes.BlockData, error) 24 | } 25 | 26 | //go:generate mockgen -source=$GOFILE -destination=mock/$GOFILE -package=mock -typed 27 | type DalApi interface { 28 | Blobs(ctx context.Context, height pkgTypes.Level, hash ...string) ([]types.Blob, error) 29 | Blob(ctx context.Context, height pkgTypes.Level, namespace, commitment string) (types.Blob, error) 30 | } 31 | 32 | //go:generate mockgen -source=$GOFILE -destination=mock/$GOFILE -package=mock -typed 33 | type CosmosApi interface { 34 | ModuleAccounts(ctx context.Context) ([]types.Account, error) 35 | } 36 | -------------------------------------------------------------------------------- /pkg/node/api/module_accounts.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package api 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/celenium-io/celestia-indexer/pkg/node/types" 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | func (api *API) ModuleAccounts(ctx context.Context) ([]types.Account, error) { 14 | var response types.Auth 15 | if err := api.get(ctx, "cosmos/auth/v1beta1/module_accounts", nil, &response); err != nil { 16 | return nil, errors.Wrap(err, "get") 17 | } 18 | return response.Accounts, nil 19 | } 20 | -------------------------------------------------------------------------------- /pkg/node/rpc/genesis.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package rpc 5 | 6 | import ( 7 | "context" 8 | "strconv" 9 | 10 | "github.com/celenium-io/celestia-indexer/pkg/node/types" 11 | "github.com/pkg/errors" 12 | ) 13 | 14 | type GenesisChunk struct { 15 | Chunk int64 `json:"chunk,string"` 16 | Total int64 `json:"total,string"` 17 | Data []byte `json:"data"` 18 | } 19 | 20 | func (api *API) Genesis(ctx context.Context) (types.Genesis, error) { 21 | path := "genesis_chunked" 22 | 23 | genesisData := make([]byte, 0) 24 | var chunk int64 25 | var total int64 26 | 27 | for chunk == 0 || chunk < total { 28 | args := map[string]string{ 29 | "chunk": strconv.FormatInt(chunk, 10), 30 | } 31 | 32 | var gr types.Response[GenesisChunk] 33 | if err := api.get(ctx, path, args, &gr); err != nil { 34 | return types.Genesis{}, errors.Wrap(err, "genesis block request") 35 | } 36 | 37 | if gr.Error != nil { 38 | return types.Genesis{}, errors.Wrapf(types.ErrRequest, "request %d error: %s", gr.Id, gr.Error.Error()) 39 | } 40 | 41 | chunk += 1 42 | total = gr.Result.Total 43 | genesisData = append(genesisData, gr.Result.Data...) 44 | } 45 | 46 | var genesis types.Genesis 47 | err := json.Unmarshal(genesisData, &genesis) 48 | return genesis, err 49 | } 50 | -------------------------------------------------------------------------------- /pkg/node/rpc/getBlockResults.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package rpc 5 | 6 | import ( 7 | "context" 8 | pkgTypes "github.com/celenium-io/celestia-indexer/pkg/types" 9 | "strconv" 10 | 11 | "github.com/celenium-io/celestia-indexer/pkg/node/types" 12 | "github.com/pkg/errors" 13 | ) 14 | 15 | const pathBlockResults = "block_results" 16 | 17 | func (api *API) BlockResults(ctx context.Context, level pkgTypes.Level) (pkgTypes.ResultBlockResults, error) { 18 | args := make(map[string]string) 19 | if level != 0 { 20 | args["height"] = strconv.FormatUint(uint64(level), 10) 21 | } 22 | 23 | var gbr types.Response[pkgTypes.ResultBlockResults] 24 | if err := api.get(ctx, pathBlockResults, args, &gbr); err != nil { 25 | return gbr.Result, errors.Wrap(err, "api.get") 26 | } 27 | 28 | if gbr.Error != nil { 29 | return gbr.Result, errors.Wrapf(types.ErrRequest, "request %d error: %s", gbr.Id, gbr.Error.Error()) 30 | } 31 | 32 | return gbr.Result, nil 33 | } 34 | -------------------------------------------------------------------------------- /pkg/node/rpc/head.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package rpc 5 | 6 | import ( 7 | "context" 8 | "github.com/celenium-io/celestia-indexer/pkg/types" 9 | ) 10 | 11 | func (api *API) Head(ctx context.Context) (types.ResultBlock, error) { 12 | return api.Block(ctx, 0) 13 | } 14 | -------------------------------------------------------------------------------- /pkg/node/rpc/status.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package rpc 5 | 6 | import ( 7 | "context" 8 | "github.com/celenium-io/celestia-indexer/pkg/node/types" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | const pathStatus = "status" 13 | 14 | func (api *API) Status(ctx context.Context) (types.Status, error) { 15 | var sr types.Response[types.Status] 16 | if err := api.get(ctx, pathStatus, nil, &sr); err != nil { 17 | return sr.Result, errors.Wrap(err, "api.get") 18 | } 19 | 20 | if sr.Error != nil { 21 | return sr.Result, errors.Wrapf(types.ErrRequest, "status request %d error: %s", sr.Id, sr.Error.Error()) 22 | } 23 | 24 | return sr.Result, nil 25 | } 26 | -------------------------------------------------------------------------------- /pkg/node/types/blob.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package types 5 | 6 | type Blob struct { 7 | Namespace string `json:"namespace"` 8 | Data string `json:"data"` 9 | ShareVersion int `json:"share_version"` 10 | Commitment string `json:"commitment"` 11 | } 12 | 13 | type Proof struct { 14 | Start int64 `json:"start"` 15 | End int64 `json:"end"` 16 | Nodes []string `json:"nodes"` 17 | } 18 | -------------------------------------------------------------------------------- /pkg/node/types/errors.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package types 5 | 6 | import "errors" 7 | 8 | // errors 9 | var ( 10 | ErrRequest = errors.New("request error") 11 | ) 12 | -------------------------------------------------------------------------------- /pkg/node/types/jsonrpc.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package types 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | ) 10 | 11 | type Request struct { 12 | Method string `json:"method"` 13 | Params []any `json:"params"` 14 | Id int64 `json:"id"` 15 | JsonRpc string `json:"jsonrpc"` 16 | } 17 | 18 | type Response[T any] struct { 19 | Id int64 `json:"id"` 20 | JsonRpc string `json:"jsonrpc"` 21 | Error *Error `json:"error,omitempty"` 22 | Result T `json:"result"` 23 | } 24 | 25 | // Error - 26 | type Error struct { 27 | Code int64 `json:"code"` 28 | Message string `json:"message"` 29 | Data json.RawMessage `json:"data"` 30 | } 31 | 32 | // Error - 33 | func (e Error) Error() string { 34 | return fmt.Sprintf("code=%d message=%s data=%s", e.Code, e.Message, string(e.Data)) 35 | } 36 | -------------------------------------------------------------------------------- /pkg/types/blockData.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package types 5 | 6 | type BlockData struct { 7 | ResultBlock 8 | ResultBlockResults 9 | 10 | AppVersion uint64 11 | } 12 | -------------------------------------------------------------------------------- /pkg/types/level.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 PK Lab AG 2 | // SPDX-License-Identifier: MIT 3 | 4 | package types 5 | 6 | import "fmt" 7 | 8 | type Level int64 9 | 10 | func (l Level) String() string { 11 | return fmt.Sprintf("%d", l) 12 | } 13 | -------------------------------------------------------------------------------- /test/data/address.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | height: 100 3 | address: celestia1mm8yykm46ec3t0dgwls70g0jvtm055wk9ayal8 4 | hash: 0xdece425b75d67115bda877e1e7a1f262f6fa51d6 5 | last_height: 100 6 | - id: 2 7 | height: 101 8 | address: celestia1jc92qdnty48pafummfr8ava2tjtuhfdw774w60 9 | hash: 0x960aa0366b254e1ea79bda467eb3aa5c97cba5ae 10 | last_height: 101 11 | - id: 3 12 | height: 102 13 | address: celestia1cr2t0y5zu9sya67t9sp0vt9cxum408yuphkhex 14 | hash: 0xC0D4B79282E1604EEBCB2C02F62CB83737579C9C 15 | last_height: 102 16 | - id: 4 17 | height: 101 18 | address: celestia1xzsdn65hyljcmenlxyjmdmvghhd0w4ut27k3fx56jp2p69eh6srs8p3rss 19 | hash: 0x30a0d9ea9727e58de67f3125b6ed88bddaf7578b57ad149a9a90541d1737d407 20 | last_height: 101 -------------------------------------------------------------------------------- /test/data/apikey.yml: -------------------------------------------------------------------------------- 1 | - key: test_key 2 | description: valid key -------------------------------------------------------------------------------- /test/data/balance.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | spendable: 432 3 | delegated: 10 4 | unbonding: 10 5 | currency: utia 6 | - id: 2 7 | spendable: 321 8 | delegated: 5 9 | unbonding: 5 10 | currency: utia 11 | - id: 3 12 | spendable: 555 13 | delegated: 15 14 | unbonding: 15 15 | currency: utia 16 | - id: 4 17 | spendable: 210 18 | delegated: 0 19 | unbonding: 0 20 | currency: utia -------------------------------------------------------------------------------- /test/data/blob_log.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | namespace_id: 2 3 | tx_id: 4 4 | msg_id: 1 5 | commitment: RWW7eaKKXasSGK/DS8PlpErARbl5iFs1vQIycYEAlk0= 6 | size: 10 7 | height: 0 8 | time: '2023-06-04T03:10:57+00:00' 9 | signer_id: 1 10 | fee: 1000 11 | - id: 2 12 | namespace_id: 1 13 | tx_id: 3 14 | msg_id: 1 15 | commitment: RWW7eaKKXasSGK/DS8PlpErARbl5iFs1vQIycYEAlk0= 16 | size: 20 17 | height: 1000 18 | time: '2023-07-04T03:10:57+00:00' 19 | signer_id: 1 20 | fee: 1000 21 | - id: 3 22 | namespace_id: 2 23 | tx_id: 2 24 | msg_id: 1 25 | commitment: 0CsLX630cjij9DR6nqoWfQcCH2pCQSoSuq63dTkd4Bw= 26 | size: 12 27 | height: 1000 28 | time: '2023-07-04T03:10:57+00:00' 29 | signer_id: 2 30 | fee: 1000 31 | - id: 4 32 | namespace_id: 3 33 | tx_id: 2 34 | msg_id: 1 35 | commitment: 0CsLX630cjij9DR6nqoWfQcCH2pCQSoSuq63dTkd4Bw= 36 | size: 12 37 | height: 1000 38 | time: '2023-08-05T03:11:57+00:00' 39 | signer_id: 2 40 | fee: 5000 41 | - id: 5 42 | namespace_id: 2 43 | tx_id: 4 44 | msg_id: 1 45 | commitment: RWW7eaKKXasSGK/DS8PlpErARbl5iFs1vQIycYEAlk0= 46 | size: 10 47 | height: 1000 48 | time: '2023-07-05T03:11:57+00:00' 49 | signer_id: 2 50 | fee: 1000 -------------------------------------------------------------------------------- /test/data/block_signature.yml: -------------------------------------------------------------------------------- 1 | - height: 999 2 | validator_id: 1 3 | time: '2023-07-04T03:10:56+00:00' 4 | - height: 1000 5 | validator_id: 1 6 | time: '2023-07-04T03:10:57+00:00' -------------------------------------------------------------------------------- /test/data/block_stats.yml: -------------------------------------------------------------------------------- 1 | - id: 2 2 | height: 1000 3 | time: '2023-07-04T03:10:57+00:00' 4 | tx_count: 2 5 | events_count: 0 6 | fee: 2873468273 7 | supply_change: 30930476 8 | inflation_rate: 0.080000000000000000 9 | block_time: 11000 10 | blobs_size: 1234 11 | blobs_count: 4 12 | gas_limit: 160820 13 | gas_used: 154966 14 | rewards: 0 15 | commissions: 0 16 | square_size: 2 17 | 18 | - id: 1 19 | height: 999 20 | time: '2023-07-04T03:10:56+00:00' 21 | tx_count: 0 22 | events_count: 0 23 | fee: 1726351723 24 | supply_change: 20930476 25 | inflation_rate: 0.080000000000000000 26 | block_time: 11423 27 | blobs_size: 2412 28 | blobs_count: 0 29 | gas_limit: 80410 30 | gas_used: 77483 31 | rewards: 0 32 | commissions: 0 33 | square_size: 2 34 | -------------------------------------------------------------------------------- /test/data/celestial.yml: -------------------------------------------------------------------------------- 1 | - id: name 1 2 | address_id: 1 3 | image_url: 4 | change_id: 1 5 | status: PRIMARY 6 | - id: name 2 7 | address_id: 1 8 | image_url: 9 | change_id: 2 10 | status: VERIFIED 11 | - id: name 3 12 | address_id: 2 13 | image_url: 14 | change_id: 3 15 | status: PRIMARY 16 | - id: name 4 17 | address_id: 4 18 | image_url: 19 | change_id: 4 20 | status: PRIMARY -------------------------------------------------------------------------------- /test/data/celestial_state.yml: -------------------------------------------------------------------------------- 1 | - name: indexer 2 | change_id: 3 -------------------------------------------------------------------------------- /test/data/constant.yml: -------------------------------------------------------------------------------- 1 | - module: auth 2 | name: max_memo_characters 3 | value: "256" 4 | - module: auth 5 | name: tx_size_cost_per_byte 6 | value: "10" 7 | - module: blob 8 | name: gas_per_blob_byte 9 | value: "8" -------------------------------------------------------------------------------- /test/data/delegation.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | address_id: 1 3 | validator_id: 1 4 | amount: 10000 5 | - id: 2 6 | address_id: 2 7 | validator_id: 1 8 | amount: 10000 -------------------------------------------------------------------------------- /test/data/denom_metadata.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | description: The native staking token of the Celestia network. 3 | name: TIA 4 | symbol: TIA 5 | base: utia 6 | display: TIA 7 | uri: 8 | units: 9 | - denom: utia 10 | exponent: 0 11 | - denom: TIA 12 | exponent: 6 13 | -------------------------------------------------------------------------------- /test/data/event.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | height: 1000 3 | time: '2023-07-04T03:10:57+00:00' 4 | position: 0 5 | type: burn 6 | tx_id: null 7 | data: null 8 | - id: 2 9 | height: 1000 10 | time: '2023-07-04T03:10:57+00:00' 11 | position: 1 12 | type: mint 13 | tx_id: 1 14 | data: null 15 | - id: 3 16 | height: 1000 17 | time: '2023-07-04T03:10:57+00:00' 18 | position: 2 19 | type: mint 20 | tx_id: 2 21 | data: null -------------------------------------------------------------------------------- /test/data/grant.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | height: 1000 3 | granter_id: 2 4 | grantee_id: 1 5 | authorization: "/cosmos.staking.v1beta1.MsgDelegate" 6 | expiration: null 7 | revoked: false 8 | revoke_height: null 9 | params: 10 | Msg: "/cosmos.staking.v1beta1.MsgDelegate" -------------------------------------------------------------------------------- /test/data/ibc_channel.yml: -------------------------------------------------------------------------------- 1 | - id: channel-1 2 | connection_id: connection-1 3 | client_id: client-1 4 | port_id: transfer 5 | counterparty_port_id: transfer 6 | counterparty_channel_id: channel-10 7 | version: ics20-1 8 | created_at: '2023-07-04T03:10:57+00:00' 9 | confirmed_at: '2023-07-04T03:11:57+00:00' 10 | height: 1000 11 | confirmation_height: 1001 12 | create_tx_id: 1 13 | confirmation_tx_id: 2 14 | ordering: true 15 | creator_id: 1 16 | status: opened 17 | received: 100 18 | sent: 100 19 | transfers_count: 1 -------------------------------------------------------------------------------- /test/data/ibc_client.yml: -------------------------------------------------------------------------------- 1 | - id: client-1 2 | type: client 3 | height: 1000 4 | created_at: '2023-07-04T03:10:57+00:00' 5 | updated_at: '2023-07-04T03:10:57+00:00' 6 | tx_id: 1 7 | latest_revision_height: 1000 8 | latest_revision_number: 1 9 | frozen_revision_height: 0 10 | frozen_revision_number: 0 11 | trusting_period: 20000000 12 | unbonding_period: 10000000 13 | max_clock_drift: 360000000000 14 | trust_level_denominator: 1 15 | trust_level_numerator: 2 16 | connection_count: 1 17 | creator_id: 1 18 | chain_id: osmosis-1 -------------------------------------------------------------------------------- /test/data/ibc_connection.yml: -------------------------------------------------------------------------------- 1 | - connection_id: connection-1 2 | client_id: client-1 3 | counterparty_connection_id: counterparty-1 4 | counterparty_client_id: counterparty-client-1 5 | created_at: '2023-07-04T03:10:57+00:00' 6 | connected_at: '2023-07-04T03:11:57+00:00' 7 | height: 1000 8 | connection_height: 1001 9 | create_tx_id: 1 10 | connection_tx_id: 2 11 | channels_count: 1 -------------------------------------------------------------------------------- /test/data/ibc_transfer.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | channel_id: channel-1 3 | port: transfer 4 | connection_id: connection-1 5 | time: '2023-07-04T03:10:57+00:00' 6 | height: 1000 7 | amount: 123456 8 | denom: utia 9 | memo: 10 | receiver_address: osmo1m8wg4vxkefhs374qxmmqpyusgz289wmulex5qdwpfx7jnrxzer5s9cv83q 11 | sender_id: 1 12 | tx_id: 1 13 | timeout: null 14 | height_timeout: null 15 | sequence: 321654 -------------------------------------------------------------------------------- /test/data/jail.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | time: '2023-07-04T03:10:57+00:00' 3 | height: 1000 4 | validator_id: 1 5 | burned: 10000 6 | reason: double_sign -------------------------------------------------------------------------------- /test/data/message.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | height: 1000 3 | time: '2023-07-04T03:10:57+00:00' 4 | position: 0 5 | type: MsgWithdrawDelegatorReward 6 | tx_id: 1 7 | size: 100 8 | data: null 9 | - id: 2 10 | height: 1000 11 | time: '2023-07-04T03:10:57+00:00' 12 | position: 1 13 | type: MsgDelegate 14 | tx_id: 1 15 | size: 100 16 | data: null 17 | - id: 3 18 | height: 1000 19 | time: '2023-07-04T03:10:57+00:00' 20 | position: 2 21 | type: MsgUnjail 22 | tx_id: 2 23 | size: 100 24 | data: null 25 | - id: 4 26 | height: 1000 27 | time: '2023-07-04T03:10:57+00:00' 28 | position: 3 29 | type: MsgPayForBlobs 30 | tx_id: 2 31 | size: 200 32 | data: 0x1b2e01c01c07762cb666885e04476efadbdc774ae2c8fb2af2c65c0324749e1e71405a6b6d71222da1346bcba2139fe47cbd6194d8f16bc2ef700b11c6987425f5fb6989b8a1804c16d98349c0851075b239e9a8f7ace2bf889e2744b474b98f7ecaa05874afd23d737e2ee6d23f81172afd6d51d1d513bbf9f5c659adb7d9a93b5b2fe6fe7bd1b7aedb004f364991aefc8da70a1884fc4002d3e23f19ce763c304439f7a1d4f5b39fff27287a6999967394fd14943fc84621af34a0311b2e01c01c07762cb666885e04476efadbdc774ae2c8fb2af2c65c0324749e1e71405a6b6d71222da1346bcba2139fe47cbd6194d8f16bc2ef700b11c6987425f5fb6989b8a1804c16d98349c0851075b239e9a8f7ace2bf889e2744b474b98f7ecaa05874afd23d737e2ee6d23f81172afd6d51d1d513bbf9f5c659adb7d9a93b5b2fe6fe7bd1b7aedb004f364991aefc8da70a1884fc4002d3e23f19ce763c304439f7a1d4f5b39fff27287a6999967394fd14943fc84621af34a031 33 | - id: 5 34 | height: 999 35 | position: 0 36 | time: '2023-07-04T03:11:57+00:00' 37 | type: MsgCreateValidator 38 | tx_id: 3 39 | size: 100 40 | data: null -------------------------------------------------------------------------------- /test/data/msg_address.yml: -------------------------------------------------------------------------------- 1 | - msg_id: 1 2 | address_id: 1 3 | type: fromAddress 4 | - msg_id: 1 5 | address_id: 2 6 | type: toAddress 7 | - msg_id: 2 8 | address_id: 1 9 | type: toAddress -------------------------------------------------------------------------------- /test/data/namespace.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | version: 0 3 | namespace_id: 0x5F7A8DDFE6136FE76B65B9066D4F816D707F 4 | size: 1234 5 | pfb_count: 3 6 | first_height: 1000 7 | last_height: 1000 8 | last_message_time: '2023-07-04T03:10:57+00:00' 9 | - id: 2 10 | version: 1 11 | namespace_id: 0x5F7A8DDFE6136FE76B65B9066D4F816D707F 12 | size: 1255 13 | pfb_count: 2 14 | first_height: 1000 15 | last_height: 1000 16 | last_message_time: '2023-07-04T03:10:57+00:00' 17 | - id: 3 18 | version: 0 19 | namespace_id: 0x62491A45621ABEA79EBA193FD2944B5B9EBD 20 | size: 12 21 | pfb_count: 1 22 | first_height: 1000 23 | last_height: 1000 24 | last_message_time: '2023-07-04T03:10:57+00:00' -------------------------------------------------------------------------------- /test/data/namespace_message.yml: -------------------------------------------------------------------------------- 1 | - msg_id: 3 2 | namespace_id: 2 3 | time: '2023-07-04T03:10:57+00:00' 4 | tx_id: 2 5 | height: 1000 6 | - msg_id: 2 7 | namespace_id: 1 8 | time: '2023-07-04T03:10:57+00:00' 9 | tx_id: 3 10 | height: 1000 11 | - msg_id: 1 12 | namespace_id: 2 13 | time: '2023-06-04T03:10:57+00:00' 14 | tx_id: 4 15 | height: 0 -------------------------------------------------------------------------------- /test/data/proposal.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | proposer_id: 1 3 | height: 1000 4 | status: inactive 5 | title: Title 6 | description: Description 7 | type: text 8 | created_at: '2023-06-04T03:10:57+00:00' 9 | deposit_time: '2023-06-06T03:10:57+00:00' 10 | deposit: 10000 11 | votes_count: 16 12 | yes: 10 13 | no: 1 14 | no_with_veto: 3 15 | abstain: 2 16 | yes_vals: 10 17 | no_vals: 0 18 | no_with_veto_vals: 3 19 | abstain_vals: 1 20 | yes_addrs: 0 21 | no_addrs: 1 22 | no_with_veto_addrs: 0 23 | abstain_addrs: 1 24 | changes: 0x0001 25 | total_voting_power: 0 26 | voting_power: 0 27 | yes_voting_power: 0 28 | no_voting_power: 0 29 | no_with_veto_voting_power: 0 30 | abstain_voting_power: 0 31 | quorum: 32 | veto_quorum: 33 | threshold: 34 | min_deposit: 35 | - id: 2 36 | proposer_id: 2 37 | height: 1001 38 | status: active 39 | title: Title 40 | description: Description 41 | type: community_pool_spend 42 | created_at: '2023-06-04T03:10:57+00:00' 43 | deposit_time: '2023-06-06T03:10:57+00:00' 44 | activation_time: '2023-06-04T03:13:57+00:00' 45 | end_time: '2023-06-06T03:13:57+00:00' 46 | deposit: 10000 47 | votes_count: 16 48 | yes: 10 49 | no: 1 50 | no_with_veto: 3 51 | abstain: 2 52 | yes_vals: 10 53 | no_vals: 0 54 | no_with_veto_vals: 3 55 | abstain_vals: 1 56 | yes_addrs: 0 57 | no_addrs: 1 58 | no_with_veto_addrs: 0 59 | abstain_addrs: 1 60 | changes: 0x0001 61 | total_voting_power: 0 62 | voting_power: 0 63 | yes_voting_power: 0 64 | no_voting_power: 0 65 | no_with_veto_voting_power: 0 66 | abstain_voting_power: 0 67 | quorum: 68 | veto_quorum: 69 | threshold: 70 | min_deposit: -------------------------------------------------------------------------------- /test/data/redelegation.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | address_id: 1 3 | src_id: 1 4 | dest_id: 2 5 | amount: 1000 6 | time: '2023-07-04T03:10:57+00:00' 7 | height: 1000 8 | completion_time: '2023-08-04T03:10:57+00:00' -------------------------------------------------------------------------------- /test/data/rollback/address.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | height: 100 3 | address: celestia1mm8yykm46ec3t0dgwls70g0jvtm055wk9ayal8 4 | hash: 0xdece425b75d67115bda877e1e7a1f262f6fa51d6 5 | 6 | - id: 2 7 | height: 101 8 | address: celestia1jc92qdnty48pafummfr8ava2tjtuhfdw774w60 9 | hash: 0x960aa0366b254e1ea79bda467eb3aa5c97cba5ae 10 | 11 | - id: 3 12 | height: 1001 13 | address: celestia1jc92qdnty48pafummfr8ava2tjtuhfdw774333 14 | hash: 0x960aa0366b254e1ea79bda467eb3aa5c97cba333 15 | 16 | - id: 4 17 | height: 103 18 | address: celestia1xzsdn65hyljcmenlxyjmdmvghhd0w4ut27k3fx56jp2p69eh6srs8p3rss 19 | hash: 0x30a0d9ea9727e58de67f3125b6ed88bddaf7578b57ad149a9a90541d1737d407 -------------------------------------------------------------------------------- /test/data/rollback/balance.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | spendable: 123 3 | delegated: 0 4 | unbonding: 0 5 | currency: utia 6 | - id: 2 7 | spendable: 321 8 | delegated: 0 9 | unbonding: 0 10 | currency: utia -------------------------------------------------------------------------------- /test/data/rollback/blob_log.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | namespace_id: 2 3 | tx_id: 4 4 | msg_id: 1 5 | commitment: RWW7eaKKXasSGK/DS8PlpErARbl5iFs1vQIycYEAlk0= 6 | size: 10 7 | height: 999 8 | time: '2023-06-04T03:10:57+00:00' 9 | signer_id: 2 10 | fee: 1000 11 | - id: 2 12 | namespace_id: 1 13 | tx_id: 3 14 | msg_id: 1 15 | commitment: RWW7eaKKXasSGK/DS8PlpErARbl5iFs1vQIycYEAlk0= 16 | size: 20 17 | height: 1000 18 | time: '2023-07-04T03:10:57+00:00' 19 | signer_id: 2 20 | fee: 1000 21 | - id: 3 22 | namespace_id: 2 23 | tx_id: 2 24 | msg_id: 1 25 | commitment: 0CsLX630cjij9DR6nqoWfQcCH2pCQSoSuq63dTkd4Bw= 26 | size: 12 27 | height: 1000 28 | time: '2023-07-04T03:10:57+00:00' 29 | signer_id: 2 30 | fee: 1000 31 | -------------------------------------------------------------------------------- /test/data/rollback/block_signature.yml: -------------------------------------------------------------------------------- 1 | - height: 999 2 | validator_id: 1 3 | time: '2023-07-04T03:10:56+00:00' 4 | - height: 1000 5 | validator_id: 1 6 | time: '2023-07-04T03:10:57+00:00' 7 | - height: 1001 8 | validator_id: 1 9 | time: '2023-07-04T03:10:58+00:00' -------------------------------------------------------------------------------- /test/data/rollback/block_stats.yml: -------------------------------------------------------------------------------- 1 | - id: 3 2 | height: 1001 3 | time: '2023-07-04T03:10:58+00:00' 4 | tx_count: 0 5 | events_count: 0 6 | blobs_size: 100 7 | blobs_count: 2 8 | fee: 497012 9 | supply_change: 23590834 10 | inflation_rate: 0 11 | block_time: 11000 12 | gas_limit: 0 13 | gas_used: 0 14 | rewards: 0 15 | commissions: 0 16 | square_size: 2 17 | 18 | - id: 2 19 | height: 1000 20 | time: '2023-07-04T03:10:57+00:00' 21 | tx_count: 2 22 | events_count: 0 23 | blobs_size: 900 24 | blobs_count: 2 25 | fee: 2873468273 26 | supply_change: 30930476 27 | inflation_rate: 0.080000000000000000 28 | block_time: 11000 29 | gas_limit: 0 30 | gas_used: 0 31 | rewards: 0 32 | commissions: 0 33 | square_size: 2 34 | 35 | - id: 1 36 | height: 999 37 | time: '2023-07-04T03:10:56+00:00' 38 | tx_count: 1 39 | events_count: 0 40 | blobs_size: 800 41 | blobs_count: 1 42 | fee: 1726351723 43 | supply_change: 20930476 44 | inflation_rate: 0.080000000000000000 45 | block_time: 11000 46 | gas_limit: 0 47 | gas_used: 0 48 | rewards: 0 49 | commissions: 0 50 | square_size: 2 51 | -------------------------------------------------------------------------------- /test/data/rollback/constant.yml: -------------------------------------------------------------------------------- 1 | - module: auth 2 | name: max_memo_characters 3 | value: "256" 4 | - module: auth 5 | name: tx_size_cost_per_byte 6 | value: "10" 7 | - module: blob 8 | name: gas_per_blob_byte 9 | value: "8" -------------------------------------------------------------------------------- /test/data/rollback/delegation.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | address_id: 1 3 | validator_id: 1 4 | amount: 10000 5 | - id: 2 6 | address_id: 2 7 | validator_id: 1 8 | amount: 10000 -------------------------------------------------------------------------------- /test/data/rollback/denom_metadata.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | description: The native staking token of the Celestia network. 3 | name: TIA 4 | symbol: TIA 5 | base: utia 6 | display: TIA 7 | uri: 8 | units: 9 | - denom: utia 10 | exponent: 0 11 | - denom: TIA 12 | exponent: 6 13 | -------------------------------------------------------------------------------- /test/data/rollback/event.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | height: 1000 3 | time: '2023-07-04T03:10:57+00:00' 4 | position: 0 5 | type: burn 6 | tx_id: null 7 | data: null 8 | - id: 2 9 | height: 1000 10 | time: '2023-07-04T03:10:57+00:00' 11 | position: 1 12 | type: mint 13 | tx_id: 1 14 | data: null 15 | - id: 3 16 | height: 1000 17 | time: '2023-07-04T03:10:57+00:00' 18 | position: 2 19 | type: mint 20 | tx_id: 2 21 | data: null -------------------------------------------------------------------------------- /test/data/rollback/grant.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | height: 1000 3 | granter_id: 2 4 | grantee_id: 1 5 | authorization: "/cosmos.staking.v1beta1.MsgDelegate" 6 | expiration: null 7 | revoked: false 8 | revoke_height: null 9 | params: 10 | Msg: "/cosmos.staking.v1beta1.MsgDelegate" -------------------------------------------------------------------------------- /test/data/rollback/jail.yml: -------------------------------------------------------------------------------- 1 | - validator_id: 1 2 | time: '2023-07-01T00:00:00' 3 | height: 10 4 | burned: 0 -------------------------------------------------------------------------------- /test/data/rollback/message.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | height: 1000 3 | time: '2023-07-04T03:10:57+00:00' 4 | position: 0 5 | type: MsgWithdrawDelegatorReward 6 | tx_id: 1 7 | size: 100 8 | data: null 9 | - id: 2 10 | height: 1000 11 | time: '2023-07-04T03:10:57+00:00' 12 | position: 1 13 | type: MsgDelegate 14 | tx_id: 1 15 | size: 100 16 | data: null 17 | - id: 3 18 | height: 1000 19 | time: '2023-07-04T03:10:57+00:00' 20 | position: 2 21 | type: MsgUnjail 22 | tx_id: 2 23 | size: 100 24 | data: null 25 | - id: 4 26 | height: 1000 27 | time: '2023-07-04T03:10:57+00:00' 28 | position: 3 29 | type: MsgPayForBlobs 30 | tx_id: 2 31 | size: 200 32 | data: 0x1b2e01c01c07762cb666885e04476efadbdc774ae2c8fb2af2c65c0324749e1e71405a6b6d71222da1346bcba2139fe47cbd6194d8f16bc2ef700b11c6987425f5fb6989b8a1804c16d98349c0851075b239e9a8f7ace2bf889e2744b474b98f7ecaa05874afd23d737e2ee6d23f81172afd6d51d1d513bbf9f5c659adb7d9a93b5b2fe6fe7bd1b7aedb004f364991aefc8da70a1884fc4002d3e23f19ce763c304439f7a1d4f5b39fff27287a6999967394fd14943fc84621af34a0311b2e01c01c07762cb666885e04476efadbdc774ae2c8fb2af2c65c0324749e1e71405a6b6d71222da1346bcba2139fe47cbd6194d8f16bc2ef700b11c6987425f5fb6989b8a1804c16d98349c0851075b239e9a8f7ace2bf889e2744b474b98f7ecaa05874afd23d737e2ee6d23f81172afd6d51d1d513bbf9f5c659adb7d9a93b5b2fe6fe7bd1b7aedb004f364991aefc8da70a1884fc4002d3e23f19ce763c304439f7a1d4f5b39fff27287a6999967394fd14943fc84621af34a031 33 | - id: 5 34 | height: 999 35 | position: 0 36 | time: '2023-07-04T03:11:57+00:00' 37 | type: MsgCreateValidator 38 | tx_id: 3 39 | size: 100 40 | data: null -------------------------------------------------------------------------------- /test/data/rollback/msg_address.yml: -------------------------------------------------------------------------------- 1 | - msg_id: 1 2 | address_id: 1 3 | type: fromAddress 4 | - msg_id: 1 5 | address_id: 2 6 | type: toAddress -------------------------------------------------------------------------------- /test/data/rollback/namespace.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | version: 0 3 | namespace_id: 0x5F7A8DDFE6136FE76B65B9066D4F816D707F 4 | size: 1234 5 | pfb_count: 3 6 | first_height: 1000 7 | last_height: 1000 8 | last_message_time: '2023-07-04T03:10:57+00:00' 9 | - id: 2 10 | version: 1 11 | namespace_id: 0x5F7A8DDFE6136FE76B65B9066D4F816D707F 12 | size: 1255 13 | pfb_count: 2 14 | first_height: 1000 15 | last_height: 1000 16 | last_message_time: '2023-07-04T03:10:57+00:00' 17 | - id: 3 18 | version: 0 19 | namespace_id: 0x62491A45621ABEA79EBA193FD2944B5B9EBD 20 | size: 12 21 | pfb_count: 1 22 | first_height: 1000 23 | last_height: 1000 24 | last_message_time: '2023-07-04T03:10:57+00:00' -------------------------------------------------------------------------------- /test/data/rollback/namespace_message.yml: -------------------------------------------------------------------------------- 1 | - msg_id: 3 2 | namespace_id: 2 3 | time: '2023-07-04T03:10:57+00:00' 4 | tx_id: 2 5 | height: 1000 -------------------------------------------------------------------------------- /test/data/rollback/redelegation.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | address_id: 1 3 | src_id: 1 4 | dest_id: 2 5 | amount: 1000 6 | time: '2023-07-04T03:10:57+00:00' 7 | height: 1000 8 | completion_time: '2023-08-04T03:10:57+00:00' -------------------------------------------------------------------------------- /test/data/rollback/rollup.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | name: Rollup 1 3 | description: The best rollup ever 4 | website: https://rollup1.com 5 | twitter: https://x.com/rollup1 6 | github: https://github.com/rollup1 7 | logo: https://rollup1.com/image.png 8 | slug: rollup_1 9 | settled_on: Arbitrum 10 | -------------------------------------------------------------------------------- /test/data/rollback/rollup_provider.yml: -------------------------------------------------------------------------------- 1 | - rollup_id: 1 2 | namespace_id: 1 3 | address_id: 1 4 | - rollup_id: 1 5 | namespace_id: 2 6 | address_id: 1 -------------------------------------------------------------------------------- /test/data/rollback/signer.yml: -------------------------------------------------------------------------------- 1 | - tx_id: 1 2 | address_id: 1 -------------------------------------------------------------------------------- /test/data/rollback/state.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | last_height: 1001 3 | last_time: '2023-07-04T03:10:58+00:00' 4 | name: test_indexer 5 | total_tx: 3 6 | total_accounts: 12512357 7 | total_fee: 172635712635813 8 | total_namespaces: 1000 9 | total_blobs_size: 324234 10 | total_supply: 263471253613 11 | total_validators: 1 12 | total_stake: 8 13 | -------------------------------------------------------------------------------- /test/data/rollback/tx.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | hash: 0x652452A670018D629CC116E510BA88C1CABE061336661B1F3D206D248BD558AF 3 | height: 1000 4 | time: '2023-07-04T03:10:57+00:00' 5 | position: 0 6 | gas_wanted: 80410 7 | gas_used: 77483 8 | timeout_height: 0 9 | events_count: 1 10 | messages_count: 2 11 | fee: 80410 12 | status: success 13 | codespace: sdk 14 | memo: memo 15 | message_types: "00000000000000000000000000000000000000000000000000000000000000000000000000000000001000100" 16 | - id: 2 17 | hash: 0x652452A670011D629CC116E510BA88C1CABE061336661B1F3D206D248BD55811 18 | height: 1000 19 | time: '2023-07-04T03:10:57+00:00' 20 | position: 1 21 | gas_wanted: 80410 22 | gas_used: 77483 23 | timeout_height: 0 24 | events_count: 1 25 | messages_count: 1 26 | fee: 80410 27 | status: success 28 | codespace: 29 | memo: memo2 30 | message_types: "00000000000000000000000000000000000000000000000000000000000000000000000000000000100000000" 31 | - id: 3 32 | hash: 0xBA37478C3E9A804697271ACC474D484E9160899C86E551D737EEA819FCC75003 33 | height: 999 34 | time: '2023-07-04T03:10:56+00:00' 35 | position: 0 36 | gas_wanted: 80410 37 | gas_used: 77483 38 | timeout_height: 0 39 | events_count: 0 40 | messages_count: 1 41 | fee: 80410 42 | status: success 43 | codespace: 44 | memo: 45 | message_types: "00000000000000000000000000000000000000000000000000000000000000000000000000000000000100000" -------------------------------------------------------------------------------- /test/data/rollback/undelegation.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | address_id: 1 3 | validator_id: 1 4 | amount: 1000 5 | time: '2023-07-04T03:10:57+00:00' 6 | height: 1000 7 | completion_time: '2023-08-04T03:10:57+00:00' -------------------------------------------------------------------------------- /test/data/rollback/validator.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | address: celestiavaloper17vmk8m246t648hpmde2q7kp4ft9uwrayy09dmw 3 | delegator: celestia17vmk8m246t648hpmde2q7kp4ft9uwrayps85dg 4 | rate: 0.070000000000000000 5 | max_rate: 0.200000000000000000 6 | max_change_rate: 0.010000000000000000 7 | min_self_delegation: 1 8 | moniker: Conqueror 9 | identity: EAD22B173DE57E6A 10 | cons_address: 81A24EE534DEFE1557A4C7C437E8E8FBC2F834E8 11 | website: https://github.com/DasRasyo 12 | contacts: https://t.me/DasRasyo || conqueror.prime 13 | details: Stake with me 14 | stake: 4 15 | commissions: 1 16 | rewards: 1 17 | height: 999 18 | jailed: false 19 | - id: 2 20 | address: celestiavaloper189ecvq5avj0wehrcfnagpd5sd8pup9aqmdglmr 21 | delegator: celestia189ecvq5avj0wehrcfnagpd5sd8pup9aq7j2xd9 22 | rate: 0.05 23 | max_rate: 0.2 24 | max_change_rate: 0.01 25 | min_self_delegation: 1 26 | moniker: Witval 27 | identity: 51468B615127273A 28 | cons_address: 57ECB5E6BB4CFBE08541B971DC240F33EEC880BD 29 | website: 30 | contacts: contact@vitwit.com 31 | details: Witval is the validator arm from Vitwit. We are a web3 technology consulting, services and infrastructure company. We are validating across 20+ production networks, majorly focusing on Cosmos ecosystem 32 | stake: 4 33 | commissions: 1 34 | rewards: 1 35 | height: 1000 36 | jailed: false -------------------------------------------------------------------------------- /test/data/rollback/vesting_account.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | address_id: 1 3 | height: 0 4 | time: '2023-01-01T00:00:00Z' 5 | amount: 100000 6 | start_time: null 7 | end_time: '2024-01-01T00:00:00Z' 8 | type: delayed -------------------------------------------------------------------------------- /test/data/rollup_provider.yml: -------------------------------------------------------------------------------- 1 | - rollup_id: 1 2 | namespace_id: 1 3 | address_id: 1 4 | - rollup_id: 1 5 | namespace_id: 2 6 | address_id: 1 7 | - rollup_id: 2 8 | namespace_id: 2 9 | address_id: 2 10 | - rollup_id: 3 11 | namespace_id: 0 12 | address_id: 2 13 | - rollup_id: 4 14 | namespace_id: 0 15 | address_id: 2 -------------------------------------------------------------------------------- /test/data/signer.yml: -------------------------------------------------------------------------------- 1 | - tx_id: 1 2 | address_id: 1 3 | - tx_id: 2 4 | address_id: 1 5 | - tx_id: 2 6 | address_id: 2 7 | - tx_id: 3 8 | address_id: 1 -------------------------------------------------------------------------------- /test/data/state.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | last_height: 1000 3 | last_hash: 0x6A30C94091DA7C436D64E62111D6890D772E351823C41496B4E52F28F5B000BF 4 | last_time: '2023-07-04T03:10:57+00:00' 5 | name: test_indexer 6 | total_tx: 394067 7 | total_accounts: 12512357 8 | total_fee: 172635712635813 9 | total_namespaces: 1000 10 | total_blobs_size: 324234 11 | total_supply: 263471253613 12 | total_validators: 1 13 | total_stake: 2000200 14 | -------------------------------------------------------------------------------- /test/data/undelegation.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | address_id: 1 3 | validator_id: 1 4 | amount: 1000 5 | time: '2023-07-04T03:10:57+00:00' 6 | height: 1000 7 | completion_time: '2023-08-04T03:10:57+00:00' -------------------------------------------------------------------------------- /test/data/validator.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | address: celestiavaloper17vmk8m246t648hpmde2q7kp4ft9uwrayy09dmw 3 | delegator: celestia17vmk8m246t648hpmde2q7kp4ft9uwrayps85dg 4 | rate: 0.07 5 | max_rate: 0.2 6 | max_change_rate: 0.01 7 | min_self_delegation: 1 8 | moniker: Conqueror 9 | identity: EAD22B173DE57E6A 10 | cons_address: 81A24EE534DEFE1557A4C7C437E8E8FBC2F834E8 11 | website: https://github.com/DasRasyo 12 | contacts: https://t.me/DasRasyo || conqueror.prime 13 | details: Stake with me 14 | stake: 1000100 15 | commissions: 1 16 | rewards: 1 17 | height: 999 18 | jailed: false 19 | - id: 2 20 | address: celestiavaloper189ecvq5avj0wehrcfnagpd5sd8pup9aqmdglmr 21 | delegator: celestia189ecvq5avj0wehrcfnagpd5sd8pup9aq7j2xd9 22 | rate: 0.05 23 | max_rate: 0.2 24 | max_change_rate: 0.01 25 | min_self_delegation: 1 26 | moniker: Witval 27 | identity: 51468B615127273A 28 | cons_address: 57ECB5E6BB4CFBE08541B971DC240F33EEC880BD 29 | website: 30 | contacts: contact@vitwit.com 31 | details: Witval is the validator arm from Vitwit. We are a web3 technology consulting, services and infrastructure company. We are validating across 20+ production networks, majorly focusing on Cosmos ecosystem 32 | stake: 1000100 33 | commissions: 1 34 | rewards: 1 35 | height: 1000 36 | jailed: false -------------------------------------------------------------------------------- /test/data/vesting_account.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | address_id: 1 3 | height: 0 4 | time: '2023-01-01T00:00:00Z' 5 | amount: 100000 6 | start_time: null 7 | end_time: '2024-01-01T00:00:00Z' 8 | type: delayed -------------------------------------------------------------------------------- /test/data/vote.yml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | proposal_id: 1 3 | voter_id: 1 4 | option: yes 5 | weight: 1.0 6 | time: '2023-06-04T03:10:57+00:00' 7 | height: 1000 8 | - id: 2 9 | proposal_id: 1 10 | voter_id: 2 11 | option: no 12 | weight: 1.0 13 | time: '2023-06-04T03:10:57+00:00' 14 | height: 1000 15 | - id: 3 16 | proposal_id: 2 17 | validator_id: 1 18 | option: abstain 19 | weight: 1.0 20 | time: '2023-06-04T03:10:57+00:00' 21 | height: 1000 22 | - id: 4 23 | proposal_id: 2 24 | voter_id: 2 25 | option: yes 26 | weight: 1.0 27 | time: '2023-06-04T03:10:57+00:00' 28 | height: 1000 -------------------------------------------------------------------------------- /test/newman/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "390e19aa-b2d2-46a3-abfc-a6f4b8c3337b", 3 | "name": "Celestia Indexer", 4 | "values": [ 5 | { 6 | "key": "baseUrl", 7 | "value": "http://api.celestia.dipdup.net", 8 | "type": "default", 9 | "enabled": true 10 | } 11 | ], 12 | "_postman_variable_scope": "environment", 13 | "_postman_exported_at": "2023-09-12T20:11:46.747Z", 14 | "_postman_exported_using": "Postman/10.17.7" 15 | } --------------------------------------------------------------------------------