NBXplorer/docs/Postgres-Schema.md
2025-04-02 21:43:58 +09:00

261 lines
19 KiB
Markdown

# Documentation of SQL Schema
Knowing the SQL schema is useful in three situations:
1. You want to query the schema directly as NBXplorer's API doesn't provide an API endpoint for your needs
2. You want to do your own indexer on top of it (in a different language or for a crypto we don't support)
3. You want to create your own API on top of it for features not exposed by NBXplorer's API.
This model is contained in [this SQL script](https://github.com/dgarage/NBXplorer/blob/master/NBXplorer/DBScripts/FullSchema.sql).
There are additional comments there about the meaning of columns and views.
Here is a diagram:
![NBXplorer Database Schema](./images/nxplorer-schema.png)
Or look it interactively on [LiamERD](https://liambx.com/erd/p/github.com/dgarage/NBXplorer/blob/master/NBXplorer/DBScripts/FullSchema.sql?showMode=ALL_FIELDS).
The SQL Schema of NBXplorer is multi-tenant, multi-asset, multi-chain.
It is divided in several parts:
* Blocks and transactions: `blks`, `blks_txs` and `txs`
* Outputs, Inputs and Scripts: `ins`, `outs`, `ins_outs`, `scripts`
* Descriptors: `descriptors`, `descriptors_scripts`
* Wallets: `wallets`, `wallets_wallets`, `wallets_descriptors`, `wallets_scripts`
* Double spending detection: `spent_outs`
Most if the data in those tables is denormalized for better query performance. The denormalization is handled automatically by triggers.
## Using wallets tables
A wallet is a collection of `wallets_descriptors` and `wallets_scripts`.
If a descriptor belongs to a wallet, then any `descriptors_scripts` will be added by trigger in the `wallets_scripts`.
A descriptor is also called `output descriptor` in the Bitcoin jargon, it represents a way to derive a collection of addresses deterministically. How to represent and interpret a descriptor is up to the indexer. (see next part)
It is also possible to add directly scripts to `wallets_scripts`.
On top of it, a wallet can have children by using `wallets_wallets`.
Any script from a child wallet will be added to the scripts of the parent wallet.
This allow you to define a hierarchy of wallet.
## Making your own indexer
An indexer typical flow is the following:
1. Insert block in blks with `confirmed='f'`
2. Call `fetch_matches` with all the ins and outs of a block
3. This will create temporary tables `matched_outs`, `matched_ins` and `matched_conflicts` the indexer can use to inspect what has been matched
4. Call save_matches to instruct the database to insert all matched ins/outs.
5. Turn `confirmed='t'` of the block.
On top of this, an indexer is responsible for creating `descriptors_scripts`. It can do so by monitoring `descriptors.gap`, and if the gap become too low, the indexer need to insert more scripts from the descriptor into `descriptors_scripts`.
This allow indexer to define their own descriptors.
## Views and functions
### Function: to_btc
`to_btc(v NUMERIC)`
Helper function format a satoshi based value into numeric bitcoin.
```SQL
SELECT to_btc(150000000);
```
Output:
```
1.50000000
```
### Function: get_wallets_recent
`get_wallets_recent(in_wallet_id TEXT, in_interval INTERVAL, in_limit INT, in_offset INT)`
`get_wallets_recent(in_wallet_id TEXT, in_code TEXT, in_interval INTERVAL, in_limit INT, in_offset INT)`
See what has going on recently in a wallet.
Better to use on Postgres 13+, as it greatly benefits from incremental sort.
For example, this returns the `5` most recent `KPrAFh3ZOIS5umpbwuYkU0sF8JW+` that happened for one year.
```SQL
SELECT * FROM get_wallets_recent('KPrAFh3ZOIS5umpbwuYkU0sF8JW+', interval '1 year', 5, 0);
```
Output:
| "code" | "asset_id" | "tx_id" | "seen_at" | "balance_change" | "balance_total" |
|--------|------------|--------------------------------------------------------------------|---------------------------------|------------------|-----------------|
| "BTC" | "" | "0e42124df086ef05b0d8cb3a5b2028828b3af27b31d7c5045803cc8541410a9f" | "2022-03-07 17:14:27.269382+00" | -199996.0 | 55020 |
| "BTC" | "" | "529d92bcdba7545befa4b1101256544def3ca9726ebbe4a6bd8122d7762129a2" | "2022-03-06 21:15:48.505111+00" | -229995.0 | 255016 |
| "BTC" | "" | "e9ce704adc9c64e099c2513169d6856bbf2fcdb2de92b465d4d63b67961b2bd2" | "2022-03-04 15:07:08.923081+00" | 200000 | 485011 |
| "BTC" | "" | "0581c1545ce58ecf93d70e97321fa27c7d409bb0666a124470491403cb0d91f4" | "2022-03-03 10:59:44.259948+00" | 230000 | 285011 |
| "BTC" | "" | "c12d1339295d6a69af8dc0fb411b67d5571d31265b385586341a4126bcda76da" | "2022-02-22 17:00:02.697965+00" | -519989.0 | 55011 |
### View: utxos
List all the UTXOs available:
**Warning**: It also returns the UTXO that are confirmed but spent in the mempool, as well as immature utxos and utxos that are being double spent.
If you want the available UTXOs which can be spent use `WHERE input_mempool IS FALSE AND immature IS FALSE`.
```SQL
SELECT * FROM utxos
WHERE input_mempool IS FALSE AND
immature IS FALSE;
```
Output:
| "code" | "tx_id" | "idx" | "script" | "value" | "asset_id" | "input_tx_id" | "input_idx" | "input_mempool" | "immature" | "blk_id" | "blk_idx" | "blk_height" | "mempool" | "replaced_by" | "seen_at" |
|--------|--------------------------------------------------------------------|-------|------------------------------------------------|---------|------------|---------------|-------------|-----------------|------------|--------------------------------------------------------------------|-----------|--------------|-----------|---------------|---------------------------------|
| "BTC" | "6bf5a95c11c819ff22da3933bef2931977b4906c5820bb48ab5336a77502e4a6" | 0 | "00144d60ec59ed28267bf897543a659662cddabd384b" | 19490 | "" | NULL | NULL | False | False | "0000010e1bc83fa20421880db7424740392a459c6ac699cefe7eac5004247e46" | NULL | 58943 | False | NULL | "2021-10-08 14:25:17.96779+00" |
| "BTC" | "6c43019cb584391a766b1c7d5c67f09174d98b4306edeedf8586c8fdbf1048d6" | 0 | "0014f8a89fd6d6d256df24e05b01cade70d2abcdb6f6" | 113872 | "" | NULL | NULL | False | False | "00000088cb1683d609054866ff72131af46dd7f6ee51136fdc2184f5ad1bbbf6" | NULL | 58502 | False | NULL | "2021-10-05 10:21:39.483849+00" |
| "BTC" | "1457ce82a3eb87a8982429e5185896ddee09c773519fa198de3bba4d6c9b42b7" | 0 | "0014002ca9212872715f0a5789d52084a9daecb5dce7" | 3899577 | "" | NULL | NULL | False | False | "000001090d8f4c50c77021026d5ec5fbe2072cb95a1f5592635f70dfc30297d0" | NULL | 59348 | False | NULL | "2021-10-11 12:30:12.008471+00" |
| "BTC" | "75b7ba9a0718a95234e0b72edd2c721f3066be96eb8076f5b8f3f7160498d07f" | 1 | "0014ca76914ddc4fca3c37768e599302765ae633b62c" | 119727 | "" | NULL | NULL | False | False | "0000009d64990b4a2b4bff9511d5c9713124905a0932026db5737a3ad44f0138" | NULL | 60492 | False | NULL | "2021-10-19 10:08:54.301111+00" |
| "BTC" | "9fc4bc1b9dc2d60202fe4e483b54afb5da7efbbfe6966e5e51f4dc6dbc2363b5" | 0 | "0014ad054e107c046e69b5ab5abc1e44d25ac9378ff6" | 9900 | "" | NULL | NULL | False | False | "0000010003e91e0f56787b9e02b5ae424bb681f09f2ec82f8e23b19a7a93b6e6" | NULL | 53506 | False | NULL | "2021-09-01 02:13:21.469658+00" |
### View: descriptors_scripts_unused
List all the unused scripts from descriptors.
Example: Get the next unused address:
```SQL
SELECT * FROM descriptors_scripts_unused
WHERE code='BTC' AND descriptor='7obopunl9/6GVYkej++YMw6DHWXk'
ORDER BY idx LIMIT 1 OFFSET 0;
```
Output:
|code |descriptor |script |idx |addr |d_metadata |ds_metadata|
|-------------------------------------------------------------------------------|----------------------------|--------------------------------------------|----|------------------------------------------|---------------------------|-----------|
|BTC |7obopunl9/6GVYkej++YMw6DHWXk|001482137a4bbc144f8eb5a02fd2acda36c999a96242|3261|tb1qsgfh5jauz38caddq9lf2ek3kexv6jcjzxu9sfw|{"type": "NBXv1-Derivation", "feature": "Deposit", "derivation": "tpubDCtUtuEgpKLQJMu5uUUuDQub7D4CHjCJsfGhwrA3HV6X6CuV7Zj6RizRFwLPNKqkLrd8TM1Xt3QmwumqRRZAUTrVAA9T8uhyMfodChhFykD", "keyPathTemplate": "0/*"}| |
### View: wallets_utxos
List all the wallets UTXOs available:
**Warning**: It also returns the UTXO that are confirmed but spent in the mempool, as well as immature utxos and utxos that are being double spent.
If you want the available UTXOs which can be spent use `WHERE input_mempool IS FALSE AND immature IS FALSE`.
Also, keep in mind that if an UTXO belong to two wallets, then you will see two entries for this UTXO.
```SQL
SELECT * FROM wallets_utxos
WHERE input_mempool IS FALSE AND
immature IS FALSE;
```
Output:
| "wallet_id" | "code" | "tx_id" | "idx" | "script" | "value" | "asset_id" | "input_tx_id" | "input_idx" | "input_mempool" | "immature" | "blk_id" | "blk_idx" | "blk_height" | "mempool" | "replaced_by" | "seen_at" |
|--------------------------------|--------|--------------------------------------------------------------------|-------|------------------------------------------------|---------|------------|---------------|-------------|-----------------|------------|--------------------------------------------------------------------|-----------|--------------|-----------|---------------|---------------------------------|
| "XObUaAdU2HBJk4XHGhIRya3ZObB3" | "BTC" | "6bf5a95c11c819ff22da3933bef2931977b4906c5820bb48ab5336a77502e4a6" | 0 | "00144d60ec59ed28267bf897543a659662cddabd384b" | 19490 | "" | NULL | NULL | False | False | "0000010e1bc83fa20421880db7424740392a459c6ac699cefe7eac5004247e46" | NULL | 58943 | False | NULL | "2021-10-08 14:25:17.96779+00" |
| "XObUaAdU2HBJk4XHGhIRya3ZObB3" | "BTC" | "6c43019cb584391a766b1c7d5c67f09174d98b4306edeedf8586c8fdbf1048d6" | 0 | "0014f8a89fd6d6d256df24e05b01cade70d2abcdb6f6" | 113872 | "" | NULL | NULL | False | False | "00000088cb1683d609054866ff72131af46dd7f6ee51136fdc2184f5ad1bbbf6" | NULL | 58502 | False | NULL | "2021-10-05 10:21:39.483849+00" |
| "ZDP5CJ1pOWLITR1API3aJkEpdQz+" | "BTC" | "1457ce82a3eb87a8982429e5185896ddee09c773519fa198de3bba4d6c9b42b7" | 0 | "0014002ca9212872715f0a5789d52084a9daecb5dce7" | 3899577 | "" | NULL | NULL | False | False | "000001090d8f4c50c77021026d5ec5fbe2072cb95a1f5592635f70dfc30297d0" | NULL | 59348 | False | NULL | "2021-10-11 12:30:12.008471+00" |
| "ZDP5CJ1pOWLITR1API3aJkEpdQz+" | "BTC" | "75b7ba9a0718a95234e0b72edd2c721f3066be96eb8076f5b8f3f7160498d07f" | 1 | "0014ca76914ddc4fca3c37768e599302765ae633b62c" | 119727 | "" | NULL | NULL | False | False | "0000009d64990b4a2b4bff9511d5c9713124905a0932026db5737a3ad44f0138" | NULL | 60492 | False | NULL | "2021-10-19 10:08:54.301111+00" |
| "ZDP5CJ1pOWLITR1API3aJkEpdQz+" | "BTC" | "9fc4bc1b9dc2d60202fe4e483b54afb5da7efbbfe6966e5e51f4dc6dbc2363b5" | 0 | "0014ad054e107c046e69b5ab5abc1e44d25ac9378ff6" | 9900 | "" | NULL | NULL | False | False | "0000010003e91e0f56787b9e02b5ae424bb681f09f2ec82f8e23b19a7a93b6e6" | NULL | 53506 | False | NULL | "2021-09-01 02:13:21.469658+00" |
| "ZDP5CJ1pOWLITR1API3aJkEpdQz+" | "BTC" | "fec5c75b87378068f1d7d3276e90b2579e5fad8854a7002e14c275f3da9823be" | 0 | "00145674d00638e0da30ae216df4bb539e94cd2d13d1" | 1168447 | "" | NULL | NULL | False | False | "000000b43e91de514fec8f9fc418ecb1e0a2b1d14027b01abaad9d4e39e19e8d" | NULL | 60590 | False | NULL | "2021-10-20 02:18:16.980641+00" |
### View: wallets_balances
See the current balance of wallets.
* `confirmed_balance` only include the sum of value of all currently confirmed UTXOs.
* `unconfirmed_balance` will become the next `confirmed_balance` of the wallet if all non-double spent transactions get confirmed.
* `immature_balance` the sum of utxos that can't be spent before they are immature (ie, the UTXO comes from a coinbase that is less than 100 blocks old)
* `available_balance` is the balance that can be spent. (`unconfirmed_balance` - `immature_balance`)
```SQL
SELECT * FROM wallets_balances
```
Output:
| "wallet_id" | "code" | "asset_id" | "unconfirmed_balance" | "confirmed_balance" | "available_balance" | "immature_balance" |
|--------------------------------|--------|------------|-----------------------|---------------------|---------------------|--------------------|
| "4Tdfhh5kK8TFKcDVvsp6xdKOFdmA" | "BTC" | "" | 389520 | 389520 | 389520 | 0 |
| "7ERM4iRjhk1gmeyHxEJQp0fhzh8z" | "BTC" | "" | 420000000 | 420000000 | 420000000 | 0 |
| "IIKKVY87qBLjbaa/KAfUfcNP9HeC" | "BTC" | "" | 100000 | 100000 | 100000 | 0 |
| "KPrAFh3ZOIS5umpbwuYkU0sF8JW+" | "BTC" | "" | 55020 | 55020 | 55020 | 0 |
| "Q+z/7E0DCjazcm5Q7t9wZSoEJ0IP" | "BTC" | "" | 200000 | 200000 | 200000 | 0 |
| "XObUaAdU2HBJk4XHGhIRya3ZObB3" | "BTC" | "" | 133362 | 133362 | 133362 | 0 |
| "ZDP5CJ1pOWLITR1API3aJkEpdQz+" | "BTC" | "" | 5197651 | 5197651 | 5197651 | 0 |
### Materialized view: wallets_history
Provide an history of wallets (time ordered list of wallet_id, code, asset_id, balance-change, total-balance), it only takes into account confirmed transactions.
This materialized view is intensive to refresh (for 220K transactions, it takes around 5 seconds)
This is meant to be used for reports and histograms.
If you want the latest history of a wallet, use `get_wallets_recent` instead.
To refresh the materialized view, use `SELECT wallets_history_refresh();`. This will return `true` if the view has been refreshed.
```SQL
SELECT * FROM wallets_history WHERE wallet_id='KPrAFh3ZOIS5umpbwuYkU0sF8JW+';
```
Output:
| "wallet_id" | "code" | "asset_id" | "tx_id" | "seen_at" | "balance_change" | "balance_total" | "nth" |
|--------------------------------|--------|------------|--------------------------------------------------------------------|---------------------------------|------------------|-----------------|-------|
| "KPrAFh3ZOIS5umpbwuYkU0sF8JW+" | "BTC" | "" | "0e42124df086ef05b0d8cb3a5b2028828b3af27b31d7c5045803cc8541410a9f" | "2022-03-07 17:14:27.269382+00" | -199996 | 55020 | 10 |
| "KPrAFh3ZOIS5umpbwuYkU0sF8JW+" | "BTC" | "" | "529d92bcdba7545befa4b1101256544def3ca9726ebbe4a6bd8122d7762129a2" | "2022-03-06 21:15:48.505111+00" | -229995 | 255016 | 9 |
| "KPrAFh3ZOIS5umpbwuYkU0sF8JW+" | "BTC" | "" | "e9ce704adc9c64e099c2513169d6856bbf2fcdb2de92b465d4d63b67961b2bd2" | "2022-03-04 15:07:08.923081+00" | 200000 | 485011 | 8 |
| "KPrAFh3ZOIS5umpbwuYkU0sF8JW+" | "BTC" | "" | "0581c1545ce58ecf93d70e97321fa27c7d409bb0666a124470491403cb0d91f4" | "2022-03-03 10:59:44.259948+00" | 230000 | 285011 | 7 |
| "KPrAFh3ZOIS5umpbwuYkU0sF8JW+" | "BTC" | "" | "c12d1339295d6a69af8dc0fb411b67d5571d31265b385586341a4126bcda76da" | "2022-02-22 17:00:02.697965+00" | -519989 | 55011 | 6 |
| "KPrAFh3ZOIS5umpbwuYkU0sF8JW+" | "BTC" | "" | "081466d46632b56d28260b928fea87addeed24cfd25e3d6504b5ddfacf6176e1" | "2022-02-04 10:42:31.059182+00" | 310000 | 575000 | 5 |
| "KPrAFh3ZOIS5umpbwuYkU0sF8JW+" | "BTC" | "" | "f0165974071064b32122449dbf72a67ab884723923e8536efb5cf4301bcb7469" | "2022-02-04 10:22:04.47652+00" | 164000 | 265000 | 4 |
| "KPrAFh3ZOIS5umpbwuYkU0sF8JW+" | "BTC" | "" | "a4e165729e5fb406d878b3c9c8dfabd1a3465084c2c2b63602fd8704c41442ac" | "2022-02-04 09:40:00.427892+00" | 101000 | 101000 | 3 |
| "KPrAFh3ZOIS5umpbwuYkU0sF8JW+" | "BTC" | "" | "61247ae11a770963ffa26a63bb4ac03a14f759f7691eda8ce10175db6b6aaa77" | "2021-10-11 13:09:22.977488+00" | -850000 | 0 | 2 |
| "KPrAFh3ZOIS5umpbwuYkU0sF8JW+" | "BTC" | "" | "6bf5a95c11c819ff22da3933bef2931977b4906c5820bb48ab5336a77502e4a6" | "2021-10-08 14:25:17.96779+00" | 850000 | 850000 | 1 |
### Function: get_wallets_histogram
`get_wallets_histogram(in_wallet_id TEXT, in_code TEXT, in_asset_id TEXT, in_from TIMESTAMPTZ, in_to TIMESTAMPTZ, in_interval INTERVAL)`
This function depends on `wallets_history`, as such, you should make sure the materialized view is refreshed time for up-to-date histogram.
For example, this will show how the balance of `KPrAFh3ZOIS5umpbwuYkU0sF8JW+` changed every week from `2022-01-23` to now.
```SQL
SELECT * FROM get_wallets_histogram('KPrAFh3ZOIS5umpbwuYkU0sF8JW+', 'BTC', '', '2022-01-23', CURRENT_TIMESTAMP, interval '1 week');
```
Output:
| "date" | "balance_change" | "balance" |
|--------------------------|------------------|-----------|
| "2022-01-23 00:00:00+00" | 0 | 0 |
| "2022-01-30 00:00:00+00" | 575000 | 575000 |
| "2022-02-06 00:00:00+00" | 0 | 575000 |
| "2022-02-13 00:00:00+00" | 0 | 575000 |
| "2022-02-20 00:00:00+00" | -519989 | 55011 |
| "2022-02-27 00:00:00+00" | 430000 | 485011 |
| "2022-03-06 00:00:00+00" | -429991 | 55020 |
| "2022-03-13 00:00:00+00" | 0 | 55020 |
| "2022-03-20 00:00:00+00" | 0 | 55020 |
### Function: nbxv1_get_wallet_id
`nbxv1_get_wallet_id(in_code TEXT, in_scheme_or_address TEXT) RETURNS TEXT`
This function is specific to the current API of NBXplorer. It calculates the `wallet_id` based on the chain code (ie. `BTC`) and an address or derivation scheme.
### Function: nbxv1_get_descriptor_id
`nbxv1_get_descriptor_id(in_code TEXT, in_scheme TEXT, in_feature TEXT) RETURNS TEXT`
This function is specific to the current API of NBXplorer. It calculates the `descriptor_id` based on the chain code (ie. `BTC`), a derivation scheme, and the feature (ie. `Deposit` or `Change`).