├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ └── ci.yml ├── .gitignore ├── .golangci.yml ├── .images ├── babylon-fp-dashboard.png ├── babylon-network-dashboard.png ├── babylon-validator-fp-dashboard.png ├── network-mode-dashboard1.png ├── network-mode-dashboard2.png └── validator-mode-dashboard.png ├── .prettierrc ├── .resource ├── .env.example ├── docker-compose.override.yaml.persistent_mode ├── example-network-config.yaml ├── example-persistence-mode.override.yaml └── example-validator-config.yaml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmd ├── cvms │ └── main.go ├── flag.go ├── start.go ├── validate.go └── version.go ├── docker-compose.yaml ├── docker ├── alertmanager │ └── alertmanager.yaml ├── cvms │ ├── custom_chains.yaml.example │ └── support_chains.yaml ├── flyway │ ├── V1_01__init_meta.sql │ ├── V1_02__init_axelar_amplifier_verifier.sql │ ├── V1_03__init_babylon_btc_delegation.sql │ ├── V1_04__init_babylon_btc_lightclient.sql │ ├── V1_05__init_babylon_checkpoint.sql │ ├── V1_06__init_babylon_covenant_signature.sql │ ├── V1_07__init_babylon_finality_provider.sql │ ├── V1_08__init_block_data_analytics.sql │ ├── V1_09__init_veindexer.sql │ └── V1_10__init_voteindexer.sql ├── grafana │ ├── grafana.ini │ └── provisioning │ │ ├── dashboards │ │ ├── cvms-cadvisor-dashboard.json │ │ ├── cvms-management-dashboard.json │ │ ├── dashboard.yaml │ │ ├── network │ │ │ ├── all-duty-dashboard.json │ │ │ ├── axelar_dashboard.json │ │ │ ├── babylon_dashboard.json │ │ │ ├── babylon_fp_health_dashboard.json │ │ │ ├── hippocrat_dashboard.json │ │ │ └── network-status-dashboard.json │ │ └── validator │ │ │ ├── babylon_validator_fp_dashboard.json │ │ │ ├── validator-consensus-dashboard.json │ │ │ └── validator-trend-dashboard.json │ │ └── datasources │ │ └── datasources.yaml ├── loki │ └── loki.yaml ├── postgres │ └── schema │ │ ├── 01-init-meta.sql │ │ ├── 02-init-axelar_amplifier_verifier.sql │ │ ├── 02-init-babylon_btc_delegation.sql │ │ ├── 02-init-babylon_btc_lightclient.sql │ │ ├── 02-init-babylon_checkpoint.sql │ │ ├── 02-init-babylon_covenant_signature.sql │ │ ├── 02-init-babylon_finality_provider.sql │ │ ├── 02-init-block-data-analytics.sql │ │ ├── 02-init-veindexer.sql │ │ └── 02-init-voteindexer.sql ├── prometheus │ ├── custom_rules │ │ └── custom_rule.yaml.example │ ├── prometheus.yaml │ └── rules │ │ ├── axelar.yaml │ │ ├── babylon.yaml │ │ ├── block.yaml │ │ ├── consensus-vote.yaml │ │ ├── eventnonce.yaml │ │ ├── extension-vote.yaml │ │ ├── oracle.yaml │ │ ├── root.yaml │ │ ├── upgrade.yaml │ │ ├── uptime.yaml │ │ └── yoda.yaml └── promtail │ └── promtail-config.yaml ├── docs ├── add_support_chains.md ├── consumer_chains.md ├── linter.md ├── setup.md └── setup_for_babylon.md ├── go.mod ├── go.sum ├── helm ├── .helmignore ├── Chart.lock ├── Chart.yaml ├── README.md ├── files │ ├── axelar-evm.yaml │ ├── balance.yaml │ ├── block.yaml │ ├── consensus-vote.yaml │ ├── custom_chains.yaml.example │ ├── eventnonce.yaml │ ├── extension-vote.yaml │ ├── oracle.yaml │ ├── root.yaml │ ├── upgrade.yaml │ ├── uptime.yaml │ └── yoda.yaml ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── configmap.yaml │ ├── custom-configmap.yaml │ ├── exporter │ │ ├── deployment.yaml │ │ ├── metrics.yaml │ │ └── servicemonitor.yaml │ ├── indexer │ │ ├── deployment.yaml │ │ ├── metrics.yaml │ │ └── servicemonitor.yaml │ ├── prometheus-rules.yaml │ ├── sql-schema-configmap.yaml │ ├── support_chains-cm.yaml │ └── tests │ │ └── test-connection.yaml └── values.yaml ├── internal ├── app │ ├── exporter │ │ ├── exporter.go │ │ ├── exporter_test.go │ │ ├── register.go │ │ └── select.go │ └── indexer │ │ ├── indexer.go │ │ ├── prometheus.go │ │ ├── register.go │ │ └── select.go ├── common │ ├── api │ │ ├── api.go │ │ ├── api_test.go │ │ ├── babylon.go │ │ ├── babylon_api_test.go │ │ └── consensus_params.go │ ├── db.go │ ├── errors.go │ ├── exporter.go │ ├── factory.go │ ├── function │ │ └── function.go │ ├── indexer.go │ ├── indexer │ │ ├── model │ │ │ └── meta.go │ │ ├── repository │ │ │ ├── chain_info.go │ │ │ ├── covenant_commitee_info.go │ │ │ ├── finality_provider_info.go │ │ │ ├── index_pointer.go │ │ │ ├── index_pointer_test.go │ │ │ ├── interfaces.go │ │ │ ├── message_type.go │ │ │ ├── repository.go │ │ │ ├── validator_info.go │ │ │ ├── verifier_info.go │ │ │ └── vigilante_info.go │ │ └── types │ │ │ └── types.go │ ├── labels.go │ ├── packager.go │ ├── parser │ │ ├── babylon.go │ │ ├── celestia.go │ │ ├── cosmos.go │ │ ├── initia.go │ │ └── story.go │ ├── types.go │ └── types │ │ ├── babylon.go │ │ ├── celestia.go │ │ ├── cosmos.go │ │ ├── initia.go │ │ └── story.go ├── helper │ ├── backoff.go │ ├── chain_name.go │ ├── chainid_parser.go │ ├── config │ │ ├── config.go │ │ ├── config_test.go │ │ └── support_chain.go │ ├── contain.go │ ├── converter.go │ ├── db │ │ ├── retention_period.go │ │ └── table_name.go │ ├── grpc │ │ ├── grpc_query.go │ │ ├── grpc_reflection.go │ │ └── grpc_resolver.go │ ├── healthcheck │ │ ├── healthcheck.go │ │ └── healthcheck_test.go │ ├── helper_test.go │ ├── logger │ │ ├── logger.go │ │ └── logger_test.go │ ├── onchain_status.go │ ├── recover.go │ ├── reflect.go │ ├── sdk │ │ ├── bech32.go │ │ ├── bech32_error.go │ │ ├── decode_abci.go │ │ ├── decode_abci_test.go │ │ ├── export_valcons_prefix.go │ │ ├── keys_parse.go │ │ ├── make_valcons_address.go │ │ ├── ve_decode_test.go │ │ └── vote_extension_codec.go │ └── url.go ├── packages │ ├── axelar │ │ ├── amplifier-verifier │ │ │ ├── indexer │ │ │ │ ├── axelar.go │ │ │ │ ├── indexer.go │ │ │ │ ├── indexer_test.go │ │ │ │ ├── metrics.go │ │ │ │ └── sync.go │ │ │ ├── model │ │ │ │ └── model.go │ │ │ └── repository │ │ │ │ └── repository.go │ │ └── vald │ │ │ └── heartbeats │ │ │ ├── api │ │ │ └── api.go │ │ │ ├── collector │ │ │ └── collector.go │ │ │ ├── parser │ │ │ ├── parser.go │ │ │ └── parser_test.go │ │ │ ├── router │ │ │ └── router.go │ │ │ └── types │ │ │ └── types.go │ ├── babylon │ │ ├── btc-delegations │ │ │ ├── api.go │ │ │ ├── api_test.go │ │ │ ├── collector.go │ │ │ └── types.go │ │ ├── btc-lightclient │ │ │ ├── indexer │ │ │ │ ├── babylon.go │ │ │ │ ├── indexer.go │ │ │ │ ├── indexer_test.go │ │ │ │ ├── metrics.go │ │ │ │ └── sync.go │ │ │ ├── model │ │ │ │ └── model.go │ │ │ └── repository │ │ │ │ └── repository.go │ │ ├── checkpoint │ │ │ ├── indexer │ │ │ │ ├── batch_sync.go │ │ │ │ ├── indexer.go │ │ │ │ ├── indexer_test.go │ │ │ │ ├── metrics.go │ │ │ │ ├── parser.go │ │ │ │ └── types.go │ │ │ ├── model │ │ │ │ └── babylon.go │ │ │ └── repository │ │ │ │ └── repository.go │ │ ├── covenant-committee │ │ │ ├── indexer │ │ │ │ ├── batch_sync.go │ │ │ │ ├── indexer.go │ │ │ │ ├── indexer_test.go │ │ │ │ ├── metrics.go │ │ │ │ ├── parser.go │ │ │ │ ├── parser_test.go │ │ │ │ └── types.go │ │ │ ├── model │ │ │ │ └── babylon_covenant_signature.go │ │ │ └── repository │ │ │ │ ├── btc_delegation_repository.go │ │ │ │ └── covenant_signature_repository.go │ │ └── finality-provider │ │ │ ├── api │ │ │ ├── api.go │ │ │ └── api_test.go │ │ │ ├── collector │ │ │ └── collector.go │ │ │ ├── indexer │ │ │ ├── babylon.go │ │ │ ├── batch_sync.go │ │ │ ├── indexer.go │ │ │ ├── indexer_test.go │ │ │ └── metrics.go │ │ │ ├── model │ │ │ └── babylon.go │ │ │ ├── repository │ │ │ └── repository.go │ │ │ └── types │ │ │ └── status.go │ ├── block-data-analytics │ │ ├── collector │ │ │ ├── api.go │ │ │ ├── collector.go │ │ │ └── types.go │ │ ├── indexer │ │ │ ├── indexer.go │ │ │ ├── indexer_test.go │ │ │ ├── metrics.go │ │ │ └── sync.go │ │ ├── model │ │ │ └── model.go │ │ └── repository │ │ │ └── repository.go │ ├── consensus │ │ ├── uptime │ │ │ ├── api │ │ │ │ ├── api.go │ │ │ │ └── uptime.go │ │ │ ├── collector │ │ │ │ └── collector.go │ │ │ ├── router │ │ │ │ ├── router.go │ │ │ │ └── router_test.go │ │ │ └── types │ │ │ │ └── types_common.go │ │ ├── veindexer │ │ │ ├── indexer │ │ │ │ ├── batch_sync.go │ │ │ │ ├── indexer.go │ │ │ │ └── metrics.go │ │ │ ├── model │ │ │ │ └── cosmos.go │ │ │ └── repository │ │ │ │ ├── repository.go │ │ │ │ └── repository_test.go │ │ └── voteindexer │ │ │ ├── indexer │ │ │ ├── batch_sync.go │ │ │ ├── indexer.go │ │ │ ├── metrics.go │ │ │ └── repository_test.go │ │ │ ├── model │ │ │ └── cosmos.go │ │ │ └── repository │ │ │ ├── repository.go │ │ │ └── repository_test.go │ ├── duty │ │ ├── axelar-evm │ │ │ ├── api │ │ │ │ └── api.go │ │ │ ├── collector │ │ │ │ ├── collector.go │ │ │ │ └── collector_test.go │ │ │ ├── parser │ │ │ │ └── parser.go │ │ │ ├── router │ │ │ │ ├── router.go │ │ │ │ └── router_test.go │ │ │ └── types │ │ │ │ └── types.go │ │ ├── eventnonce │ │ │ ├── api │ │ │ │ ├── api.go │ │ │ │ └── grpc.go │ │ │ ├── collector │ │ │ │ ├── collector.go │ │ │ │ └── collector_test.go │ │ │ ├── parser │ │ │ │ └── parser.go │ │ │ ├── router │ │ │ │ ├── router.go │ │ │ │ └── router_test.go │ │ │ └── types │ │ │ │ └── types.go │ │ ├── oracle │ │ │ ├── api │ │ │ │ └── api.go │ │ │ ├── collector │ │ │ │ ├── collector.go │ │ │ │ └── collector_test.go │ │ │ ├── parser │ │ │ │ └── parser.go │ │ │ ├── router │ │ │ │ ├── router.go │ │ │ │ └── router_test.go │ │ │ └── types │ │ │ │ └── types.go │ │ └── yoda │ │ │ ├── api │ │ │ └── api.go │ │ │ ├── collector │ │ │ ├── collector.go │ │ │ └── collector_test.go │ │ │ ├── parser │ │ │ ├── parser.go │ │ │ └── parser_test.go │ │ │ ├── processor │ │ │ ├── processor.go │ │ │ └── processor_test.go │ │ │ ├── router │ │ │ ├── router.go │ │ │ └── router_test.go │ │ │ └── types │ │ │ └── types.go │ ├── health │ │ └── block │ │ │ ├── api │ │ │ └── api.go │ │ │ ├── collector │ │ │ ├── collector.go │ │ │ └── collector_test.go │ │ │ ├── parser │ │ │ └── parser.go │ │ │ ├── router │ │ │ ├── router.go │ │ │ └── router_test.go │ │ │ └── types │ │ │ ├── celestia.go │ │ │ ├── common.go │ │ │ ├── cosmos.go │ │ │ └── ethereum.go │ └── utility │ │ ├── balance │ │ ├── api │ │ │ └── api.go │ │ ├── collector │ │ │ ├── collector.go │ │ │ └── collector_test.go │ │ ├── errors │ │ │ └── errors.go │ │ ├── parser │ │ │ ├── parser.go │ │ │ └── parser_test.go │ │ ├── router │ │ │ ├── router.go │ │ │ └── router_test.go │ │ └── types │ │ │ └── types.go │ │ └── upgrade │ │ ├── api │ │ └── api.go │ │ ├── collector │ │ ├── collector.go │ │ └── collector_test.go │ │ ├── router │ │ ├── router.go │ │ └── router_test.go │ │ └── types │ │ └── types.go └── testutil │ ├── .env.test │ └── setup.go └── scripts ├── add_custom_chains.sh ├── generate_flyway_files.sh ├── init_persistent_db.sh ├── load_test.sh ├── make_support_chains.sh ├── performancetest.js ├── sort_support_chains.sh └── tx_result_decoder.js /.dockerignore: -------------------------------------------------------------------------------- 1 | # state 2 | .git 3 | .github 4 | .idea 5 | .*ignore 6 | 7 | # etc.. 8 | docs 9 | bin 10 | scripts 11 | .image 12 | 13 | # dev 14 | deploy.sh 15 | prometheus.* 16 | docker-compose.* 17 | tests/** 18 | 19 | # personal 20 | TODO 21 | UNUSED -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug and help resolve the issue 4 | title: '[BUG] ' 5 | labels: bug 6 | assignees: ['Jeongseup'] 7 | --- 8 | 9 | #### Problem Description 10 | 11 | Please describe the situation in which the bug occurred and what the problem is. 12 | 13 | #### Steps to Reproduce 14 | 15 | Explain how to reproduce the issue step by step: 16 | 17 | 1. Go to '...' 18 | 2. Click on '...' 19 | 3. Scroll down to '...' 20 | 4. See error 21 | 22 | #### Expected Behavior 23 | 24 | Describe how it should have worked if it functioned correctly. 25 | 26 | #### Screenshots 27 | 28 | If you have screenshots to help illustrate the problem, please attach them. 29 | 30 | #### Environment Information (please include the following): 31 | 32 | • OS: [e.g., Windows, MacOS] 33 | • Browser: [e.g., Chrome, Safari] 34 | • Version: [e.g., 22] 35 | 36 | #### Additional Information 37 | 38 | Provide any additional information that may help resolve the issue. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Request a new feature or suggest an improvement 4 | title: '[FEATURE] ' 5 | labels: feature 6 | assignees: ['Jeongseup'] 7 | --- 8 | 9 | #### Feature Description 10 | 11 | Describe the feature you would like to add and how it would contribute to the project. 12 | 13 | #### Reason for Need 14 | 15 | Explain why this feature is necessary and what problem it could solve. 16 | 17 | #### Proposed Solution 18 | 19 | Describe the solution you have in mind in detail. 20 | 21 | #### Alternatives 22 | 23 | If there are alternative ways to address this issue, please describe them. 24 | 25 | #### Additional Information 26 | 27 | If you have any additional information or examples to help understand the feature better, please provide them. 28 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## PR(Pull Request) Overview 2 | 3 | Briefly describe the changes made in this PR. 4 | 5 | #### Changes 6 | 7 | - [ ] Major feature addition or modification 8 | - [ ] Bug fix 9 | - [ ] Code improvement 10 | - [ ] Documentation update 11 | 12 | #### Related Issue 13 | 14 | Please reference the Issue number this PR addresses: #123 15 | 16 | #### Description of Changes 17 | 18 | Describe the key changes or parts of the code that have been modified. 19 | 20 | #### Testing Method 21 | 22 | Explain how to test the changes step by step: 1. Run ‘…’ 2. Check ‘…’ 23 | 24 | #### Additional Information 25 | 26 | If there is any additional information relevant to this PR, please include it here. 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # exclude binary files 2 | bin 3 | bin/* 4 | 5 | # personal 6 | TODO 7 | UNUSED 8 | 9 | # .DS_Store 10 | .DS_Store 11 | **/.DS_Store 12 | 13 | # test envs 14 | tests/test.go 15 | 16 | # prod envs 17 | .env 18 | config.yaml 19 | docker-compose.override.yaml 20 | custom-prometheus.yaml 21 | 22 | **/custom_rules/*.yaml 23 | **/custom_rules/*.yml 24 | 25 | custom-alertmanager.yaml 26 | custom_chains.yaml 27 | 28 | # IDEA editor 29 | .idea 30 | **/.idea 31 | 32 | # dev 33 | lint-output.txt -------------------------------------------------------------------------------- /.images/babylon-fp-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmostation/cvms/367545acaacac3fb71474108753829e67c6e63d3/.images/babylon-fp-dashboard.png -------------------------------------------------------------------------------- /.images/babylon-network-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmostation/cvms/367545acaacac3fb71474108753829e67c6e63d3/.images/babylon-network-dashboard.png -------------------------------------------------------------------------------- /.images/babylon-validator-fp-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmostation/cvms/367545acaacac3fb71474108753829e67c6e63d3/.images/babylon-validator-fp-dashboard.png -------------------------------------------------------------------------------- /.images/network-mode-dashboard1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmostation/cvms/367545acaacac3fb71474108753829e67c6e63d3/.images/network-mode-dashboard1.png -------------------------------------------------------------------------------- /.images/network-mode-dashboard2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmostation/cvms/367545acaacac3fb71474108753829e67c6e63d3/.images/network-mode-dashboard2.png -------------------------------------------------------------------------------- /.images/validator-mode-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmostation/cvms/367545acaacac3fb71474108753829e67c6e63d3/.images/validator-mode-dashboard.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": false, 3 | "trailingComma": "all", 4 | "bracketSpacing": true, 5 | "jsxBracketSameLine": false, 6 | "tabWidth": 2, 7 | "printWidth": 140 8 | } 9 | -------------------------------------------------------------------------------- /.resource/.env.example: -------------------------------------------------------------------------------- 1 | ###### CVMS Services ####### 2 | # EXPORTER_PORT=9200 3 | # INDEXER_PORT=9300 4 | # LOG_COLOR_DISABLE=false 5 | # LOG_LEVEL=4 6 | # CONFIG_PATH=./config.yaml 7 | # CUSTOM_CHAINS_FILE=custom_chains.yaml 8 | # If you don't want to delete old records, use "persistence" instead of specific period 9 | # DB_RETENTION_PERIOD=1h 10 | # If you're operating docker service in not default directory, please enable this env for cadvisor and promtail 11 | # DOCKER_ROOT=/data/docker 12 | 13 | ####### Prometheus Service ####### 14 | # PROM_SERVER_PORT=9090 15 | # PROM_CONFIG_FILE=custom-prometheus.yml 16 | # PROM_RETENTION_TIME=1m 17 | 18 | ####### Postgres Service ####### 19 | # DB_HOST=localhost 20 | # DB_PORT=5432 21 | # DB_NAME=cvms 22 | # DB_USER=cvms 23 | # DB_PASSWORD=mysecretpassword 24 | 25 | 26 | ####### Grafana Service ####### 27 | #ADMIN_USER=admin 28 | #ADMIN_PASSWORD=admin 29 | #GRAFANA_HOME_DASHBOARD=/etc/grafana/provisioning/dashboards/network/network-status-dashboard.json 30 | #GRAFANA_SERVER_PORT=3000 31 | 32 | ####### Alertmanager Service ####### 33 | #ALERTMANAGER_SERVER_PORT=9093 -------------------------------------------------------------------------------- /.resource/docker-compose.override.yaml.persistent_mode: -------------------------------------------------------------------------------- 1 | services: 2 | postgres: 3 | profiles: ['disables'] 4 | -------------------------------------------------------------------------------- /.resource/example-network-config.yaml: -------------------------------------------------------------------------------- 1 | # NOTE: Customize this variables by your needs 2 | # 1. network mode: 3 | # ex) monikers: ['all'] 4 | # des) This will enable network mode to monitor all validators status in the blockchain network 5 | # 6 | # 2. validator mode: 7 | # ex) monikers: ['Cosmostation1', 'Cosmostation2'] 8 | # des) This will enable validator mode for whitelisted specific validators 9 | monikers: ['all'] 10 | 11 | # If the user is a blockchain network maintainer, they will want to operate on one own chain. 12 | chains: 13 | # NOTE: display name will be used only this config to indicate followed arguments to communicate internal team members 14 | - display_name: 'cosmos' 15 | # NOTE: chain_id is a KEY to find applicable packages in support_chains list. YOU SHOULD MATCH CORRECT CHAIN ID! 16 | chain_id: cosmoshub-4 17 | # NOTE: these addresses will be used for balance usage tracking such as validator, broadcaster or something. 18 | tracking_addresses: 19 | - 'cosmos1xxx' 20 | - 'cosmos1xxx' 21 | - 'cosmos1xxx' 22 | nodes: 23 | # NOTE: currently grpc endpoint doesn't support ssl 24 | - rpc: 'http://localhost:26657' 25 | api: 'http://localhost:1337' 26 | grpc: 'localhost:9090' 27 | 28 | - rpc: 'http://localhost:26657' 29 | api: 'http://localhost:1337' 30 | grpc: 'localhost:9090' 31 | 32 | - rpc: 'http://localhost:36657' 33 | api: 'http://localhost:1337' 34 | grpc: 'localhost:9090' 35 | -------------------------------------------------------------------------------- /.resource/example-persistence-mode.override.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | postgres: 3 | profiles: ['disables'] 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 1st stage, build app 2 | FROM golang:1.23 AS builder 3 | 4 | WORKDIR /build 5 | 6 | COPY go.mod go.sum ./ 7 | 8 | RUN go mod download 9 | 10 | COPY . . 11 | 12 | RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -w" -trimpath -o ./cvms ./cmd/cvms/main.go 13 | 14 | # 2nd stage, copy CA certificates 15 | FROM alpine:latest AS certs 16 | RUN apk --no-cache add ca-certificates 17 | 18 | # 3rd stage, run app 19 | FROM scratch AS production 20 | 21 | WORKDIR /var/lib/cvms 22 | 23 | COPY --from=builder /build/cvms /bin/cvms 24 | 25 | COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 26 | 27 | ENTRYPOINT ["/bin/cvms"] 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Cosmostation 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 | -------------------------------------------------------------------------------- /cmd/cvms/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/cosmostation/cvms/cmd" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func init() { 11 | rootCmd.AddCommand( 12 | cmd.VersionCmd, 13 | cmd.StartCmd(), 14 | cmd.ValidateCmd(), 15 | ) 16 | } 17 | 18 | func main() { 19 | err := rootCmd.Execute() 20 | if err != nil { 21 | os.Exit(1) 22 | } 23 | } 24 | 25 | var rootCmd = &cobra.Command{ 26 | Short: ` 27 | ________ ___ ___ _____ ______ ________ 28 | |\ ____\|\ \ / /|\ _ \ _ \|\ ____\ 29 | \ \ \___|\ \ \ / / | \ \\\__\ \ \ \ \___|_ 30 | \ \ \ \ \ \/ / / \ \ \\|__| \ \ \_____ \ 31 | \ \ \____\ \ / / \ \ \ \ \ \|____|\ \ 32 | \ \_______\ \__/ / \ \__\ \ \__\____\_\ \ 33 | \|_______|\|__|/ \|__| \|__|\_________\`, 34 | Use: "cvms [ start || version ]", 35 | Args: cobra.NoArgs, 36 | CompletionOptions: cobra.CompletionOptions{ 37 | DisableDefaultCmd: true, 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /cmd/flag.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/pflag" 5 | ) 6 | 7 | const ( 8 | // exporter & indexer 9 | Config = "config" 10 | LogLevel = "log-level" 11 | LogColorDisable = "log-color-disable" 12 | Port = "port" 13 | 14 | // dev 15 | PackageFilter = "package-filter" 16 | ) 17 | 18 | func ConfigFlag() *pflag.FlagSet { 19 | flag := &pflag.FlagSet{} 20 | 21 | flag.String( 22 | Config, // name 23 | "./config.yaml", // default value 24 | "The Path of config yaml file", // usage 25 | ) 26 | 27 | return flag 28 | } 29 | 30 | func LogFlags() *pflag.FlagSet { 31 | flag := &pflag.FlagSet{} 32 | 33 | flag.String( 34 | LogColorDisable, 35 | "", 36 | "The colored log option. default is false for production level if you want to see debug mode, recommend turn it on true", 37 | ) 38 | flag.String( 39 | LogLevel, 40 | "", 41 | "The level of log for cvms application. default is 4 means INFO level...", 42 | ) 43 | 44 | return flag 45 | } 46 | 47 | func PortFlag() *pflag.FlagSet { 48 | flag := &pflag.FlagSet{} 49 | 50 | flag.String( 51 | Port, 52 | "9200", 53 | "The port is going to listen", 54 | ) 55 | 56 | return flag 57 | } 58 | 59 | func FilterFlag() *pflag.FlagSet { 60 | flag := &pflag.FlagSet{} 61 | 62 | flag.String( 63 | PackageFilter, 64 | "", 65 | "default is null\nonly one package running when you want to run specific package", 66 | ) 67 | 68 | return flag 69 | } 70 | -------------------------------------------------------------------------------- /cmd/validate.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/cosmostation/cvms/internal/helper/config" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | func ValidateCmd() *cobra.Command { 9 | cmd := &cobra.Command{ 10 | Use: "validate", 11 | Short: "Validate current config.yaml before starting application", 12 | Args: cobra.NoArgs, 13 | RunE: func(cmd *cobra.Command, args []string) (err error) { 14 | ctx := cmd.Context() 15 | _ = ctx 16 | configfile := cmd.Flag(Config).Value.String() 17 | 18 | cfg, err := config.GetConfig(configfile) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | cmd.Println("Your config.yaml file would be parsed successfully") 24 | cmd.Printf("\tMoniker : %v\n", cfg.Monikers) 25 | cmd.Printf("\tChains : %d\n", len(cfg.ChainConfigs)) 26 | return nil 27 | }, 28 | } 29 | setFlags(cmd) 30 | return cmd 31 | } 32 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var ( 10 | AppName = "" 11 | Version = "" 12 | Commit = "" 13 | ) 14 | 15 | var VersionCmd = &cobra.Command{ 16 | Use: "version", 17 | Short: "Show information about the current binary build", 18 | Args: cobra.NoArgs, 19 | Run: printBuildInfo, 20 | } 21 | 22 | func printBuildInfo(_ *cobra.Command, _ []string) { 23 | fmt.Printf("AppName : %s\n", AppName) 24 | fmt.Printf("Version : %s\n", Version) 25 | fmt.Printf("Commit : %s\n", Commit) 26 | } 27 | -------------------------------------------------------------------------------- /docker/alertmanager/alertmanager.yaml: -------------------------------------------------------------------------------- 1 | global: 2 | # Also possible to place this URL in a file. 3 | # Ex: `slack_api_url_file: '/etc/alertmanager/slack_url'` 4 | slack_api_url: 'https://hooks.slack.com/services/XXX' 5 | 6 | route: 7 | receiver: 'slack-notifications' 8 | group_by: [alertname] 9 | 10 | receivers: 11 | - name: 'slack-notifications' 12 | slack_configs: 13 | - channel: '#alerts' 14 | text: 'https://internal.myorg.net/wiki/alerts/{{ .GroupLabels.app }}/{{ .GroupLabels.alertname }}' 15 | -------------------------------------------------------------------------------- /docker/cvms/custom_chains.yaml.example: -------------------------------------------------------------------------------- 1 | --- 2 | demochain: 3 | protocol_type: cosmos 4 | support_asset: 5 | denom: stake 6 | decimal: 6 7 | packages: 8 | - block 9 | - upgrade 10 | - uptime 11 | - voteindexer 12 | -------------------------------------------------------------------------------- /docker/flyway/V1_02__init_axelar_amplifier_verifier.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "public"."axelar_amplifier_verifier" ( 2 | "id" BIGINT GENERATED ALWAYS AS IDENTITY, 3 | "chain_info_id" INT NOT NULL, 4 | "created_at" timestamptz NOT NULL, 5 | "chain_and_poll_id" TEXT NOT NULL, 6 | "poll_start_height" INT NOT NULL, 7 | "poll_vote_height" INT NOT NULL, 8 | "verifier_id" INT NOT NULL, 9 | "status" SMALLINT NOT NUll, 10 | PRIMARY KEY ("id", "chain_info_id"), 11 | CONSTRAINT fk_chain_info_id FOREIGN KEY (chain_info_id) REFERENCES meta.chain_info (id) ON DELETE CASCADE ON UPDATE CASCADE, 12 | CONSTRAINT fk_verifier_id FOREIGN KEY (verifier_id, chain_info_id) REFERENCES meta.verifier_info (id, chain_info_id), 13 | CONSTRAINT uniq_verifier_id_by_poll UNIQUE ("chain_info_id","chain_and_poll_id","verifier_id") 14 | ) 15 | PARTITION BY 16 | LIST ("chain_info_id"); 17 | 18 | -- CREATE INDEX IF NOT EXISTS axelar_amplifier_verifier_idx_01 ON public.axelar_amplifier_verifier (height); 19 | -- CREATE INDEX IF NOT EXISTS axelar_amplifier_verifier_idx_02 ON public.axelar_amplifier_verifier (verifier_id, height); 20 | -- CREATE INDEX IF NOT EXISTS axelar_amplifier_verifier_idx_03 ON public.axelar_amplifier_verifier USING btree (chain_info_id, verifier_id, height desc); -------------------------------------------------------------------------------- /docker/flyway/V1_03__init_babylon_btc_delegation.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "public"."babylon_btc_delegation" ( 2 | "id" BIGINT GENERATED ALWAYS AS IDENTITY, 3 | "chain_info_id" INT NOT NULL, 4 | "height" BIGINT NOT NULL, 5 | "btc_staking_tx_hash" VARCHAR(64) NOT NULL, 6 | "timestamp" timestamptz NOT NULL, 7 | PRIMARY KEY ("id", "chain_info_id"), 8 | CONSTRAINT fk_chain_info_id FOREIGN KEY (chain_info_id) REFERENCES meta.chain_info (id) ON DELETE CASCADE ON UPDATE CASCADE, 9 | CONSTRAINT uniq_babylon_btc_delegation_by_btc_staking_tx_hash UNIQUE ("chain_info_id","height","btc_staking_tx_hash") 10 | ) 11 | PARTITION BY 12 | LIST ("chain_info_id"); 13 | 14 | CREATE INDEX IF NOT EXISTS babylon_btc_delegation_01 ON public.babylon_btc_delegation (height); 15 | CREATE INDEX IF NOT EXISTS babylon_btc_delegation_02 ON public.babylon_btc_delegation (btc_staking_tx_hash); 16 | CREATE INDEX IF NOT EXISTS babylon_btc_delegation_03 ON public.babylon_btc_delegation USING btree (chain_info_id, btc_staking_tx_hash, height asc); -------------------------------------------------------------------------------- /docker/flyway/V1_04__init_babylon_btc_lightclient.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "public"."babylon_btc_lightclient" ( 2 | "id" BIGINT GENERATED ALWAYS AS IDENTITY, 3 | "chain_info_id" INT NOT NULL, 4 | "height" BIGINT NOT NULL, 5 | "reporter_id" INT NOT NULL, 6 | "roll_forward_count" SMALLINT NOT NULL, 7 | "roll_back_count" SMALLINT NOT NULL, 8 | "btc_height" BIGINT NOT NULL, 9 | "is_roll_back" BOOLEAN NOT NULL, 10 | "btc_headers" TEXT NOT NUll, 11 | PRIMARY KEY ("id", "chain_info_id"), 12 | CONSTRAINT fk_chain_info_id FOREIGN KEY (chain_info_id) REFERENCES meta.chain_info (id) ON DELETE CASCADE ON UPDATE CASCADE, 13 | CONSTRAINT fk_reporter_id FOREIGN KEY (reporter_id, chain_info_id) REFERENCES meta.vigilante_info (id, chain_info_id), 14 | CONSTRAINT uniq_babylon_btc_lightclient_by_height UNIQUE ("chain_info_id","height","reporter_id") 15 | ) 16 | PARTITION BY 17 | LIST ("chain_info_id"); 18 | 19 | CREATE INDEX IF NOT EXISTS babylon_btc_lightclient_idx_01 ON public.babylon_btc_lightclient (height); 20 | CREATE INDEX IF NOT EXISTS babylon_btc_lightclient_idx_02 ON public.babylon_btc_lightclient (reporter_id, height); 21 | CREATE INDEX IF NOT EXISTS babylon_btc_lightclient_idx_03 ON public.babylon_btc_lightclient USING btree (chain_info_id, reporter_id, height desc); -------------------------------------------------------------------------------- /docker/flyway/V1_05__init_babylon_checkpoint.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "public"."babylon_checkpoint" ( 2 | "id" BIGINT GENERATED ALWAYS AS IDENTITY, 3 | "chain_info_id" INT NOT NULL, 4 | "epoch" INT NOT NULL, 5 | "height" BIGINT NOT NULL, 6 | "timestamp" timestamptz NOT NULL, 7 | "validator_hex_address_id" INT NOT NULL, 8 | "status" SMALLINT NOT NULL, 9 | PRIMARY KEY ("id", "chain_info_id"), 10 | CONSTRAINT fk_chain_info_id FOREIGN KEY (chain_info_id) REFERENCES meta.chain_info (id) ON DELETE CASCADE ON UPDATE CASCADE, 11 | CONSTRAINT fk_validator_hex_address_id FOREIGN KEY (validator_hex_address_id, chain_info_id) REFERENCES meta.validator_info (id, chain_info_id), 12 | CONSTRAINT uniq_babylon_checkpoint UNIQUE ("chain_info_id","height","validator_hex_address_id") 13 | ) 14 | PARTITION BY 15 | LIST ("chain_info_id"); 16 | 17 | CREATE INDEX IF NOT EXISTS babylon_checkpoint_idx_01 ON public.babylon_checkpoint (height); 18 | CREATE INDEX IF NOT EXISTS babylon_checkpoint_idx_02 ON public.babylon_checkpoint (validator_hex_address_id, height); 19 | CREATE INDEX IF NOT EXISTS babylon_checkpoint_idx_03 ON public.babylon_checkpoint USING btree (chain_info_id, validator_hex_address_id, height asc); -------------------------------------------------------------------------------- /docker/flyway/V1_06__init_babylon_covenant_signature.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "public"."babylon_covenant_signature" ( 2 | "id" BIGINT GENERATED ALWAYS AS IDENTITY, 3 | "chain_info_id" INT NOT NULL, 4 | "height" BIGINT NOT NULL, 5 | "covenant_btc_pk_id" INT NOT NULL, 6 | "btc_staking_tx_hash" VARCHAR(64) NOT NULL, 7 | "timestamp" timestamptz NOT NULL, 8 | PRIMARY KEY ("id", "chain_info_id"), 9 | CONSTRAINT fk_chain_info_id FOREIGN KEY (chain_info_id) REFERENCES meta.chain_info (id) ON DELETE CASCADE ON UPDATE CASCADE, 10 | CONSTRAINT fk_covenant_btc_pk_id FOREIGN KEY (covenant_btc_pk_id, chain_info_id) REFERENCES meta.covenant_committee_info (id, chain_info_id), 11 | CONSTRAINT uniq_babylon_covenant_signature_by_btc_staking_tx_hash UNIQUE ("chain_info_id", "height", "covenant_btc_pk_id", "btc_staking_tx_hash") 12 | ) 13 | PARTITION BY 14 | LIST ("chain_info_id"); 15 | 16 | CREATE INDEX IF NOT EXISTS babylon_covenant_signature_01 ON public.babylon_covenant_signature (height); 17 | CREATE INDEX IF NOT EXISTS babylon_covenant_signature_02 ON public.babylon_covenant_signature (covenant_btc_pk_id); 18 | CREATE INDEX IF NOT EXISTS babylon_covenant_signature_03 ON public.babylon_covenant_signature (covenant_btc_pk_id, height); 19 | CREATE INDEX IF NOT EXISTS babylon_covenant_signature_04 ON public.babylon_covenant_signature USING btree (chain_info_id, covenant_btc_pk_id, height asc); 20 | -------------------------------------------------------------------------------- /docker/flyway/V1_07__init_babylon_finality_provider.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "public"."babylon_finality_provider" ( 2 | "id" BIGINT GENERATED ALWAYS AS IDENTITY, 3 | "chain_info_id" INT NOT NULL, 4 | "height" BIGINT NOT NULL, 5 | "finality_provider_pk_id" INT NOT NULL, 6 | "status" SMALLINT NOT NULL, 7 | "timestamp" timestamptz NOT NULL, 8 | PRIMARY KEY ("id", "chain_info_id"), 9 | CONSTRAINT fk_chain_info_id FOREIGN KEY (chain_info_id) REFERENCES meta.chain_info (id) ON DELETE CASCADE ON UPDATE CASCADE, 10 | CONSTRAINT fk_finality_provider_pk_id FOREIGN KEY (finality_provider_pk_id, chain_info_id) REFERENCES meta.finality_provider_info (id, chain_info_id), 11 | CONSTRAINT uniq_babylon_finality_provider_by_height UNIQUE ("chain_info_id","height","finality_provider_pk_id") 12 | ) 13 | PARTITION BY 14 | LIST ("chain_info_id"); 15 | 16 | CREATE INDEX IF NOT EXISTS babylon_finality_provider_idx_01 ON public.babylon_finality_provider (height); 17 | CREATE INDEX IF NOT EXISTS babylon_finality_provider_idx_02 ON public.babylon_finality_provider (finality_provider_pk_id, height); 18 | CREATE INDEX IF NOT EXISTS babylon_finality_provider_idx_03 ON public.babylon_finality_provider USING btree (chain_info_id, finality_provider_pk_id, height asc); -------------------------------------------------------------------------------- /docker/flyway/V1_08__init_block_data_analytics.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "public"."block_data_analytics" ( 2 | "id" BIGINT GENERATED ALWAYS AS IDENTITY, 3 | "chain_info_id" INT NOT NULL, 4 | "height" BIGINT NOT NULL, 5 | "timestamp" timestamptz NOT NULL, 6 | 7 | "total_txs_bytes" INT NOT NULL, 8 | "total_gas_used" INT NOT NULL, 9 | "total_gas_wanted" INT NOT NULL, 10 | "success_txs_count" INT NOT NULL, 11 | "failed_txs_count" INT NOT NULL, 12 | 13 | PRIMARY KEY ("id", "chain_info_id"), 14 | CONSTRAINT fk_chain_info_id FOREIGN KEY (chain_info_id) REFERENCES meta.chain_info (id) ON DELETE CASCADE ON UPDATE CASCADE 15 | ) 16 | PARTITION BY 17 | LIST ("chain_info_id"); 18 | 19 | CREATE INDEX IF NOT EXISTS block_data_analytics_idx_01 ON public.block_data_analytics (height); 20 | 21 | 22 | CREATE TABLE IF NOT EXISTS "public"."block_message_analytics" ( 23 | "id" BIGINT GENERATED ALWAYS AS IDENTITY, 24 | "chain_info_id" INT NOT NULL, 25 | "height" BIGINT NOT NULL, 26 | "timestamp" timestamptz NOT NULL, 27 | 28 | "message_type_id" INT NOT NULL, 29 | "success" BOOLEAN NOT NULL, 30 | 31 | PRIMARY KEY ("id", "chain_info_id"), 32 | CONSTRAINT fk_chain_info_id FOREIGN KEY (chain_info_id) REFERENCES meta.chain_info (id) ON DELETE CASCADE ON UPDATE CASCADE, 33 | CONSTRAINT fk_message_type_id FOREIGN KEY (message_type_id, chain_info_id) REFERENCES meta.message_type (id, chain_info_id) 34 | ) 35 | PARTITION BY 36 | LIST ("chain_info_id"); 37 | 38 | CREATE INDEX IF NOT EXISTS block_message_analytics_idx_01 ON public.block_message_analytics (height); 39 | CREATE INDEX IF NOT EXISTS block_message_analytics_idx_02 ON public.block_message_analytics (message_type_id, height); 40 | CREATE INDEX IF NOT EXISTS block_message_analytics_idx_03 ON public.block_message_analytics USING btree (chain_info_id, success, height asc); -------------------------------------------------------------------------------- /docker/flyway/V1_09__init_veindexer.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "public"."veindexer" ( 2 | "id" BIGINT GENERATED ALWAYS AS IDENTITY, 3 | "chain_info_id" INT NOT NULL, 4 | "height" BIGINT NOT NULL, 5 | "validator_hex_address_id" INT NOT NULL, 6 | "status" SMALLINT NOT NULL, 7 | "vote_extension_length" INT NOT NULL, 8 | "timestamp" timestamptz NOT NULL, 9 | PRIMARY KEY ("id", "chain_info_id"), 10 | CONSTRAINT fk_chain_info_id FOREIGN KEY (chain_info_id) REFERENCES meta.chain_info (id) ON DELETE CASCADE ON UPDATE CASCADE, 11 | CONSTRAINT fk_validator_hex_address_id FOREIGN KEY (validator_hex_address_id, chain_info_id) REFERENCES meta.validator_info (id, chain_info_id), 12 | CONSTRAINT uniq_validator_hex_address_by_height UNIQUE ("chain_info_id","height","validator_hex_address_id") 13 | ) 14 | PARTITION BY 15 | LIST ("chain_info_id"); 16 | 17 | CREATE INDEX IF NOT EXISTS veindexer_idx_01 ON public.veindexer (height); 18 | CREATE INDEX IF NOT EXISTS veindexer_idx_02 ON public.veindexer (validator_hex_address_id, height); 19 | CREATE INDEX IF NOT EXISTS veindexer_idx_03 ON public.veindexer USING btree (chain_info_id, validator_hex_address_id, height asc); -------------------------------------------------------------------------------- /docker/flyway/V1_10__init_voteindexer.sql: -------------------------------------------------------------------------------- 1 | -- status := 0 is NaN(jailed or inactive) 1 is missed, 2 is voted, 3 is proposed 2 | CREATE TABLE IF NOT EXISTS "public"."voteindexer" ( 3 | "id" BIGINT GENERATED ALWAYS AS IDENTITY, 4 | "chain_info_id" INT NOT NULL, 5 | "height" BIGINT NOT NULL, 6 | "validator_hex_address_id" INT NOT NULL, 7 | "status" SMALLINT NOT NULL, 8 | "timestamp" timestamptz NOT NULL, 9 | PRIMARY KEY ("id", "chain_info_id"), 10 | CONSTRAINT fk_chain_info_id FOREIGN KEY (chain_info_id) REFERENCES meta.chain_info (id) ON DELETE CASCADE ON UPDATE CASCADE, 11 | CONSTRAINT fk_validator_hex_address_id FOREIGN KEY (validator_hex_address_id, chain_info_id) REFERENCES meta.validator_info (id, chain_info_id), 12 | CONSTRAINT uniq_block_missed_validator_hex_address_by_height UNIQUE ("chain_info_id","height","validator_hex_address_id") 13 | ) 14 | PARTITION BY 15 | LIST ("chain_info_id"); 16 | 17 | CREATE INDEX IF NOT EXISTS voteindexer_idx_01 ON public.voteindexer (height); 18 | CREATE INDEX IF NOT EXISTS voteindexer_idx_02 ON public.voteindexer (validator_hex_address_id, height); 19 | CREATE INDEX IF NOT EXISTS voteindexer_idx_03 ON public.voteindexer USING btree (chain_info_id, validator_hex_address_id, height asc); -------------------------------------------------------------------------------- /docker/grafana/provisioning/dashboards/dashboard.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: 1 3 | 4 | providers: 5 | - name: 'CVMS Validator Mode' 6 | orgId: 1 7 | folder: 'Validator Dashboards' 8 | type: file 9 | disableDeletion: false 10 | editable: true 11 | options: 12 | path: /etc/grafana/provisioning/dashboards/validator 13 | 14 | - name: 'CVMS Network Mode' 15 | orgId: 1 16 | folder: 'Network Dashboards' 17 | type: file 18 | disableDeletion: false 19 | editable: true 20 | options: 21 | path: /etc/grafana/provisioning/dashboards/network 22 | 23 | - name: 'CVMS Logs' 24 | orgId: 1 25 | type: file 26 | disableDeletion: false 27 | editable: true 28 | options: 29 | path: /etc/grafana/provisioning/dashboards/cvms-management-dashboard.json 30 | 31 | - name: 'CVMS cadvisor' 32 | orgId: 1 33 | type: file 34 | disableDeletion: false 35 | editable: true 36 | options: 37 | path: /etc/grafana/provisioning/dashboards/cvms-cadvisor-dashboard.json -------------------------------------------------------------------------------- /docker/grafana/provisioning/datasources/datasources.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: 1 3 | 4 | deleteDatasources: 5 | - name: CVMSExporterDB 6 | - name: CVMSIndexerDB 7 | 8 | datasources: 9 | - name: CVMSExporterDB 10 | type: prometheus 11 | access: proxy 12 | orgId: 1 13 | uid: cvms_exporter_db 14 | url: http://cvms-exporter-db:9090 15 | editable: false 16 | 17 | - name: CVMSIndexerDB 18 | type: postgres 19 | uid: cvms_indexer_db 20 | url: ${DB_HOST}:${DB_PORT} 21 | user: ${DB_USER} 22 | secureJsonData: 23 | password: ${DB_PASSWORD} 24 | jsonData: 25 | database: ${DB_NAME} 26 | sslmode: 'disable' # disable/require/verify-ca/verify-full 27 | postgresVersion: 1604 # 903=9.3, 904=9.4, 905=9.5, 906=9.6, 1000=10 28 | timescaledb: false 29 | 30 | - name: Loki 31 | type: loki 32 | access: proxy 33 | orgId: 1 34 | uid: cvms_loki 35 | url: http://loki:3100 36 | editable: true -------------------------------------------------------------------------------- /docker/loki/loki.yaml: -------------------------------------------------------------------------------- 1 | auth_enabled: false 2 | 3 | server: 4 | http_listen_port: 3100 5 | grpc_listen_port: 9096 6 | 7 | common: 8 | path_prefix: /tmp/loki 9 | storage: 10 | filesystem: 11 | chunks_directory: /tmp/loki/chunks 12 | rules_directory: /tmp/loki/rules 13 | replication_factor: 1 14 | ring: 15 | instance_addr: 127.0.0.1 16 | kvstore: 17 | store: inmemory 18 | 19 | compactor: 20 | working_directory: /tmp/loki/retention 21 | compaction_interval: 10m 22 | retention_enabled: true 23 | retention_delete_delay: 2h 24 | retention_delete_worker_count: 150 25 | delete_request_store: filesystem 26 | 27 | schema_config: 28 | configs: 29 | - from: 2024-05-01 30 | store: tsdb 31 | object_store: filesystem 32 | schema: v13 33 | index: 34 | prefix: tsdb_index 35 | period: 24h 36 | 37 | limits_config: 38 | retention_period: 30d 39 | ingestion_rate_mb: 128 40 | ingestion_burst_size_mb: 512 -------------------------------------------------------------------------------- /docker/postgres/schema/02-init-axelar_amplifier_verifier.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "public"."axelar_amplifier_verifier" ( 2 | "id" BIGINT GENERATED ALWAYS AS IDENTITY, 3 | "chain_info_id" INT NOT NULL, 4 | "created_at" timestamptz NOT NULL, 5 | "chain_and_poll_id" TEXT NOT NULL, 6 | "poll_start_height" INT NOT NULL, 7 | "poll_vote_height" INT NOT NULL, 8 | "verifier_id" INT NOT NULL, 9 | "status" SMALLINT NOT NUll, 10 | PRIMARY KEY ("id", "chain_info_id"), 11 | CONSTRAINT fk_chain_info_id FOREIGN KEY (chain_info_id) REFERENCES meta.chain_info (id) ON DELETE CASCADE ON UPDATE CASCADE, 12 | CONSTRAINT fk_verifier_id FOREIGN KEY (verifier_id, chain_info_id) REFERENCES meta.verifier_info (id, chain_info_id), 13 | CONSTRAINT uniq_verifier_id_by_poll UNIQUE ("chain_info_id","chain_and_poll_id","verifier_id") 14 | ) 15 | PARTITION BY 16 | LIST ("chain_info_id"); 17 | 18 | -- CREATE INDEX IF NOT EXISTS axelar_amplifier_verifier_idx_01 ON public.axelar_amplifier_verifier (height); 19 | -- CREATE INDEX IF NOT EXISTS axelar_amplifier_verifier_idx_02 ON public.axelar_amplifier_verifier (verifier_id, height); 20 | -- CREATE INDEX IF NOT EXISTS axelar_amplifier_verifier_idx_03 ON public.axelar_amplifier_verifier USING btree (chain_info_id, verifier_id, height desc); -------------------------------------------------------------------------------- /docker/postgres/schema/02-init-babylon_btc_delegation.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "public"."babylon_btc_delegation" ( 2 | "id" BIGINT GENERATED ALWAYS AS IDENTITY, 3 | "chain_info_id" INT NOT NULL, 4 | "height" BIGINT NOT NULL, 5 | "btc_staking_tx_hash" VARCHAR(64) NOT NULL, 6 | "timestamp" timestamptz NOT NULL, 7 | PRIMARY KEY ("id", "chain_info_id"), 8 | CONSTRAINT fk_chain_info_id FOREIGN KEY (chain_info_id) REFERENCES meta.chain_info (id) ON DELETE CASCADE ON UPDATE CASCADE, 9 | CONSTRAINT uniq_babylon_btc_delegation_by_btc_staking_tx_hash UNIQUE ("chain_info_id","height","btc_staking_tx_hash") 10 | ) 11 | PARTITION BY 12 | LIST ("chain_info_id"); 13 | 14 | CREATE INDEX IF NOT EXISTS babylon_btc_delegation_01 ON public.babylon_btc_delegation (height); 15 | CREATE INDEX IF NOT EXISTS babylon_btc_delegation_02 ON public.babylon_btc_delegation (btc_staking_tx_hash); 16 | CREATE INDEX IF NOT EXISTS babylon_btc_delegation_03 ON public.babylon_btc_delegation USING btree (chain_info_id, btc_staking_tx_hash, height asc); -------------------------------------------------------------------------------- /docker/postgres/schema/02-init-babylon_btc_lightclient.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "public"."babylon_btc_lightclient" ( 2 | "id" BIGINT GENERATED ALWAYS AS IDENTITY, 3 | "chain_info_id" INT NOT NULL, 4 | "height" BIGINT NOT NULL, 5 | "reporter_id" INT NOT NULL, 6 | "roll_forward_count" SMALLINT NOT NULL, 7 | "roll_back_count" SMALLINT NOT NULL, 8 | "btc_height" BIGINT NOT NULL, 9 | "is_roll_back" BOOLEAN NOT NULL, 10 | "btc_headers" TEXT NOT NUll, 11 | PRIMARY KEY ("id", "chain_info_id"), 12 | CONSTRAINT fk_chain_info_id FOREIGN KEY (chain_info_id) REFERENCES meta.chain_info (id) ON DELETE CASCADE ON UPDATE CASCADE, 13 | CONSTRAINT fk_reporter_id FOREIGN KEY (reporter_id, chain_info_id) REFERENCES meta.vigilante_info (id, chain_info_id), 14 | CONSTRAINT uniq_babylon_btc_lightclient_by_height UNIQUE ("chain_info_id","height","reporter_id") 15 | ) 16 | PARTITION BY 17 | LIST ("chain_info_id"); 18 | 19 | CREATE INDEX IF NOT EXISTS babylon_btc_lightclient_idx_01 ON public.babylon_btc_lightclient (height); 20 | CREATE INDEX IF NOT EXISTS babylon_btc_lightclient_idx_02 ON public.babylon_btc_lightclient (reporter_id, height); 21 | CREATE INDEX IF NOT EXISTS babylon_btc_lightclient_idx_03 ON public.babylon_btc_lightclient USING btree (chain_info_id, reporter_id, height desc); -------------------------------------------------------------------------------- /docker/postgres/schema/02-init-babylon_checkpoint.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "public"."babylon_checkpoint" ( 2 | "id" BIGINT GENERATED ALWAYS AS IDENTITY, 3 | "chain_info_id" INT NOT NULL, 4 | "epoch" INT NOT NULL, 5 | "height" BIGINT NOT NULL, 6 | "timestamp" timestamptz NOT NULL, 7 | "validator_hex_address_id" INT NOT NULL, 8 | "status" SMALLINT NOT NULL, 9 | PRIMARY KEY ("id", "chain_info_id"), 10 | CONSTRAINT fk_chain_info_id FOREIGN KEY (chain_info_id) REFERENCES meta.chain_info (id) ON DELETE CASCADE ON UPDATE CASCADE, 11 | CONSTRAINT fk_validator_hex_address_id FOREIGN KEY (validator_hex_address_id, chain_info_id) REFERENCES meta.validator_info (id, chain_info_id), 12 | CONSTRAINT uniq_babylon_checkpoint UNIQUE ("chain_info_id","height","validator_hex_address_id") 13 | ) 14 | PARTITION BY 15 | LIST ("chain_info_id"); 16 | 17 | CREATE INDEX IF NOT EXISTS babylon_checkpoint_idx_01 ON public.babylon_checkpoint (height); 18 | CREATE INDEX IF NOT EXISTS babylon_checkpoint_idx_02 ON public.babylon_checkpoint (validator_hex_address_id, height); 19 | CREATE INDEX IF NOT EXISTS babylon_checkpoint_idx_03 ON public.babylon_checkpoint USING btree (chain_info_id, validator_hex_address_id, height asc); -------------------------------------------------------------------------------- /docker/postgres/schema/02-init-babylon_covenant_signature.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "public"."babylon_covenant_signature" ( 2 | "id" BIGINT GENERATED ALWAYS AS IDENTITY, 3 | "chain_info_id" INT NOT NULL, 4 | "height" BIGINT NOT NULL, 5 | "covenant_btc_pk_id" INT NOT NULL, 6 | "btc_staking_tx_hash" VARCHAR(64) NOT NULL, 7 | "timestamp" timestamptz NOT NULL, 8 | PRIMARY KEY ("id", "chain_info_id"), 9 | CONSTRAINT fk_chain_info_id FOREIGN KEY (chain_info_id) REFERENCES meta.chain_info (id) ON DELETE CASCADE ON UPDATE CASCADE, 10 | CONSTRAINT fk_covenant_btc_pk_id FOREIGN KEY (covenant_btc_pk_id, chain_info_id) REFERENCES meta.covenant_committee_info (id, chain_info_id), 11 | CONSTRAINT uniq_babylon_covenant_signature_by_btc_staking_tx_hash UNIQUE ("chain_info_id", "height", "covenant_btc_pk_id", "btc_staking_tx_hash") 12 | ) 13 | PARTITION BY 14 | LIST ("chain_info_id"); 15 | 16 | CREATE INDEX IF NOT EXISTS babylon_covenant_signature_01 ON public.babylon_covenant_signature (height); 17 | CREATE INDEX IF NOT EXISTS babylon_covenant_signature_02 ON public.babylon_covenant_signature (covenant_btc_pk_id); 18 | CREATE INDEX IF NOT EXISTS babylon_covenant_signature_03 ON public.babylon_covenant_signature (covenant_btc_pk_id, height); 19 | CREATE INDEX IF NOT EXISTS babylon_covenant_signature_04 ON public.babylon_covenant_signature USING btree (chain_info_id, covenant_btc_pk_id, height asc); 20 | -------------------------------------------------------------------------------- /docker/postgres/schema/02-init-babylon_finality_provider.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "public"."babylon_finality_provider" ( 2 | "id" BIGINT GENERATED ALWAYS AS IDENTITY, 3 | "chain_info_id" INT NOT NULL, 4 | "height" BIGINT NOT NULL, 5 | "finality_provider_pk_id" INT NOT NULL, 6 | "status" SMALLINT NOT NULL, 7 | "timestamp" timestamptz NOT NULL, 8 | PRIMARY KEY ("id", "chain_info_id"), 9 | CONSTRAINT fk_chain_info_id FOREIGN KEY (chain_info_id) REFERENCES meta.chain_info (id) ON DELETE CASCADE ON UPDATE CASCADE, 10 | CONSTRAINT fk_finality_provider_pk_id FOREIGN KEY (finality_provider_pk_id, chain_info_id) REFERENCES meta.finality_provider_info (id, chain_info_id), 11 | CONSTRAINT uniq_babylon_finality_provider_by_height UNIQUE ("chain_info_id","height","finality_provider_pk_id") 12 | ) 13 | PARTITION BY 14 | LIST ("chain_info_id"); 15 | 16 | CREATE INDEX IF NOT EXISTS babylon_finality_provider_idx_01 ON public.babylon_finality_provider (height); 17 | CREATE INDEX IF NOT EXISTS babylon_finality_provider_idx_02 ON public.babylon_finality_provider (finality_provider_pk_id, height); 18 | CREATE INDEX IF NOT EXISTS babylon_finality_provider_idx_03 ON public.babylon_finality_provider USING btree (chain_info_id, finality_provider_pk_id, height asc); -------------------------------------------------------------------------------- /docker/postgres/schema/02-init-block-data-analytics.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "public"."block_data_analytics" ( 2 | "id" BIGINT GENERATED ALWAYS AS IDENTITY, 3 | "chain_info_id" INT NOT NULL, 4 | "height" BIGINT NOT NULL, 5 | "timestamp" timestamptz NOT NULL, 6 | 7 | "total_txs_bytes" INT NOT NULL, 8 | "total_gas_used" INT NOT NULL, 9 | "total_gas_wanted" INT NOT NULL, 10 | "success_txs_count" INT NOT NULL, 11 | "failed_txs_count" INT NOT NULL, 12 | 13 | PRIMARY KEY ("id", "chain_info_id"), 14 | CONSTRAINT fk_chain_info_id FOREIGN KEY (chain_info_id) REFERENCES meta.chain_info (id) ON DELETE CASCADE ON UPDATE CASCADE 15 | ) 16 | PARTITION BY 17 | LIST ("chain_info_id"); 18 | 19 | CREATE INDEX IF NOT EXISTS block_data_analytics_idx_01 ON public.block_data_analytics (height); 20 | 21 | 22 | CREATE TABLE IF NOT EXISTS "public"."block_message_analytics" ( 23 | "id" BIGINT GENERATED ALWAYS AS IDENTITY, 24 | "chain_info_id" INT NOT NULL, 25 | "height" BIGINT NOT NULL, 26 | "timestamp" timestamptz NOT NULL, 27 | 28 | "message_type_id" INT NOT NULL, 29 | "success" BOOLEAN NOT NULL, 30 | 31 | PRIMARY KEY ("id", "chain_info_id"), 32 | CONSTRAINT fk_chain_info_id FOREIGN KEY (chain_info_id) REFERENCES meta.chain_info (id) ON DELETE CASCADE ON UPDATE CASCADE, 33 | CONSTRAINT fk_message_type_id FOREIGN KEY (message_type_id, chain_info_id) REFERENCES meta.message_type (id, chain_info_id) 34 | ) 35 | PARTITION BY 36 | LIST ("chain_info_id"); 37 | 38 | CREATE INDEX IF NOT EXISTS block_message_analytics_idx_01 ON public.block_message_analytics (height); 39 | CREATE INDEX IF NOT EXISTS block_message_analytics_idx_02 ON public.block_message_analytics (message_type_id, height); 40 | CREATE INDEX IF NOT EXISTS block_message_analytics_idx_03 ON public.block_message_analytics USING btree (chain_info_id, success, height asc); -------------------------------------------------------------------------------- /docker/postgres/schema/02-init-veindexer.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "public"."veindexer" ( 2 | "id" BIGINT GENERATED ALWAYS AS IDENTITY, 3 | "chain_info_id" INT NOT NULL, 4 | "height" BIGINT NOT NULL, 5 | "validator_hex_address_id" INT NOT NULL, 6 | "status" SMALLINT NOT NULL, 7 | "vote_extension_length" INT NOT NULL, 8 | "timestamp" timestamptz NOT NULL, 9 | PRIMARY KEY ("id", "chain_info_id"), 10 | CONSTRAINT fk_chain_info_id FOREIGN KEY (chain_info_id) REFERENCES meta.chain_info (id) ON DELETE CASCADE ON UPDATE CASCADE, 11 | CONSTRAINT fk_validator_hex_address_id FOREIGN KEY (validator_hex_address_id, chain_info_id) REFERENCES meta.validator_info (id, chain_info_id), 12 | CONSTRAINT uniq_validator_hex_address_by_height UNIQUE ("chain_info_id","height","validator_hex_address_id") 13 | ) 14 | PARTITION BY 15 | LIST ("chain_info_id"); 16 | 17 | CREATE INDEX IF NOT EXISTS veindexer_idx_01 ON public.veindexer (height); 18 | CREATE INDEX IF NOT EXISTS veindexer_idx_02 ON public.veindexer (validator_hex_address_id, height); 19 | CREATE INDEX IF NOT EXISTS veindexer_idx_03 ON public.veindexer USING btree (chain_info_id, validator_hex_address_id, height asc); -------------------------------------------------------------------------------- /docker/postgres/schema/02-init-voteindexer.sql: -------------------------------------------------------------------------------- 1 | -- status := 0 is NaN(jailed or inactive) 1 is missed, 2 is voted, 3 is proposed 2 | CREATE TABLE IF NOT EXISTS "public"."voteindexer" ( 3 | "id" BIGINT GENERATED ALWAYS AS IDENTITY, 4 | "chain_info_id" INT NOT NULL, 5 | "height" BIGINT NOT NULL, 6 | "validator_hex_address_id" INT NOT NULL, 7 | "status" SMALLINT NOT NULL, 8 | "timestamp" timestamptz NOT NULL, 9 | PRIMARY KEY ("id", "chain_info_id"), 10 | CONSTRAINT fk_chain_info_id FOREIGN KEY (chain_info_id) REFERENCES meta.chain_info (id) ON DELETE CASCADE ON UPDATE CASCADE, 11 | CONSTRAINT fk_validator_hex_address_id FOREIGN KEY (validator_hex_address_id, chain_info_id) REFERENCES meta.validator_info (id, chain_info_id), 12 | CONSTRAINT uniq_block_missed_validator_hex_address_by_height UNIQUE ("chain_info_id","height","validator_hex_address_id") 13 | ) 14 | PARTITION BY 15 | LIST ("chain_info_id"); 16 | 17 | CREATE INDEX IF NOT EXISTS voteindexer_idx_01 ON public.voteindexer (height); 18 | CREATE INDEX IF NOT EXISTS voteindexer_idx_02 ON public.voteindexer (validator_hex_address_id, height); 19 | CREATE INDEX IF NOT EXISTS voteindexer_idx_03 ON public.voteindexer USING btree (chain_info_id, validator_hex_address_id, height asc); -------------------------------------------------------------------------------- /docker/prometheus/custom_rules/custom_rule.yaml.example: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: BalancePackage 3 | rules: 4 | - alert: SomeValidatorBalanceUnder1 5 | expr: cvms_balance_remaining_amount{balance_address='cosmos1clpqr4nrk4khgkxj78fcwwh6dl3uw4ep4tgu9q'} < 1 6 | for: 5m 7 | labels: 8 | severity: info 9 | annotations: 10 | summary: 'The Cosmostation Validator ({{ $labels.balance_address }}) has less than 1 tokens remaining. Current balance: {{ $value }}' 11 | -------------------------------------------------------------------------------- /docker/prometheus/prometheus.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # global settings 3 | global: 4 | scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. 5 | evaluation_interval: 60s # Evaluate rules every 15 seconds. The default is every 1 minute. 6 | # scrape_timeout is set to the global default (10s). 7 | 8 | alerting: 9 | alertmanagers: 10 | - timeout: 10s 11 | - static_configs: 12 | - targets: ['alertmanager:9093'] 13 | # - basic_auth: NOTE: Currently CVMS is NOT USED 14 | 15 | # Load rules once and periodically evaluate them according to the global 'evaluation_interval'. 16 | rule_files: 17 | - /etc/prometheus/rules/*.yaml 18 | - /etc/prometheus/custom_rules/*.yaml 19 | 20 | # A scrape configuration containing exactly one endpoint to scrape: 21 | # Here it's Prometheus itself. 22 | scrape_configs: 23 | # The job name is added as a label `job=` to any timeseries scraped from this config. 24 | - job_name: 'exporter' 25 | metrics_path: '/metrics' 26 | static_configs: 27 | - targets: ['cvms-exporter:9200'] 28 | labels: 29 | service: 'cvms' 30 | 31 | - job_name: 'indexer' 32 | metrics_path: '/metrics' 33 | static_configs: 34 | - targets: ['cvms-indexer:9300'] 35 | labels: 36 | service: 'cvms' 37 | 38 | - job_name: 'prometheus' 39 | metrics_path: '/metrics' 40 | static_configs: 41 | - targets: ['cvms-exporter-db:9090'] 42 | 43 | - job_name: 'cadvisor' 44 | metrics_path: '/metrics' 45 | static_configs: 46 | - targets: ['cvms-cadvisor:8080'] 47 | 48 | - job_name: 'loki' 49 | metrics_path: '/metrics' 50 | static_configs: 51 | - targets: ['loki:3100'] 52 | 53 | - job_name: 'promtail' 54 | metrics_path: '/metrics' 55 | static_configs: 56 | - targets: ['promtail:9080'] 57 | -------------------------------------------------------------------------------- /docker/prometheus/rules/axelar.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: AxelarEVMPackage 3 | rules: 4 | - alert: AxelarEVMChainMaintainerInactive 5 | expr: cvms_axelar_evm_maintainer_status == 0 6 | for: 1m 7 | labels: 8 | severity: warning 9 | annotations: 10 | summary: 'Axelar EVM Maintainer for `{{ $labels.evm_chain }}` is inactive in {{ $labels.chain_id }}' 11 | 12 | - name: AxelarAmplifierVerifierPackage 13 | rules: 14 | - alert: AmplifierVerifierFailedVoting 15 | expr: cvms_axelar_amplifier_verifier_poll_vote{status!~"SucceededOnChain"} - (cvms_axelar_amplifier_verifier_poll_vote offset 10m) > 0 16 | labels: 17 | severity: warning 18 | annotations: 19 | summary: 'Axelar Amplifier Verifer Missed vote in {{ $labels.chain_id }}' 20 | description: | 21 | In {{ $labels.chain_id }}, {{ $labels.verifier }} did not vote for some chains. 22 | Check it now 23 | 24 | - name: AxelarValdHeartbeatsPackage 25 | rules: 26 | - alert: AxelarValdHeartbeatsStopped 27 | expr: increase(cvms_axelar_vald_heartbeats_count_total{status="success"}[5m]) == 0 28 | for: 10m 29 | labels: 30 | severity: warning 31 | annotations: 32 | summary: 'Axelar Vald heartbeat has stopped for {{ $labels.moniker }} in {{ $labels.chain_id }}.' 33 | -------------------------------------------------------------------------------- /docker/prometheus/rules/babylon.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: BabylonFinalityProviderPackage 3 | rules: 4 | - alert: IncreasingBabylonMissedVotesOver5PercentDuring1h 5 | expr: (delta(cvms_babylon_finality_provider_missed_votes_counter[1h]) / on (chain_id) group_left max by (chain_id) (delta(cvms_block_height[1h]))) * 100 > 5 6 | labels: 7 | severity: warning 8 | annotations: 9 | summary: | 10 | 'In {{ $labels.chain }}-{{ $labels.chain_id }} network, the validator has missed Babylon votes' 11 | description: | 12 | '{{ $labels.moniker }} has missed more than 5% (actual: {{ printf "%.2f" $value }}%) of votes in the last hour. Please check your Babylon signer or node status.' 13 | 14 | - name: BabylonCheckpointPackage 15 | rules: 16 | - alert: BabylonCheckpointBLSSignatureMissedOver1During1h 17 | expr: delta(cvms_babylon_checkpoint_bls_signature_missed_total{status!="BLOCK_ID_FLAG_COMMIT"}[1h]) > 1 18 | labels: 19 | severity: warning 20 | annotations: 21 | summary: 'In {{ $labels.chain }}-{{ $labels.chain_id }} network, missed BLS signatures detected for Babylon checkpoints' 22 | description: '{{ $labels.moniker }} has missed more than 1 BLS signature with status "{{ $labels.status }}" in the last hour. Please investigate your Babylon signer or validator node setup.' 23 | -------------------------------------------------------------------------------- /docker/prometheus/rules/block.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: BlockPackage 3 | rules: 4 | # - alert: LastestBlockTimeDiffOver60s 5 | # expr: (time() - cvms_block_timestamp) > 60 6 | # for: 30s 7 | # labels: 8 | # severity: warning 9 | # annotations: 10 | # summary: 'Latest Block Timestamp is over 60 seconds from now' 11 | # description: | 12 | # The block timestamp for the {{ $labels.chain }} chain at {{ $labels.endpoint }} has exceeded 60 seconds. Please check the synchronization status of the node. 13 | 14 | - alert: LastestBlockTimeDiffOver300s 15 | expr: (time() - cvms_block_timestamp{chain!~'gravity-bridge'}) > 300 16 | labels: 17 | severity: critical 18 | annotations: 19 | summary: 'The {{ $labels.chain }}-{{ $labels.chain_id }} latest block timestamp has exceeded 5 minutes' 20 | description: | 21 | The block timestamp at {{ $labels.endpoint }} has exceeded 5 minutes. Please check the node's synchronization status immediately. 22 | 23 | - alert: GravityBridgeBlockTimeDiffOver600s 24 | expr: (time() - cvms_block_timestamp{chain='gravity-bridge'}) > 600 25 | labels: 26 | severity: critical 27 | annotations: 28 | summary: 'The {{ $labels.chain }}-{{ $labels.chain_id }} latest block timestamp has exceeded 10 minutes' 29 | description: | 30 | The block timestamp at {{ $labels.endpoint }} has exceeded 10 minutes. Please check the node's synchronization status immediately. 31 | -------------------------------------------------------------------------------- /docker/prometheus/rules/consensus-vote.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: VoteIndexerPackage 3 | rules: 4 | # - alert: VoteIndexerSyncSlow 5 | # expr: (time() - cvms_consensus_vote_latest_index_pointer_block_timestamp) > 300 6 | # for: 30s 7 | # labels: 8 | # severity: warning 9 | # annotations: 10 | # summary: The Vote Indexer Package sync is slow to sync blocks for {{ $labels.chain_id }} 11 | 12 | # - alert: IncreaseRecentConsensusVoteMiss 13 | # expr: increase(cvms_consensus_vote_recent_miss_counter[1m]) > 15 14 | # for: 30s 15 | # labels: 16 | # severity: critical 17 | # annotations: 18 | # summary: The validator is missing too many votes in the consensus for {{ $labels.chain_id }} 19 | -------------------------------------------------------------------------------- /docker/prometheus/rules/eventnonce.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: EventNoncePackage 3 | rules: 4 | - alert: CosmosEventNonceDiffOver0 5 | expr: (cvms_eventnonce_highest_nonce - on (chain_id) group_right cvms_eventnonce_nonce) > 0 6 | for: 15m 7 | labels: 8 | severity: warning 9 | annotations: 10 | summary: 'Validator node event nonce is behind in {{ $labels.chain }}' 11 | description: '{{ $labels.chain }} has an event nonce that is {{ $value }} behind other validators.' 12 | 13 | - alert: CosmosEventNonceDiffOver1h 14 | expr: (cvms_eventnonce_highest_nonce - on (chain_id) group_right cvms_eventnonce_nonce) > 0 15 | for: 1h 16 | labels: 17 | severity: critical 18 | annotations: 19 | summary: 'Validator node event nonce is behind in {{ $labels.chain }} during 1h' 20 | description: | 21 | The event nonce for {{ $labels.chain }} is more than {{ $value }} behind other validators. Immediate action is required. 22 | -------------------------------------------------------------------------------- /docker/prometheus/rules/extension-vote.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: VoteExtensionIndexerPackage 3 | # rules: 4 | # - alert: VoteExtensionIndexerSyncSlow 5 | # expr: (time() - cvms_extension_vote_latest_index_pointer_block_timestamp) > 300 6 | # for: 30s 7 | # labels: 8 | # severity: warning 9 | # annotations: 10 | # summary: The Vote Extension Indexer Package sync is slow to sync blocks for {{ $labels.chain_id }} 11 | 12 | # - alert: IncreaseRecentExtensionVoteMiss 13 | # expr: increase(cvms_extension_vote_recent_miss_counter[1m]) > 15 14 | # for: 30s 15 | # labels: 16 | # severity: critical 17 | # annotations: 18 | # summary: The validator is missing too many votes in the extension voting for {{ $labels.chain_id }} 19 | -------------------------------------------------------------------------------- /docker/prometheus/rules/oracle.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: OraclePackage 3 | rules: 4 | - alert: IncreasingMissCounterOver30%During1h 5 | expr: (delta(cvms_oracle_miss_counter[1h]) / on (chain_id) group_left delta(cvms_oracle_block_height[1h])) * on (chain_id) group_left cvms_oracle_vote_period > 0.30 6 | for: 7 | labels: 8 | severity: warning 9 | annotations: 10 | summary: The Oracle Miss Counter for the network {{ $labels.chain }}-{{ $labels.chain_id }} is increasing by over 30% during the past hour 11 | 12 | - alert: IncreasingMissCounterOver50%During1h 13 | expr: (delta(cvms_oracle_miss_counter[1h]) / on (chain_id) group_left delta(cvms_oracle_block_height[1h])) * on (chain_id) group_left cvms_oracle_vote_period > 0.50 14 | for: 15 | labels: 16 | severity: critical 17 | annotations: 18 | summary: The Oracle Miss Counter for the network {{ $labels.chain }}-{{ $labels.chain_id }} is increasing by over 50% during the past hour 19 | 20 | - alert: OracleUptimeUnder90% 21 | expr: | 22 | ( 23 | (cvms_oracle_vote_window - on (chain_id) group_right () cvms_oracle_miss_counter) 24 | / on (chain_id) group_left () 25 | cvms_oracle_vote_window 26 | ) < 0.9 27 | labels: 28 | severity: critical 29 | annotations: 30 | summary: The Validator's Oracle vote rate is too low for the {{ $labels.chain }}-{{ $labels.chain_id }} network 31 | 32 | - alert: OracleUptimeUnder50% 33 | expr: | 34 | ( 35 | (cvms_oracle_vote_window - on (chain_id) group_right () cvms_oracle_miss_counter) 36 | / on (chain_id) group_left () 37 | cvms_oracle_vote_window 38 | ) < 0.5 39 | labels: 40 | severity: critical 41 | annotations: 42 | summary: The Validator's Oracle vote rate is too low for the {{ $labels.chain }}-{{ $labels.chain_id }} network 43 | -------------------------------------------------------------------------------- /docker/prometheus/rules/upgrade.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: Upgrade 3 | rules: 4 | - alert: OnchainUpgradeRegistered 5 | expr: (cvms_upgrade_remaining_time - cvms_upgrade_remaining_time offset 30m > 3600) and (cvms_upgrade_remaining_time offset 30m == 0) 6 | labels: 7 | severity: info 8 | channel: upgrade 9 | annotations: 10 | summary: 'An upgrade named {{ $labels.upgrade_name }} has been registered on the {{ $labels.chain }}-{{ $labels.chain_id }} network.' 11 | 12 | - alert: onchain_upgrade_remaining_3h 13 | expr: cvms_upgrade_remaining_time != 0 and cvms_upgrade_remaining_time < 10800 14 | labels: 15 | severity: info 16 | channel: upgrade 17 | annotations: 18 | summary: 'The upgrade {{ $labels.upgrade_name }} on the {{ $labels.chain }}-{{ $labels.chain_id }} network is approximately 3 hours away.' 19 | 20 | - alert: onchain_upgrade_remaining_1h 21 | expr: cvms_upgrade_remaining_time != 0 and cvms_upgrade_remaining_time < 3600 22 | annotations: 23 | summary: 'The upgrade {{ $labels.upgrade_name }} on the {{ $labels.chain }}-{{ $labels.chain_id }} network is approximately 1 hour away.' 24 | labels: 25 | severity: info 26 | channel: upgrade 27 | -------------------------------------------------------------------------------- /docker/prometheus/rules/uptime.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: UptimePackage 3 | rules: 4 | # NOTE: for weekly uptime checking 5 | # sort_desc(delta(cvms_uptime_missed_blocks_counter[1h]) / on (chain_id) group_left max by (chain_id) (delta(cvms_block_height[1h])) * 100 > 0) 6 | 7 | - alert: IncreasingConsensusMissCounterOver10%During1h 8 | expr: (delta(cvms_uptime_missed_blocks_counter[1h]) / on (chain_id) group_left max by (chain_id) (delta(cvms_block_height[1h]))) * 100 > 5 9 | labels: 10 | severity: warning 11 | annotations: 12 | summary: 'In {{ $labels.chain }}-{{ $labels.chain_id }} network, The Validator have missed some blocks' 13 | description: '{{ $labels.moniker }} had missed some blocks for consensus. Please check your node or signer status' 14 | 15 | # - alert: Remaining1000BlocksBeforeGettingSlashing 16 | # # max missed - current missed 17 | # expr: | 18 | # ceil(cvms_uptime_signed_blocks_window * (1 - cvms_uptime_min_signed_per_window)) - on (chain_id) group_right () cvms_uptime_missed_blocks_counter < 1000 19 | # labels: 20 | # severity: critical 21 | # annotations: 22 | # summary: The Validator's is missing too many! Check your validator in {{ $labels.chain }}-{{ $labels.chain_id }} network -------------------------------------------------------------------------------- /docker/prometheus/rules/yoda.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: YodaPackage 3 | rules: 4 | - alert: BandYodaStatusInactivated 5 | expr: cvms_yoda_status == 0 6 | for: 1m 7 | labels: 8 | severity: warning 9 | annotations: 10 | summary: 'The Yoda status of the validator has been deactivated in the {{ $labels.chain }}-{{ $labels.chain_id }} network' 11 | -------------------------------------------------------------------------------- /docker/promtail/promtail-config.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | http_listen_port: 9080 3 | grpc_listen_port: 9081 4 | log_level: warn 5 | 6 | positions: 7 | filename: /tmp/positions.yaml 8 | 9 | scrape_configs: 10 | - job_name: containers 11 | static_configs: 12 | - targets: 13 | - localhost 14 | labels: 15 | job: containerlogs 16 | server: cvms 17 | __path__: /var/lib/docker/containers/*/*log 18 | 19 | pipeline_stages: 20 | - json: 21 | expressions: 22 | output: log 23 | stream: stream 24 | attrs: 25 | 26 | - json: 27 | expressions: 28 | tag: 29 | source: attrs 30 | 31 | - regex: 32 | expression: (?P(?:[^|]*[^|])).(?P(?:[^|]*[^|])).(?P(?:[^|]*[^|])).(?P(?:[^|]*[^|])) 33 | source: tag 34 | 35 | - timestamp: 36 | format: RFC3339Nano 37 | source: time 38 | 39 | - labels: 40 | stream: 41 | container_name: 42 | 43 | - labeldrop: 44 | - filename 45 | 46 | - output: 47 | source: output 48 | 49 | clients: 50 | - url: http://loki:3100/loki/api/v1/push -------------------------------------------------------------------------------- /docs/add_support_chains.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmostation/cvms/367545acaacac3fb71474108753829e67c6e63d3/docs/add_support_chains.md -------------------------------------------------------------------------------- /helm/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | # img folder 25 | img/ 26 | # Changelog 27 | CHANGELOG.md -------------------------------------------------------------------------------- /helm/Chart.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: postgresql 3 | repository: oci://registry-1.docker.io/bitnamicharts 4 | version: 16.3.5 5 | - name: common 6 | repository: oci://registry-1.docker.io/bitnamicharts 7 | version: 2.29.0 8 | digest: sha256:8c7c60055f2b8834c742f2b389606d430018a7fd08449ce2c8c31b31a94f0ca4 9 | generated: "2025-01-10T15:05:20.436906+03:30" 10 | -------------------------------------------------------------------------------- /helm/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: cvms 3 | description: The Cosmos Validator Monitoring Service (CVMS) is an integrated monitoring system for validators within the Cosmos app chain ecosystem 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "1.0.0" 25 | 26 | dependencies: 27 | - condition: postgresql.enabled 28 | name: postgresql 29 | repository: oci://registry-1.docker.io/bitnamicharts 30 | version: 16.x.x 31 | - name: common 32 | repository: oci://registry-1.docker.io/bitnamicharts 33 | tags: 34 | - bitnami-common 35 | version: 2.x.x -------------------------------------------------------------------------------- /helm/files/axelar-evm.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: AxelarEVMPackage 3 | rules: 4 | - alert: AxelarEVMChainMaintainerInactive 5 | expr: cvms_axelar_evm_maintainer_status == 0 6 | for: 1m 7 | labels: 8 | severity: warning 9 | annotations: 10 | summary: 'Axelar EVM Maintainer for {{ $labels.evm_chain }} is inactive in {{ $labels.chain_id }}' -------------------------------------------------------------------------------- /helm/files/balance.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: BalancePackage 3 | rules: 4 | - alert: AxelarEVMBroadcasterBalanceUnder10 5 | expr: cvms_balance_remaining_amount{balance_address='axelar146kdz9stlycvacm03hg0t5fxq6jszlc4gtxgpr'} < 10 6 | for: 5m 7 | labels: 8 | severity: info 9 | annotations: 10 | summary: 'The broadcaster ({{ $labels.balance_address }}) has less than 10 tokens remaining. Current balance: {{ $value }}' 11 | 12 | - alert: KavaOracleBroadcasterBalanceUnder10 13 | expr: cvms_balance_remaining_amount{balance_address='kava1ujfrlcd0ted58mzplnyxzklsw0sqevlgxndanp'} < 10 14 | for: 5m 15 | labels: 16 | severity: info 17 | annotations: 18 | summary: 'The broadcaster ({{ $labels.balance_address }}) has less than 10 tokens remaining. Current balance: {{ $value }}' 19 | 20 | - alert: InjectiveEventnonceBroadcasterBalanceUnder1 21 | expr: cvms_balance_remaining_amount{balance_address='inj1mtxhcchfyvvs6u4nmnylgkxvkrax7c2la69l8w'} < 1 22 | for: 5m 23 | labels: 24 | severity: info 25 | annotations: 26 | summary: 'The broadcaster ({{ $labels.balance_address }}) has less than 1 tokens remaining. Current balance: {{ $value }}' 27 | 28 | - alert: NibiruOracleBroadcasterBalanceUnder10 29 | expr: cvms_balance_remaining_amount{balance_address='nibi14zc23q3qcewscy7wnt3s95h32chytenqxe633l'} < 10 30 | for: 5m 31 | labels: 32 | severity: info 33 | annotations: 34 | summary: 'The broadcaster ({{ $labels.balance_address }}) has less than 10 tokens remaining. Current balance: {{ $value }}' -------------------------------------------------------------------------------- /helm/files/block.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: BlockPackage 3 | rules: 4 | - alert: LastestBlockTimeDiffOver60s 5 | expr: (time() - cvms_block_timestamp) > 60 6 | for: 30s 7 | labels: 8 | severity: warning 9 | annotations: 10 | summary: 'Latest Block Timestamp is over 60 seconds from now' 11 | description: | 12 | The block timestamp for the {{ $labels.chain }} chain at {{ $labels.endpoint }} has exceeded 60 seconds. Please check the synchronization status of the node. 13 | 14 | - alert: LastestBlockTimeDiffOver300s 15 | expr: (time() - cvms_block_timestamp) > 300 16 | for: 2m 17 | labels: 18 | severity: critical 19 | annotations: 20 | summary: 'The {{ $labels.chain }}-{{ $labels.chain_id }} latest block timestamp has exceeded 5 minutes' 21 | description: | 22 | The block timestamp at {{ $labels.endpoint }} has exceeded 5 minutes. Please check the node's synchronization status immediately. -------------------------------------------------------------------------------- /helm/files/consensus-vote.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: VoteIndexerPackage 3 | rules: 4 | - alert: VoteIndexerSyncSlow 5 | expr: (time() - cvms_consensus_vote_latest_index_pointer_block_timestamp) > 300 6 | for: 30s 7 | labels: 8 | severity: warning 9 | annotations: 10 | summary: The Vote Indexer Package sync is slow to sync blocks for {{ $labels.chain_id }} 11 | 12 | - alert: IncreaseRecentConsensusVoteMiss 13 | expr: increase(cvms_consensus_vote_recent_miss_counter[1m]) > 15 14 | for: 30s 15 | labels: 16 | severity: critical 17 | annotations: 18 | summary: The validator is missing too many votes in the consensus for {{ $labels.chain_id }} -------------------------------------------------------------------------------- /helm/files/custom_chains.yaml.example: -------------------------------------------------------------------------------- 1 | --- 2 | mintstation-1: 3 | protocol_type: cosmos 4 | support_asset: 5 | denom: umint 6 | decimal: 6 7 | packages: 8 | - block 9 | - upgrade 10 | - uptime 11 | - voteindexer #for cometbft consensus vote 12 | - veindexer # for vote-extension -------------------------------------------------------------------------------- /helm/files/eventnonce.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: EventNoncePackage 3 | rules: 4 | - alert: CosmosEventNonceDiffOver0 5 | expr: (cvms_eventnonce_highest_nonce - on (chain_id) group_right cvms_eventnonce_nonce) > 0 6 | for: 15m 7 | labels: 8 | severity: warning 9 | annotations: 10 | summary: 'Validator node event nonce is behind in {{ $labels.chain }}' 11 | description: '{{ $labels.chain }} has an event nonce that is {{ $value }} behind other validators.' 12 | 13 | - alert: CosmosEventNonceDiffOver1h 14 | expr: (cvms_eventnonce_highest_nonce - on (chain_id) group_right cvms_eventnonce_nonce) > 0 15 | for: 1h 16 | labels: 17 | severity: critical 18 | annotations: 19 | summary: 'Validator node event nonce is behind in {{ $labels.chain }} during 1h' 20 | description: | 21 | The event nonce for {{ $labels.chain }} is more than {{ $value }} behind other validators. Immediate action is required. -------------------------------------------------------------------------------- /helm/files/extension-vote.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: VoteExtensionIndexerPackage 3 | rules: 4 | - alert: VoteExtensionIndexerSyncSlow 5 | expr: (time() - cvms_extension_vote_latest_index_pointer_block_timestamp) > 300 6 | for: 30s 7 | labels: 8 | severity: warning 9 | annotations: 10 | summary: The Vote Extension Indexer Package sync is slow to sync blocks for {{ $labels.chain_id }} 11 | 12 | - alert: IncreaseRecentExtensionVoteMiss 13 | expr: increase(cvms_extension_vote_recent_miss_counter[1m]) > 15 14 | for: 30s 15 | labels: 16 | severity: critical 17 | annotations: 18 | summary: The validator is missing too many votes in the extension voting for {{ $labels.chain_id }} -------------------------------------------------------------------------------- /helm/files/oracle.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: OraclePackage 3 | rules: 4 | - alert: IncreasingMissCounterOver30%During1h 5 | expr: (delta(cvms_oracle_miss_counter[1h]) / on (chain_id) group_left delta(cvms_oracle_block_height[1h])) * on (chain_id) group_left cvms_oracle_vote_period > 0.30 6 | for: 30m 7 | labels: 8 | severity: critical 9 | annotations: 10 | summary: The Oracle Miss Counter for the network {{ $labels.chain }}-{{ $labels.chain_id }} is increasing by over 30% during the past hour 11 | 12 | - alert: OracleUptimeUnder50% 13 | expr: | 14 | ( 15 | (cvms_oracle_vote_window - on (chain_id) group_right () cvms_oracle_miss_counter) 16 | / on (chain_id) group_left () 17 | cvms_oracle_vote_window 18 | ) < 0.5 19 | for: 5m 20 | labels: 21 | severity: critical 22 | annotations: 23 | summary: The Validator's Oracle vote rate is too low for the {{ $labels.chain }}-{{ $labels.chain_id }} network 24 | description: | 25 | Oracle vote rate has dropped below 50%, indicating severe issues with the validator's participation. 26 | Current vote rate: {{ $value | humanizePercentage }} -------------------------------------------------------------------------------- /helm/files/root.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: Prometheus 3 | rules: 4 | - alert: CVMSExporterDown 5 | expr: up == 0 6 | for: 1m 7 | labels: 8 | severity: critical 9 | annotations: 10 | summary: 'One of CVMS Service was down.' 11 | description: | 12 | The {{ $labels.job }} was down. you should recover this service right now to keep CVMS monitoring service. 13 | 14 | - alert: FoundFailedEvaluationRules 15 | expr: increase(prometheus_rule_evaluation_failures_total[5m]) > 0 16 | for: 1m 17 | labels: 18 | severity: critical 19 | annotations: 20 | summary: 'Prometheus rule evaluation failures detected in CVMS.' 21 | description: | 22 | Some of rules in {{ $labels.rule_group }} is not normal. Please check the rules ASAP to monitor normally. 23 | 24 | - name: Root 25 | rules: 26 | - alert: AbnormalPackageOperations 27 | expr: changes(cvms_root_processed_ops_total[3m]) < 1 28 | for: 1m 29 | labels: 30 | severity: warning 31 | annotations: 32 | summary: summary: 'No package operations detected in CVMS service.' 33 | description: | 34 | The {{ $labels.package }} service being collected from {{ $labels.chain }} is not operating correctly. 35 | 36 | - alert: FoundUnhealthyPackage 37 | expr: cvms_root_health_checker == 0 38 | for: 5m 39 | labels: 40 | severity: warning 41 | annotations: 42 | summary: 'There are abnormal packages in the CVMS service.' 43 | description: | 44 | Health check failed for {{ $labels.package }} service on chain {{ $labels.chain }}. Verify service health metrics. 45 | 46 | - alert: FoundSkippedPackage 47 | expr: cvms_root_skip_counter == 1 48 | for: 5m 49 | labels: 50 | severity: warning 51 | annotations: 52 | summary: 'There are skipped packages in the CVMS service.' 53 | description: | 54 | Package {{ $labels.package }} on instance {{ $labels.instance_name }} has been skipped. Check for configuration issues or resource constraints. -------------------------------------------------------------------------------- /helm/files/upgrade.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: Upgrade 3 | rules: 4 | - alert: OnchainUpgradeRegistered 5 | expr: (cvms_upgrade_remaining_time - cvms_upgrade_remaining_time offset 30m > 3600) and (cvms_upgrade_remaining_time offset 30m == 0) 6 | labels: 7 | severity: info 8 | channel: upgrade 9 | annotations: 10 | summary: 'An upgrade named {{ $labels.upgrade_name }} has been registered on the {{ $labels.chain }}-{{ $labels.chain_id }} network.' 11 | 12 | - alert: onchain_upgrade_remaining_3h 13 | expr: cvms_upgrade_remaining_time != 0 and cvms_upgrade_remaining_time < 10800 14 | labels: 15 | severity: info 16 | channel: upgrade 17 | annotations: 18 | summary: 'The upgrade {{ $labels.upgrade_name }} on the {{ $labels.chain }}-{{ $labels.chain_id }} network is approximately 3 hours away.' 19 | 20 | - alert: onchain_upgrade_remaining_1h 21 | expr: cvms_upgrade_remaining_time != 0 and cvms_upgrade_remaining_time < 3600 22 | annotations: 23 | summary: 'The upgrade {{ $labels.upgrade_name }} on the {{ $labels.chain }}-{{ $labels.chain_id }} network is approximately 1 hour away.' 24 | labels: 25 | severity: info 26 | channel: upgrade -------------------------------------------------------------------------------- /helm/files/uptime.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: UptimePackage 3 | rules: 4 | - alert: IncreasingMissCounterOver30%During1h 5 | expr: | 6 | # 30mins ago, the validator is unjailed. but got jailed 7 | cvms_uptime_jailed - cvms_uptime_jailed offset 30m > 0 and (cvms_uptime_jailed offset 30m == 0) 8 | labels: 9 | severity: critical 10 | annotations: 11 | summary: The validator is jailed now in {{ $labels.chain_id }} 12 | 13 | - alert: UptimeMissCounterIsIncreasing10%During1h 14 | expr: | 15 | # miss counter during 1h / blocks during 1h 16 | (delta(cvms_uptime_missed_blocks_counter[1h]) / on (chain_id) group_left max by (chain_id) (delta(cvms_block_height[1h]))) > 0.1 17 | labels: 18 | severity: critical 19 | annotations: 20 | summary: The Validator's uptime is decreasing now! Check your validator in {{ $labels.chain }}-{{ $labels.chain_id }} network 21 | 22 | - alert: Remaining1000BlocksBeforeGettingSlashing 23 | expr: | 24 | # max missed - current missed 25 | ceil(cvms_uptime_signed_blocks_window * (1 - cvms_uptime_min_signed_per_window)) - on (chain_id) group_right () cvms_uptime_missed_blocks_counter < 1000 26 | labels: 27 | severity: critical 28 | annotations: 29 | summary: The Validator is missing too many blocks! Check your validator in {{ $labels.chain }}-{{ $labels.chain_id }} network -------------------------------------------------------------------------------- /helm/files/yoda.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: YodaPackage 3 | rules: 4 | - alert: BandYodaStatusInactivated 5 | expr: cvms_yoda_status == 0 6 | for: 1m 7 | labels: 8 | severity: warning 9 | annotations: 10 | summary: 'The Yoda status of the validator has been deactivated in the {{ $labels.chain }}-{{ $labels.chain_id }} network' -------------------------------------------------------------------------------- /helm/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | 3 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 4 | You can watch the status of by running 'kubectl get svc -w' 5 | 6 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "cvms.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 7 | echo http://$SERVICE_IP:{{ .Values.indexer.service.port }} 8 | 9 | 2. To delete the release, run: 10 | 11 | helm delete {{ .Release.Name }} --namespace {{ .Release.Namespace }} -------------------------------------------------------------------------------- /helm/templates/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: {{ .Values.cvmsConfig.name }} 5 | namespace: {{ default .Release.Namespace .Values.namespaceOverride | quote }} 6 | labels: 7 | {{- include "cvms.labels" . | nindent 4 }} 8 | {{- with .Values.cvmsConfig.extraLabels }} 9 | {{- toYaml . | nindent 4 }} 10 | {{- end }} 11 | {{- if .Values.cvmsConfig.annotations }} 12 | annotations: 13 | {{ toYaml .Values.cvmsConfig.annotations | indent 4 }} 14 | {{- end }} 15 | data: 16 | config.yaml: | 17 | monikers: {{ .Values.cvmsConfig.monikers | toJson }} 18 | 19 | chains: {{- if .Values.cvmsConfig.chains }} 20 | {{- .Values.cvmsConfig.chains | toYaml | nindent 6 }} 21 | {{- else }} [] 22 | {{- end }} 23 | 24 | -------------------------------------------------------------------------------- /helm/templates/custom-configmap.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.customChainsConfig.enabled -}} 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: {{ .Values.customChainsConfig.name }} 6 | namespace: {{ default .Release.Namespace .Values.namespaceOverride | quote }} 7 | labels: 8 | {{- include "cvms.labels" . | nindent 4 }} 9 | {{- with .Values.customChainsConfig.extraLabels }} 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- if .Values.customChainsConfig.annotations }} 13 | annotations: 14 | {{ toYaml .Values.customChainsConfig.annotations | indent 4 }} 15 | {{- end }} 16 | data: 17 | custom_chains.yaml.example: | 18 | {{- .Files.Get "files/custom_chains.yaml.example" | nindent 4 }} 19 | {{- end }} -------------------------------------------------------------------------------- /helm/templates/exporter/metrics.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "cvms.fullname" . }}-exporter-metrics 5 | namespace: {{ default .Release.Namespace .Values.namespaceOverride | quote }} 6 | labels: 7 | {{- include "cvms.exporterSelectorMetricLabels" . | nindent 4 }} 8 | {{- with .Values.exporter.metrics.labels }} 9 | {{- toYaml . | nindent 4 }} 10 | {{- end }} 11 | {{- if .Values.exporter.metrics.annotations }} 12 | annotations: 13 | {{- range $key, $value := .Values.exporter.metrics.annotations }} 14 | {{ $key }}: {{ $value | quote }} 15 | {{- end }} 16 | {{- end }} 17 | spec: 18 | type: {{ .Values.exporter.metrics.type }} 19 | {{- if and .Values.exporter.metrics.clusterIP (eq .Values.exporter.metrics.type "ClusterIP") }} 20 | clusterIP: {{ .Values.exporter.metrics.clusterIP }} 21 | {{- end }} 22 | ports: 23 | - name: {{ .Values.exporter.metrics.portName }} 24 | protocol: TCP 25 | port: {{ .Values.exporter.metrics.servicePort }} 26 | targetPort: metrics 27 | selector: 28 | {{- include "cvms.exporterSelectorMetricLabels" . | nindent 4 }} 29 | -------------------------------------------------------------------------------- /helm/templates/exporter/servicemonitor.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.exporter.serviceMonitor.enabled }} 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: ServiceMonitor 4 | metadata: 5 | name: {{ include "cvms.fullname" . }}-exporter 6 | namespace: {{ default .Release.Namespace .Values.exporter.serviceMonitor.namespace | quote }} 7 | labels: 8 | {{- include "cvms.labels" . | nindent 4 }} 9 | {{- include "cvms.exporterSelectorMetricLabels" . | nindent 4 }} 10 | {{- with .Values.exporter.metrics.labels }} 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | {{- with .Values.exporter.serviceMonitor.selector }} 14 | {{- toYaml . | nindent 4 }} 15 | {{- end }} 16 | {{- with .Values.exporter.serviceMonitor.additionalLabels }} 17 | {{- toYaml . | nindent 4 }} 18 | {{- end }} 19 | {{- with .Values.exporter.serviceMonitor.annotations }} 20 | annotations: 21 | {{- toYaml . | nindent 4 }} 22 | {{- end }} 23 | spec: 24 | endpoints: 25 | - port: {{ .Values.exporter.metrics.portName }} 26 | {{- with .Values.exporter.serviceMonitor.interval }} 27 | interval: {{ . }} 28 | {{- end }} 29 | {{- with .Values.exporter.serviceMonitor.scrapeTimeout }} 30 | scrapeTimeout: {{ . }} 31 | {{- end }} 32 | {{- with .Values.exporter.serviceMonitor.path }} 33 | path: {{ . }} 34 | {{- end }} 35 | {{- with .Values.exporter.serviceMonitor.relabelings }} 36 | relabelings: 37 | {{- toYaml . | nindent 8 }} 38 | {{- end }} 39 | {{- with .Values.exporter.serviceMonitor.metricRelabelings }} 40 | metricRelabelings: 41 | {{- toYaml . | nindent 8 }} 42 | {{- end }} 43 | {{- with .Values.exporter.serviceMonitor.scheme }} 44 | scheme: {{ . }} 45 | {{- end }} 46 | {{- with .Values.exporter.serviceMonitor.tlsConfig }} 47 | tlsConfig: 48 | {{- toYaml . | nindent 8 }} 49 | {{- end }} 50 | namespaceSelector: 51 | matchNames: 52 | - {{ default .Release.Namespace .Values.namespaceOverride | quote }} 53 | selector: 54 | matchLabels: 55 | {{- include "cvms.exporterSelectorMetricLabels" . | nindent 6 }} 56 | {{- end }} 57 | -------------------------------------------------------------------------------- /helm/templates/indexer/metrics.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "cvms.fullname" . }}-indexer-metrics 5 | namespace: {{ default .Release.Namespace .Values.namespaceOverride | quote }} 6 | labels: 7 | {{- include "cvms.indexerSelectorMetricLabels" . | nindent 4 }} 8 | {{- with .Values.indexer.metrics.labels }} 9 | {{- toYaml . | nindent 4 }} 10 | {{- end }} 11 | {{- if .Values.indexer.metrics.annotations }} 12 | annotations: 13 | {{- range $key, $value := .Values.indexer.metrics.annotations }} 14 | {{ $key }}: {{ $value | quote }} 15 | {{- end }} 16 | {{- end }} 17 | spec: 18 | type: {{ .Values.indexer.metrics.type }} 19 | {{- if and .Values.indexer.metrics.clusterIP (eq .Values.indexer.metrics.type "ClusterIP") }} 20 | clusterIP: {{ .Values.indexer.metrics.clusterIP }} 21 | {{- end }} 22 | ports: 23 | - name: {{ .Values.indexer.metrics.portName }} 24 | protocol: TCP 25 | port: {{ .Values.indexer.metrics.servicePort }} 26 | targetPort: metrics 27 | selector: 28 | {{- include "cvms.indexerSelectorMetricLabels" . | nindent 4 }} 29 | -------------------------------------------------------------------------------- /helm/templates/indexer/servicemonitor.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.indexer.serviceMonitor.enabled }} 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: ServiceMonitor 4 | metadata: 5 | name: {{ include "cvms.fullname" . }}-indexer 6 | namespace: {{ default .Release.Namespace .Values.indexer.serviceMonitor.namespace | quote }} 7 | labels: 8 | {{- include "cvms.labels" . | nindent 4 }} 9 | {{- include "cvms.indexerSelectorMetricLabels" . | nindent 4 }} 10 | {{- with .Values.indexer.metrics.labels }} 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | {{- with .Values.indexer.serviceMonitor.selector }} 14 | {{- toYaml . | nindent 4 }} 15 | {{- end }} 16 | {{- with .Values.indexer.serviceMonitor.additionalLabels }} 17 | {{- toYaml . | nindent 4 }} 18 | {{- end }} 19 | {{- with .Values.indexer.serviceMonitor.annotations }} 20 | annotations: 21 | {{- toYaml . | nindent 4 }} 22 | {{- end }} 23 | spec: 24 | endpoints: 25 | - port: {{ .Values.indexer.metrics.portName }} 26 | {{- with .Values.indexer.serviceMonitor.interval }} 27 | interval: {{ . }} 28 | {{- end }} 29 | {{- with .Values.indexer.serviceMonitor.scrapeTimeout }} 30 | scrapeTimeout: {{ . }} 31 | {{- end }} 32 | {{- with .Values.indexer.serviceMonitor.path }} 33 | path: {{ . }} 34 | {{- end }} 35 | {{- with .Values.indexer.serviceMonitor.relabelings }} 36 | relabelings: 37 | {{- toYaml . | nindent 8 }} 38 | {{- end }} 39 | {{- with .Values.indexer.serviceMonitor.metricRelabelings }} 40 | metricRelabelings: 41 | {{- toYaml . | nindent 8 }} 42 | {{- end }} 43 | {{- with .Values.indexer.serviceMonitor.scheme }} 44 | scheme: {{ . }} 45 | {{- end }} 46 | {{- with .Values.indexer.serviceMonitor.tlsConfig }} 47 | tlsConfig: 48 | {{- toYaml . | nindent 8 }} 49 | {{- end }} 50 | namespaceSelector: 51 | matchNames: 52 | - {{ default .Release.Namespace .Values.namespaceOverride | quote }} 53 | selector: 54 | matchLabels: 55 | {{- include "cvms.indexerSelectorMetricLabels" . | nindent 6 }} 56 | {{- end }} 57 | -------------------------------------------------------------------------------- /helm/templates/prometheus-rules.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.exporter.serviceMonitor.enabled .Values.exporter.serviceMonitor.prometheusRule.enabled -}} 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: PrometheusRule 4 | metadata: 5 | labels: 6 | {{- include "cvms.labels" . | nindent 4 }} 7 | {{- include "cvms.exporterSelectorMetricLabels" . | nindent 4 }} 8 | {{- with .Values.exporter.metrics.labels }} 9 | {{- toYaml . | nindent 4 }} 10 | {{- end }} 11 | {{- with .Values.exporter.serviceMonitor.prometheusRule.additionalLabels }} 12 | {{- toYaml . | nindent 4 }} 13 | {{- end }} 14 | {{- with .Values.exporter.serviceMonitor.prometheusRule.annotations }} 15 | annotations: 16 | {{- toYaml . | nindent 4 }} 17 | {{- end }} 18 | name: {{ include "cvms.fullname" . }} 19 | namespace: {{ coalesce .Values.exporter.serviceMonitor.prometheusRule.namespace .Values.exporter.serviceMonitor.namespace .Release.Namespace | quote }} 20 | spec: 21 | groups: 22 | {{- $files := .Files }} 23 | {{- range .Values.exporter.serviceMonitor.prometheusRule.ruleSets }} 24 | {{- if .enabled }} 25 | {{- $filePath := printf "files/%s.yaml" .name }} 26 | {{- $fileContent := $files.Get $filePath | default "" }} 27 | {{- if $fileContent }} 28 | {{- $parsedYaml := fromYaml $fileContent }} 29 | {{- range $parsedYaml.groups }} 30 | - name: {{ .name }} 31 | rules: 32 | {{- toYaml .rules | nindent 8 }} 33 | {{- end }} 34 | {{- else }} 35 | {{ printf "Warning: File %s not found or empty" $filePath | nindent 4 }} 36 | {{- end }} 37 | {{- end }} 38 | {{- end }} 39 | {{- if .Values.exporter.serviceMonitor.prometheusRule.rules }} 40 | - name: {{ include "cvms.fullname" . }} 41 | rules: {{- toYaml .Values.exporter.serviceMonitor.prometheusRule.rules | nindent 8 }} 42 | {{- end }} 43 | {{- end }} -------------------------------------------------------------------------------- /helm/templates/sql-schema-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: postgresql-init-script 5 | namespace: {{ default .Release.Namespace .Values.namespaceOverride | quote }} 6 | data: 7 | init-schema.sh: | 8 | #!/bin/sh 9 | 10 | # Exit on error 11 | set -e 12 | 13 | # Variables 14 | DB_HOST={{ include "cvms.postgresql.host" . }} 15 | DB_PORT={{ include "cvms.postgresql.port" . }} 16 | DB_USER={{ include "cvms.postgresql.user" . }} 17 | DB_NAME={{ include "cvms.postgresql.database" . }} 18 | DB_PASSWORD={{ include "cvms.postgresql.password" . }} 19 | 20 | # GitHub API URL for the schema directory 21 | SCHEMA_API_URL="https://api.github.com/repos/cosmostation/cvms/contents/docker/postgres/schema" 22 | 23 | # Fetch schema URLs dynamically 24 | echo "Fetching schema URLs from GitHub..." 25 | SCHEMA_URLS=$(curl -s "$SCHEMA_API_URL" | jq -r '.[].download_url') 26 | 27 | echo "Found the following schema files:" 28 | echo "$SCHEMA_URLS" 29 | 30 | # Wait for PostgreSQL to be ready 31 | echo "Waiting for PostgreSQL to be ready..." 32 | until pg_isready -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER"; do 33 | sleep 2 34 | done 35 | 36 | echo "PostgreSQL is ready. Applying schemas..." 37 | 38 | # Loop through the list of URLs 39 | for SCHEMA_URL in $SCHEMA_URLS; do 40 | SCHEMA_FILE="$(basename $SCHEMA_URL)" 41 | 42 | echo "Downloading schema from $SCHEMA_URL..." 43 | curl -s -o "$SCHEMA_FILE" "$SCHEMA_URL" 44 | 45 | echo "Applying schema: $SCHEMA_FILE" 46 | PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f "$SCHEMA_FILE" 47 | done 48 | 49 | echo "All schemas applied successfully." -------------------------------------------------------------------------------- /helm/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "cvms.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "cvms.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | "helm.sh/hook-delete-policy": hook-succeeded 10 | spec: 11 | containers: 12 | - name: wget-indexer 13 | image: busybox 14 | command: ['wget'] 15 | args: ['{{ include "cvms.fullname" . }}:{{ .Values.indexer.service.port }}'] 16 | resources: 17 | limits: 18 | cpu: 100m 19 | memory: 128Mi 20 | requests: 21 | cpu: 50m 22 | memory: 64Mi 23 | securityContext: 24 | allowPrivilegeEscalation: false 25 | capabilities: 26 | drop: ["ALL"] 27 | - name: wget-exporter 28 | image: busybox 29 | command: ['wget'] 30 | args: ['{{ include "cvms.fullname" . }}:{{ .Values.exporter.service.port }}'] 31 | resources: 32 | limits: 33 | cpu: 100m 34 | memory: 128Mi 35 | requests: 36 | cpu: 50m 37 | memory: 64Mi 38 | securityContext: 39 | allowPrivilegeEscalation: false 40 | capabilities: 41 | drop: ["ALL"] 42 | restartPolicy: Never 43 | -------------------------------------------------------------------------------- /internal/app/exporter/exporter_test.go: -------------------------------------------------------------------------------- 1 | package exporter 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/prometheus/client_golang/prometheus" 7 | "github.com/prometheus/client_golang/prometheus/promauto" 8 | ) 9 | 10 | func Test_PrometheusRegsiter(t *testing.T) { 11 | // Global variables for auto-register each package in the custom registry 12 | tRegistry := prometheus.NewRegistry() 13 | tFactory := promauto.With(tRegistry) 14 | 15 | // Define a CounterVec metric 16 | httpRequestsTotal := tFactory.NewCounterVec( 17 | prometheus.CounterOpts{ 18 | Name: "http_requests_total", 19 | Help: "Number of HTTP requests.", 20 | }, 21 | []string{"method", "path"}, 22 | ) 23 | 24 | // Register the metric with the custom registry 25 | // tRegistry.MustRegister(httpRequestsTotal) 26 | 27 | // increase counter 28 | httpRequestsTotal.WithLabelValues("GET", "/test").Inc() 29 | httpRequestsTotal.WithLabelValues("GET", "/test").Inc() 30 | 31 | // httpRequestsTotal.Reset() 32 | 33 | // Gather the metrics from the custom registry 34 | metricFamilies, err := tRegistry.Gather() 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | // Check if our metric is present in the gathered metrics 40 | found := false 41 | for _, m := range metricFamilies { 42 | 43 | for _, v := range m.GetMetric() { 44 | t.Log(v.GetCounter()) 45 | } 46 | t.Log(m.GetName()) 47 | if *m.Name == "http_requests_total" { 48 | found = true 49 | break 50 | } 51 | } 52 | 53 | if found { 54 | t.Log("Metric 'http_requests_total' found in the registry!") 55 | } else { 56 | t.Log("Metric 'http_requests_total' not found in the registry.") 57 | } 58 | 59 | // unregister 60 | tRegistry.Unregister(httpRequestsTotal) 61 | // t.Log(ok) 62 | 63 | // Gather the metrics from the custom registry 64 | metricFamilies, err = tRegistry.Gather() 65 | if err != nil { 66 | panic(err) 67 | } 68 | 69 | // Check if our metric is present in the gathered metrics 70 | found = false 71 | for _, m := range metricFamilies { 72 | 73 | for _, v := range m.GetMetric() { 74 | t.Log(v.GetCounter()) 75 | } 76 | t.Log(m.GetName()) 77 | if *m.Name == "http_requests_total" { 78 | found = true 79 | break 80 | } 81 | } 82 | 83 | if found { 84 | t.Log("Metric 'http_requests_total' found in the registry!") 85 | } else { 86 | t.Log("Metric 'http_requests_total' not found in the registry.") 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /internal/app/indexer/indexer.go: -------------------------------------------------------------------------------- 1 | package indexer 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "os" 7 | 8 | "github.com/cosmostation/cvms/internal/common" 9 | "github.com/cosmostation/cvms/internal/helper/config" 10 | dbhelper "github.com/cosmostation/cvms/internal/helper/db" 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | func Build(port string, l *logrus.Logger, cfg *config.MonitoringConfig, sc *config.SupportChains) ( 15 | /* prometheus indexer server */ *http.Server, 16 | /* unexpected error */ error, 17 | ) { 18 | // set application mode by monikers 19 | app := common.VALIDATOR 20 | for _, moniker := range cfg.Monikers { 21 | if moniker == "all" { 22 | app = common.NETWORK 23 | } 24 | } 25 | 26 | if app == common.VALIDATOR && len(cfg.Monikers) < 1 { 27 | return nil, errors.New("in cosmostation-exporter mode, you must add moniker into the moniker flag") 28 | } 29 | 30 | // register root metircs 31 | registry.MustRegister(common.Skip, common.Health, common.Ops, common.IndexPointer, common.IndexPointerTimestamp) 32 | 33 | // build prometheus server 34 | indexerServer, factory := buildPrometheusExporter(port, l) 35 | 36 | // create indexer DB 37 | dbCfg := common.IndexerDBConfig{ 38 | Host: os.Getenv("DB_HOST"), // Get from environment variable DB_HOST 39 | Database: os.Getenv("DB_NAME"), // Get from environment variable DB_NAME 40 | Port: os.Getenv("DB_PORT"), // Get from environment variable DB_PORT 41 | User: os.Getenv("DB_USER"), // Get from environment variable DB_USER 42 | Password: os.Getenv("DB_PASSWORD"), // Get from environment variable DB_PASSWORD 43 | Timeout: 30, 44 | } 45 | 46 | rt := os.Getenv("DB_RETENTION_PERIOD") // Get from environment variable DB_PASSWO 47 | if rt == "" { 48 | return nil, errors.New("DB_RETENTION_PERIOD is empty") 49 | } 50 | 51 | _, err := dbhelper.ParseRetentionPeriod(rt) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | idb, err := common.NewIndexerDB(dbCfg) 57 | if err != nil { 58 | return nil, err 59 | } 60 | idb.SetRetentionTime(rt) 61 | 62 | err = register(app, factory, l, idb, cfg, sc) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | return indexerServer, nil 68 | } 69 | -------------------------------------------------------------------------------- /internal/app/indexer/prometheus.go: -------------------------------------------------------------------------------- 1 | package indexer 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/gorilla/mux" 9 | "github.com/prometheus/client_golang/prometheus" 10 | "github.com/prometheus/client_golang/prometheus/promauto" 11 | "github.com/prometheus/client_golang/prometheus/promhttp" 12 | "github.com/sirupsen/logrus" 13 | ) 14 | 15 | var ( 16 | // global variables for auto-register each packages in the custom registry 17 | registry = prometheus.NewRegistry() 18 | factory = promauto.With(registry) 19 | router = mux.NewRouter() 20 | ) 21 | 22 | func buildPrometheusExporter(port string, logger *logrus.Logger) (*http.Server, promauto.Factory) { 23 | router. 24 | HandleFunc("/", defaultHandleFunction). 25 | Methods("GET") 26 | router. 27 | Handle("/metrics", buildPrometheusHandler(registry, logger)). 28 | Methods("GET") 29 | router. 30 | PathPrefix("/debug/pprof/"). 31 | Handler(http.DefaultServeMux). 32 | Methods("GET") 33 | 34 | return &http.Server{ 35 | Addr: fmt.Sprintf(":%s", port), 36 | Handler: router, 37 | }, factory 38 | } 39 | 40 | func defaultHandleFunction(w http.ResponseWriter, r *http.Request) { 41 | w.Write([]byte(` 42 | 43 | CVMS(Cosmos Validator Monitoring Service) 44 | 45 | 46 |

Cosmos Validator Monitoring Service - Indexer

47 |

Prod by Cosmostation

48 |

Metrics

49 | 50 | `)) 51 | } 52 | 53 | func buildPrometheusHandler(registry prometheus.Gatherer, logger promhttp.Logger) http.Handler { 54 | return promhttp.HandlerFor(registry, promhttp.HandlerOpts{ 55 | ErrorLog: logger, 56 | ErrorHandling: promhttp.ContinueOnError, 57 | Timeout: time.Second * 5, 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /internal/common/api/api_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cosmostation/cvms/internal/common" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func Test_Cosmos_GetCosmosConsensusParams(t *testing.T) { 11 | commonApp := common.NewCommonApp(p) 12 | commonApp.SetAPIEndPoint("https://lcd-office.cosmostation.io/babylon-testnet") 13 | 14 | maxBytes, maxGas, err := GetCosmosConsensusParams(commonApp.CommonClient) 15 | assert.NoError(t, err) 16 | assert.NotZero(t, maxBytes) 17 | assert.NotZero(t, maxGas) 18 | 19 | t.Logf("block max bytes: %.f", maxBytes) 20 | t.Logf("block max gas: %.f", maxGas) 21 | } 22 | -------------------------------------------------------------------------------- /internal/common/api/babylon_api_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cosmostation/cvms/internal/common" 7 | "github.com/cosmostation/cvms/internal/helper/logger" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | var p = common.Packager{Logger: logger.GetTestLogger()} 12 | 13 | func TestCheckGetBlockResultAndExtractFpVoting(t *testing.T) { 14 | commonApp := common.NewCommonApp(p) 15 | commonApp.SetRPCEndPoint("https://rpc-office.cosmostation.io/babylon-testnet") 16 | 17 | txsEvents, _, _, err := GetBlockResults(commonApp.CommonClient, 92664) 18 | assert.NoError(t, err) 19 | 20 | const msg = "/babylon.finality.v1.MsgAddFinalitySig" 21 | for _, e := range txsEvents { 22 | for _, a := range e.Attributes { 23 | if a.Value == msg { 24 | t.Log(a) 25 | t.Log(e) 26 | } 27 | } 28 | } 29 | } 30 | 31 | func Test_Babylon_GetFP(t *testing.T) { 32 | commonApp := common.NewCommonApp(p) 33 | commonApp.SetAPIEndPoint("https://lcd-office.cosmostation.io/babylon-testnet") 34 | 35 | fps, err := GetBabylonFinalityProviderInfos(commonApp.CommonClient) 36 | assert.NoError(t, err) 37 | 38 | for _, fp := range fps { 39 | t.Logf("%v", fp) 40 | } 41 | 42 | } 43 | 44 | func Test_Babylon_GetBTCDelegations(t *testing.T) { 45 | commonApp := common.NewCommonApp(p) 46 | commonApp.SetAPIEndPoint("https://lcd-office.cosmostation.io/babylon-testnet") 47 | 48 | delegations, err := GetBabylonBTCDelegations(commonApp.CommonClient) 49 | assert.NoError(t, err) 50 | assert.NotEmpty(t, delegations) 51 | 52 | for status, count := range delegations { 53 | t.Logf("found %d in %s", count, status) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /internal/common/api/consensus_params.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/cosmostation/cvms/internal/common" 8 | "github.com/cosmostation/cvms/internal/common/parser" 9 | "github.com/cosmostation/cvms/internal/common/types" 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | func GetCosmosConsensusParams(c common.CommonClient) (float64, float64, error) { 14 | ctx, cancel := context.WithTimeout(context.Background(), common.Timeout) 15 | defer cancel() 16 | 17 | requester := c.RPCClient.R().SetContext(ctx) 18 | resp, err := requester.Get(types.CosmosConsensusParamsQueryPath) 19 | if err != nil { 20 | return 0, 0, errors.Errorf("rpc call is failed from %s: %s", resp.Request.URL, err) 21 | } 22 | 23 | if resp.StatusCode() != http.StatusOK { 24 | return 0, 0, errors.Errorf("stanage status code from %s: [%d]", resp.Request.URL, resp.StatusCode()) 25 | } 26 | 27 | maxBytes, maxGas, err := parser.CosmosConsensusmParamsParser(resp.Body()) 28 | if err != nil { 29 | return 0, 0, errors.WithStack(err) 30 | } 31 | 32 | return maxBytes, maxGas, nil 33 | } 34 | -------------------------------------------------------------------------------- /internal/common/errors.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | const ErrorPrefix = "cvms common errors" 9 | 10 | var ErrCanSkip = fmt.Errorf("skip") 11 | 12 | var ( 13 | ErrFailedToBuildPackager = errors.New("failed to build the package").Error() 14 | ErrUnDefinedSomeConfiguration = errors.New("undefinded port or something in your prometheus config file") 15 | ErrUnSupportedMessageType = errors.New("unsupported message type") 16 | ErrUnSupportedEventType = errors.New("unsupported event type") 17 | ErrUnDefinedApp = fmt.Errorf("%s: undefinded app name", ErrorPrefix) 18 | ErrUnSupportedPackage = fmt.Errorf("%s: this is unsupported monitoring package", ErrorPrefix) 19 | ErrUnSupportedMethod = fmt.Errorf("%s: this is unsupported method", ErrorPrefix) 20 | ErrUnsetHttpSchema = fmt.Errorf("%s: failed to unset http schema for grpc connecting", ErrorPrefix) 21 | ErrFailedHttpRequest = fmt.Errorf("%s: failed to request from node", ErrorPrefix) 22 | ErrGotStrangeStatusCode = fmt.Errorf("%s: got strange status code in request", ErrorPrefix) 23 | ErrUnExpectedMethodCall = fmt.Errorf("%s: got unexpected call method", ErrorPrefix) 24 | ErrFailedJsonUnmarshal = fmt.Errorf("%s: failed to unmarshing json data", ErrorPrefix) 25 | ErrFailedConvertTypes = fmt.Errorf("%s: failed to converting number types", ErrorPrefix) 26 | ErrOutOfSwitchCases = fmt.Errorf("%s: out of switch case in router", ErrorPrefix) 27 | ErrFailedGatheringMiddleData = fmt.Errorf("%s: failed to gather middle data like orchestrator address or somethings", ErrorPrefix) 28 | ErrFailedCreateGrpcConnection = fmt.Errorf("%s: failed to create grpc connection", ErrorPrefix) 29 | ErrFailedGrpcRequest = fmt.Errorf("%s: failed to grpc request from node", ErrorPrefix) 30 | ErrFailedBuildingLogger = fmt.Errorf("%s: failed to build logger by not found workspace string", ErrorPrefix) 31 | ) 32 | 33 | // TODO: aggregate common errors from api level 34 | -------------------------------------------------------------------------------- /internal/common/exporter.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | const ( 8 | retryCount = 10 9 | retryWaitTimeDuration = 10 * time.Millisecond 10 | retryMaxWaitTimeDuration = 3 * time.Second 11 | ) 12 | 13 | type Exporter struct { 14 | CommonApp 15 | Monikers []string 16 | ChainName string 17 | ChainID string 18 | } 19 | 20 | func NewExporter(p Packager) *Exporter { 21 | app := NewCommonApp(p) 22 | // NOTE: empty monikers mean all mode 23 | monikers := []string{} 24 | if p.Mode == VALIDATOR { 25 | monikers = p.Monikers 26 | } 27 | return &Exporter{ 28 | app, 29 | monikers, 30 | p.ChainName, 31 | p.ChainID, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /internal/common/indexer/repository/chain_info.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/cosmostation/cvms/internal/common/indexer/model" 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | func (repo *MetaRepository) InsertChainInfo(chainName, chainID string, isMainnet bool) (int64, error) { 11 | ctx := context.Background() 12 | defer ctx.Done() 13 | 14 | chainInfo := &model.ChainInfo{ 15 | ChainName: chainName, 16 | Mainnet: isMainnet, 17 | ChainID: chainID, 18 | } 19 | 20 | _, err := repo. 21 | NewInsert(). 22 | Model(chainInfo). 23 | ExcludeColumn("id"). 24 | Returning("*"). 25 | Exec(ctx) 26 | 27 | if err != nil { 28 | return 0, errors.Wrapf(err, "failed to insert new chain_info") 29 | } 30 | 31 | return chainInfo.ID, nil 32 | } 33 | 34 | func (repo *MetaRepository) SelectChainInfoIDByChainID(chainID string) (int64, error) { 35 | ctx := context.Background() 36 | defer ctx.Done() 37 | 38 | chainInfo := &model.ChainInfo{} 39 | err := repo. 40 | NewSelect(). 41 | ModelTableExpr("meta.chain_info"). 42 | ColumnExpr("*"). 43 | Where("chain_id = ?", chainID). 44 | Scan(ctx, chainInfo) 45 | if err != nil { 46 | return 0, err 47 | } 48 | 49 | return chainInfo.ID, nil 50 | } 51 | -------------------------------------------------------------------------------- /internal/common/indexer/repository/index_pointer_test.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | "os" 7 | "testing" 8 | "time" 9 | 10 | "github.com/cosmostation/cvms/internal/testutil" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | var ( 15 | metarepo *MetaRepository 16 | TestChainName = "cosmos" 17 | TestChainID = "cosmoshub-4" 18 | IsMainnet = true 19 | ) 20 | 21 | func TestMain(m *testing.M) { 22 | _ = testutil.SetupForTest() 23 | metarepo = &MetaRepository{10 * time.Second, testutil.TestDB} 24 | prerunForTest() 25 | r := m.Run() 26 | metarepo.Close() 27 | os.Exit(r) 28 | } 29 | 30 | func Test_GetTablesRecords(t *testing.T) { 31 | _, err := metarepo.CheckIndexPointerAlreadyInitialized("", 0) 32 | assert.NoError(t, err) 33 | } 34 | 35 | func prerunForTest() { 36 | isNewChain := false 37 | chainInfoID, err := metarepo.SelectChainInfoIDByChainID(TestChainID) 38 | if err != nil { 39 | if err == sql.ErrNoRows { 40 | log.Println("this is new chain id") 41 | isNewChain = true 42 | } else { 43 | log.Fatalln(err) 44 | } 45 | } 46 | 47 | if isNewChain { 48 | chainInfoID, err = metarepo.InsertChainInfo(TestChainName, TestChainID, IsMainnet) 49 | if err != nil { 50 | log.Fatalln(err) 51 | } 52 | log.Printf("%s(%s) chain's chain info id: %d", TestChainName, TestChainID, chainInfoID) 53 | return 54 | } 55 | 56 | log.Printf("%s(%s) chain's chain info id: %d", TestChainName, TestChainID, chainInfoID) 57 | } 58 | -------------------------------------------------------------------------------- /internal/common/indexer/repository/message_type.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/cosmostation/cvms/internal/common/indexer/model" 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | const MESSAGE_TYPE_TABLE_NAME = "message_type" 11 | 12 | func (repo *MetaRepository) GetMessageTypeListByChainInfoID(chainInfoID int64) ([]model.MessageType, error) { 13 | ctx := context.Background() 14 | defer ctx.Done() 15 | 16 | modelList := make([]model.MessageType, 0) 17 | err := repo. 18 | NewSelect(). 19 | Model(&modelList). 20 | Where("chain_info_id = ?", chainInfoID). 21 | Scan(ctx) 22 | if err != nil { 23 | return nil, errors.Wrapf(err, "failed to query validator_info list by chain_info_id") 24 | } 25 | 26 | return modelList, nil 27 | } 28 | 29 | func (repo *MetaRepository) InsertMessageTypeList(messageTypeList []model.MessageType) error { 30 | ctx := context.Background() 31 | defer ctx.Done() 32 | 33 | _, err := repo.NewInsert(). 34 | Model(&messageTypeList). 35 | ExcludeColumn("id"). 36 | Exec(ctx) 37 | if err != nil { 38 | return errors.Wrapf(err, "failed to insert new message types: %v", messageTypeList) 39 | } 40 | 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /internal/common/indexer/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | const ( 9 | // Indexer common logic 10 | RPCCallTimeout = 13 * time.Second 11 | AfterFailedRetryTimeout = 10 * time.Second 12 | CatchingUpSleepDuration = 100 * time.Millisecond 13 | DefaultSleepDuration = 5 * time.Second 14 | UnHealthSleep = 10 * time.Second 15 | 16 | // SQL Durations 17 | SQLQueryMaxDuration = 10 * time.Second // common query timeout 18 | RetentionQuerySleepDuration = 1 * time.Hour // retention logic 19 | 20 | // Fetching Height Logic 21 | FetchTimeout = 5 * time.Second 22 | FetchSleepDuration = 5 * time.Second 23 | AfterFailedFetchSleepDuration = 10 * time.Second 24 | 25 | BatchSyncLimit = 10 26 | ) 27 | 28 | // types for indexer packages 29 | type ValidatorIDMap map[string]int64 30 | type ValidatorAddressMap map[int64]string 31 | type MonikerIDMap map[int64]bool 32 | 33 | type LatestHeightCache struct { 34 | Mutex sync.RWMutex 35 | LatestHeight int64 36 | } 37 | -------------------------------------------------------------------------------- /internal/common/labels.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | const ( 4 | // labels for common 5 | ChainLabel = "chain" 6 | ChainIDLabel = "chain_id" 7 | TableChainIDLabel = "table_chain_id" 8 | PackageLabel = "package" 9 | BaseURLLabel = "endpoint" 10 | ErrLabel = "err" 11 | MainnetLabel = "mainnet" 12 | 13 | // labels for packages 14 | ValidatorAddressLabel = "validator_operator_address" 15 | ConsensusAddressLabel = "validator_consensus_address" 16 | BroadcastorAddressLabel = "broadcastor_address" 17 | MonikerLabel = "moniker" 18 | ProposerAddressLabel = "proposer_address" 19 | EvmChainLabel = "evm_chain" 20 | OrchestratorAddressLabel = "orchestrator_address" 21 | BalanceAddressLabel = "balance_address" 22 | UpgradeNameLabel = "upgrade_name" 23 | BTCPKLabel = "btc_pk" 24 | JailedLabel = "jailed" 25 | ActiveLabel = "active" 26 | StatusLabel = "status" 27 | ) 28 | -------------------------------------------------------------------------------- /internal/common/parser/celestia.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strconv" 7 | 8 | "github.com/cosmostation/cvms/internal/common/types" 9 | ) 10 | 11 | // celestia upgrade parser 12 | func CelestiaUpgradeParser(resp []byte) ( 13 | /* upgrade height */ int64, 14 | /* upgrade plan name */ string, 15 | error) { 16 | var result types.CelestiaUpgradeResponse 17 | if err := json.Unmarshal(resp, &result); err != nil { 18 | return 0, "", fmt.Errorf("parsing error: %s", err.Error()) 19 | } 20 | 21 | if result.Upgrade.UpgradeHeight == "" { 22 | return 0, "", nil 23 | } 24 | 25 | upgradeHeight, err := strconv.ParseInt(result.Upgrade.UpgradeHeight, 10, 64) 26 | if err != nil { 27 | return 0, "", fmt.Errorf("converting error: %s", err.Error()) 28 | } 29 | return upgradeHeight, result.Upgrade.AppVersion, nil 30 | } 31 | -------------------------------------------------------------------------------- /internal/common/parser/initia.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/cosmostation/cvms/internal/common/types" 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | func InitiaStakingValidatorParser(resp []byte) ([]types.CosmosStakingValidator, error) { 11 | var result types.InitiaStakingValidatorsQueryResponse 12 | if err := json.Unmarshal(resp, &result); err != nil { 13 | return nil, errors.Cause(err) 14 | } 15 | commonStakingValidators := make([]types.CosmosStakingValidator, 0) 16 | for _, validator := range result.Validators { 17 | // NOTE: mstaking module is not supporting status query string yet. 18 | if validator.Status == types.BOND_STATUS_BONDED { 19 | commonStakingValidators = append(commonStakingValidators, types.CosmosStakingValidator{ 20 | OperatorAddress: validator.OperatorAddress, 21 | ConsensusPubkey: validator.ConsensusPubkey, 22 | Description: validator.Description, 23 | Tokens: "", // initia has multiple tokens on validators, so skip the tokens 24 | }) 25 | } 26 | } 27 | return commonStakingValidators, nil 28 | } 29 | -------------------------------------------------------------------------------- /internal/common/types/celestia.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const CelestiaUpgradeQueryPath = "/signal/v1/upgrade" 4 | 5 | // ref; https://github.com/celestiaorg/celestia-app/blob/main/proto/celestia/signal/v1/query.proto#L22C22-L22C23 6 | type CelestiaUpgradeResponse struct { 7 | Upgrade struct { 8 | AppVersion string `json:"app_version"` 9 | UpgradeHeight string `json:"upgrade_height"` 10 | } `json:"upgrade"` 11 | } 12 | -------------------------------------------------------------------------------- /internal/common/types/initia.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "fmt" 4 | 5 | type InitiaStakingValidatorsQueryResponse struct { 6 | Validators []InitiaStakingValidator `json:"validators"` 7 | Pagination struct { 8 | // NextKey interface{} `json:"-"` 9 | Total string `json:"total"` 10 | } `json:"pagination"` 11 | } 12 | 13 | type InitiaStakingValidator struct { 14 | OperatorAddress string `json:"operator_address"` 15 | ConsensusPubkey ConsensusPubkey `json:"consensus_pubkey"` 16 | Description struct { 17 | Moniker string `json:"moniker"` 18 | } `json:"description"` 19 | Tokens interface{} `json:"-"` 20 | Status string `json:"status"` 21 | } 22 | 23 | // ref; https://github.com/initia-labs/initia/blob/main/proto/initia/mstaking/v1/query.proto#L14 24 | var InitiaStakingValidatorQueryPath = func(status string) string { 25 | return fmt.Sprintf("/initia/mstaking/v1/validators?status=%s&pagination.count_total=true&pagination.limit=500", status) 26 | } 27 | -------------------------------------------------------------------------------- /internal/common/types/story.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "fmt" 4 | 5 | // ref; https://github.com/piplabs/story/blob/main/client/server/staking.go 6 | var StoryStakingValidatorQueryPath = func(status string) string { 7 | return fmt.Sprintf("/staking/validators?status=%s&pagination.count_total=true&pagination.limit=500", status) 8 | } 9 | 10 | type StoryStakingValidatorsQueryResponse struct { 11 | Code int64 `json:"code"` 12 | Msg struct { 13 | Validators []StoryStakingValidator `json:"validators"` 14 | Pagination struct { 15 | NextKey interface{} `json:"-"` 16 | Total string `json:"total"` 17 | } `json:"pagination"` 18 | } 19 | Error string `json:"error"` 20 | } 21 | 22 | type StoryStakingValidator struct { 23 | OperatorAddress string `json:"operator_address"` 24 | ConsensusPubkey struct { 25 | Type string `json:"type"` 26 | Value string `json:"value"` 27 | } `json:"consensus_pubkey"` 28 | Description struct { 29 | Moniker string `json:"moniker"` 30 | } `json:"description"` 31 | } 32 | 33 | var StoryUpgradeQueryPath = "/upgrade/current_plan" 34 | 35 | // ref; https://github.com/piplabs/story/blob/main/client/server/upgrade.go#L17 36 | type StoryUpgradeResponse struct { 37 | Code int64 `json:"code"` 38 | Msg struct { 39 | Plan struct { 40 | Name string `json:"name"` 41 | Time string `json:"time"` 42 | Height string `json:"height"` 43 | } `json:"plan"` 44 | } `json:"msg"` 45 | Error string `json:"error"` 46 | } 47 | 48 | // ref; https://github.com/piplabs/story/blob/main/client/server/slashing.go 49 | var StorySlashingQueryPath = func(consensusAddress string) string { 50 | return fmt.Sprintf("/slashing/signing_infos/%s", consensusAddress) 51 | } 52 | 53 | type StorySlashingResponse struct { 54 | Code int64 `json:"code"` 55 | Msg struct { 56 | ValidatorSigningInfo SigningInfo `json:"val_signing_info"` 57 | } `json:"msg"` 58 | Error string `json:"error"` 59 | } 60 | 61 | // ref; https://github.com/piplabs/story/blob/main/client/server/slashing.go 62 | var StorySlashingParamsQueryPath = "/slashing/params" 63 | 64 | type StorySlashingParamsResponse struct { 65 | Code int64 `json:"code"` 66 | Msg struct { 67 | Params SlashingParam `json:"params"` 68 | } `json:"msg"` 69 | Error string `json:"error"` 70 | } 71 | -------------------------------------------------------------------------------- /internal/helper/backoff.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import "time" 4 | 5 | func ExponentialBackoff(sleepTime *time.Duration) { 6 | *sleepTime *= 2 7 | 8 | if *sleepTime >= 30*time.Second { 9 | *sleepTime = 30 * time.Second 10 | } 11 | time.Sleep(*sleepTime) 12 | } 13 | -------------------------------------------------------------------------------- /internal/helper/chain_name.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | func ParseChainName(jobName string) (string, error) { 8 | reg, err := regexp.Compile(`-[\w]*net`) 9 | if err != nil { 10 | return "", err 11 | } 12 | indexes := reg.FindAllStringIndex(jobName, -1) 13 | 14 | if len(indexes) == 0 { 15 | return jobName, nil 16 | } 17 | 18 | for idx, element := range indexes { 19 | if idx == 0 { 20 | return jobName[:element[0]], nil 21 | } 22 | } 23 | 24 | return jobName, nil 25 | } 26 | -------------------------------------------------------------------------------- /internal/helper/chainid_parser.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import "strings" 4 | 5 | // parse to pg schema name 6 | func ParseToSchemaName(chainID string) string { 7 | var schema string 8 | schema = strings.Replace(chainID, "-", "_", -1) 9 | schema = strings.Replace(schema, ".", "_", -1) 10 | 11 | return schema 12 | } 13 | -------------------------------------------------------------------------------- /internal/helper/config/support_chain.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/pkg/errors" 7 | "gopkg.in/yaml.v3" 8 | ) 9 | 10 | type SupportChains struct { 11 | Chains map[string]ChainDetail `yaml:",inline"` 12 | } 13 | 14 | type ChainDetail struct { 15 | ChainName string `yaml:"chain_name"` 16 | ProtocolType string `yaml:"protocol_type"` 17 | Mainnet bool `yaml:"mainnet"` 18 | Consumer bool `yaml:"consumer"` 19 | Packages []string `yaml:"packages"` 20 | SupportAsset Asset `yaml:"support_asset"` 21 | } 22 | 23 | type Asset struct { 24 | Denom string `yaml:"denom"` 25 | Decimal int `yaml:"decimal"` 26 | } 27 | 28 | func (sc *SupportChains) Marshal() ([]byte, error) { 29 | yamlData, err := yaml.Marshal(sc) 30 | if err != nil { 31 | return nil, err 32 | } 33 | return yamlData, nil 34 | } 35 | 36 | func GetSupportChainConfig() (*SupportChains, error) { 37 | dataBytes, err := os.ReadFile(MustGetSupportChainPath("support_chains.yaml")) 38 | if err != nil { 39 | return nil, errors.Wrapf(err, "failed to read config file") 40 | } 41 | 42 | scCfg := &SupportChains{} 43 | err = yaml.Unmarshal(dataBytes, scCfg) 44 | if err != nil { 45 | return nil, errors.Wrapf(err, "failed to decode config file") 46 | } 47 | 48 | if fileExists(MustGetSupportChainPath("custom_chains.yaml")) { 49 | ctDataBytes, err := os.ReadFile(MustGetSupportChainPath("custom_chains.yaml")) 50 | if err != nil { 51 | return nil, errors.Wrapf(err, "failed to read config file") 52 | } 53 | ctCfg := &SupportChains{} 54 | err = yaml.Unmarshal(ctDataBytes, ctCfg) 55 | if err != nil { 56 | return nil, errors.Wrapf(err, "failed to decode second config file") 57 | } 58 | // Merge the two configurations 59 | for chainName, chainDetail := range ctCfg.Chains { 60 | // NOTE: this will override chain config for users to use custom_chains.yaml 61 | if _, exists := scCfg.Chains[chainName]; exists { 62 | scCfg.Chains[chainName] = chainDetail 63 | } 64 | 65 | // Add custom chains by custom_chains.yaml 66 | scCfg.Chains[chainName] = chainDetail 67 | } 68 | } 69 | 70 | return scCfg, nil 71 | } 72 | 73 | func fileExists(path string) bool { 74 | _, err := os.Stat(path) 75 | if os.IsNotExist(err) { 76 | return false 77 | } 78 | return err == nil 79 | } 80 | -------------------------------------------------------------------------------- /internal/helper/contain.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import "strings" 4 | 5 | // Contains checks if the slice contains the exact string 6 | func Contains(s []string, str string) bool { 7 | for _, v := range s { 8 | if strings.Contains(str, v) { 9 | return true 10 | } 11 | } 12 | return false 13 | } 14 | -------------------------------------------------------------------------------- /internal/helper/converter.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | func HexaNumberToInteger(hexaString string) string { 10 | // replace 0x or 0X with empty String 11 | numberStr := strings.Replace(hexaString, "0x", "", -1) 12 | numberStr = strings.Replace(numberStr, "0X", "", -1) 13 | return numberStr 14 | } 15 | 16 | func ParsingfromHexaNumberBaseHexaDecimal(intString string) (uint64, error) { 17 | output, err := strconv.ParseUint(intString, 16, 64) 18 | if err != nil { 19 | fmt.Println(err) 20 | return 0, err 21 | } 22 | return output, nil 23 | } 24 | 25 | func ParsingfromHexaNumberBaseDecimal(intString string) (uint64, error) { 26 | output, err := strconv.ParseUint(intString, 10, 64) 27 | if err != nil { 28 | fmt.Println(err) 29 | return 0, err 30 | } 31 | return output, nil 32 | } 33 | -------------------------------------------------------------------------------- /internal/helper/db/retention_period.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | const ( 11 | maxDuration time.Duration = 1<<63 - 1 12 | ) 13 | 14 | var PersistenceMode string = "persistence" 15 | 16 | // Parse retention period 17 | func ParseRetentionPeriod(rawRetentionPeriod string) (time.Duration, error) { 18 | retentionPeriod := strings.TrimSpace(rawRetentionPeriod) // ← 공백 제거 19 | if retentionPeriod == PersistenceMode { 20 | return maxDuration, nil 21 | } 22 | 23 | // Split the value and unit (e.g., "1d" -> "1", "d") 24 | value, unit := retentionPeriod[:len(retentionPeriod)-1], retentionPeriod[len(retentionPeriod)-1:] 25 | 26 | // Parse the numeric part 27 | num, err := strconv.Atoi(value) 28 | if err != nil { 29 | return 0, fmt.Errorf("invalid number in retention period: %s", retentionPeriod) 30 | } 31 | 32 | // Calculate the appropriate duration based on the unit 33 | switch unit { 34 | case "d": // 1 day ~ 7 days 35 | if num < 1 || num > 7 { 36 | return 0, fmt.Errorf("days out of range: %d (valid range: 1d to 7d)", num) 37 | } 38 | return time.Duration(-num) * 24 * time.Hour, nil 39 | case "w": // 1 week ~ 4 weeks 40 | if num < 1 || num > 4 { 41 | return 0, fmt.Errorf("weeks out of range: %d (valid range: 1w to 4w)", num) 42 | } 43 | return time.Duration(-num) * 7 * 24 * time.Hour, nil 44 | case "h": // 1 hour ~ 24 hours 45 | if num < 1 || num > 24 { 46 | return 0, fmt.Errorf("hours out of range: %d (valid range: 1h to 24h)", num) 47 | } 48 | return time.Duration(-num) * time.Hour, nil 49 | case "m": // 1 month ~ 12 months 50 | if num < 1 || num > 12 { 51 | return 0, fmt.Errorf("months out of range: %d (valid range: 1m to 12m)", num) 52 | } 53 | return time.Duration(-num) * 30 * 24 * time.Hour, nil // Assuming 1 month is 30 days 54 | default: 55 | return 0, fmt.Errorf("invalid unit in retention period: %s", retentionPeriod) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /internal/helper/db/table_name.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cosmostation/cvms/internal/helper" 7 | ) 8 | 9 | func MakePartitionTableName(indexName, chainID string) string { 10 | return fmt.Sprintf("public.%s_%s", indexName, helper.ParseToSchemaName(chainID)) 11 | } 12 | 13 | func MakeCreatePartitionTableQuery(indexName, chainID string, chainInfoID int64) string { 14 | return fmt.Sprintf( 15 | `CREATE TABLE IF NOT EXISTS %s PARTITION OF "public"."%s" FOR VALUES IN ('%d');`, 16 | MakePartitionTableName(indexName, chainID), indexName, chainInfoID, 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /internal/helper/grpc/grpc_reflection.go: -------------------------------------------------------------------------------- 1 | package grpchelper 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/golang/protobuf/jsonpb" 8 | "github.com/jhump/protoreflect/desc" 9 | "github.com/jhump/protoreflect/dynamic" 10 | "github.com/jhump/protoreflect/grpcreflect" 11 | ) 12 | 13 | func ResolveMessage(fullMethodName string, rcli *grpcreflect.Client) (*desc.MethodDescriptor, error) { 14 | // assume that fully-qualified method name cosists of 15 | // FULL_SERVER_NAME + "." + METHOD_NAME 16 | // so split the last dot to get service name 17 | n := strings.LastIndex(fullMethodName, ".") 18 | if n < 0 { 19 | return nil, fmt.Errorf("invalid method name: %v", fullMethodName) 20 | } 21 | serviceName := fullMethodName[0:n] 22 | methodName := fullMethodName[n+1:] 23 | 24 | sdesc, err := rcli.ResolveService(serviceName) 25 | if err != nil { 26 | return nil, fmt.Errorf("service couldn't be resolve: %v: %v", err, serviceName) 27 | } 28 | 29 | mdesc := sdesc.FindMethodByName(methodName) 30 | if mdesc == nil { 31 | return nil, fmt.Errorf("method couldn't be found") 32 | } 33 | 34 | return mdesc, nil 35 | } 36 | 37 | func CreateMessage(mdesc *desc.MethodDescriptor, unmarshaler *jsonpb.Unmarshaler, inputJsonString string) (*dynamic.Message, error) { 38 | msg := dynamic.NewMessage(mdesc.GetInputType()) 39 | 40 | if err := msg.UnmarshalJSONPB(unmarshaler, []byte(inputJsonString)); err != nil { 41 | return nil, fmt.Errorf("unmarshal %v", err) 42 | } 43 | return msg, nil 44 | } 45 | -------------------------------------------------------------------------------- /internal/helper/grpc/grpc_resolver.go: -------------------------------------------------------------------------------- 1 | package grpchelper 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | 8 | "github.com/golang/protobuf/jsonpb" 9 | "github.com/golang/protobuf/proto" 10 | "github.com/golang/protobuf/ptypes/empty" 11 | ) 12 | 13 | type DynamicAnyResolver struct { 14 | jsonpb.AnyResolver // https://godoc.org/github.com/golang/protobuf/jsonpb#AnyResolver 15 | } 16 | 17 | // Resolve implements jsonpb.AnyResolver.Resolve 18 | func (DynamicAnyResolver) Resolve(typeURL string) (proto.Message, error) { 19 | msg, err := defaultResolveAny(typeURL) 20 | if err == nil { 21 | return msg, nil 22 | } 23 | return &empty.Empty{}, nil 24 | } 25 | 26 | // copied from https://github.com/golang/protobuf/blob/c823c79ea1570fb5ff454033735a8e68575d1d0f/jsonpb/jsonpb.go#L92-L103 27 | func defaultResolveAny(typeURL string) (proto.Message, error) { 28 | // Only the part of typeUrl after the last slash is relevant. 29 | mname := typeURL 30 | if slash := strings.LastIndex(mname, "/"); slash >= 0 { 31 | mname = mname[slash+1:] 32 | } 33 | mt := proto.MessageType(mname) 34 | if mt == nil { 35 | return nil, fmt.Errorf("unknown message type %q", mname) 36 | } 37 | return reflect.New(mt.Elem()).Interface().(proto.Message), nil 38 | } 39 | -------------------------------------------------------------------------------- /internal/helper/healthcheck/healthcheck_test.go: -------------------------------------------------------------------------------- 1 | package healthcheck 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestHealthCheck(t *testing.T) { 8 | // client := &http.Client{ 9 | // Timeout: 5 * time.Second, 10 | // } 11 | 12 | // for _, endpoint := range endpoints { 13 | // fmt.Printf("Checking RPC URL: %s\n", endpoint.RPCURL) 14 | // checkURL(client, endpoint.RPCURL) 15 | 16 | // fmt.Printf("Checking LCD URL: %s\n", endpoint.LCDURL) 17 | // checkURL(client, endpoint.LCDURL) 18 | // } 19 | } 20 | -------------------------------------------------------------------------------- /internal/helper/logger/logger_test.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | "time" 7 | 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | func TestLoggerPrint(t *testing.T) { 12 | logrus.StandardLogger().Println("test start") 13 | 14 | testLogger, _ := GetLogger("false", "5") 15 | 16 | testEntry := testLogger.WithFields(logrus.Fields{ 17 | "package": "walrus", 18 | "instance_name": 10, 19 | "test": "test", 20 | }) 21 | 22 | testEntry.Infoln("test") 23 | testEntry.Errorln("test") 24 | testEntry.Debugln("test") 25 | testEntry.Warnln("test") 26 | 27 | App := "testApp" 28 | sleep := 15 * time.Second 29 | 30 | testEntry.Infof("[%s] updated metrics successfully and going to sleep %s...", App, sleep.String()) 31 | 32 | } 33 | 34 | func TestSortFunction(t *testing.T) { 35 | testFields := []string{"time", "level", "instance_name", "package", "msg"} 36 | // var fieldSeq = map[string]int{ 37 | // "time": 0, 38 | // "level": 1, 39 | // "instance_name": 2, 40 | // "package": 3, 41 | // "msg": 4, 42 | // } 43 | sortingFunc(testFields) 44 | } 45 | 46 | func TestFileName(t *testing.T) { 47 | testString := "/Users/jeongseup/Workspace/validator-monitoring-service/packages/uptime/collector/collector.go" 48 | 49 | result := strings.SplitAfter(testString, WORKSPACE) 50 | t.Log(result[1]) 51 | } 52 | -------------------------------------------------------------------------------- /internal/helper/recover.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | ) 6 | 7 | type Result struct { 8 | Item interface{} 9 | Success bool 10 | Index int64 11 | } 12 | 13 | func HandleOutOfNilResponse(logger *logrus.Entry) { 14 | if r := recover(); r != nil { 15 | logger.Debugln("Recovering from panic:", r) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /internal/helper/reflect.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "reflect" 7 | "strings" 8 | ) 9 | 10 | // SetFieldByTag sets a struct field by matching a JSON tag, supporting both string and []string types. 11 | func SetFieldByTag(obj interface{}, tag string, value string) { 12 | val := reflect.ValueOf(obj).Elem() 13 | typ := val.Type() 14 | 15 | for i := 0; i < val.NumField(); i++ { 16 | field := val.Field(i) 17 | fieldType := typ.Field(i) 18 | 19 | // Match struct field's tag with the given key 20 | if fieldType.Tag.Get("json") == tag && field.CanSet() { 21 | switch field.Kind() { 22 | case reflect.Slice: 23 | // Try parsing JSON array 24 | var strSlice []string 25 | if err := json.Unmarshal([]byte(value), &strSlice); err == nil { 26 | field.Set(reflect.ValueOf(strSlice)) 27 | } else { 28 | // If not JSON, assume comma-separated values 29 | field.Set(reflect.ValueOf(strings.Split(value, ","))) 30 | } 31 | case reflect.String: 32 | // Directly set string values 33 | field.SetString(value) 34 | default: 35 | log.Printf("Unsupported field type for tag %s", tag) 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /internal/helper/sdk/decode_abci.go: -------------------------------------------------------------------------------- 1 | package sdkhelper 2 | 3 | // import ( 4 | // "regexp" 5 | 6 | // sdk "github.com/cosmos/cosmos-sdk/types" 7 | // "github.com/cosmos/gogoproto/proto" 8 | // ) 9 | 10 | /* 11 | NOTE: this function is decode the data field of ABCI Check/DeliverTx response. 12 | 13 | ref; https://github.com/cosmos/cosmos-sdk/blob/main/baseapp/baseapp.go#L1137 14 | // makeABCIData generates the Data field to be sent to ABCI Check/DeliverTx. 15 | func makeABCIData(msgResponses []*codectypes.Any) ([]byte, error) { 16 | return proto.Marshal(&sdk.TxMsgData{MsgResponses: msgResponses}) 17 | } 18 | */ 19 | // func DecodeABCIData(bz []byte) (sdk.TxMsgData, error) { 20 | // var txMsgData sdk.TxMsgData 21 | // if err := proto.Unmarshal(bz, &txMsgData); err != nil { 22 | // return sdk.TxMsgData{}, err 23 | // } 24 | 25 | // return txMsgData, nil 26 | // } 27 | 28 | // func ExtractMsgTypeInResponse(msg string) string { 29 | // re := regexp.MustCompile(`^(\/[\w.]+)Response$`) 30 | // return re.ReplaceAllString(msg, `$1`) 31 | // } 32 | -------------------------------------------------------------------------------- /internal/helper/sdk/decode_abci_test.go: -------------------------------------------------------------------------------- 1 | package sdkhelper 2 | 3 | // func TestDecodeABCIData(t *testing.T) { 4 | // // Test for one message in the tx 5 | // dataStr1 := "EjAKLi9iYWJ5bG9uLmZpbmFsaXR5LnYxLk1zZ0FkZEZpbmFsaXR5U2lnUmVzcG9uc2U=" 6 | // dataBz, err := base64.StdEncoding.DecodeString(dataStr1) 7 | // assert.NoError(t, err) 8 | // result, err := DecodeABCIData(dataBz) 9 | // assert.NoError(t, err) 10 | // for _, msg := range result.MsgResponses { 11 | // t.Logf("decodedData: %v", msg.TypeUrl) 12 | // } 13 | 14 | // // Test for multiple messages in the tx 15 | // dataStr2 := "EiYKJC9jb3Ntb3MuYmFuay52MWJldGExLk1zZ1NlbmRSZXNwb25zZRImCiQvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kUmVzcG9uc2USJgokL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZFJlc3BvbnNlEiYKJC9jb3Ntb3MuYmFuay52MWJldGExLk1zZ1NlbmRSZXNwb25zZRImCiQvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kUmVzcG9uc2USJgokL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZFJlc3BvbnNlEiYKJC9jb3Ntb3MuYmFuay52MWJldGExLk1zZ1NlbmRSZXNwb25zZRImCiQvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kUmVzcG9uc2USJgokL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZFJlc3BvbnNlEiYKJC9jb3Ntb3MuYmFuay52MWJldGExLk1zZ1NlbmRSZXNwb25zZRImCiQvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kUmVzcG9uc2USJgokL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZFJlc3BvbnNlEiYKJC9jb3Ntb3MuYmFuay52MWJldGExLk1zZ1NlbmRSZXNwb25zZRImCiQvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kUmVzcG9uc2USJgokL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZFJlc3BvbnNlEiYKJC9jb3Ntb3MuYmFuay52MWJldGExLk1zZ1NlbmRSZXNwb25zZRImCiQvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kUmVzcG9uc2USJgokL2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZFJlc3BvbnNlEiYKJC9jb3Ntb3MuYmFuay52MWJldGExLk1zZ1NlbmRSZXNwb25zZQ==" 16 | // dataBz, err = base64.StdEncoding.DecodeString(dataStr2) 17 | // assert.NoError(t, err) 18 | // result, err = DecodeABCIData(dataBz) 19 | // assert.NoError(t, err) 20 | // for _, msg := range result.MsgResponses { 21 | // t.Logf("decodedData: %v", msg.TypeUrl) 22 | // } 23 | // } 24 | 25 | // func TestExtractType(t *testing.T) { 26 | // samples := []string{ 27 | // "/babylon.finality.v1.MsgAddFinalitySigResponse", 28 | // "/cosmos.bank.v1beta1.MsgSendResponse", 29 | // } 30 | 31 | // assert.Equal(t, "/babylon.finality.v1.MsgAddFinalitySig", ExtractMsgTypeInResponse(samples[0])) 32 | // assert.Equal(t, "/cosmos.bank.v1beta1.MsgSend", ExtractMsgTypeInResponse(samples[1])) 33 | // } 34 | -------------------------------------------------------------------------------- /internal/helper/sdk/export_valcons_prefix.go: -------------------------------------------------------------------------------- 1 | package sdkhelper 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | // NOTE: this is not cosmos-sdk native. so, I placed this function into uptime package 9 | // parse valcons prefix with valoper address 10 | func ExportBech32ValconsPrefix(bech32ValoperPrefix string) (string, error) { 11 | split := strings.Split(bech32ValoperPrefix, "1") 12 | 13 | ok := strings.Contains(split[0], "valoper") 14 | if ok { 15 | chainPrefix := strings.Split(split[0], "valoper") 16 | prefix := chainPrefix[0] + "valcons" 17 | return prefix, nil 18 | } else { 19 | switch split[0] { 20 | case "iva": 21 | prefix := "ica" 22 | return prefix, nil 23 | case "crocncl": 24 | prefix := "crocnclcons" 25 | return prefix, nil 26 | 27 | default: 28 | return "", errors.New("TODO") 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /internal/helper/sdk/make_valcons_address.go: -------------------------------------------------------------------------------- 1 | package sdkhelper 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/hex" 6 | ) 7 | 8 | // NOTE: only support ed25519 key type for ICS 9 | func MakeValconsAddressFromPubeky(pubkey string, hrp string) ( 10 | /* valcons address */ string, 11 | /* unexpected err */ error, 12 | ) { 13 | decodedKey, err := base64.StdEncoding.DecodeString(pubkey) 14 | if err != nil { 15 | return "", err 16 | } 17 | hexAddress, err := MakeProposerAddress(Ed25519, decodedKey) 18 | if err != nil { 19 | return "", err 20 | } 21 | bz, err := hex.DecodeString(hexAddress) 22 | if err != nil { 23 | return "", err 24 | } 25 | valconsAddress, err := ConvertAndEncode(hrp, bz) 26 | if err != nil { 27 | return "", err 28 | } 29 | return valconsAddress, nil 30 | } 31 | -------------------------------------------------------------------------------- /internal/helper/url.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "net/url" 5 | "strings" 6 | 7 | "github.com/go-playground/validator/v10" 8 | ) 9 | 10 | func SetHttpURI(rawRPCEndpoint string) string { 11 | return "http://" + rawRPCEndpoint 12 | } 13 | 14 | func UnsetHttpURI(baseURL string) (string, error) { 15 | type URLRequest struct { 16 | URL string `validate:"required,url"` 17 | } 18 | 19 | validate := validator.New() 20 | request := URLRequest{URL: baseURL} 21 | 22 | // validation before parse 23 | err := validate.Struct(request) 24 | if err != nil { 25 | // for _, err := range err.(validator.ValidationErrors) { 26 | // fmt.Println("Validation failed for field:", err.Field()) 27 | // fmt.Println("Condition failed:", err.ActualTag()) 28 | // } 29 | return "", err 30 | } 31 | 32 | url, err := url.Parse(baseURL) 33 | if err != nil { 34 | return "", err 35 | } 36 | 37 | // for grpc 38 | if url.Host == "" { 39 | return url.String(), nil 40 | } 41 | 42 | return url.Host, nil 43 | } 44 | 45 | func MakeBaseURL(port, ipAddress string) string { 46 | baseURL := url.URL{ 47 | Scheme: "http", 48 | Host: strings.Join([]string{ipAddress, ":", port}, ""), 49 | } 50 | return baseURL.String() 51 | } 52 | 53 | func ValidateURL(inputURL string) bool { 54 | _, err := url.ParseRequestURI(inputURL) 55 | return err == nil 56 | } 57 | 58 | func MustExtractHostname(endpoint string) string { 59 | // Parse the URL 60 | parsedURL, _ := url.Parse(endpoint) 61 | return parsedURL.Hostname() 62 | } 63 | -------------------------------------------------------------------------------- /internal/packages/axelar/vald/heartbeats/parser/parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "fmt" 7 | 8 | commontypes "github.com/cosmostation/cvms/internal/common/types" 9 | "github.com/cosmostation/cvms/internal/packages/axelar/vald/heartbeats/types" 10 | ) 11 | 12 | func AxelarProxyResisterParser(resp []byte) (types.AxelarProxyResisterStatus, error) { 13 | var result types.AxelarProxyResisterResponse 14 | if err := json.Unmarshal(resp, &result); err != nil { 15 | return types.AxelarProxyResisterStatus{}, err 16 | } 17 | 18 | decoded, err := base64.StdEncoding.DecodeString(result.Result.Response.Value) 19 | if err != nil { 20 | return types.AxelarProxyResisterStatus{}, err 21 | } 22 | 23 | var proxyResister types.AxelarProxyResisterStatus 24 | err = json.Unmarshal(decoded, &proxyResister) 25 | if err != nil { 26 | return types.AxelarProxyResisterStatus{}, err 27 | } 28 | 29 | return proxyResister, nil 30 | } 31 | 32 | func AxelarHeartbeatsFilterInTx(tx commontypes.CosmosTx, operatorAddr string) (bool, error) { 33 | for _, rawMessage := range tx.Body.Messages { 34 | var msg map[string]json.RawMessage 35 | if err := json.Unmarshal(rawMessage, &msg); err != nil { 36 | return false, fmt.Errorf("json unmashal error: %s", err) 37 | } 38 | 39 | if rawType, ok := msg["@type"]; ok { 40 | var msgType string 41 | if err := json.Unmarshal(rawType, &msgType); err != nil { 42 | return false, fmt.Errorf("json unmashal error: %s", err) 43 | } 44 | 45 | if msgType == "/axelar.reward.v1beta1.RefundMsgRequest" { 46 | if rawInnerMessage, ok := msg["inner_message"]; ok { 47 | var innerMessage map[string]interface{} 48 | if err := json.Unmarshal(rawInnerMessage, &innerMessage); err != nil { 49 | return false, fmt.Errorf("json unmashal error: %s", err) 50 | } 51 | 52 | if innerMessage["@type"] == "/axelar.tss.v1beta1.HeartBeatRequest" { 53 | if innerMessage["sender"] == operatorAddr { 54 | return true, nil 55 | } 56 | } 57 | } 58 | } 59 | } 60 | } 61 | return false, nil 62 | } 63 | -------------------------------------------------------------------------------- /internal/packages/axelar/vald/heartbeats/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/cosmostation/cvms/internal/common" 5 | "github.com/cosmostation/cvms/internal/packages/axelar/vald/heartbeats/api" 6 | "github.com/cosmostation/cvms/internal/packages/axelar/vald/heartbeats/parser" 7 | "github.com/cosmostation/cvms/internal/packages/axelar/vald/heartbeats/types" 8 | ) 9 | 10 | func GetHeartbeats(exporter *common.Exporter, chainName string, latestHeartbeatsHeight int64) (types.CommonAxelarHeartbeats, error) { 11 | var ( 12 | commonProxyResisterQueryPath string 13 | commonProxyResisterParser func(resp []byte) (types.AxelarProxyResisterStatus, error) 14 | ) 15 | 16 | switch chainName { 17 | case "axelar": 18 | commonProxyResisterQueryPath = types.AxelarProxyResisterQueryPath 19 | commonProxyResisterParser = parser.AxelarProxyResisterParser 20 | 21 | return api.GetAxelarHeartbeatsStatus( 22 | exporter, 23 | commonProxyResisterQueryPath, commonProxyResisterParser, 24 | latestHeartbeatsHeight, 25 | ) 26 | 27 | default: 28 | return types.CommonAxelarHeartbeats{}, common.ErrOutOfSwitchCases 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /internal/packages/axelar/vald/heartbeats/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | var ( 4 | SupportedChains = []string{"axelar"} 5 | ) 6 | 7 | const ( 8 | // common 9 | CommonValidatorQueryPath = "/cosmos/staking/v1beta1/validators?status=BOND_STATUS_BONDED&pagination.count_total=true&pagination.limit=500" 10 | 11 | // axelar 12 | AxelarChainMaintainersQueryPath = "/axelar/nexus/v1beta1/chain_maintainers/{chain}" 13 | AxelarProxyResisterQueryPath = `/abci_query?path="/custom/snapshot/proxy/{validator_operator_address}"` 14 | ) 15 | 16 | type CommonAxelarHeartbeats struct { 17 | Validators []BroadcastorStatus 18 | LatestHeartBeatsHeight int64 19 | } 20 | 21 | type BroadcastorStatus struct { 22 | Moniker string `json:"moniker"` 23 | ValidatorOperatorAddress string `json:"validator_operator_address"` 24 | BroadcastorAddress string `json:"broadcastor_address"` 25 | Status string `json:"status"` 26 | LatestHeartBeat float64 `json:"latest_heartbeat"` 27 | } 28 | 29 | type CommonValidatorsQueryResponse struct { 30 | Validators []struct { 31 | OperatorAddress string `json:"operator_address"` 32 | Description struct { 33 | Moniker string `json:"moniker"` 34 | } `json:"description"` 35 | } `json:"validators"` 36 | Pagination struct { 37 | NextKey interface{} `json:"-"` 38 | Total string `json:"-"` 39 | } `json:"-"` 40 | } 41 | 42 | type AxelarProxyResisterResponse struct { 43 | Result struct { 44 | Response struct { 45 | Code int `json:"code"` 46 | Log string `json:"log"` 47 | Info string `json:"info"` 48 | Index string `json:"index"` 49 | Key any `json:"key"` 50 | Value string `json:"value"` 51 | ProofOps any `json:"proofOps"` 52 | Height string `json:"height"` 53 | Codespace string `json:"codespace"` 54 | } `json:"response"` 55 | } `json:"result"` 56 | } 57 | 58 | type AxelarProxyResisterStatus struct { 59 | Address string `json:"address"` 60 | Status string `json:"status"` 61 | } 62 | -------------------------------------------------------------------------------- /internal/packages/babylon/btc-delegations/api.go: -------------------------------------------------------------------------------- 1 | package btcdelegation 2 | 3 | import ( 4 | "github.com/cosmostation/cvms/internal/common" 5 | commonapi "github.com/cosmostation/cvms/internal/common/api" 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | func GetBabylonBTCDelegationsStatus(exporter *common.Exporter) (BabylonBTCDelegationStatus, error) { 10 | // 1. get finality provider infos 11 | delegations, err := commonapi.GetBabylonBTCDelegations(exporter.CommonClient) 12 | if err != nil { 13 | return BabylonBTCDelegationStatus{}, errors.Wrap(err, "failed to get babylon btc delegations") 14 | } 15 | 16 | return delegations, nil 17 | } 18 | -------------------------------------------------------------------------------- /internal/packages/babylon/btc-delegations/api_test.go: -------------------------------------------------------------------------------- 1 | package btcdelegation 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/cosmostation/cvms/internal/common" 8 | "github.com/cosmostation/cvms/internal/testutil" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestGetStatus(t *testing.T) { 14 | _ = testutil.SetupForTest() 15 | testMoniker := os.Getenv("TEST_MONIKER") 16 | testCases := []struct { 17 | testingName string 18 | chainName string 19 | protocolType string 20 | chainID string 21 | isConsumer bool 22 | endpoint common.Endpoints 23 | providerEndpoint common.Endpoints 24 | expectResult float64 25 | }{ 26 | { 27 | testingName: "Babylon Chain", 28 | chainName: "babylon", 29 | protocolType: "cosmos", 30 | isConsumer: false, 31 | endpoint: common.Endpoints{ 32 | RPCs: []string{os.Getenv("TEST_BABYLON_RPC_ENDPOINT")}, 33 | APIs: []string{os.Getenv("TEST_BABYLON_API_ENDPOINT")}, 34 | }, 35 | }, 36 | } 37 | 38 | for _, tc := range testCases { 39 | exporter := testutil.GetTestExporter() 40 | t.Run(tc.testingName, func(t *testing.T) { 41 | if !assert.NotEqualValues(t, tc.endpoint, "") { 42 | // endpoint is empty 43 | t.FailNow() 44 | } 45 | 46 | // setup 47 | exporter.SetRPCEndPoint(tc.endpoint.RPCs[0]) 48 | exporter.SetAPIEndPoint(tc.endpoint.APIs[0]) 49 | exporter.ChainName = tc.chainName 50 | 51 | _ = testMoniker 52 | // // start test 53 | // status, err := GetFinalityProviderUptime(exporter) 54 | // if err != nil { 55 | // t.Fatalf("%s: %s", tc.testingName, err.Error()) 56 | // } 57 | 58 | // for _, fp := range status.FinalityProvidersStatus { 59 | // if fp.Moniker == testMoniker { 60 | // t.Log("fp moniker :", fp.Moniker) 61 | // t.Log("fp operator address :", fp.Address) 62 | // t.Log("fp btc pk :", fp.BTCPK) 63 | // t.Log("fp miss count :", fp.MissedBlockCounter) 64 | // t.Log("fp miss count :", fp.MissedBlockCounter) 65 | // } 66 | // } 67 | }) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /internal/packages/babylon/btc-delegations/types.go: -------------------------------------------------------------------------------- 1 | package btcdelegation 2 | 3 | type BabylonBTCDelegationStatus map[string]int64 4 | -------------------------------------------------------------------------------- /internal/packages/babylon/btc-lightclient/indexer/metrics.go: -------------------------------------------------------------------------------- 1 | package indexer 2 | 3 | import ( 4 | "github.com/cosmostation/cvms/internal/common" 5 | "github.com/cosmostation/cvms/internal/common/api" 6 | "github.com/prometheus/client_golang/prometheus" 7 | ) 8 | 9 | const ( 10 | EventCountMetricName = "event_count" 11 | BTCHeightMetricName = "btc_height" 12 | EventTypeLabel = "event_type" 13 | ) 14 | 15 | func (idx *BTCLightClientIndexer) initMetrics() { 16 | idx.MetricsCountVecMap[EventCountMetricName] = idx.Factory.NewCounterVec(prometheus.CounterOpts{ 17 | Namespace: common.Namespace, 18 | Subsystem: subsystem, 19 | Name: EventCountMetricName, 20 | ConstLabels: idx.PackageLabels, 21 | Help: "Count BTC Light Client events by event type", 22 | }, []string{EventTypeLabel}) 23 | 24 | idx.MetricsMap[BTCHeightMetricName] = idx.Factory.NewGauge(prometheus.GaugeOpts{ 25 | Namespace: common.Namespace, 26 | Subsystem: subsystem, 27 | Name: BTCHeightMetricName, 28 | ConstLabels: idx.PackageLabels, 29 | Help: "The BTC Light Client height", 30 | }) 31 | } 32 | 33 | func (idx *BTCLightClientIndexer) updateRootMetrics(indexPointer int64) { 34 | // update index pointer 35 | common.IndexPointer.With(idx.RootLabels).Set(float64(indexPointer)) 36 | 37 | // update index pointer timestamp 38 | _, timestamp, _, _, _, _, err := api.GetBlock(idx.CommonClient, indexPointer) 39 | if err != nil { 40 | idx.Warnf("failed to update index pointer timestamp metric: %s", err) 41 | return 42 | } 43 | common.IndexPointerTimestamp.With(idx.RootLabels).Set((float64(timestamp.Unix()))) 44 | idx.Debugf("update prometheus metrics %d height", indexPointer) 45 | } 46 | 47 | func (idx *BTCLightClientIndexer) updateIndexerMetrics(forwardCnt, backCnt, lastBTCHeight int64) { 48 | idx.MetricsCountVecMap[EventCountMetricName].With(prometheus.Labels{EventTypeLabel: "BTCRollForward"}).Add(float64(forwardCnt)) 49 | idx.MetricsCountVecMap[EventCountMetricName].With(prometheus.Labels{EventTypeLabel: "BTCRollBack"}).Add(float64(backCnt)) 50 | idx.MetricsMap[BTCHeightMetricName].Set(float64(lastBTCHeight)) 51 | } 52 | -------------------------------------------------------------------------------- /internal/packages/babylon/btc-lightclient/model/model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/uptrace/bun" 7 | ) 8 | 9 | type BabylonBTCRoll struct { 10 | bun.BaseModel `bun:"table:babylon_btc_lightclient"` 11 | ID int64 `bun:"id,pk,autoincrement"` 12 | ChainInfoID int64 `bun:"chain_info_id,pk,notnull"` 13 | Height int64 `bun:"height"` 14 | ReporterID int64 `bun:"reporter_id"` 15 | RollForwardCount int64 `bun:"roll_forward_count"` 16 | RollBackCount int64 `bun:"roll_back_count"` 17 | BTCHeight int64 `bun:"btc_height"` 18 | IsRollBack bool `bun:"is_roll_back"` 19 | BTCHeaders string `bun:"btc_headers"` 20 | } 21 | 22 | func (bbr BabylonBTCRoll) String() string { 23 | return fmt.Sprintf("BabylonBTCRoll<%d | forward: %d, back: %d, BTC height %d>", 24 | bbr.Height, 25 | bbr.RollForwardCount, 26 | bbr.RollBackCount, 27 | bbr.BTCHeight, 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /internal/packages/babylon/checkpoint/model/babylon.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/uptrace/bun" 8 | ) 9 | 10 | type BabylonVoteExtension struct { 11 | bun.BaseModel `bun:"table:babylon_checkpoint"` 12 | ID int64 `bun:"id,pk,autoincrement"` 13 | ChainInfoID int64 `bun:"chain_info_id,pk,notnull"` 14 | Epoch int64 `bun:"epoch,notnull"` 15 | Height int64 `bun:"height,notnull"` 16 | Timestamp time.Time `bun:"timestamp,notnull"` 17 | ValidatorHexAddressID int64 `bun:"validator_hex_address_id,notnull"` 18 | Status int64 `bun:"status,notnull"` 19 | } 20 | 21 | func (bve BabylonVoteExtension) String() string { 22 | return fmt.Sprintf("BabylonExtensionVote<%d %d %d %d %d %d %d>", 23 | bve.ID, 24 | bve.ChainInfoID, 25 | bve.Epoch, 26 | bve.Height, 27 | bve.Timestamp.Unix(), 28 | bve.ValidatorHexAddressID, 29 | bve.Status, 30 | ) 31 | } 32 | 33 | // var BlockIDFlag_value = map[string]int32{ 34 | // "BLOCK_ID_FLAG_UNKNOWN": 0, 35 | // "BLOCK_ID_FLAG_ABSENT": 1, 36 | // "BLOCK_ID_FLAG_COMMIT": 2, 37 | // "BLOCK_ID_FLAG_NIL": 3, 38 | // } 39 | 40 | type TotalBabylonVoteExtensionByMoniker struct { 41 | Moniker string `bun:"moniker"` 42 | UnknownCount int32 `bun:"unknown"` 43 | AbsentCount int32 `bun:"absent"` 44 | CommitCount int32 `bun:"commit"` 45 | NilCount int32 `bun:"nil"` 46 | } 47 | 48 | func (model TotalBabylonVoteExtensionByMoniker) String() string { 49 | return fmt.Sprintf("Current Babylon BLS Vote", 50 | model.Moniker, 51 | model.UnknownCount, 52 | model.AbsentCount, 53 | model.CommitCount, 54 | model.NilCount, 55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /internal/packages/babylon/covenant-committee/model/babylon_covenant_signature.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/uptrace/bun" 8 | ) 9 | 10 | type BabylonCovenantSignature struct { 11 | bun.BaseModel `bun:"table:babylon_covenant_signature"` 12 | ID int64 `bun:"id,pk,autoincrement"` 13 | ChainInfoID int64 `bun:"chain_info_id,pk,notnull"` 14 | Height int64 `bun:"height,notnull"` 15 | CovenantBtcPkID int64 `bun:"covenant_btc_pk_id,notnull"` 16 | BTCStakingTxHash string `bun:"btc_staking_tx_hash,notnull"` 17 | Timestamp time.Time `bun:"timestamp,notnull"` 18 | } 19 | 20 | type BabylonBtcDelegation struct { 21 | bun.BaseModel `bun:"table:babylon_btc_delegation"` 22 | ID int64 `bun:"id,pk,autoincrement"` 23 | ChainInfoID int64 `bun:"chain_info_id,pk,notnull"` 24 | Height int64 `bun:"height,notnull"` 25 | BTCStakingTxHash string `bun:"btc_staking_tx_hash,notnull"` 26 | Timestamp time.Time `bun:"timestamp,notnull"` 27 | } 28 | 29 | func (model BabylonCovenantSignature) String() string { 30 | return fmt.Sprintf("BabylonCovenantSignature(ID=%d, ChainInfoID=%d, Height=%d, CovenantBtcPkID=%d, BTCStakingTxHash=%s, Timestamp=%s)", 31 | model.ID, model.ChainInfoID, model.Height, model.CovenantBtcPkID, model.BTCStakingTxHash, model.Timestamp.Format(time.RFC3339), 32 | ) 33 | } 34 | 35 | func (model BabylonBtcDelegation) String() string { 36 | return fmt.Sprintf("BabylonBtcDelegation(ID=%d, ChainInfoID=%d, Height=%d, BTCStakingTxHash=%s, Timestamp=%s)", 37 | model.ID, model.ChainInfoID, model.Height, model.BTCStakingTxHash, model.Timestamp.Format(time.RFC3339), 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /internal/packages/babylon/covenant-committee/repository/btc_delegation_repository.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/cosmostation/cvms/internal/common" 8 | indexerrepo "github.com/cosmostation/cvms/internal/common/indexer/repository" 9 | dbhelper "github.com/cosmostation/cvms/internal/helper/db" 10 | "github.com/cosmostation/cvms/internal/packages/babylon/covenant-committee/model" 11 | "github.com/uptrace/bun" 12 | ) 13 | 14 | const SubIndexName = "babylon_btc_delegation" 15 | 16 | type BtcDelegationRepository struct { 17 | sqlTimeout time.Duration 18 | *bun.DB 19 | indexerrepo.IMetaRepository 20 | } 21 | 22 | func NewBtcDelegationRepository(indexerDB common.IndexerDB, sqlTimeout time.Duration) BtcDelegationRepository { 23 | // Instantiate the meta repository 24 | metarepo := indexerrepo.NewMetaRepository(indexerDB) 25 | 26 | // Return a repository that implements both IMetaRepository and vote-specific logic 27 | return BtcDelegationRepository{sqlTimeout, indexerDB.DB, metarepo} 28 | } 29 | 30 | func (repo *BtcDelegationRepository) DeleteOldBtcDelegationList(chainID, retentionPeriod string) ( 31 | /* deleted rows */ int64, 32 | /* unexpected error */ error, 33 | ) { 34 | ctx, cancel := context.WithTimeout(context.Background(), repo.sqlTimeout) 35 | defer cancel() 36 | 37 | // Parsing retention period 38 | duration, err := dbhelper.ParseRetentionPeriod(retentionPeriod) 39 | if err != nil { 40 | return 0, err 41 | } 42 | 43 | // Calculate cutoff time duration 44 | cutoffTime := time.Now().Add(duration) 45 | 46 | // Make partition table name 47 | partitionTableName := dbhelper.MakePartitionTableName(IndexName, chainID) 48 | 49 | // Query Execution 50 | res, err := repo.NewDelete(). 51 | Model((*model.BabylonBtcDelegation)(nil)). 52 | ModelTableExpr(partitionTableName). 53 | Where("timestamp < ?", cutoffTime). 54 | Exec(ctx) 55 | if err != nil { 56 | return 0, err 57 | } 58 | 59 | rowsAffected, _ := res.RowsAffected() 60 | return rowsAffected, nil 61 | } 62 | -------------------------------------------------------------------------------- /internal/packages/babylon/finality-provider/api/api_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/cosmostation/cvms/internal/common" 8 | "github.com/cosmostation/cvms/internal/testutil" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestGetStatus(t *testing.T) { 14 | _ = testutil.SetupForTest() 15 | testMoniker := os.Getenv("TEST_MONIKER") 16 | testCases := []struct { 17 | testingName string 18 | chainName string 19 | protocolType string 20 | chainID string 21 | isConsumer bool 22 | endpoint common.Endpoints 23 | providerEndpoint common.Endpoints 24 | expectResult float64 25 | }{ 26 | { 27 | testingName: "Babylon Chain", 28 | chainName: "babylon", 29 | protocolType: "cosmos", 30 | isConsumer: false, 31 | endpoint: common.Endpoints{ 32 | RPCs: []string{os.Getenv("TEST_BABYLON_RPC_ENDPOINT")}, 33 | APIs: []string{os.Getenv("TEST_BABYLON_API_ENDPOINT")}, 34 | }, 35 | }, 36 | } 37 | 38 | for _, tc := range testCases { 39 | exporter := testutil.GetTestExporter() 40 | t.Run(tc.testingName, func(t *testing.T) { 41 | if !assert.NotEqualValues(t, tc.endpoint, "") { 42 | // endpoint is empty 43 | t.FailNow() 44 | } 45 | 46 | // setup 47 | exporter.SetRPCEndPoint(tc.endpoint.RPCs[0]) 48 | exporter.SetAPIEndPoint(tc.endpoint.APIs[0]) 49 | exporter.ChainName = tc.chainName 50 | 51 | // start test 52 | status, err := GetFinalityProviderUptime(exporter) 53 | if err != nil { 54 | t.Fatalf("%s: %s", tc.testingName, err.Error()) 55 | } 56 | 57 | for _, fp := range status.FinalityProvidersStatus { 58 | if fp.Moniker == testMoniker { 59 | t.Log("fp moniker :", fp.Moniker) 60 | t.Log("fp operator address :", fp.Address) 61 | t.Log("fp btc pk :", fp.BTCPK) 62 | t.Log("fp miss count :", fp.MissedBlockCounter) 63 | t.Log("fp miss count :", fp.MissedBlockCounter) 64 | } 65 | } 66 | }) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /internal/packages/babylon/finality-provider/indexer/babylon.go: -------------------------------------------------------------------------------- 1 | package indexer 2 | 3 | type fpVoteMap map[string]int64 4 | 5 | type FinalityVoteSummary struct { 6 | BlockHeight int64 7 | FinalityProviderVotes fpVoteMap 8 | } 9 | -------------------------------------------------------------------------------- /internal/packages/babylon/finality-provider/indexer/metrics.go: -------------------------------------------------------------------------------- 1 | package indexer 2 | 3 | import ( 4 | "github.com/cosmostation/cvms/internal/common" 5 | "github.com/cosmostation/cvms/internal/common/api" 6 | ) 7 | 8 | func (idx *FinalityProviderIndexer) updateRootMetrics(indexPointer int64) { 9 | common.IndexPointer.With(idx.RootLabels).Set(float64(indexPointer)) 10 | _, timestamp, _, _, _, _, err := api.GetBlock(idx.CommonClient, indexPointer) 11 | if err != nil { 12 | idx.Errorf("failed to get block %d: %s", indexPointer, err) 13 | return 14 | } 15 | common.IndexPointerTimestamp.With(idx.RootLabels).Set((float64(timestamp.Unix()))) 16 | idx.Debugf("update prometheus metrics %d epoch", indexPointer) 17 | } 18 | -------------------------------------------------------------------------------- /internal/packages/babylon/finality-provider/model/babylon.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/uptrace/bun" 8 | ) 9 | 10 | type BabylonFinalityProviderVote struct { 11 | bun.BaseModel `bun:"table:babylon_finality_provider"` 12 | ID int64 `bun:"id,pk,autoincrement"` 13 | ChainInfoID int64 `bun:"chain_info_id,pk,notnull"` 14 | Height int64 `bun:"height,notnull"` 15 | FinalityProviderPKID int64 `bun:"finality_provider_pk_id,notnull"` 16 | Status int64 `bun:"status,notnull"` 17 | CreatedTime time.Time `bun:"timestamp,notnull"` 18 | } 19 | 20 | func (bfpv BabylonFinalityProviderVote) String() string { 21 | return fmt.Sprintf("BabylonFinalityProviderVote<%d %d %d %d %d>", 22 | bfpv.ID, 23 | bfpv.ChainInfoID, 24 | bfpv.Height, 25 | bfpv.FinalityProviderPKID, 26 | bfpv.Status, 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /internal/packages/babylon/finality-provider/types/status.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type BabylonFinalityProviderUptimeStatues struct { 4 | MinSignedPerWindow float64 5 | SignedBlocksWindow float64 6 | FinalityProvidersStatus []FinalityProviderUptimeStatus 7 | LastFinalizedBlockInfo 8 | FinalityProviderTotal 9 | } 10 | 11 | type FinalityProviderUptimeStatus struct { 12 | Moniker string 13 | Address string 14 | BTCPK string 15 | MissedBlockCounter float64 16 | Active float64 17 | Status float64 18 | VotingPower float64 19 | } 20 | 21 | type LastFinalizedBlockInfo struct { 22 | MissingVotes float64 23 | MissingVP float64 24 | FinalizedVP float64 25 | BlockHeight float64 26 | } 27 | 28 | type FinalityProviderTotal struct { 29 | Active int 30 | Inactive int 31 | Jailed int 32 | Slashed int 33 | } 34 | -------------------------------------------------------------------------------- /internal/packages/block-data-analytics/collector/api.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "github.com/cosmostation/cvms/internal/common" 5 | commonapi "github.com/cosmostation/cvms/internal/common/api" 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | func getStatus(exporter *common.Exporter) (CosmosBlockParamsStatus, error) { 10 | maxBytes, maxGas, err := commonapi.GetCosmosConsensusParams(exporter.CommonClient) 11 | if err != nil { 12 | return CosmosBlockParamsStatus{}, errors.Wrap(err, "failed to get cosmos consensus param") 13 | } 14 | 15 | return CosmosBlockParamsStatus{MaxBytes: maxBytes, MaxGas: maxGas}, nil 16 | } 17 | -------------------------------------------------------------------------------- /internal/packages/block-data-analytics/collector/types.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | type CosmosBlockParamsStatus struct { 4 | MaxBytes float64 5 | MaxGas float64 6 | } 7 | -------------------------------------------------------------------------------- /internal/packages/block-data-analytics/model/model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/uptrace/bun" 7 | ) 8 | 9 | type BlockDataAnalytics struct { 10 | bun.BaseModel `bun:"table:block_data_analytics"` 11 | 12 | ID int64 `bun:"id,pk,autoincrement"` 13 | ChainInfoID int64 `bun:"chain_info_id,pk,notnull"` 14 | Height int64 `bun:"height,notnull"` 15 | Timestamp time.Time `bun:"timestamp,notnull"` 16 | TotalTxsBytes int64 `bun:"total_txs_bytes"` 17 | TotalGasUsed int64 `bun:"total_gas_used"` 18 | TotalGasWanted int64 `bun:"total_gas_wanted"` 19 | SuccessTxsCount int64 `bun:"success_txs_count"` 20 | FailedTxsCount int64 `bun:"failed_txs_count"` 21 | } 22 | 23 | type BlockMessageAnalytics struct { 24 | bun.BaseModel `bun:"table:block_message_analytics"` 25 | 26 | ID int64 `bun:"id,pk,autoincrement"` 27 | ChainInfoID int64 `bun:"chain_info_id,pk,notnull"` 28 | Height int64 `bun:"height,notnull"` 29 | Timestamp time.Time `bun:"timestamp,notnull"` 30 | MessageTypeID int64 `bun:"message_type_id"` 31 | Success bool `bun:"success"` 32 | } 33 | -------------------------------------------------------------------------------- /internal/packages/consensus/uptime/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/cosmostation/cvms/internal/common" 5 | 6 | "github.com/cosmostation/cvms/internal/packages/consensus/uptime/api" 7 | "github.com/cosmostation/cvms/internal/packages/consensus/uptime/types" 8 | ) 9 | 10 | func GetStatus(exporter *common.Exporter, p common.Packager) (types.CommonUptimeStatus, error) { 11 | switch p.ProtocolType { 12 | case "cosmos": 13 | if p.IsConsumerChain { 14 | return api.GetConsumserUptimeStatus(exporter, p.ChainID) 15 | } 16 | return api.GetUptimeStatus(exporter) 17 | default: 18 | return types.CommonUptimeStatus{}, common.ErrOutOfSwitchCases 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /internal/packages/consensus/uptime/types/types_common.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | var ( 4 | SupportedValconsTypes = []string{"valcons", "ica"} 5 | SupportedProtocolTypes = []string{"cosmos"} 6 | ) 7 | 8 | // common 9 | type CommonUptimeStatus struct { 10 | MinSignedPerWindow float64 `json:"slash_winodw"` 11 | SignedBlocksWindow float64 `json:"vote_period"` 12 | Validators []ValidatorUptimeStatus `json:"validators"` 13 | } 14 | 15 | // cosmos uptime status 16 | type ValidatorUptimeStatus struct { 17 | Moniker string `json:"moniker"` 18 | ProposerAddress string `json:"proposer_address"` 19 | ValidatorOperatorAddress string `json:"validator_operator_address"` 20 | ValidatorConsensusAddress string `json:"validator_consensus_addreess"` 21 | MissedBlockCounter float64 `json:"missed_block_counter"` 22 | VotingPower float64 23 | IsTomstoned float64 24 | // Only Consumer Chain 25 | ConsumerConsensusAddress string `json:"consumer_consensus_address"` 26 | } 27 | -------------------------------------------------------------------------------- /internal/packages/consensus/veindexer/indexer/metrics.go: -------------------------------------------------------------------------------- 1 | package indexer 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/cosmostation/cvms/internal/common" 7 | "github.com/prometheus/client_golang/prometheus" 8 | ) 9 | 10 | const ( 11 | RecentMissCounterMetricName = "recent_miss_counter" 12 | ) 13 | 14 | func (vidx *VEIndexer) initLabelsAndMetrics() { 15 | recentMissCounterMetric := vidx.Factory.NewGaugeVec(prometheus.GaugeOpts{ 16 | Namespace: common.Namespace, 17 | Subsystem: subsystem, 18 | Name: RecentMissCounterMetricName, 19 | ConstLabels: vidx.PackageLabels, 20 | }, []string{ 21 | common.MonikerLabel, 22 | }) 23 | 24 | vidx.MetricsVecMap[RecentMissCounterMetricName] = recentMissCounterMetric 25 | } 26 | 27 | func (vidx *VEIndexer) updateRecentMissCounterMetric() { 28 | rveList, err := vidx.repo.SelectRecentValidatorExtensionVoteList(vidx.ChainID) 29 | if err != nil { 30 | vidx.Errorf("failed to update recent miss counter metric: %s", err) 31 | } 32 | 33 | for _, rve := range rveList { 34 | missCount := (rve.UnknownCount + rve.AbsentCount + rve.NilCount) 35 | vidx.MetricsVecMap[RecentMissCounterMetricName]. 36 | With(prometheus.Labels{common.MonikerLabel: rve.Moniker}). 37 | Set(float64(missCount)) 38 | } 39 | } 40 | 41 | func (idx *VEIndexer) updateRootMetrics(indexPointer int64, indexPointerTimestamp time.Time) { 42 | common.IndexPointer.With(idx.RootLabels).Set(float64(indexPointer)) 43 | common.IndexPointerTimestamp.With(idx.RootLabels).Set((float64(indexPointerTimestamp.Unix()))) 44 | idx.Debugf("update prometheus metrics %d height", indexPointer) 45 | } 46 | -------------------------------------------------------------------------------- /internal/packages/consensus/veindexer/model/cosmos.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/uptrace/bun" 8 | ) 9 | 10 | type ValidatorExtensionVote struct { 11 | bun.BaseModel `bun:"table:veindexer"` 12 | ID int64 `bun:"id,pk,autoincrement"` 13 | ChainInfoID int64 `bun:"chain_info_id,pk,notnull"` 14 | Height int64 `bun:"height,notnull"` 15 | ValidatorHexAddressID int64 `bun:"validator_hex_address_id,notnull"` 16 | Status int64 `bun:"status,notnull"` 17 | VELength int `bun:"vote_extension_length,notnull"` 18 | Timestamp time.Time `bun:"timestamp,notnull"` 19 | } 20 | 21 | func (vev ValidatorExtensionVote) String() string { 22 | return fmt.Sprintf("ValidatorExtensionVote<%d %d %d %d %d %d %d>", 23 | vev.ID, 24 | vev.ChainInfoID, 25 | vev.Height, 26 | vev.ValidatorHexAddressID, 27 | vev.Status, 28 | vev.VELength, 29 | vev.Timestamp.Unix(), 30 | ) 31 | } 32 | 33 | type RecentValidatorExtensionVote struct { 34 | Moniker string `bun:"moniker"` 35 | MaxHeight int64 `bun:"max_height"` 36 | MinHeight int64 `bun:"min_height"` 37 | UnknownCount int64 `bun:"unknown"` 38 | AbsentCount int64 `bun:"absent"` 39 | CommitCount int64 `bun:"commit"` 40 | NilCount int64 `bun:"nil"` 41 | } 42 | -------------------------------------------------------------------------------- /internal/packages/consensus/veindexer/repository/repository_test.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/cosmostation/cvms/internal/testutil" 8 | ) 9 | 10 | func Test_SelectRecentValidatorExtensionVoteList(t *testing.T) { 11 | _ = testutil.SetupForTest() 12 | repo := NewRepository(testutil.TestIndexerDB, 10*time.Second) 13 | list, err := repo.SelectRecentValidatorExtensionVoteList("dydx_mainnet_1") 14 | if err != nil { 15 | t.Logf("unexpeced err: %s", err) 16 | } 17 | t.Log(list) 18 | } 19 | -------------------------------------------------------------------------------- /internal/packages/consensus/voteindexer/indexer/metrics.go: -------------------------------------------------------------------------------- 1 | package indexer 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/cosmostation/cvms/internal/common" 7 | "github.com/prometheus/client_golang/prometheus" 8 | ) 9 | 10 | const ( 11 | RecentMissCounterMetricName = "recent_miss_counter" 12 | ) 13 | 14 | func (vidx *VoteIndexer) initLabelsAndMetrics() { 15 | recentMissCounterMetric := vidx.Factory.NewGaugeVec(prometheus.GaugeOpts{ 16 | Namespace: common.Namespace, 17 | Subsystem: subsystem, 18 | Name: RecentMissCounterMetricName, 19 | ConstLabels: vidx.PackageLabels, 20 | }, []string{ 21 | common.MonikerLabel, 22 | }) 23 | 24 | vidx.MetricsVecMap[RecentMissCounterMetricName] = recentMissCounterMetric 25 | } 26 | 27 | func (vidx *VoteIndexer) updateRecentMissCounterMetric() { 28 | rvvList, err := vidx.repo.SelectRecentMissValidatorVoteList(vidx.ChainID) 29 | if err != nil { 30 | vidx.Errorf("failed to update recent miss counter metric: %s", err) 31 | } 32 | 33 | for _, rvv := range rvvList { 34 | vidx.MetricsVecMap[RecentMissCounterMetricName]. 35 | With(prometheus.Labels{common.MonikerLabel: rvv.Moniker}). 36 | Set(float64(rvv.MissedCount)) 37 | } 38 | } 39 | 40 | func (idx *VoteIndexer) updateRootMetrics(indexPointer int64, indexPointerTimestamp time.Time) { 41 | common.IndexPointer.With(idx.RootLabels).Set(float64(indexPointer)) 42 | common.IndexPointerTimestamp.With(idx.RootLabels).Set((float64(indexPointerTimestamp.Unix()))) 43 | idx.Debugf("update prometheus metrics %d height", indexPointer) 44 | } 45 | -------------------------------------------------------------------------------- /internal/packages/consensus/voteindexer/model/cosmos.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/uptrace/bun" 8 | ) 9 | 10 | // status := 0 is NaN(jailed or inactive) 1 is missed, 2 is voted, 3 is proposed 11 | type ValidatorVote struct { 12 | bun.BaseModel `bun:"table:voteindexer"` 13 | ID int64 `bun:"id,pk,autoincrement"` 14 | ChainInfoID int64 `bun:"chain_info_id,pk,notnull"` 15 | Height int64 `bun:"height,notnull"` 16 | ValidatorHexAddressID int64 `bun:"validator_hex_address_id,notnull"` 17 | Status VoteStatus `bun:"status,notnull"` 18 | Timestamp time.Time `bun:"timestamp,notnull"` 19 | } 20 | 21 | func (vm ValidatorVote) String() string { 22 | return fmt.Sprintf("ValidatorVote<%d %d %d %d %d %d>", 23 | vm.ID, 24 | vm.ChainInfoID, 25 | vm.Height, 26 | vm.ValidatorHexAddressID, 27 | vm.Status, 28 | vm.Timestamp.Unix(), 29 | ) 30 | } 31 | 32 | // Methods 33 | type VoteStatus int 34 | 35 | const ( 36 | Missed VoteStatus = iota + 1 37 | Voted 38 | Proposed 39 | ) 40 | 41 | type RecentValidatorVote struct { 42 | Moniker string `bun:"moniker"` 43 | MaxHeight int64 `bun:"max_height"` 44 | MinHeight int64 `bun:"min_height"` 45 | ProposedCount int64 `bun:"proposed"` 46 | CommitedCount int64 `bun:"commited"` 47 | MissedCount int64 `bun:"missed"` 48 | } 49 | -------------------------------------------------------------------------------- /internal/packages/duty/axelar-evm/parser/parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | 7 | "github.com/cosmostation/cvms/internal/packages/duty/axelar-evm/types" 8 | ) 9 | 10 | // axelar 11 | func AxelarEvmChainsParser(resp []byte) ([]string, error) { 12 | var result types.AxelarEvmChainsResponse 13 | if err := json.Unmarshal(resp, &result); err != nil { 14 | return []string{}, nil 15 | } 16 | return result.Chains, nil 17 | } 18 | 19 | func AxelarChainMaintainersParser(resp []byte) ([]string, error) { 20 | var result types.AxelarChainMaintainersResponse 21 | if err := json.Unmarshal(resp, &result); err != nil { 22 | return []string{}, nil 23 | } 24 | return result.Maintainers, nil 25 | } 26 | 27 | func AxelarProxyResisterParser(resp []byte) (types.AxelarProxyResisterStatus, error) { 28 | var result types.AxelarProxyResisterResponse 29 | if err := json.Unmarshal(resp, &result); err != nil { 30 | return types.AxelarProxyResisterStatus{}, err 31 | } 32 | 33 | decoded, err := base64.StdEncoding.DecodeString(result.Result.Response.Value) 34 | if err != nil { 35 | return types.AxelarProxyResisterStatus{}, err 36 | } 37 | 38 | var proxyResister types.AxelarProxyResisterStatus 39 | err = json.Unmarshal(decoded, &proxyResister) 40 | if err != nil { 41 | return types.AxelarProxyResisterStatus{}, err 42 | } 43 | 44 | return proxyResister, nil 45 | } 46 | -------------------------------------------------------------------------------- /internal/packages/duty/axelar-evm/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/cosmostation/cvms/internal/common" 5 | "github.com/cosmostation/cvms/internal/packages/duty/axelar-evm/api" 6 | "github.com/cosmostation/cvms/internal/packages/duty/axelar-evm/parser" 7 | "github.com/cosmostation/cvms/internal/packages/duty/axelar-evm/types" 8 | ) 9 | 10 | func GetStatus(exporter *common.Exporter, chainName string) (types.CommonAxelarNexus, error) { 11 | var ( 12 | commonEvmChainsQueryPath string 13 | commonEvmChainsParser func(resp []byte) (activatedEvmChains []string, err error) 14 | 15 | commonEvmChainMaintainerQueryPath string 16 | commonChainMaintainersParser func(resp []byte) ([]string, error) 17 | ) 18 | 19 | switch chainName { 20 | case "axelar": 21 | commonEvmChainsQueryPath = types.AxelarEvmChainsQueryPath 22 | commonEvmChainsParser = parser.AxelarEvmChainsParser 23 | 24 | commonEvmChainMaintainerQueryPath = types.AxelarChainMaintainersQueryPath 25 | commonChainMaintainersParser = parser.AxelarChainMaintainersParser 26 | 27 | return api.GetAxelarNexusStatus( 28 | exporter, 29 | commonEvmChainsQueryPath, commonEvmChainsParser, 30 | commonEvmChainMaintainerQueryPath, commonChainMaintainersParser, 31 | ) 32 | 33 | default: 34 | return types.CommonAxelarNexus{}, common.ErrOutOfSwitchCases 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /internal/packages/duty/axelar-evm/router/router_test.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | tests "github.com/cosmostation/cvms/internal/testutil" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestGetStatus(t *testing.T) { 12 | _ = tests.SetupForTest() 13 | testMoniker := os.Getenv("TEST_MONIKER") 14 | TestCases := []struct { 15 | testingName string 16 | chainName string 17 | endpoint string 18 | expectedResult float64 19 | }{ 20 | { 21 | testingName: "Axelar EVM Chains Register Status Testing", 22 | chainName: "axelar", 23 | endpoint: os.Getenv("TEST_AXELAR_ENDPOINT"), 24 | expectedResult: 1, 25 | }, 26 | } 27 | 28 | for _, tc := range TestCases { 29 | client := tests.GetTestExporter() 30 | t.Run(tc.testingName, func(t *testing.T) { 31 | if !assert.NotEqualValues(t, tc.endpoint, "") { 32 | // endpoint is empty 33 | t.FailNow() 34 | } 35 | 36 | // setup 37 | client.SetAPIEndPoint(tc.endpoint) 38 | // get status 39 | status, err := GetStatus(client, tc.chainName) 40 | if err != nil { 41 | t.Fatalf("%s: %s", tc.testingName, err.Error()) 42 | } 43 | 44 | for _, status := range status.Validators { 45 | if status.Moniker == testMoniker { 46 | actual := status.Status 47 | if tc.expectedResult != actual { 48 | t.Logf("Expected %.f does not match actual %.f", tc.expectedResult, actual) 49 | } 50 | t.Logf("Matched status %s is registered now", status.EVMChainName) 51 | } 52 | } 53 | assert.NotEmpty(t, status) 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /internal/packages/duty/eventnonce/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/cosmostation/cvms/internal/common" 5 | "github.com/cosmostation/cvms/internal/packages/duty/eventnonce/api" 6 | "github.com/cosmostation/cvms/internal/packages/duty/eventnonce/parser" 7 | "github.com/cosmostation/cvms/internal/packages/duty/eventnonce/types" 8 | ) 9 | 10 | func GetStatus(c *common.Exporter, chainName string) (types.CommonEventNonceStatus, error) { 11 | var ( 12 | commonOrchestratorPath string 13 | commonOrchestratorParser func(resp []byte) (orchestratorAddress string, err error) 14 | 15 | commonEventNonceQueryPath string 16 | commonEventNonceParser func(resp []byte) (eventNonce float64, err error) 17 | ) 18 | 19 | switch chainName { 20 | case "injective": 21 | commonOrchestratorPath = types.InjectiveOchestratorQueryPath 22 | commonOrchestratorParser = parser.InjectiveOrchestratorParser 23 | 24 | commonEventNonceQueryPath = types.InjectiveEventNonceQueryPath 25 | commonEventNonceParser = parser.InjectiveEventNonceParser 26 | 27 | return api.GetEventNonceStatusByGRPC(c, commonOrchestratorPath, commonOrchestratorParser, commonEventNonceQueryPath, commonEventNonceParser) 28 | 29 | case "gravity-bridge": 30 | commonOrchestratorPath = types.GravityBridgeOrchestarorQueryPath 31 | commonOrchestratorParser = parser.GravityBridgeOrchestratorParser 32 | 33 | commonEventNonceQueryPath = types.GravityBridgeEventNonceQueryPath 34 | commonEventNonceParser = parser.GravityBridgeEventNonceParser 35 | 36 | return api.GetEventNonceStatusByGRPC(c, commonOrchestratorPath, commonOrchestratorParser, commonEventNonceQueryPath, commonEventNonceParser) 37 | 38 | case "sommelier": 39 | commonOrchestratorPath = types.SommelierOrchestratorQueryPath 40 | commonOrchestratorParser = parser.SommelierOrchestratorParser 41 | 42 | commonEventNonceQueryPath = types.SommelierEventNonceQueryPath 43 | commonEventNonceParser = parser.SommelierEventNonceParser 44 | 45 | return api.GetEventNonceStatusByGRPC(c, commonOrchestratorPath, commonOrchestratorParser, commonEventNonceQueryPath, commonEventNonceParser) 46 | 47 | default: 48 | return types.CommonEventNonceStatus{}, common.ErrOutOfSwitchCases 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /internal/packages/duty/eventnonce/router/router_test.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | tests "github.com/cosmostation/cvms/internal/testutil" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestGetStatus(t *testing.T) { 12 | _ = tests.SetupForTest() 13 | testMoniker := os.Getenv("TEST_MONIKER") 14 | TestCases := []struct { 15 | testingName string 16 | chainName string 17 | endpoint string 18 | expectedResult float64 19 | }{ 20 | { 21 | testingName: "InjectiveEventnonceStatusTesting", 22 | chainName: "injective", 23 | endpoint: os.Getenv("TEST_INJECTIVE_GRPC_ENDPOINT"), 24 | expectedResult: 0, 25 | }, 26 | { 27 | testingName: "GravityBridgeEventnoncePackageTesting", 28 | chainName: "gravity-bridge", 29 | endpoint: os.Getenv("TEST_GRAVITY_GRPC_ENDPOINT"), 30 | expectedResult: 0, 31 | }, 32 | { 33 | testingName: "SommelierEventnoncePackageTesting", 34 | chainName: "sommelier", 35 | endpoint: os.Getenv("TEST_SOMMELIER_GRPC_ENDPOINT"), 36 | expectedResult: 0, 37 | }, 38 | } 39 | 40 | for _, tc := range TestCases { 41 | client := tests.GetTestExporter() 42 | t.Run(tc.testingName, func(t *testing.T) { 43 | if !assert.NotEqualValues(t, tc.endpoint, "") { 44 | // endpoint is empty 45 | t.FailNow() 46 | } 47 | 48 | // setup 49 | client.SetGRPCEndPoint(tc.endpoint) 50 | // get status 51 | status, err := GetStatus(client, tc.chainName) 52 | if err != nil { 53 | t.Fatalf("%s: %s", tc.testingName, err.Error()) 54 | } 55 | 56 | for _, validator := range status.Validators { 57 | if validator.Moniker == testMoniker { 58 | t.Log("got our test validator information for this package") 59 | t.Logf("moniker: %s", validator.Moniker) 60 | t.Logf("valoper address: %s", validator.ValidatorOperatorAddress) 61 | t.Logf("orchesrator address: %s", validator.OrchestratorAddress) 62 | t.Logf("eventnonce value: %.f", validator.EventNonce) 63 | 64 | actual := (status.HeighestNonce - validator.EventNonce) 65 | assert.Equalf(t, 66 | tc.expectedResult, 67 | actual, 68 | "Expected %.f does not match actual %.f", tc.expectedResult, actual, 69 | ) 70 | } 71 | } 72 | }) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /internal/packages/duty/oracle/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/cosmostation/cvms/internal/common" 5 | "github.com/cosmostation/cvms/internal/packages/duty/oracle/api" 6 | "github.com/cosmostation/cvms/internal/packages/duty/oracle/parser" 7 | "github.com/cosmostation/cvms/internal/packages/duty/oracle/types" 8 | ) 9 | 10 | func GetStatus(client *common.Exporter, chainName string) (types.CommonOracleStatus, error) { 11 | var ( 12 | commonOracleQueryPath string 13 | commonOracleParser func(resp []byte) (missCount uint64, err error) 14 | commonOracleParamsQueryPath string 15 | commonOracleParamsParser func(resp []byte) (slashWindow, votePeriod, minValidPerWindow, voteWindow float64, err error) 16 | ) 17 | 18 | switch chainName { 19 | case "umee": 20 | commonOracleQueryPath = types.UmeeOracleQueryPath 21 | commonOracleParser = parser.UmeeOracleParser 22 | 23 | commonOracleParamsQueryPath = types.UmeeOracleParamsQueryPath 24 | commonOracleParamsParser = parser.UmeeOracleParamParser 25 | 26 | return api.GetOracleStatus(client, commonOracleQueryPath, commonOracleParser, commonOracleParamsQueryPath, commonOracleParamsParser) 27 | 28 | case "sei": 29 | commonOracleQueryPath = types.SeiOracleQueryPath 30 | commonOracleParser = parser.SeiOracleParser 31 | 32 | commonOracleParamsQueryPath = types.SeiOracleParamsQueryPath 33 | commonOracleParamsParser = parser.SeiOracleParamParser 34 | 35 | return api.GetOracleStatus(client, commonOracleQueryPath, commonOracleParser, commonOracleParamsQueryPath, commonOracleParamsParser) 36 | 37 | case "nibiru": 38 | commonOracleQueryPath = types.NibiruOracleQueryPath 39 | commonOracleParser = parser.NibiruOracleParser 40 | 41 | commonOracleParamsQueryPath = types.NibiruOracleParamsQueryPath 42 | commonOracleParamsParser = parser.NibiruOracleParamParser 43 | 44 | return api.GetOracleStatus(client, commonOracleQueryPath, commonOracleParser, commonOracleParamsQueryPath, commonOracleParamsParser) 45 | 46 | default: 47 | return types.CommonOracleStatus{}, common.ErrOutOfSwitchCases 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /internal/packages/duty/oracle/router/router_test.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | tests "github.com/cosmostation/cvms/internal/testutil" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestRequestMissCounter(t *testing.T) { 12 | _ = tests.SetupForTest() 13 | testMoniker := os.Getenv("TEST_MONIKER") 14 | TestCases := []struct { 15 | testingName string 16 | chainName string 17 | endpoint string 18 | }{ 19 | { 20 | testingName: "Umee Oracle Status Check", 21 | chainName: "umee", 22 | endpoint: os.Getenv("TEST_UMEE_ENDPOINT"), 23 | }, 24 | { 25 | testingName: "Sei Oracle Status Check", 26 | chainName: "sei", 27 | endpoint: os.Getenv("TEST_SEI_ENDPOINT"), 28 | }, 29 | { 30 | testingName: "Nibiru Oracle Status Check", 31 | chainName: "nibiru", 32 | endpoint: os.Getenv("TEST_NIBIRU_ENDPOINT"), 33 | }, 34 | } 35 | 36 | for _, tc := range TestCases { 37 | exporter := tests.GetTestExporter() 38 | t.Run(tc.testingName, func(t *testing.T) { 39 | if !assert.NotEqualValues(t, tc.endpoint, "") { 40 | // endpoint is empty 41 | t.FailNow() 42 | } 43 | // setup 44 | exporter.SetAPIEndPoint(tc.endpoint) 45 | // get status 46 | status, err := GetStatus(exporter, tc.chainName) 47 | if err != nil { 48 | t.Fatalf("%s: %s", tc.testingName, err.Error()) 49 | } 50 | 51 | // assert.NotNil(t, status) 52 | for _, item := range status.Validators { 53 | if item.Moniker == testMoniker { 54 | t.Log("got our test validator information for oracle package") 55 | t.Logf("valoper address: %s", item.ValidatorOperatorAddress) 56 | t.Logf("miss_counter: %d", item.MissCounter) 57 | } 58 | } 59 | t.Logf("block height: %.2f", status.BlockHeight) 60 | t.Logf("min_valid_per_window: %.2f", status.MinimumValidPerWindow) 61 | t.Logf("slash_window: %.f", status.SlashWindow) 62 | t.Logf("vote_period: %.f", status.VotePeriod) 63 | t.Logf("vote_window: %.f", status.VoteWindow) 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /internal/packages/duty/yoda/processor/processor.go: -------------------------------------------------------------------------------- 1 | package processor 2 | 3 | import ( 4 | "github.com/cosmostation/cvms/internal/packages/duty/yoda/types" 5 | ) 6 | 7 | func ProcessYodaMisses(oldValidatorStatus []types.ValidatorStatus, newValidatorStatus []types.ValidatorStatus) []types.MissedRequests { 8 | // This function compares two slices of validator statuses to find the differences between the old and new validator statuses. 9 | // The difference is a list of finished requests per validator. 10 | // By only counting request misses which are no longer part of newValidatorStatus 11 | // current slice double counting is prevented 12 | 13 | var reqsFinished []types.MissedRequests 14 | 15 | newRequestsMap := make(map[string]map[int64]struct{}) 16 | for _, newVal := range newValidatorStatus { 17 | reqIDs := make(map[int64]struct{}) 18 | for _, req := range newVal.Requests { 19 | reqIDs[req.RequestID] = struct{}{} 20 | } 21 | newRequestsMap[newVal.ValidatorOperatorAddress] = reqIDs 22 | } 23 | 24 | // Iterate through old validators and compare 25 | for _, oldVal := range oldValidatorStatus { 26 | if newReqIDs, found := newRequestsMap[oldVal.ValidatorOperatorAddress]; found { 27 | for _, oldReq := range oldVal.Requests { 28 | if _, exists := newReqIDs[oldReq.RequestID]; !exists && oldReq.RequestID > 0 { 29 | // Record the miss 30 | reqsFinished = append(reqsFinished, types.MissedRequests{Validator: oldVal, Request: oldReq}) 31 | } 32 | } 33 | } 34 | } 35 | 36 | return reqsFinished // Placeholder return statement 37 | } 38 | -------------------------------------------------------------------------------- /internal/packages/duty/yoda/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/cosmostation/cvms/internal/common" 5 | "github.com/cosmostation/cvms/internal/packages/duty/yoda/api" 6 | "github.com/cosmostation/cvms/internal/packages/duty/yoda/parser" 7 | "github.com/cosmostation/cvms/internal/packages/duty/yoda/types" 8 | ) 9 | 10 | func GetStatus(client *common.Exporter, chainName string) (types.CommonYodaStatus, error) { 11 | var ( 12 | commonYodaQueryPath string 13 | commonYodaParser func(resp []byte) (isActive float64, err error) 14 | commonYodaParamsPath string 15 | commonYodaParamsParser func(resp []byte) (slashingWindow float64, err error) 16 | commonYodaRequestCountsPath string 17 | commonYodaRequestCountsParser func(resp []byte) (requestCount float64, err error) 18 | commonYodaRequestPath string 19 | commonYodaRequestParser func(resp []byte) (requestBlock int64, validatorsFailedToRespond []string, status string, err error) 20 | ) 21 | 22 | switch chainName { 23 | case "band": 24 | commonYodaQueryPath = types.BandYodaQueryPath 25 | commonYodaParser = parser.BandYodaParser 26 | commonYodaParamsPath = types.BandYodaParamsPath 27 | commonYodaParamsParser = parser.BandYodaParamsParser 28 | commonYodaRequestCountsPath = types.BandYodaRequestCountsPath 29 | commonYodaRequestCountsParser = parser.BandYodaRequestCountParser 30 | commonYodaRequestPath = types.BandYodaRequestsPath 31 | commonYodaRequestParser = parser.BandYodaRequestParser 32 | 33 | return api.GetYodaStatus( 34 | client, 35 | commonYodaQueryPath, 36 | commonYodaParser, 37 | commonYodaParamsPath, 38 | commonYodaParamsParser, 39 | commonYodaRequestCountsPath, 40 | commonYodaRequestCountsParser, 41 | commonYodaRequestPath, 42 | commonYodaRequestParser, 43 | ) 44 | default: 45 | return types.CommonYodaStatus{}, common.ErrOutOfSwitchCases 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /internal/packages/duty/yoda/router/router_test.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | tests "github.com/cosmostation/cvms/internal/testutil" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestYodaStatus(t *testing.T) { 12 | 13 | _ = tests.SetupForTest() 14 | testMoniker := os.Getenv("TEST_MONIKER") 15 | TestCases := []struct { 16 | testingName string 17 | chainName string 18 | endpoint string 19 | expectedResult float64 20 | }{ 21 | { 22 | testingName: "Band Yoda Status Check", 23 | chainName: "band", 24 | endpoint: os.Getenv("TEST_BAND_ENDPOINT"), 25 | expectedResult: 1, 26 | }, 27 | } 28 | for _, tc := range TestCases { 29 | exporter := tests.GetTestExporter() 30 | t.Run(tc.testingName, func(t *testing.T) { 31 | if !assert.NotEqualValues(t, tc.endpoint, "") { 32 | // endpoint is empty 33 | t.FailNow() 34 | } 35 | 36 | // setup 37 | exporter.SetAPIEndPoint(tc.endpoint) 38 | // get status 39 | status, err := GetStatus(exporter, tc.chainName) 40 | if err != nil { 41 | t.Fatalf("%s: %s", tc.testingName, err.Error()) 42 | } 43 | assert.NotEmpty(t, status) 44 | for _, validator := range status.Validators { 45 | if validator.Moniker == testMoniker { 46 | actual := validator.IsActive 47 | assert.Equalf(t, 48 | tc.expectedResult, 49 | actual, 50 | "Expected %.f does not match actual %.f", tc.expectedResult, actual, 51 | ) 52 | } 53 | } 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /internal/packages/health/block/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/cosmostation/cvms/internal/common" 8 | "github.com/cosmostation/cvms/internal/packages/health/block/types" 9 | 10 | "github.com/go-resty/resty/v2" 11 | ) 12 | 13 | func GetBlockStatus( 14 | c *common.Exporter, 15 | CommonBlockCallClient common.ClientType, 16 | CommonBlockCallMethod common.Method, CommonBlockQueryPath string, CommonBlockPayload string, 17 | CommonBlockParser func([]byte) (float64, float64, error), 18 | ) (types.CommonBlock, error) { 19 | // init context 20 | ctx := context.Background() 21 | ctx, cancel := context.WithTimeout(ctx, common.Timeout) 22 | defer cancel() 23 | 24 | // create requester 25 | var requester *resty.Request 26 | if CommonBlockCallClient == common.RPC { 27 | requester = c.RPCClient.R().SetContext(ctx) 28 | } else { 29 | requester = c.APIClient.R().SetContext(ctx) 30 | } 31 | 32 | var err error 33 | var resp *resty.Response 34 | 35 | if CommonBlockCallMethod == common.GET { 36 | resp, err = requester. 37 | SetHeader("Content-Type", "application/json"). 38 | SetBody(CommonBlockPayload). 39 | Get(CommonBlockQueryPath) 40 | } else if CommonBlockCallMethod == common.POST { 41 | resp, err = requester. 42 | SetHeader("Content-Type", "application/json"). 43 | SetBody(CommonBlockPayload). 44 | Post(CommonBlockQueryPath) 45 | } else { 46 | return types.CommonBlock{}, common.ErrUnSupportedMethod 47 | } 48 | 49 | if err != nil { 50 | c.Errorf("api error: %s", err) 51 | return types.CommonBlock{}, common.ErrFailedHttpRequest 52 | } 53 | 54 | if resp.StatusCode() != http.StatusOK { 55 | c.Errorf("request error: [%d] %s", resp.StatusCode(), err) 56 | return types.CommonBlock{}, common.ErrGotStrangeStatusCode 57 | } 58 | 59 | blockHeight, blockTimeStamp, err := CommonBlockParser(resp.Body()) 60 | if err != nil { 61 | c.Errorf("parser error: %s", err) 62 | return types.CommonBlock{}, common.ErrFailedJsonUnmarshal 63 | } 64 | 65 | c.Debugf("got block timestamp: %d", int(blockTimeStamp)) 66 | return types.CommonBlock{ 67 | LastBlockHeight: blockHeight, 68 | LastBlockTimeStamp: blockTimeStamp, 69 | }, nil 70 | } 71 | -------------------------------------------------------------------------------- /internal/packages/health/block/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/cosmostation/cvms/internal/common" 5 | "github.com/cosmostation/cvms/internal/packages/health/block/api" 6 | "github.com/cosmostation/cvms/internal/packages/health/block/parser" 7 | "github.com/cosmostation/cvms/internal/packages/health/block/types" 8 | ) 9 | 10 | func GetStatus(client *common.Exporter, protocolType string) (types.CommonBlock, error) { 11 | var ( 12 | CommonBlockCallClient common.ClientType 13 | CommonBlockCallMethod common.Method 14 | CommonBlockQueryPath string 15 | CommonBlockPayload string 16 | CommonBlockParser func(resp []byte) (blockHeight, timeStamp float64, err error) 17 | ) 18 | 19 | switch protocolType { 20 | case "cosmos": 21 | CommonBlockCallClient = common.RPC 22 | CommonBlockCallMethod = common.GET 23 | CommonBlockQueryPath = types.CosmosBlockQueryPath 24 | CommonBlockPayload = types.CosmosBlockQueryPayload 25 | CommonBlockParser = parser.CosmosBlockParser 26 | 27 | return api.GetBlockStatus(client, CommonBlockCallClient, CommonBlockCallMethod, CommonBlockQueryPath, CommonBlockPayload, CommonBlockParser) 28 | 29 | case "ethereum": 30 | CommonBlockCallClient = common.RPC 31 | CommonBlockCallMethod = common.POST 32 | CommonBlockQueryPath = types.EthereumBlockQueryPath 33 | CommonBlockPayload = types.EthereumBlockQueryPayLoad 34 | CommonBlockParser = parser.EthereumBlockParser 35 | 36 | return api.GetBlockStatus(client, CommonBlockCallClient, CommonBlockCallMethod, CommonBlockQueryPath, CommonBlockPayload, CommonBlockParser) 37 | 38 | case "celestia": 39 | CommonBlockCallClient = common.RPC 40 | CommonBlockCallMethod = common.POST 41 | CommonBlockQueryPath = types.CelestiaBlockQueryPath 42 | CommonBlockPayload = types.CelestiaBlockQueryPayLoad 43 | CommonBlockParser = parser.CelestiaBlockParser 44 | 45 | return api.GetBlockStatus(client, CommonBlockCallClient, CommonBlockCallMethod, CommonBlockQueryPath, CommonBlockPayload, CommonBlockParser) 46 | 47 | default: 48 | return types.CommonBlock{}, common.ErrOutOfSwitchCases 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /internal/packages/health/block/router/router_test.go: -------------------------------------------------------------------------------- 1 | package router_test 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | "time" 7 | 8 | "github.com/cosmostation/cvms/internal/helper" 9 | "github.com/cosmostation/cvms/internal/packages/health/block/router" 10 | tests "github.com/cosmostation/cvms/internal/testutil" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestRequestBlockTimeStamp(t *testing.T) { 15 | _ = tests.SetupForTest() 16 | 17 | type testCase struct { 18 | testingName string 19 | chainType string 20 | hostAddress string 21 | } 22 | 23 | testCases := []testCase{ 24 | { 25 | testingName: "CosomsChainType", 26 | chainType: "cosmos", 27 | hostAddress: os.Getenv("TEST_COSMOS_HOST_ADDRESS"), 28 | }, 29 | { 30 | testingName: "EthereumChainType", 31 | chainType: "ethereum", 32 | hostAddress: os.Getenv("TEST_ETHEREUM_HOST_ADDRESS"), 33 | }, 34 | { 35 | testingName: "Celestia Block Package Testing", 36 | chainType: "celestia", 37 | hostAddress: os.Getenv("TEST_CELESTIA_HOST_ADDRESS"), 38 | }, 39 | { 40 | testingName: "Bera Block Package Testing", 41 | chainType: "ethereum", 42 | hostAddress: os.Getenv("TEST_BERA_HOST_ADDRESS"), 43 | }, 44 | { 45 | testingName: "Namada Block Package Testing", 46 | chainType: "cosmos", 47 | hostAddress: os.Getenv("TEST_NAMADA_HOST_ADDRESS"), 48 | }, 49 | } 50 | 51 | for _, tc := range testCases { 52 | timestamp := uint64(time.Now().Unix()) 53 | exporter := tests.GetTestExporter() 54 | 55 | t.Run(tc.testingName, func(t *testing.T) { 56 | if tc.hostAddress == "" { 57 | t.Logf("%s has empty base URL. check .env.test file", tc.testingName) 58 | t.SkipNow() 59 | } 60 | 61 | if !helper.ValidateURL(tc.hostAddress) { 62 | t.Logf("%s is unvalidate endpoint for block package", tc.testingName) 63 | t.SkipNow() 64 | } 65 | 66 | exporter.SetRPCEndPoint(tc.hostAddress) 67 | exporter.SetAPIEndPoint(tc.hostAddress) 68 | status, err := router.GetStatus(exporter, tc.chainType) 69 | if err != nil { 70 | t.Logf("%s: %s", tc.testingName, err.Error()) 71 | t.FailNow() 72 | } 73 | 74 | t.Logf("latest block height: %d", int(status.LastBlockHeight)) 75 | t.Logf("latest block timestamp: %d", int(status.LastBlockTimeStamp)) 76 | 77 | timeInterval := int(float64(timestamp) - status.LastBlockTimeStamp) 78 | assert.LessOrEqualf(t, timeInterval, 300, "error message: %v", timeInterval) 79 | }) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /internal/packages/health/block/types/celestia.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "time" 4 | 5 | // ref; https://node-rpc-docs.celestia.org/?version=v0.17.1#header.LocalHead 6 | const ( 7 | CelestiaBlockQueryPath = "" 8 | CelestiaBlockQueryPayLoad = `{ 9 | "jsonrpc":"2.0", 10 | "id":1, 11 | "method":"header.LocalHead", 12 | "params":[] 13 | }` 14 | ) 15 | 16 | type CelestiaBlockResponse struct { 17 | JsonRPC string `json:"jsonrpc"` 18 | ID int `json:"id"` 19 | Result struct { 20 | Header struct { 21 | Version interface{} `json:"-"` 22 | ChainID string `json:"chain_id"` 23 | Height string `json:"height"` 24 | Time time.Time `json:"time"` 25 | LastBlockID interface{} `json:"-"` 26 | LastCommitHash string `json:"last_commit_hash"` 27 | DataHash string `json:"data_hash"` 28 | ValidatorsHash string `json:"validators_hash"` 29 | NextValidatorsHash string `json:"next_validators_hash"` 30 | ConsensusHash string `json:"consensus_hash"` 31 | AppHash string `json:"app_hash"` 32 | LastResultsHash string `json:"last_results_hash"` 33 | EvidenceHash string `json:"evidence_hash"` 34 | ProposerAddress string `json:"proposer_address"` 35 | } `json:"header"` 36 | ValidatorSet interface{} `json:"-"` 37 | Commit interface{} `json:"-"` 38 | Dah interface{} `json:"-"` 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/packages/health/block/types/common.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // NOTE: SupportedChainTypes = []string{"cosmos", "ethereum", "aptos", "sui", "avalanche", "celestia", "polkadot", "aleo"} 4 | var ( 5 | SupportedChainTypes = []string{"cosmos", "ethereum", "celestia"} 6 | ) 7 | 8 | type CommonBlock struct { 9 | LastBlockHeight float64 10 | LastBlockTimeStamp float64 11 | } 12 | -------------------------------------------------------------------------------- /internal/packages/health/block/types/cosmos.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "time" 4 | 5 | const ( 6 | CosmosBlockQueryPath = "/status" 7 | CosmosBlockQueryPayload = "" 8 | ) 9 | 10 | type CosmosV34BlockResponse struct { 11 | JsonRPC string `json:"jsonrpc" validate:"required"` 12 | ID int `json:"id" validate:"required"` 13 | Result struct { 14 | NodeInfo map[string]any `json:"node_info"` 15 | SyncInfo struct { 16 | LatestBlockHash string `json:"latest_block_hash"` 17 | LatestAppHash string `json:"latest_app_hash"` 18 | LatestBlockHeight string `json:"latest_block_height"` 19 | LatestBlockTime time.Time `json:"latest_block_time"` 20 | EarliestBlockHash string `json:"earliest_block_hash"` 21 | EarliestAppHash string `json:"earliest_app_hash"` 22 | EarliestBlcokHeight string `json:"earliest_block_height"` 23 | EarliestBlockTime time.Time `json:"earliest_block_time"` 24 | CatchingUp bool `json:"catching_up"` 25 | } `json:"sync_info"` 26 | ValidatorInfo map[string]any `json:"validator_info"` 27 | } `json:"result" validate:"required"` 28 | } 29 | 30 | type CosmosV37BlockResponse struct { 31 | NodeInfo map[string]any `json:"node_info"` 32 | SyncInfo struct { 33 | LatestBlockHash string `json:"latest_block_hash"` 34 | LatestAppHash string `json:"latest_app_hash"` 35 | LatestBlockHeight string `json:"latest_block_height"` 36 | LatestBlockTime time.Time `json:"latest_block_time"` 37 | EarliestBlockHash string `json:"earliest_block_hash"` 38 | EarliestAppHash string `json:"earliest_app_hash"` 39 | EarliestBlcokHeight string `json:"earliest_block_height"` 40 | EarliestBlockTime time.Time `json:"earliest_block_time"` 41 | CatchingUp bool `json:"catching_up"` 42 | } `json:"sync_info" validate:"required"` 43 | ValidatorInfo map[string]any `json:"validator_info"` 44 | } 45 | -------------------------------------------------------------------------------- /internal/packages/health/block/types/ethereum.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const ( 4 | EthereumBlockQueryPath = "" 5 | EthereumBlockQueryPayLoad = `{ 6 | "jsonrpc":"2.0", 7 | "id":1, 8 | "method":"eth_getBlockByNumber", 9 | "params":["latest",false] 10 | }` 11 | ) 12 | 13 | type EthereumBlockResponse struct { 14 | JsonRPC string `json:"jsonrpc"` 15 | ID int `json:"id"` 16 | Result struct { 17 | BaseFeePerGas string `json:"baseFeePerGas"` 18 | Difficulty string `json:"difficulty"` 19 | ExtraData string `json:"extraData"` 20 | GasLimit string `json:"gasLimit"` 21 | GasUsed string `json:"gasUsed"` 22 | Hash string `json:"hash"` 23 | LogsBloom string `json:"logsBloom"` 24 | Miner string `json:"miner"` 25 | MinHash string `json:"mixHash"` 26 | Nonce string `json:"nonce"` 27 | Number string `json:"number"` 28 | ParentHash string `json:"parentHash"` 29 | ReceiptsRoot string `json:"receiptsRoot"` 30 | Sha3Uncles string `json:"sha3Uncles"` 31 | Size string `json:"size"` 32 | StateRoot string `json:"stateRoot"` 33 | TimeStamp string `json:"timestamp"` 34 | TotalDifficulty string `json:"totalDifficulty"` 35 | Transactions []string `json:"transactions"` 36 | TransactionRoot string `json:"transactionsRoot"` 37 | Uncles []string `json:"uncles"` 38 | } `json:"result"` 39 | } 40 | -------------------------------------------------------------------------------- /internal/packages/utility/balance/errors/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | // ErrBalanceNotFound is returned when the requested balance is not found. 9 | ErrBalanceNotFound = errors.New("balance not found for denom") 10 | ) 11 | -------------------------------------------------------------------------------- /internal/packages/utility/balance/parser/parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strconv" 7 | 8 | "github.com/cosmostation/cvms/internal/helper" 9 | balanceErrors "github.com/cosmostation/cvms/internal/packages/utility/balance/errors" 10 | "github.com/cosmostation/cvms/internal/packages/utility/balance/types" 11 | ) 12 | 13 | // cosmos balance parser 14 | func CosmosBalanceParser(resp []byte, denom string) (float64, error) { 15 | var result types.CosmosBalanceResponse 16 | if err := json.Unmarshal(resp, &result); err != nil { 17 | return 0, fmt.Errorf("parsing error: %s", err.Error()) 18 | } 19 | 20 | var stringBalance string 21 | for _, balance := range result.Balances { 22 | if balance.Denom == denom { 23 | stringBalance = balance.Amount 24 | break 25 | } 26 | } 27 | 28 | if stringBalance == "" { 29 | return 0, balanceErrors.ErrBalanceNotFound 30 | } 31 | 32 | balance, err := strconv.ParseFloat(stringBalance, 64) 33 | if err != nil { 34 | return 0, fmt.Errorf("converting error: %s", err.Error()) 35 | } 36 | 37 | return balance, nil 38 | } 39 | 40 | // ethereum balance parser 41 | func EthereumBalanceParser(resp []byte, denom string) (float64, error) { 42 | var result types.EthereumBalanceResponse 43 | if err := json.Unmarshal(resp, &result); err != nil { 44 | return 0, fmt.Errorf("parsing error: %s", err.Error()) 45 | } 46 | 47 | balance, err := helper.ParsingfromHexaNumberBaseHexaDecimal(helper.HexaNumberToInteger(result.Result)) 48 | if err != nil { 49 | return 0, fmt.Errorf("converting error: %s", err.Error()) 50 | } 51 | 52 | return float64(balance), nil 53 | } 54 | -------------------------------------------------------------------------------- /internal/packages/utility/balance/parser/parser_test.go: -------------------------------------------------------------------------------- 1 | package parser_test 2 | 3 | import ( 4 | "testing" 5 | 6 | balanceErrors "github.com/cosmostation/cvms/internal/packages/utility/balance/errors" 7 | parser "github.com/cosmostation/cvms/internal/packages/utility/balance/parser" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestCosmosBalanceParsing(t *testing.T) { 12 | 13 | resp := []byte(`{ 14 | "balances": [ 15 | { 16 | "denom": "factory/neutronxyzasdf/Governance", 17 | "amount": "123" 18 | }, 19 | { 20 | "denom": "ibc/bla", 21 | "amount": "120" 22 | }, 23 | { 24 | "denom": "untrn", 25 | "amount": "10000" 26 | } 27 | ], 28 | "pagination": { 29 | "next_key": null, 30 | "total": "3" 31 | } 32 | }`) 33 | 34 | rep_no_balance := []byte(`{ 35 | "balances": [], 36 | "pagination": { 37 | "next_key": null, 38 | "total": "0" 39 | } 40 | }`) 41 | 42 | balance_untrn, err := parser.CosmosBalanceParser(resp, "untrn") 43 | assert.Nil(t, err) 44 | balance_unknown, err := parser.CosmosBalanceParser(resp, "asdf") 45 | assert.ErrorIs(t, balanceErrors.ErrBalanceNotFound, err) 46 | balance_unknown_no_balance, err := parser.CosmosBalanceParser(rep_no_balance, "asdf") 47 | assert.ErrorIs(t, balanceErrors.ErrBalanceNotFound, err) 48 | 49 | assert.Equal(t, float64(10000), balance_untrn) 50 | assert.Equal(t, float64(0), balance_unknown) 51 | assert.Equal(t, float64(0), balance_unknown_no_balance) 52 | 53 | } 54 | -------------------------------------------------------------------------------- /internal/packages/utility/balance/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/cosmostation/cvms/internal/common" 5 | "github.com/cosmostation/cvms/internal/packages/utility/balance/api" 6 | "github.com/cosmostation/cvms/internal/packages/utility/balance/parser" 7 | "github.com/cosmostation/cvms/internal/packages/utility/balance/types" 8 | ) 9 | 10 | func GetCommonHealthChecker() { 11 | 12 | } 13 | 14 | func GetStatus(client *common.Exporter, p common.Packager) (types.CommonBalance, error) { 15 | var ( 16 | CommonBalanceCallMethod common.Method 17 | CommonBalanceQueryPath string 18 | CommonBalancePayload string 19 | CommonBalanceParser func(resp []byte, denom string) (float64, error) 20 | ) 21 | 22 | switch p.ProtocolType { 23 | case "cosmos": 24 | CommonBalanceCallMethod = common.GET 25 | CommonBalanceQueryPath = types.CosmosBalanceQueryPath 26 | CommonBalancePayload = types.CosmosBalancePayload 27 | CommonBalanceParser = parser.CosmosBalanceParser 28 | 29 | return api.GetBalanceStatus( 30 | client, 31 | CommonBalanceCallMethod, 32 | CommonBalanceQueryPath, 33 | CommonBalancePayload, 34 | CommonBalanceParser, 35 | p.BalanceAddresses, p.BalanceDenom, p.BalanceExponent, 36 | ) 37 | 38 | // NOTE: this is for bridge relayer 39 | case "ethereum": 40 | CommonBalanceCallMethod = common.POST 41 | CommonBalanceQueryPath = types.EthereumBalanceQueryPath 42 | CommonBalancePayload = types.EthereumBalancePayLoad 43 | CommonBalanceParser = parser.EthereumBalanceParser 44 | 45 | return api.GetBalanceStatus( 46 | client, 47 | CommonBalanceCallMethod, 48 | CommonBalanceQueryPath, 49 | CommonBalancePayload, 50 | CommonBalanceParser, 51 | p.BalanceAddresses, p.BalanceDenom, p.BalanceExponent, 52 | ) 53 | default: 54 | return types.CommonBalance{}, common.ErrOutOfSwitchCases 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /internal/packages/utility/balance/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | var ( 4 | // common 5 | SupportedProtocolTypes = []string{"cosmos", "ethereum"} 6 | ) 7 | 8 | const ( 9 | // cosmos 10 | CosmosBalanceQueryPath = "/cosmos/bank/v1beta1/balances/{balance_address}" 11 | CosmosBalancePayload = "" 12 | 13 | // ethereum 14 | EthereumBalanceQueryPath = "" 15 | EthereumBalancePayLoad = `{ 16 | "jsonrpc":"2.0", 17 | "id":1, 18 | "method":"eth_getBalance", 19 | "params":["{balance_address}","latest"] 20 | }` 21 | ) 22 | 23 | type CommonBalance struct { 24 | Balances []BalanceStatus 25 | } 26 | 27 | type BalanceStatus struct { 28 | Address string 29 | RemainingBalance float64 30 | } 31 | 32 | type CosmosBalanceResponse struct { 33 | Balances []struct { 34 | Denom string `json:"denom"` 35 | Amount string `json:"amount"` 36 | } `json:"balances"` 37 | Pagination interface{} `json:"-"` 38 | } 39 | 40 | type EthereumBalanceResponse struct { 41 | JsonRPC string `json:"jsonrpc"` 42 | ID int `json:"id"` 43 | Result string `json:"result"` 44 | } 45 | -------------------------------------------------------------------------------- /internal/packages/utility/upgrade/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/cosmostation/cvms/internal/common" 5 | commonparser "github.com/cosmostation/cvms/internal/common/parser" 6 | commontypes "github.com/cosmostation/cvms/internal/common/types" 7 | "github.com/cosmostation/cvms/internal/packages/utility/upgrade/api" 8 | "github.com/cosmostation/cvms/internal/packages/utility/upgrade/types" 9 | ) 10 | 11 | func GetStatus(client *common.Exporter, chainName string) (types.CommonUpgrade, error) { 12 | var ( 13 | commonUpgradeQueryPath string 14 | commonUpgradeParser func([]byte) (int64, string, error) 15 | ) 16 | 17 | switch chainName { 18 | case "celestia": 19 | commonUpgradeQueryPath = commontypes.CelestiaUpgradeQueryPath 20 | commonUpgradeParser = commonparser.CelestiaUpgradeParser 21 | return api.GetUpgradeStatus(client, commonUpgradeQueryPath, commonUpgradeParser) 22 | 23 | case "story": 24 | commonUpgradeQueryPath = commontypes.StoryUpgradeQueryPath 25 | commonUpgradeParser = commonparser.StoryUpgradeParser 26 | return api.GetUpgradeStatus(client, commonUpgradeQueryPath, commonUpgradeParser) 27 | 28 | default: 29 | commonUpgradeQueryPath = commontypes.CosmosUpgradeQueryPath 30 | commonUpgradeParser = commonparser.CosmosUpgradeParser 31 | return api.GetUpgradeStatus(client, commonUpgradeQueryPath, commonUpgradeParser) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /internal/packages/utility/upgrade/router/router_test.go: -------------------------------------------------------------------------------- 1 | package router_test 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/cosmostation/cvms/internal/common" 8 | "github.com/cosmostation/cvms/internal/packages/utility/upgrade/router" 9 | tests "github.com/cosmostation/cvms/internal/testutil" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestRouter(t *testing.T) { 15 | _ = tests.SetupForTest() 16 | 17 | TestCases := []struct { 18 | testingName string 19 | chainName string 20 | rpcEndpoint string 21 | apiEndpoint string 22 | }{ 23 | { 24 | testingName: "Upgrade exist Chain", 25 | chainName: "cosmso", 26 | rpcEndpoint: os.Getenv("TEST_UPGRADE_RPC_ENDPOINT"), 27 | apiEndpoint: os.Getenv("TEST_UPGRADE_API_ENDPOINT"), 28 | }, 29 | { 30 | testingName: "Celestia Signal Upgrade", 31 | chainName: "celestia", 32 | rpcEndpoint: os.Getenv("TEST_CELESTIA_RPC_ENDPOINT"), 33 | apiEndpoint: os.Getenv("TEST_CELESTIA_API_ENDPOINT"), 34 | }, 35 | { 36 | testingName: "Story Upgrade", 37 | chainName: "story", 38 | rpcEndpoint: os.Getenv("TEST_STORY_RPC_ENDPOINT"), 39 | apiEndpoint: os.Getenv("TEST_STORY_API_ENDPOINT"), 40 | }, 41 | } 42 | 43 | for _, tc := range TestCases { 44 | exporter := tests.GetTestExporter() 45 | t.Run(tc.testingName, func(t *testing.T) { 46 | if !assert.NotEqualValues(t, tc.apiEndpoint, "") && !assert.NotEqualValues(t, tc.rpcEndpoint, "") { 47 | // hostaddress is empty 48 | t.FailNow() 49 | } 50 | 51 | exporter.SetAPIEndPoint(tc.apiEndpoint) 52 | exporter.SetRPCEndPoint(tc.rpcEndpoint) 53 | CommonUpgrade, err := router.GetStatus(exporter, tc.chainName) 54 | if err != nil && err != common.ErrCanSkip { 55 | t.Log("Unexpected Error Occured!") 56 | t.Skip() 57 | } 58 | 59 | if err == common.ErrCanSkip { 60 | t.Logf("There is no onchain upgrade now in %s", tc.testingName) 61 | } else { 62 | t.Log("onchain upgrade is found", CommonUpgrade.UpgradeName, CommonUpgrade.RemainingTime) 63 | } 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /internal/packages/utility/upgrade/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | var ( 4 | SupportedProtocolTypes = []string{"cosmos"} 5 | ) 6 | 7 | type CommonUpgrade struct { 8 | RemainingTime float64 9 | RemainingBlocks float64 10 | UpgradeName string 11 | } 12 | -------------------------------------------------------------------------------- /internal/testutil/.env.test: -------------------------------------------------------------------------------- 1 | # test monikers 2 | TEST_MONIKER=Cosmostation 3 | 4 | # test helper module 5 | TEST_COSMOS_ENDPOINT_1= 6 | TEST_COSMOS_ENDPOINT_2= 7 | TEST_COSMOS_ENDPOINT_3= 8 | TEST_COSMOS_ENDPOINT_4= 9 | 10 | # block package test 11 | TEST_COSMOS_HOST_ADDRESS= 12 | TEST_ETHEREUM_HOST_ADDRESS= 13 | TEST_CELESTIA_HOST_ADDRESS= 14 | TEST_BERA_HOST_ADDRESS= 15 | TEST_NAMADA_HOST_ADDRESS= 16 | 17 | # upgrade package test 18 | TEST_UPGRADE_ENDPOINT_1= 19 | TEST_UPGRADE_ENDPOINT_2= 20 | TEST_UPGRADE_ENDPOINT_3= 21 | 22 | # balance package test 23 | TEST_KAVA_ENDPOINT= 24 | TEST_KAVA_DENOM= 25 | TEST_KAVA_ADDRESS= 26 | TEST_KAVA_EXPONENT= 27 | 28 | TEST_INJECTIVE_ENDPOINT_1= 29 | TEST_INJECTIVE_ENDPOINT_2= 30 | TEST_INJECTIVE_ADDRESS_1= 31 | TEST_INJECTIVE_ADDRESS_2= 32 | TEST_INJECTIVE_DENOM= 33 | TEST_INJECTIVE_EXPONENT= 34 | 35 | TEST_ETHEREUM_ENDPOINT= 36 | TEST_ETHEREUM_ADDRESS_1= 37 | TEST_ETHEREUM_ADDRESS_2= 38 | TEST_ETHEREUM_ADDRESS_3= 39 | TEST_ETHEREUM_DENOM= 40 | TEST_ETHEREUM_EXPONENT= 41 | 42 | # oracle package test 43 | TEST_UMEE_ENDPOINT= 44 | TEST_SEI_ENDPOINT= 45 | TEST_NIBIRU_ENDPOINT= 46 | 47 | # eventnonce package test 48 | TEST_INJECTIVE_GRPC_ENDPOINT= 49 | TEST_GRAVITY_GRPC_ENDPOINT= 50 | TEST_SOMMELIER_GRPC_ENDPOINT= 51 | 52 | # eventnonce package test 53 | TEST_INJECTIVE_GRPC_ENDPOINT=injective-grpc.publicnode.com:443 54 | TEST_GRAVITY_GRPC_ENDPOINT=gravity-grpc.polkachu.com:14290 55 | TEST_SOMMELIER_GRPC_ENDPOINT=sommelier-grpc.polkachu.com:14190 56 | 57 | 58 | # axelar-evm package test 59 | TEST_AXELAR_ENDPOINT= 60 | 61 | # yoda package test 62 | TEST_BAND_ENDPOINT= 63 | 64 | # uptime package test 65 | TEST_COSMOS_RPC_ENDPOINT=https://cosmos-rpc.publicnode.com 66 | TEST_COSMOS_API_ENDPOINT=https://cosmos-rest.publicnode.com 67 | 68 | TEST_STRIDE_RPC_ENDPOINT=https://stride-rpc.publicnode.com 69 | TEST_STRIDE_API_ENDPOINT=https://stride-rest.publicnode.com 70 | 71 | TEST_UNION_RPC_ENDPOINT=https://rpc-office.cosmostation.io/union-testnet 72 | TEST_UNION_API_ENDPOINT=https://lcd-office.cosmostation.io/union-testnet 73 | 74 | TEST_INITIA_RPC_ENDPOINT=https://initia.rpc.kjnodes.com 75 | TEST_INITIA_API_ENDPOINT=https://initia.api.kjnodes.com 76 | 77 | # indexer 78 | TEST_DB_DNS="postgres://cvms@localhost:5432/cvms?sslmode=disable" 79 | -------------------------------------------------------------------------------- /scripts/add_custom_chains.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | YAML_FILE="support_chains.yaml" 4 | 5 | 6 | # Define the YAML content as a multi-line variable 7 | additional_yaml_content=$(cat <> $YAML_FILE 43 | -------------------------------------------------------------------------------- /scripts/generate_flyway_files.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Configuration 4 | SOURCE_DIR="./docker/postgres/schema" 5 | TARGET_DIR="./docker/flyway" 6 | VERSION_PREFIX="V1" 7 | COUNTER=1 8 | 9 | # Create the flyway directory 10 | mkdir -p "$TARGET_DIR" 11 | 12 | # Remove existing .sql files (since we'll be using actual files, not symbolic links!) 13 | find "$TARGET_DIR" -name "*.sql" -type f -delete 14 | 15 | # Loop through sorted SQL files 16 | for file in $(ls "$SOURCE_DIR"/*.sql | sort); do 17 | # Extract only the filename (e.g. 02-init-voteindexer.sql) 18 | filename=$(basename "$file") 19 | 20 | # Convert to Flyway naming convention: replace spaces and hyphens with underscores 21 | description=$(echo "$filename" | sed -E 's/^[0-9]+-init-//' | sed 's/[-]/_/g' | sed 's/\.sql$//') 22 | 23 | # Format counter to two digits (e.g. 01, 02...) 24 | counter_padded=$(printf "%02d" $COUNTER) 25 | 26 | # Target filename 27 | target="$TARGET_DIR/${VERSION_PREFIX}_${counter_padded}__init_${description}.sql" 28 | 29 | # Copy the actual file 30 | cp "$file" "$target" 31 | 32 | echo "Copied: $target" 33 | ((COUNTER++)) 34 | done 35 | 36 | # Show directory tree of target 37 | tree "$TARGET_DIR" 38 | -------------------------------------------------------------------------------- /scripts/init_persistent_db.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | # Load .env file (if exists) 6 | if [ -f .env ]; then 7 | export $(grep -v '^#' .env | xargs) 8 | fi 9 | 10 | # Default values (fallback if environment variables are not defined) 11 | DB_HOST="${DB_HOST:-localhost}" 12 | DB_PORT="${DB_PORT:-5432}" 13 | DB_NAME="${DB_NAME:-mydb}" 14 | DB_USER="${DB_USER:-postgres}" 15 | DB_PASSWORD="${DB_PASSWORD:-secret}" 16 | 17 | # Path to Flyway SQL files 18 | FLYWAY_SQL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../docker/flyway" && pwd)" 19 | 20 | echo "💡 Running Flyway migrations against $DB_HOST:$DB_PORT/$DB_NAME..." 21 | 22 | docker run --rm \ 23 | -v $(pwd)/docker/flyway:/flyway/sql \ 24 | flyway/flyway:latest \ 25 | -url="jdbc:postgresql://$DB_HOST:$DB_PORT/$DB_NAME" \ 26 | -user="$DB_USER" \ 27 | -password="$DB_PASSWORD" \ 28 | -locations=filesystem:/flyway/sql \ 29 | migrate 30 | -------------------------------------------------------------------------------- /scripts/load_test.sh: -------------------------------------------------------------------------------- 1 | k6 run -o influxdb=http://localhost:8086/k6 performancetest.js 2 | -------------------------------------------------------------------------------- /scripts/performancetest.js: -------------------------------------------------------------------------------- 1 | import { sleep } from 'k6'; 2 | import http from 'k6/http'; 3 | 4 | export let options = { 5 | thresholds: {}, 6 | scenarios: { 7 | Scenario_1: { 8 | executor: 'ramping-vus', 9 | gracefulStop: '30s', 10 | stages: [ 11 | { target: 100, duration: '1m' }, // Ramp-up to 20 users in 1 minute 12 | { target: 100, duration: '30s' }, // Stay at 20 users for 30 seconds 13 | { target: 0, duration: '15s' }, // Ramp-down to 0 users in 15 seconds 14 | ], 15 | startVUs: 10, // Start with 10 virtual users 16 | gracefulRampDown: '30s', 17 | exec: 'scenario_1', // Specify the function to execute 18 | }, 19 | }, 20 | }; 21 | 22 | export function scenario_1() { 23 | let response; 24 | 25 | // Send an HTTP GET request to the target URL 26 | response = http.get('https://public-cosmos-vms.cosmostation.io/?orgId=1&refresh=5s'); 27 | 28 | // Simulate the user waiting for 0.1 seconds 29 | sleep(0.1); 30 | } 31 | -------------------------------------------------------------------------------- /scripts/sort_support_chains.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | yq eval 'sort_keys(.)' -i ./docker/cvms/support_chains.yaml -------------------------------------------------------------------------------- /scripts/tx_result_decoder.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // modules 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | // functions 7 | const decodeBase64IfPossible = function (text) { 8 | if (text === null) { 9 | return null; 10 | } 11 | 12 | try { 13 | const decodedText = Buffer.from(text, 'base64').toString('utf8'); 14 | // Check if the decoded string, when re-encoded to base64, matches the original text 15 | if (Buffer.from(decodedText).toString('base64') === text) { 16 | return decodedText; 17 | } 18 | } catch (error) { 19 | // If an error occurs, return the input text as is 20 | } 21 | 22 | return text; 23 | }; 24 | const decodeAttributes = function (attributes) { 25 | return attributes.map((attr) => { 26 | return { 27 | ...attr, 28 | key: decodeBase64IfPossible(attr.key), 29 | value: decodeBase64IfPossible(attr.value), 30 | }; 31 | }); 32 | }; 33 | 34 | // Get the file name from the command-line arguments 35 | const inputFilePath = process.argv[2]; // First argument after `node script.js` 36 | if (!inputFilePath) { 37 | console.error('Please provide an input file name as an argument.'); 38 | process.exit(1); // Exit the process with a failure code 39 | } 40 | 41 | const absolutePath = path.resolve(inputFilePath); 42 | 43 | // Extract the directory name and base name 44 | const dirName = path.dirname(absolutePath); // Get the directory name 45 | const fileName = path.basename(absolutePath); // Get the file name including extension 46 | // Construct the output file name and path 47 | 48 | const outputFileName = `decoded_${fileName}`; // Add a prefix to the base name 49 | const outputFilePath = path.join(dirName, outputFileName); // Combine directory and output file name 50 | 51 | // Try decoding 52 | try { 53 | const jsonData = JSON.parse(fs.readFileSync(inputFilePath, 'utf-8')); 54 | jsonData.result.txs_results.forEach((tx) => { 55 | tx.events.forEach((event) => { 56 | event.attributes = decodeAttributes(event.attributes); 57 | }); 58 | }); 59 | jsonData.result.begin_block_events.forEach((event) => { 60 | event.attributes = decodeAttributes(event.attributes); 61 | }); 62 | 63 | // Write processed JSON to the output file 64 | fs.writeFileSync(outputFilePath, JSON.stringify(jsonData, null, 2), 'utf-8'); 65 | console.log(`Decoded JSON data saved to ${outputFilePath}`); 66 | } catch (err) { 67 | console.error('Error reading or processing the file:', err.message); 68 | process.exit(1); // Exit the process with a failure code 69 | } 70 | --------------------------------------------------------------------------------