All packages
uniswap-database-changes-margin-markets-mainnet

uniswap-database-changes-margin-markets-mainnet

ciyengar3
v0.0.1/2 downloads

Package ref

uniswap-database-changes-margin-markets-mainnet@v0.0.1

Run package

CLI

Run db_out from the command line.

substreams run uniswap-database-changes-margin-markets-mainnet@v0.0.1 db_out -e mainnet
Authenticate by running substreams auth or directly on thegraph.market (see docs).

README

Database Changes Margin Markets

Turns uniswap-margin-markets state messages into database changes, one block-level row per message.

src/ is hand-written, not autogen. The rows are per entity per block and carry no log identity (tx_hash, caller, ordinal, log_index, ...), which the autogen Rust generator requires on every message. autogen.config.json (blockLevel: true) still drives yarn generate-all for events.proto, the BigQuery schema and events_pb.ts, as for pool-state and tx-digest.

IndexTableOrdering key
2100uniswap_margin_markets_morpho_blue_market_statemorpho_blue:<market_id>
2101uniswap_margin_markets_meta_morpho_vault_statemetamorpho:<vault>
2102uniswap_margin_markets_meta_morpho_vault_market_statemetamorpho:<vault>:<market_id>
2103uniswap_margin_markets_aave_v3_reserve_stateaave_v3:<asset>
2104uniswap_margin_markets_aave_v4_spoke_reserve_stateaave_v4:<spoke>:<reserve_id>
2105uniswap_margin_markets_compound_v3_comet_statecompound_v3:<comet>
2106uniswap_margin_markets_compound_v3_comet_asset_statecompound_v3:<comet>:<asset>

Every row carries block_num, block_hash, timestamp, index, global_sequence and ordering_key; the sink adds chain_id and sink_created_at. global_sequence is a row id, (block << 64) | row index zero-padded to 39 digits, unique within the block. Fields are sorted by name and the primary key is the single string <block>:<index>, so a replay is byte-identical. An unset optional field is left out of the row, so it is NULL in BigQuery and undefined in TS; a consumer reads it as unknown, not zero.

The sink maps a table to its events.proto message by name, so the table names above are the event contract, and every column must exist in that message.

cd .. && ./build-database-changes-spkg.sh mainnet v0.0.1 margin-markets

Reorgs: the UNDO rule for consumers

A row's index depends on what else its block touched. So when a reorg replaces block N, the new block can give an entity a lower global_sequence than the replaced block did. A consumer that keeps the latest row per key and writes only when global_sequence is greater than the stored last_sequence would then drop the canonical row.

The sink handles a reorg by hash (PubSubPublisher.arbitrateBlock, high priority stream). When block N arrives with a new hash, it first publishes an UNDO for each row it cached at N under the old hash, on the row's ordering key. Every UNDO carries reorg_context.last_valid_global_sequence, which is ((N - 1) << 64) | (2^64 - 1) padded to 39 digits. Then it publishes the new block's rows. A consumer follows two rules per key:

  • APPLY: write the row when its global_sequence is greater than the stored last_sequence.
  • UNDO: when the stored last_sequence is greater than the UNDO's last_valid_global_sequence, set last_sequence to that value and mark the row stale, so gates fail closed, until an APPLY lands. Otherwise ignore the UNDO: the undone row never became current.

After the UNDO, any row from height N or later passes the guard, whatever its index. A key can stay stale longer than needed: one that the new blocks do not touch waits for a later block, because the rows are snapshots and an UNDO cannot restore the row before it.

Over several blocks the sink undoes height by height, before each new block, so the rule does not cover a key the orphaned branch touched only at a later height. Say orphan N does not touch E and orphan N+1 does. When canonical N arrives, E gets no UNDO, so the replaced N+1 row stays current and canonical N's row for E is dropped by the guard. When canonical N+1 arrives, E's UNDO marks it stale, and it stays stale until a later APPLY. So a multi-block reorg can serve a replaced row until the sink reaches its height, and can lose a canonical row for that key; both end in a stale row, never a wrong current one, once the reorg is fully replayed.

No numbering on the stream side can avoid this. The two blocks at height N are computed independently, and a guard compares numbers, not blocks. Numbering rows by entity instead would give both blocks the same number for an entity, which a strict guard also drops.

Modules

Execution graph

8 modules
Show 7 dependencies