|
| 1 | +# BlockDAO |
| 2 | +BlockDAO manages data access to the blockchain, for example reading a raw data block, querying a transaction by hash, and committing a block to the blockchain |
| 3 | + |
| 4 | +The blockchain data consist of multiple block files (chain.db, chain-00000001.db, etc), the state trie db file (trie.db), and the index db (index.db) |
| 5 | + |
| 6 | +We'll be focusing on block file in the following |
| 7 | + |
| 8 | +## Legacy file (before db split activation at Ithaca height) |
| 9 | +Each db file is a key-value db storing raw data in multiple buckets, using *block hash as the key* |
| 10 | +1. block header in bucket named "bhr" |
| 11 | +2. block body in bucket named "bbd" |
| 12 | +3. block footer in bucket named "bfr" |
| 13 | +4. block receipt in bucket named "rpt" |
| 14 | + |
| 15 | +In particular, the very first db file (`chain.db`) stores the additional information: |
| 16 | +1. the height and hash of the tip block is stored in bucket named "blk" |
| 17 | +2. the height/hash mapping of all blocks in bucket named "h2h" |
| 18 | +3. transaction log of all blocks in bucket named "syl" |
| 19 | + |
| 20 | +When committing a block, 2 things happen sequentially: |
| 21 | +1. block data are written into corresponding db file (like chain-00000001.db) |
| 22 | +2. height/hash of the new block, height/hash mapping, and transaction log are written to `chain.db` |
| 23 | + |
| 24 | +## New file (after db split activation) |
| 25 | +The request of a new file format design comes from 2 observations: |
| 26 | + |
| 27 | +- Storing the height/hash mapping of all blocks in the first file (chain.db) has led to dependency of the BlockDAO service on this special file, which is a dependency we want to remove |
| 28 | +- Using hash as primary key and breaking a block into 3 parts has brought about the following issues: |
| 29 | + 1) it costs 3 DB reads to fetch a block |
| 30 | + 2) it consumed more storage space. Earlier testing result shows that using block height as (single incrementing) key and storing the whole block can reduce file size by 30% (10GB chain.db reduced to 7GB using new file format) |
| 31 | + |
| 32 | +The new file is still a key-value db, but *using block height as the key* |
| 33 | +1. the height/hash mapping, and transaction log are written in the file itself (to remove dependency on `chain.db`) |
| 34 | +2. in addition to height/hash of tip block, we also store the height of the starting block in this file. Doing so allows us to quickly know if a block is stored within a particular db file (starting height <= height <= tip height) |
| 35 | + |
| 36 | +With these adjustments, each block db file is autonomous, meaning it can serve block/transaction query in itself, without having to rely on any other special db or index db |
| 37 | + |
| 38 | +Here's the comparison |
| 39 | + |
| 40 | +File | Primary key | Height/hash mapping | Transaction log | Start height |
| 41 | +--- | --- | :---: | :---: | :---: |
| 42 | +Legacy | block hash | No (except `chain.db') | No (except `chain.db') | No |
| 43 | +New | block height | Yes | Yes | Yes |
| 44 | + |
| 45 | +## Starting the chain soley from state DB |
| 46 | +Given the chain db size steadily increasing (today about 30GB), it could become curbersome that a node has to carry on all the history files for it to work. |
| 47 | +Now with the help of autonomous db file restructure, we want to achieve a more flexible and lightweight configuration: |
| 48 | + |
| 49 | +1. chain db are broken into multiple data files with user-defined segment size (default 4GB) |
| 50 | +2. build the intelligence into BlockDAO, such that a node is able to start up and running from any block db file, or even without a block db file (in which case BlockDAO will create a new one and start from there) |
| 51 | + |
| 52 | +Here are 3 examples: |
| 53 | + |
| 54 | +1. node starts from `chain-00000002.db` which is the latest db file, node continues to add blocks, creating new file `chain-00000003.db` once the current file size grows to defined segment size |
| 55 | +2. node starts without any chain db file. BlockDAO will create new file `chain.db` and starts adding blocks into it. Once the file size grows to defined segment size, a new file `chain-00000001.db` will be created |
| 56 | +3. node starts from `chain-00000002.db` which has height 500. The next block comes in with height = 1000, BlockDAO will create a new file `chain-00000003.db` and starts adding blocks into it. Once the file size grows to defined segment size, a new file `chain-00000004.db` will be created |
| 57 | + |
| 58 | +In example 3 above, blocks 501~1000 are missing, we might consider improving blocksync module's functionality to reconcile this kind of data missing/inconsistency. This could be another topic for enhancement |
| 59 | + |
| 60 | +## Managing legacy and new files in BlockDAO |
| 61 | +In order to handle each individual db file we abstract a FileDAO interface, which contains API to read/write block/action in current BlockDAO interface |
| 62 | +```go |
| 63 | +FileDAO interface { |
| 64 | + Start(ctx context.Context) error |
| 65 | + Stop(ctx context.Context) error |
| 66 | + Height() (uint64, error) |
| 67 | + GetBlockHash(uint64) (hash.Hash256, error) |
| 68 | + GetBlockHeight(hash.Hash256) (uint64, error) |
| 69 | + GetBlock(hash.Hash256) (*block.Block, error) |
| 70 | + GetBlockByHeight(uint64) (*block.Block, error) |
| 71 | + GetReceipts(uint64) ([]*action.Receipt, error) |
| 72 | + ContainsTransactionLog() bool |
| 73 | + TransactionLogs(uint64) (*iotextypes.TransactionLogs, error) |
| 74 | + PutBlock(context.Context, *block.Block) error |
| 75 | + DeleteTipBlock() error |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +and a FileDAONew interface for the new db file |
| 80 | + |
| 81 | +```go |
| 82 | +FileDAONew interface { |
| 83 | + FileDAO |
| 84 | + Bottom() (uint64, error) |
| 85 | + ContainsHeight(uint64) bool |
| 86 | +} |
| 87 | +``` |
| 88 | + |
| 89 | +the fileDAO object to implement the interface |
| 90 | + |
| 91 | +```go |
| 92 | +// fileDAO implements FileDAO |
| 93 | +fileDAO struct { |
| 94 | + currFd FileDAO |
| 95 | + legacyFd FileDAO |
| 96 | + newFd map[uint64]FileDAONew |
| 97 | +} |
| 98 | +``` |
| 99 | + |
| 100 | +1. legacyFd is the interface that manages **all** legacy files |
| 101 | +2. newFd is a map of FileDAONew interface, each of which points to **one** new file |
| 102 | +3. currFd points to the newest db file (that commit block should write to) |
| 103 | + |
| 104 | + |
0 commit comments