Author: Derek

Introduction to the

Github address: github.com/Bytom/bytom

Gitee address: gitee.com/BytomBlockc…

This chapter introduces Derek interpretation -Bytom source analysis – Persistent storage LevelDB

The author uses MacOS, and other platforms are much the same

Golang Version: 1.8

LevelDB is introduced

The levelDB database is used by default. Leveldb is a very efficient KV database implemented by Google. LevelDB is a single-process service with very high performance. On a 4-core Q6600 CPU machine, over 40W writes per second and over 10W random reads per second. Because Leveldb is a single-process service, you cannot have multiple processes reading or writing to a database at the same time. Only one process or multiple concurrent processes can read and write data at a time. The data store layer stores all the addresses, asset transactions and other information on the chain.

LevelDB add, delete, change and check operations

LevelDB is a high performance K/V storage system developed by Google. In this section, we will introduce how LevelDB can add, delete, change and check LevelDB.

package main

import (
	"fmt"

	dbm "github.com/tendermint/tmlibs/db"
)

var (
	Key        = "TESTKEY"
	LevelDBDir = "/tmp/data"
)

func main() {
	db := dbm.NewDB("test"."leveldb", LevelDBDir)
	defer db.Close()

	db.Set([]byte(Key), []byte("This is a test."))

	value := db.Get([]byte(Key))
	if value == nil {
		return
	}
	fmt.Printf("key:%v, value:%v\n", Key, string(value))

	db.Delete([]byte(Key))
}

// Output
// key:TESTKEY, value:This is a test.
Copy the code

The above Output is the Output result of executing the program.

This program for LevelD to add, delete, change, check operations. Dbm.newdb gets the db object and a directory called test.db will be generated in/TMP /data. This directory holds all the data for the database. Db. Set Sets the value of the key. If the key does not exist, the new value is created; if the key does exist, the value is changed. Db. Get gets the value data in the key. Db. Delete Deletes the data of the key and value.

Than the original chain database

By default, the data is stored in the data directory under the –home parameter. In the case of the Darwin platform, the default database is stored in $HOME/Library/Bytom/data.

  • Accesstoken. db Token information (wallet access control permission) core.db Core database, which stores data related to the main chain. Includes block information, transaction information, and asset information. Db End-to-end node information in the distributed network
  • Trusthistory. Db txdb.db stores transaction related information txfeeds. Stores information about users, assets, transactions, and Utox

All of the above databases are managed by the Database module

Than the original database interface

In the original chain, data persistence is managed by the Database module, but the persistence interface is in protocol/store.go

type Store interface {
	BlockExist(*bc.Hash) bool

	GetBlock(*bc.Hash) (*types.Block, error)
	GetStoreStatus() *BlockStoreState
	GetTransactionStatus(*bc.Hash) (*bc.TransactionStatus, error)
	GetTransactionsUtxo(*state.UtxoViewpoint, []*bc.Tx) error
	GetUtxo(*bc.Hash) (*storage.UtxoEntry, error)

	LoadBlockIndex() (*state.BlockIndex, error)
	SaveBlock(*types.Block, *bc.TransactionStatus) error
	SaveChainStatus(*state.BlockNode, *state.UtxoViewpoint) error
}
Copy the code
  • BlockExist determines whether a block exists based on the hash
  • GetBlock retrieves the block based on the hash
  • GetStoreStatus Obtains the storage status of a store
  • GetTransactionStatus retrieves the status of all transactions in the block based on the hash
  • GetTransactionsUtxo caches all UTXOs associated with the input TXS
  • GetUtxo(* bc.hash) Obtains all UTXOs in the block based on the Hash
  • LoadBlockIndex Loads the block index. It reads all block headers from the DB and caches them in memory
  • SaveBlock stores block and transaction status
  • SaveChainStatus sets the status of the main chain. When the node is started for the first time, the node will determine whether to initialize the main chain according to the content whose key is blockStore.

Prefix than the original chain database key

** database/leveldb/store.go **

var (
	blockStoreKey     = []byte("blockStore")
	blockPrefix       = []byte("B:")
	blockHeaderPrefix = []byte("BH:")
	txStatusPrefix    = []byte("BTS:"))Copy the code
  • BlockStoreKey Main chain status prefix
  • BlockPrefix Block information prefix
  • BlockHeaderPrefix Specifies the prefix of the bulk information
  • TxStatusPrefix Transaction status prefix

GetBlock Query block process analysis

** database/leveldb/store.go **

func (s *Store) GetBlock(hash *bc.Hash) (*types.Block, error) {
	return s.cache.lookup(hash)}Copy the code

** database/leveldb/cache.go **

func (c *blockCache) lookup(hash *bc.Hash) (*types.Block, error) {
	if b, ok := c.get(hash); ok {
		return b, nil
	}

	block, err := c.single.Do(hash.String(), func() (interface{}, error) {
		b := c.fillFn(hash)
		if b == nil {
			return nil, fmt.Errorf("There are no block with given hash %s", hash.String())
		}

		c.add(b)
		return b, nil
	})
	iferr ! = nil {return nil, err
	}
	return block.(*types.Block), nil
}
Copy the code

The GetBlock function will eventually execute the lookup function. The lookup function has two steps:

  • Retrieves the hash value from the cache and returns it if found
  • The fillFn callback function is called if it is queried from the cache. The fillFn callback stores the block information obtained from disk into the cache and returns the block information.

FillFn callback function actually obtain the database/leveldb/store. Go under GetBlock, it will block information and returns from the disk.