github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/docs/app-dev/indexing-transactions.md (about)

     1  ---
     2  order: 6
     3  ---
     4  
     5  # Indexing Transactions
     6  
     7  CometBFT allows you to index transactions and blocks and later query or
     8  subscribe to their results. Transactions are indexed by `TxResult.Events` and
     9  blocks are indexed by `Response(Begin|End)Block.Events`. However, transactions
    10  are also indexed by a primary key which includes the transaction hash and maps
    11  to and stores the corresponding `TxResult`. Blocks are indexed by a primary key
    12  which includes the block height and maps to and stores the block height, i.e.
    13  the block itself is never stored.
    14  
    15  Each event contains a type and a list of attributes, which are key-value pairs
    16  denoting something about what happened during the method's execution. For more
    17  details on `Events`, see the
    18  [ABCI](https://github.com/cometbft/cometbft/blob/v0.34.x/spec/abci/abci.md#events)
    19  documentation.
    20  
    21  An `Event` has a composite key associated with it. A `compositeKey` is
    22  constructed by its type and key separated by a dot.
    23  
    24  For example:
    25  
    26  ```json
    27  "jack": [
    28    "account.number": 100
    29  ]
    30  ```
    31  
    32  would be equal to the composite key of `jack.account.number`.
    33  
    34  By default, CometBFT will index all transactions by their respective hashes
    35  and height and blocks by their height.
    36  
    37  CometBFT allows for different events within the same height to have 
    38  equal attributes.
    39  
    40  ## Configuration
    41  
    42  Operators can configure indexing via the `[tx_index]` section. The `indexer`
    43  field takes a series of supported indexers. If `null` is included, indexing will
    44  be turned off regardless of other values provided.
    45  
    46  ```toml
    47  [tx-index]
    48  
    49  # The backend database to back the indexer.
    50  # If indexer is "null", no indexer service will be used.
    51  #
    52  # The application will set which txs to index. In some cases a node operator will be able
    53  # to decide which txs to index based on configuration set in the application.
    54  #
    55  # Options:
    56  #   1) "null"
    57  #   2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend).
    58  #     - When "kv" is chosen "tx.height" and "tx.hash" will always be indexed.
    59  #   3) "psql" - the indexer services backed by PostgreSQL.
    60  # indexer = "kv"
    61  ```
    62  
    63  ### Supported Indexers
    64  
    65  #### KV
    66  
    67  The `kv` indexer type is an embedded key-value store supported by the main
    68  underlying CometBFT database. Using the `kv` indexer type allows you to query
    69  for block and transaction events directly against CometBFT's RPC. However, the
    70  query syntax is limited and so this indexer type might be deprecated or removed
    71  entirely in the future.
    72  
    73  **Implementation and data layout**
    74  
    75  The kv indexer stores each attribute of an event individually, by creating a composite key 
    76  of the *event type*, *attribute key*, *attribute value*, *height* and *event sequence*. 
    77  
    78  For example the following events:
    79   
    80  ```
    81  Type: "transfer",
    82    Attributes: []abci.EventAttribute{
    83     {Key: []byte("sender"), Value: []byte("Bob"), Index: true},
    84     {Key: []byte("recipient"), Value: []byte("Alice"), Index: true},
    85     {Key: []byte("balance"), Value: []byte("100"), Index: true},
    86     {Key: []byte("note"), Value: []byte("nothing"), Index: true},
    87     },
    88   
    89  ```
    90   
    91  ```
    92  Type: "transfer",
    93    Attributes: []abci.EventAttribute{
    94     {Key: []byte("sender"), Value: []byte("Tom"), Index: true},
    95     {Key: []byte("recipient"), Value: []byte("Alice"), Index: true},
    96     {Key: []byte("balance"), Value: []byte("200"), Index: true},
    97     {Key: []byte("note"), Value: []byte("nothing"), Index: true},
    98     },
    99  ```
   100  
   101  will be represented as follows in the store: 
   102  
   103  ```
   104  Key                                 value
   105  transferSenderBobEndBlock1           1
   106  transferRecipientAliceEndBlock11     1
   107  transferBalance100EndBlock11         1
   108  transferNodeNothingEndblock11        1
   109  ---- event2 ------
   110  transferSenderTomEndBlock12          1
   111  transferRecipientAliceEndBlock12     1
   112  transferBalance200EndBlock12         1
   113  transferNodeNothingEndblock12        1
   114   
   115  ```
   116  The key is thus formed of the event type, the attribute key and value, the event the attribute belongs to (`EndBlock` or `BeginBlock`),
   117  the height and the event number. The event number is a local variable kept by the indexer and incremented when a new event is processed. 
   118  
   119  It is an `int64` variable and has no other semantics besides being used to associate attributes belonging to the same events within a height. 
   120  This variable is not atomically incremented as event indexing is deterministic. **Should this ever change**, the event id generation
   121  will be broken. 
   122  
   123  #### PostgreSQL
   124  
   125  The `psql` indexer type allows an operator to enable block and transaction event
   126  indexing by proxying it to an external PostgreSQL instance allowing for the events
   127  to be stored in relational models. Since the events are stored in a RDBMS, operators
   128  can leverage SQL to perform a series of rich and complex queries that are not
   129  supported by the `kv` indexer type. Since operators can leverage SQL directly,
   130  searching is not enabled for the `psql` indexer type via CometBFT's RPC -- any
   131  such query will fail.
   132  
   133  Note, the SQL schema is stored in `state/indexer/sink/psql/schema.sql` and operators
   134  must explicitly create the relations prior to starting CometBFT and enabling
   135  the `psql` indexer type.
   136  
   137  Example:
   138  
   139  ```shell
   140  $ psql ... -f state/indexer/sink/psql/schema.sql
   141  ```
   142  
   143  ## Default Indexes
   144  
   145  The CometBFT tx and block event indexer indexes a few select reserved events
   146  by default.
   147  
   148  ### Transactions
   149  
   150  The following indexes are indexed by default:
   151  
   152  - `tx.height`
   153  - `tx.hash`
   154  
   155  ### Blocks
   156  
   157  The following indexes are indexed by default:
   158  
   159  - `block.height`
   160  
   161  ## Adding Events
   162  
   163  Applications are free to define which events to index. CometBFT does not
   164  expose functionality to define which events to index and which to ignore. In
   165  your application's `DeliverTx` method, add the `Events` field with pairs of
   166  UTF-8 encoded strings (e.g. "transfer.sender": "Bob", "transfer.recipient":
   167  "Alice", "transfer.balance": "100").
   168  
   169  Example:
   170  
   171  ```go
   172  func (app *KVStoreApplication) DeliverTx(req types.RequestDeliverTx) types.Result {
   173      //...
   174      events := []abci.Event{
   175          {
   176              Type: "transfer",
   177              Attributes: []abci.EventAttribute{
   178                  {Key: []byte("sender"), Value: []byte("Bob"), Index: true},
   179                  {Key: []byte("recipient"), Value: []byte("Alice"), Index: true},
   180                  {Key: []byte("balance"), Value: []byte("100"), Index: true},
   181                  {Key: []byte("note"), Value: []byte("nothing"), Index: true},
   182              },
   183          },
   184      }
   185      return types.ResponseDeliverTx{Code: code.CodeTypeOK, Events: events}
   186  }
   187  ```
   188  
   189  If the indexer is not `null`, the transaction will be indexed. Each event is
   190  indexed using a composite key in the form of `{eventType}.{eventAttribute}={eventValue}`,
   191  e.g. `transfer.sender=bob`.
   192  
   193  ## Querying Transactions Events
   194  
   195  You can query for a paginated set of transaction by their events by calling the
   196  `/tx_search` RPC endpoint:
   197  
   198  ```bash
   199  curl "localhost:26657/tx_search?query=\"message.sender='cosmos1...'\"&prove=true"
   200  ```
   201  If the conditions are related to transaction events and the user wants to make sure the
   202  conditions are true within the same events, the `match_events` keyword should be used, 
   203  as described [below](#querying_block_events)
   204  
   205  Check out [API docs](https://docs.cometbft.com/v0.34/rpc/#/Info/tx_search)
   206  for more information on query syntax and other options.
   207  
   208  ## Subscribing to Transactions
   209  
   210  Clients can subscribe to transactions with the given tags via WebSocket by providing
   211  a query to `/subscribe` RPC endpoint.
   212  
   213  ```json
   214  {
   215    "jsonrpc": "2.0",
   216    "method": "subscribe",
   217    "id": "0",
   218    "params": {
   219      "query": "message.sender='cosmos1...'"
   220    }
   221  }
   222  ```
   223  
   224  Check out [API docs](https://docs.cometbft.com/v0.34/rpc/#subscribe) for more information
   225  on query syntax and other options.
   226  
   227  ## Querying Block Events
   228  
   229  You can query for a paginated set of blocks by their events by calling the
   230  `/block_search` RPC endpoint:
   231  
   232  ```bash
   233  curl "localhost:26657/block_search?query=\"block.height > 10 AND val_set.num_changed > 0\""
   234  ```
   235  
   236  ## `match_events` keyword 
   237  
   238  The query results in the height number(s) (or transaction hashes when querying transactions) which contain events whose attributes match the query conditions. 
   239  However, there are two options to query the indexers. To demonstrate the two modes, we reuse the two events
   240  where Bob and Tom send money to Alice and query the block indexer. We issue the following query:
   241  
   242  ```bash
   243  curl "localhost:26657/block_search?query=\"sender=Bob AND balance = 200\""
   244  ```
   245  
   246  The result will return height 1 even though the attributes matching the conditions in the query 
   247  occurred in different events. 
   248  
   249  If we wish to retrieve only heights where the attributes occurred within the same event,
   250  the query syntax is as follows:
   251  
   252  ```bash
   253  curl "localhost:26657/block_search?query=\"sender=Bob AND balance = 200\"&match_events=true"
   254  ```
   255  Currently the default behavior is if `match_events` is set  to false.
   256  
   257  Check out [API docs](https://docs.cometbft.com/v0.34/rpc/#/Info/block_search)
   258  for more information on query syntax and other options.
   259  
   260  **Backwards compatibility**
   261  
   262  Storing the event sequence was introduced in CometBFT 0.34.25. As there are no previous releases of CometBFT,
   263  all nodes running CometBFT will include the event sequence. However, mixed networks running CometBFT v0.34.25 and greater
   264  and Tendermint Core versions before v0.34.25 are possible. On nodes running Tendermint Core, the `match_events` keyword
   265  is ignored and the data is retrieved as if `match_events=false`.
   266  
   267  Additionally, if a node that was running Tendermint Core 
   268  when the data was first indexed, and switched to CometBFT, is queried, it will retrieve this previously indexed
   269  data as if `match_events=false` (attributes can match the query conditions across different events on the same height).