---
# System prepended metadata

title: Space-box user story (WIP)

---

# Space-box user story (WIP)

## Tables and fields

state table - подразумевает хранение только последнего состояния БЧ в таблице. Блок нужен для понимания последнего обновления

historical table - подразумевает хранение исторических состояний поля

#### auth module

```sql=
/*state*/
CREATE TABLE account 
(
    address    TEXT NOT NULL PRIMARY KEY,
    height     BIGINT  NOT NULL
);
```

#### bank module

ПОЛЕ COIN думаем еще, пока везде JSONB
таблица COIN как вариант вместо JSONB или кастом типа

```sql=
CREATE TABLE supply
/*history*/
(
    height     BIGINT  NOT NULL, 
    supply     JSONB   NOT NULL /* [{"amount": amount, "denom": denom}]  если удобнее ввести тип COIN то я не против, нужно понять производительность, фильтрация по полю в массиве*/
);
```

```sql=
CREATE TABLE send_message
/*history*/
(
    height          BIGINT  NOT NULL,
    address_from    TEXT    NOT NULL,
    address_to      TEXT    NOT NULL,
    coins           JSONB   NOT NULL /* [{"amount": amount, "denom": denom}]  если удобнее ввести тип COIN то я не против, нужно понять производительность, фильтрация по полю в массиве*/
    tx_hash,
    msg_index  
    
);
```

```sql=
CREATE TABLE multisend_message
/*history*/
(
    height          BIGINT  NOT NULL,
    address_from    TEXT    NOT NULL,
    addresses_to    []      NOT NULL,
    coins           []      NOT NULL /* [{"amount": amount, "denom": denom}]  если удобнее ввести тип COIN то я не против, нужно понять производительность, фильтрация по полю в массиве*/
    tx_hash,
    msg_index
    
);
```


```sql=
/*history*/
CREATE TABLE account_balance (
    address    TEXT NOT NULL PRIMARY KEY,
    height     BIGINT  NOT NULL,
    coins      JSONB   NOT NULL /* [{"amount": amount, "denom": denom}]  если удобнее ввести тип COIN то я не против */
);
```

#### consensus module

genesis - хз как отобразить эту таблицу... надо думать, нужна ли она. 
среднее время блока во всех видах на следующую итерацию, если понадобится

```sql=
/*?*/
CREATE TABLE average_block_time_from_genesis (
    height     BIGINT  NOT NULL,
    time       INT     NOT NULL /* ms */
);
```

среднее время блока во всех видах на следующую итерацию, если понадобится
```sql=
/*?*/
CREATE TABLE average_block_time_per_day (
    date       DATE    NOT NULL,
    time       INT     NOT NULL /* ms */
);
```

```sql=
/*history*/
CREATE TABLE block
(
    height           BIGINT  UNIQUE PRIMARY KEY,
    hash             TEXT    NOT NULL UNIQUE,
    num_txs          INTEGER DEFAULT 0,
    total_gas        BIGINT  DEFAULT 0,
    proposer_address TEXT REFERENCES validator (consensus_address),
    timestamp        TIMESTAMP WITHOUT TIME ZONE NOT NULL
);
```

#### distribution module

попытаться записывать только блок изменения параметров, чтобы не нагружать базу дубликатами

```sql=
/*history*/
CREATE TABLE distribution_params
(
    params     JSONB   NOT NULL,
    height     BIGINT  NOT NULL
);
```

```sql=
/*history*/
CREATE TABLE community_pool
(
    coins      JSONB      NOT NULL /* [{"amount": amount, "denom": denom}]  если удобнее ввести тип COIN то я не против */
    height     BIGINT     NOT NULL
);
```

```sql=
/*state*/
CREATE TABLE validator
(
    consensus_address TEXT    NOT NULL PRIMARY KEY, /* Validator consensus address */
    consensus_pubkey  TEXT    NOT NULL UNIQUE /* Validator consensus public key */
);
```

```sql=
/*state*/
CREATE TABLE validator_info
(
    consensus_address     TEXT      NOT NULL UNIQUE PRIMARY KEY REFERENCES validator (consensus_address),
    operator_address      TEXT      NOT NULL UNIQUE,
    self_delegate_address TEXT      REFERENCES account (address),
    min_self_delegation   BIGINT    NOT NULL,
    height                BIGINT    NOT NULL
);
```

```sql=
/*history*/ !!!paused!!!
CREATE TABLE delegation_reward
(
    operator_address  TEXT       NOT NULL REFERENCES validator_info (operator_address),
    delegator_address TEXT       NOT NULL REFERENCES account (address),
    withdraw_address  TEXT       NOT NULL,
    amount(coins)     JSONB      NOT NULL,
    height            BIGINT     NOT NULL
);
```


историческая таблица с распаршенными месседжами от транзакции `cosmos-sdk/MsgWithdrawDelegationReward`

```sql=
/*history*/ посмотреть тело транзакций, разобраться с withdraw_address
CREATE TABLE delegation_reward_message
(
    validator_address  TEXT       NOT NULL REFERENCES validator_info (operator_address),
    delegator_address  TEXT       NOT NULL REFERENCES account (address),
    withdraw_address   TEXT       NOT NULL,
    amount(coins)      JSONB      NOT NULL
    height,
    tx_hash,
    msg_index
);
```

Добавить validator commission, validator description


#### gov module

```sql=
/*history*/
CREATE TABLE gov_params
(
    deposit_params JSONB   NOT NULL,
    voting_params  JSONB   NOT NULL,
    tally_params   JSONB   NOT NULL,
    height         BIGINT  NOT NULL
);
```
```sql=
CREATE TABLE proposal
/*state*/
(
    id                INTEGER   NOT NULL PRIMARY KEY,
    title             TEXT      NOT NULL,
    description       TEXT      NOT NULL,
    content           JSONB     NOT NULL,
    proposal_route    TEXT      NOT NULL,
    proposal_type     TEXT      NOT NULL,
    proposal_deposit_amount(coin), 
    submit_time       TIMESTAMP NOT NULL,
    deposit_end_time  TIMESTAMP,
    voting_start_time TIMESTAMP,
    voting_end_time   TIMESTAMP,
    proposer_address  TEXT      NOT NULL REFERENCES account (address),
    status            TEXT
);
```

```sql=
/*history, по сути распаршенный месседж deposit proposal*/ 
CREATE TABLE proposal_deposit_message
(
    proposal_id       INTEGER REFERENCES proposal (id) NOT NULL,
    depositor_address TEXT REFERENCES account (address),
    amount            COIN[],
    height            BIGINT,
    tx_hash,
    msg_index
);
```

```sql=
/*state*/
CREATE TABLE proposal_tally_result
(
    proposal_id  INTEGER REFERENCES proposal (id) PRIMARY KEY,
    yes          BIGINT NOT NULL,
    abstain      BIGINT NOT NULL,
    no           BIGINT NOT NULL,
    no_with_veto BIGINT NOT NULL
);
```

```sql=
/*history, распаршенный месседж vote*/
CREATE TABLE proposal_vote(message)
(
    proposal_id   INTEGER NOT NULL REFERENCES proposal (id),
    voter_address TEXT    NOT NULL REFERENCES account (address),
    option        TEXT    NOT NULL,
    height        BIGINT  NOT NULL,
    tx_hash,
    msg_index
);
```

#### slashing(?) пока пропускаем

```sql=
CREATE TABLE validator_signing_info
(
    validator_address     TEXT                        NOT NULL PRIMARY KEY,
    start_height          BIGINT                      NOT NULL,
    index_offset          BIGINT                      NOT NULL,
    jailed_until          TIMESTAMP WITHOUT TIME ZONE NOT NULL,
    tombstoned            BOOLEAN                     NOT NULL,
    missed_blocks_counter BIGINT                      NOT NULL,
    height                BIGINT                      NOT NULL
);
```

```sql=
CREATE TABLE slashing_params
(
    params     JSONB   NOT NULL,
    height     BIGINT  NOT NULL
);
```

#### mint

```sql=
CREATE TABLE mint_params
(
    params     JSONB   NOT NULL,
    height     BIGINT  NOT NULL
);
```

историческая таблица изменения инфляции
```sql=
-- CREATE TABLE inflation
-- (
--     height             BIGINT  NOT NULL,
--     inflation          DECIMAL NOT NULL,
--     block_provision    JSONB   NOT NULL,  // доп поле
--     annual_provision   JSONB   NOT NULL  // доп поле
-- );
```

историческая таблица изменения годового дохода
```sql=
CREATE TABLE annual_provision
(
    height             BIGINT  NOT NULL,
    annual_provision   JSONB   NOT NULL  // доп поле
);
```


#### staking

```sql=
/*state*/
CREATE TABLE validator
(
    consensus_address TEXT    NOT NULL PRIMARY KEY, /* Validator consensus address */
    consensus_pubkey  TEXT    NOT NULL UNIQUE, /* Validator consensus public key */
    operator_address  TEXT    NOT NULL UNIQUE,
    height
);
``` 

```sql=
/*history*/
CREATE TABLE validator_status
(
    validator_address TEXT    NOT NULL REFERENCES validator (operator_address) PRIMARY KEY,
    status            INT     NOT NULL,
    jailed            BOOLEAN NOT NULL,
    height            BIGINT  NOT NULL
);
```

```sql=
/*history*/
CREATE TABLE validator_info
(
    consensus_address     TEXT      NOT NULL UNIQUE PRIMARY KEY REFERENCES validator (consensus_address),
    operator_address      TEXT      NOT NULL UNIQUE,
    self_delegate_address TEXT      REFERENCES account (address),
    min_self_delegation   BIGINT    NOT NULL,
    height                BIGINT    NOT NULL
);
``` 

```sql=
CREATE TABLE validator_description
(
    validator_address TEXT   NOT NULL REFERENCES validator (operator_address) PRIMARY KEY,
    moniker           TEXT,
    identity          TEXT,
    avatar_url        TEXT,
    website           TEXT,
    security_contact  TEXT,
    details           TEXT,
    height            BIGINT NOT NULL
);
```

```sql=
CREATE TABLE validator_commission
(
    validator_address   TEXT    NOT NULL REFERENCES validator (operator_address) PRIMARY KEY,
    commission          DECIMAL NOT NULL,
    min_self_delegation BIGINT  NOT NULL,
    height              BIGINT  NOT NULL
);
```

```sql=
/*history*/
CREATE TABLE staking_params
(
    params     JSONB   NOT NULL,
    height     BIGINT  NOT NULL
);
```

```sql=
/*history*/
CREATE TABLE validator_voting_power(?)
(
    validator_address TEXT   NOT NULL REFERENCES validator (consensus_address) PRIMARY KEY,
    voting_power      BIGINT NOT NULL,
    height            BIGINT NOT NULL REFERENCES block (height)
);
``` 

```sql=
CREATE TABLE double_sign_vote(?)
(
    id                SERIAL PRIMARY KEY,
    type              SMALLINT NOT NULL,
    height            BIGINT   NOT NULL,
    round             INT      NOT NULL,
    block_id          TEXT     NOT NULL,
    validator_address TEXT     NOT NULL REFERENCES validator (consensus_address),
    validator_index   INT      NOT NULL,
    signature         TEXT     NOT NULL,
    UNIQUE (block_id, validator_address)
);
```

```sql=
CREATE TABLE double_sign_evidence(?)
(
    height    BIGINT NOT NULL,
    vote_a_id BIGINT NOT NULL REFERENCES double_sign_vote (id),
    vote_b_id BIGINT NOT NULL REFERENCES double_sign_vote (id)
);
```

```sql=
/*history*/
CREATE TABLE staking_pool
(
    bonded_tokens     BIGINT  NOT NULL,
    not_bonded_tokens BIGINT  NOT NULL,
    height            BIGINT  NOT NULL
);
```

```sql=
/*state*/
CREATE TABLE redelegation
(
    delegator_address     TEXT                        NOT NULL REFERENCES account (address),
    src_validator_address TEXT                        NOT NULL REFERENCES validator (operator_address),
    dst_validator_address TEXT                        NOT NULL REFERENCES validator (operator_address),
    amount(coin)          JSONB                       NOT NULL,
    completion_time       TIMESTAMP WITHOUT TIME ZONE NOT NULL
);

```

```sql=
/*history*/
CREATE TABLE redelegation_message
(
    delegator_address     TEXT                        NOT NULL REFERENCES account (address),
    src_validator_address TEXT                        NOT NULL REFERENCES validator (consensus_address),
    dst_validator_address TEXT                        NOT NULL REFERENCES validator (consensus_address),
    amount(coin)          JSONB                       NOT NULL,
    completion_time       TIMESTAMP WITHOUT TIME ZONE NOT NULL,
    height                BIGINT                      NOT NULL,
    tx_hash               TEXT                        NOT NULL,
    msg_index
);
```

```sql=
/*state*/
CREATE TABLE unbonding_delegation
(
    validator_address    TEXT                        NOT NULL REFERENCES validator (consensus_address),
    delegator_address    TEXT                        NOT NULL REFERENCES account (address),
    amount               COIN                        NOT NUll,
    completion_timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL
);
```

Новая таблица, парсится из месседжа транзакции 
```sql=
/*history*/
CREATE TABLE unbonding_delegation_message
(
    validator_address    TEXT                        NOT NULL REFERENCES validator (consensus_address),
    delegator_address    TEXT                        NOT NULL REFERENCES account (address),
    amount               COIN                        NOT NUll,
    completion_timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL,
    height               BIGINT                      NOT NULL,
    tx_hash              TEXT                        NOT NULL,
    msg_index
);
```

gRPC?
```sql=
/*state*/
CREATE TABLE delegation
(
    operator_address    TEXT    NOT NULL validator_info?,
    delegator_address   TEXT    NOT NULL REFERENCES account (account),
    coins               JSONB   NOT NULL /* [{"amount": amount, "denom": denom}]  если удобнее ввести тип COIN то я не против */
    height              BIGINT  NOT NULL
);
```

Новая таблица, парсится из месседжа транзакции 
```sql=
/*history*/
CREATE TABLE delegation_message
(
    operator_address    TEXT    NOT NULL ,
    delegator_address   TEXT    NOT NULL REFERENCES account (account),
    coins               JSONB   NOT NULL /* [{"amount": amount, "denom": denom}]  если удобнее ввести тип COIN то я не против */
    height              BIGINT  NOT NULL
    tx_hash             TEXT    NOT NULL,
    msg_index
);
```

#### history ?
- account. исторически имеется в таблице аккаунт, есть номер блока, когда добавлен адрес, не актуально
- account_balance модуль банк, такая же история 
- delegation. введена таблица delegation_message, там история 
- redelegation. также
- unbonding_delegation. также
- delegation_reward. также
- validator_commission_amount. модуль distribution. по сути, если писать блок, то таблица может выполнять 2 функции. 



if history module enabled: account_balance (get) delegation (get) redelegation (get) unbonding_delegation (get) delegation_reward (get) validator_commission_amount (get) account_balance_history (insert)

```sql=
/*history*/ !!!paused!!!
-- CREATE TABLE account_full_balance
-- (
--     address      TEXT      NOT NULL REFERENCES account (address),
--     balance      JSONB     NOT NULL DEFAULT '{}',
--     delegated    JSONB     NOT NULL DEFAULT '{}',
--     unbonding    JSONB     NOT NULL DEFAULT '{}',
--     redelegating JSONB     NOT NULL DEFAULT '{}',
--     commission   JSONB     NOT NULL DEFAULT '{}',
--     reward       JSONB     NOT NULL DEFAULT '{}',
--     height       BIGINT    NOT NULL
-- );
```

```sql=
/*history*/
CREATE TABLE transaction
(
    hash         TEXT    NOT NULL UNIQUE PRIMARY KEY,
    height       BIGINT  NOT NULL REFERENCES block (height),
    success      BOOLEAN NOT NULL,

    /* Body */
    messages     JSONB   NOT NULL DEFAULT '[]'::JSONB,
    memo         TEXT,
    signatures   TEXT[]  NOT NULL,

    /* AuthInfo */
    signer_infos JSONB   NOT NULL DEFAULT '[]'::JSONB,
    fee          JSONB   NOT NULL DEFAULT '{}'::JSONB,
    signer       TEXT    NOT NULL DEFAULT '{}'::JSONB REFERENCES accounts(address),

    /* Tx response */
    gas_wanted   BIGINT           DEFAULT 0,
    gas_used     BIGINT           DEFAULT 0,
    raw_log      TEXT,
    logs         JSONB,
    success
);
```

```sql=
/*history*/
CREATE TABLE message
(
    transaction_hash            TEXT   NOT NULL REFERENCES transaction (hash),
    index                       BIGINT NOT NULL,
    type                        TEXT   NOT NULL,
    value                       JSONB  NOT NULL,
    involved_accounts_addresses TEXT[] NULL
    signer                      TEXT   NOT NULL DEFAULT '{}'::JSONB REFERENCES accounts(address)
);
```

---



## Введение

Данный текст является ориентировочным заданием на разработку продукта space-box (рабочее название от bro index) в котором будет поставлена идеальная задача (подразумеваются корректировки), разъяснен требуемый функционал, декомпозиция проекта на стейджи (количество, веса), ислючая финансовые детали и дальнешее развитие и поддержка проекта, после выполнения основной части. 

Проект является open-source, под лицензией MIT, в связи с этим, весь код, ровно как и история разработки должны быть прозрачными и находиться в репозитории организации [bro-n-bro](). Все необходимые ссылки на разработчиков могут быть добавлены к README названного репозитория в любом статусе, за исключением владельцев данного репозитория. 

Разработка должна быть "чистой", документированной, упорядоченной видом [issues] -> PR -> Merge
Ветка master бует закрыта от прямых коммитов.

## Постановка задачи

Требуется разработать индексатор блокчейнов, основой которых является [cosmos-sdk](). Данные необходимо записывать в [ClickHouse DB](), также третьим инстансом необходим GraphQL движок, Hasura, в идеале. Все три инстанса должны быть докерезированы и обернуты в единый docker-compose файл для легкого запуска. Конфигурация индексатора перед запуском должна проводиться в файле `.env`:

```bash
CHAIN_PREFIX=prefix # префикс индексируемой сети
CLICKHOUSE_DB_FOLDER=$HOME/.space-box/clickhouse # место хранения файлов БД
CLICKHOUSE_DB_HOST=localhost # хост БД
CLICKHOUSE_DB_PORT=5432 # порт БД
CLICKHOUSE_DB_NAME=space-box # назване БД
CLICKHOUSE_USER_NAME=space-box # аккаунт пользователя БД
CLICKHOUSE_DB_PASSWORD=space-box # пароль пользователя БД
CLICKHOUSE_SSL_MODE=disable # хз, че это
HASURA_PORT=8090 # порт хасуры
HASURA_ADMIN_SECRET=hasura # пароль хасуры
SPACE_BOX_WORKERS=1 # количество параллельных процессов
RPC_URL=http://localhost:26657 # RPC API индексируемой сети
GRPC_URL=http://localhost:9090 # GRPC API индексируемой цепи
START_HEIGHT=1 # Блок, с которого проводить индексацию
START_FROM_SNAPSHOT=False # флан на случай старта неполной ноды, а со снапшота
```

Запуск docker-compose означает поднятия всех необходимых сервисов и инициализации всех таблиц базы данных. Makefile или баш скрипт могут присутствовать. [Пример](https://github.com/cybercongress/cyberindex/blob/main/scripts/start-docker.sh). Также необходимо автоматизировать процесс создания роли `anonymous` в хасуре, с предоставленными этой роли правами на чтение данных таблицы, ровно как и агрегированных данных. 

Сервис может индексировать блокчейн как с нуля, при наличии архивной ноды, так и с заданного блока, при наличии снэпшота. Данные необходимо индексировать в двух режимах sync и listen, что значит индексация пропущенных блоков с начала функионирования блокчейна и запись по ws свежих блоков. 

Предполагаемая скорость индексакции 1000 блоков в секунду при задействовании 8 воркеров. 

## Индексируемые данные

cosmos-sdk предоставляет возможность разработки блокчейна с подключаемыми модулями. Модули, которые можно подключить для индексации тоже можно указать в `.env` файле. Готовым продуктом будет считаться такой индексатор, который может проиндексировать блокчейн cosmoshub, версия cosmos-sdk которого не ниже `v0.45.6`.

Также, данные модули должны индексироваться на других cosmos-based блокчейнах, при условии, что версия их сдк не ниже названной. Список сетей по приоритизации:

- cosmos cosmos_sdk_version: v0.45.9
- bostrom cosmos_sdk_version: v0.45.9
- juno cosmos_sdk_version: v0.45.9
- osmosis cosmos_sdk_version: v0.46.1
- evmos cosmos_sdk_version: v0.45.10
- stargaze cosmos_sdk_version: v0.45.9
- gravity cosmos_sdk_version: v0.45.9
- crescent cosmos_sdk_version: v0.45.9
- desmos cosmos_sdk_version: v0.45.8
- e-money cosmos_sdk_version: v0.42.11
- omniflix cosmos_sdk_version: v0.45.9
- stride cosmos_sdk_version: v0.45.9

Список модулей приведен [здесь](https://docs.cosmos.network/v0.46/modules/), хорошим примером схемы БД является [такая схема](https://github.com/forbole/bdjuno/tree/cosmos/v0.44.x/database/schema) c некоторыми уточнениями, такими как добавления таблицы с историческими балансами аккаунтов, добавления поля APR в модуле mint таблице inflation, добавление явного вида адреса подписанта транзакции. Уточнение всех таблиц лучше сделать вместе. А также добавление модулей, которые не представлены в этой схеме, например staking, ibc, wasm. 

## Реперные точки (стейджи)

Предлагается разделить транши за выполнение работы на три стейджа:

1. Реализация модульной архитекторы индексера, упаковка в docker-compose, реализация всего того, что написано в секции постановка задачи и реализация индексации модулей auth, bank, gov, mint, params, slashing, staking. (40%).
2. Добавление остальных модулей: authz, capability, crisis, distribution, epoching, evidence, feegrant, group, nft, upgrade, ibc. (40%)
3. Тестирование, багхантинг, уточнение таблиц (20%)

## Дальнешее развитие и поддержка

После успешной реализации базового индексатора для cosmoshub будет предложено за дополнительную плату реализовывать модули для остальных блокчейнов, в порядке приоритета, указанного выше. 

## Заключение

Данные текст является ориентировочным планом действий, без лишних, а возможно и нужных деталий. Все уточнения могут и должны быть обсуждаемы. 


