参考资料:
文章目录
什么是账本?
Fabric 中的账本存储了有关业务对象的重要事实信息,既包括了对象属性当前的值,也包括产生这些当前值的交易的历史。这两个概念可以用下面的术语来概括,即账本包括以下两个主要内容:
- 世界状态(代表着账本状态的当前值)
- 区块链(代表着账本的交易记录)
一个通道持有一本账本,但是该通道上的所有 peer 节点都有该账本的副本,即逻辑上账本只有一份,但是物理上账本存储在每个 peer 节点的本地数据库中,且每个 peer 节点的账本保持一致(通过排序共识)。
下面来讲一下关于世界状态与区块链的相关概念
世界状态
世界状态将账本的当前值保存为唯一的账本状态。程序通常会需要账本对象当前的值,如果遍历整个区块链来计算对象的当前值会很麻烦——那么我们就可以从世界状态中获取当前值。
当我们调用链码发起查询操作的时候,查的就是世界状态当中的值,因为查询操作不会对账本的当前状态造成任何改变。
而当我们调用链码发起写操作(更改,删除,增加)时,应用程序提交一个交易提案到背书节点,背书节点背书之后返回,应用程序再将交易发送给排序节点排序。之后排序节点将交易打包成区块,将区块分发给各个组织的 peer 节点。此时,如果该交易是一笔无效交易(比如验证签名失败,背书失败等等原因),那么该笔交易只会被存储到本地的交易记录中,但是不会更改账本的世界状态——即无效交易不会导致账本世界状态的改变。
另外,世界状态中的每个状态都有一个版本号,版本号供 Fabric 内部使用,并且每次状态更改时版本号都会发生递增。举个例子,其当前状态版本号都是0,当发生了一次对账本对象的写操作的时候,账本的状态就发生了变化,这样的新的世界状态就产生了,其中的状态的版本号就是1。
每当更新状态时,都会检查该状态的版本,以确保当前状态与背书时的版本相匹配。这就确保了世界状态是按照预期进行更新的,没有发生并发更新,是并发安全的。
首次创建账本时,世界状态是空的。因为区块链上记录了所有代表有效世界状态更新的交易,所以任何时候都可以从区块链中重新生成世界状态。这样一来就变得非常方便,例如,创建节点时会自动生成世界状态。此外,如果某个节点发生异常,重启该节点时能够在接受交易之前重新生成世界状态。
区块链
区块链上存储的是账本的交易历史记录,就好比支付宝上的本月账单可以查看本月的每笔交易,不管是收入还是支出。
区块链的结构是一群相互链接的区块的序列化日志,其中每个区块都包含一系列交易,各项交易代表了一个对世界状态进行的操作。区块链中的这些区块是以后指向链接起来的,即后面的区块会去指向前面的区块,以此形成的一个后指向的链状结构。
区块链总是被作为一个文件来实现,而与之相反的是,世界状态被作为一个数据库来实现。这是一个明智的设计,因为区块链数据结构高度偏向于非常小的一组简单操作。
通俗地来说,一个通道拥有一个账本,一个账本有一条后指向的区块链,那么可以这么理解,一个通道有一条区块链,为了方便理解它们的概念,可以暂时简单地这么认为——一条区块链就是一个通道。
区块链的第一个区块被称作创世区块,它包含着整个通道的配置交易信息,不包含任何用户交易。
关于区块的相关概念可以看我之前写的文章——Hyperledger Fabric从源码分析区块,我这里就不过多阐述了。
命名空间
前面我们讨论账本时,似乎它只包括一个世界状态和一条区块链,但这显然过于简单化了。实际上,每个链码都有自己的世界状态,并且与所有其他链码的世界状态分离。世界状态位于一个命名空间中,因此只有位于同一链码中的智能合约才能访问一个给定的命名空间。
区块链没有命名空间。它包含来自许多不同链码命名空间的交易。
上面都是我结合自己已有的知识与官方文档,总结出来的一些概念性的东西,这有助于我们先从本质上理解账本,世界状态及区块链的相关概念,下面我会从源码角度做一些简单的分析。
源码分析
首先,之前说到,每个 peer 节点都拥有一份账本的副本,当 peer 节点启动时,会去加载这个账本,这样我们就可以从 peer 节点的启动 serve 函数中找寻蛛丝马迹——peer/node/start.go
func serve(args []string) error {
//initialize resource management exit
ledgermgmt.Initialize(
&ledgermgmt.Initializer{
CustomTxProcessors: peer.ConfigTxProcessors,
PlatformRegistry: pr,
DeployedChaincodeInfoProvider: deployedCCInfoProvider,
MembershipInfoProvider: membershipInfoProvider,
MetricsProvider: metricsProvider,
HealthCheckRegistry: opsSystem,
},
)
}
主要是调用了账本管理包的初始化函数进行初始化,跟踪进行看下
var openedLedgers map[string]ledger.PeerLedger
var ledgerProvider ledger.PeerLedgerProvider
var initialized bool
func initialize(initializer *Initializer) {
logger.Info("Initializing ledger mgmt")
lock.Lock()
defer lock.Unlock()
// 初始化initialized 与 openedLedgers
initialized = true
openedLedgers = make(map[string]ledger.PeerLedger)
// 执行customtx的初始化,主要是做了是初始化了一个 Processors对象,该对象存储着指定交易类型及它对应的处理方法
customtx.Initialize(initializer.CustomTxProcessors)
// 执行链码注册账本事件的初始化,包括回调函数等信息
cceventmgmt.Initialize(&chaincodeInfoProviderImpl{
initializer.PlatformRegistry,
initializer.DeployedChaincodeInfoProvider,
})
finalStateListeners := addListenerForCCEventsHandler(initializer.DeployedChaincodeInfoProvider, []ledger.StateListener{})
// 创建账本服务提供者
provider, err := kvledger.NewProvider()
if err != nil {
panic(errors.WithMessage(err, "Error in instantiating ledger provider"))
}
// 账本服务提供者初始化
err = provider.Initialize(&ledger.Initializer{
StateListeners: finalStateListeners,
DeployedChaincodeInfoProvider: initializer.DeployedChaincodeInfoProvider,
MembershipInfoProvider: initializer.MembershipInfoProvider,
MetricsProvider: initializer.MetricsProvider,
HealthCheckRegistry: initializer.HealthCheckRegistry,
})
if err != nil {
panic(errors.WithMessage(err, "Error initializing ledger provider"))
}
// 赋值给全局账本服务提供者
ledgerProvider = provider
logger.Info("ledger mgmt initialized")
}
在该函数中,主要初始化了三个全局变量—— openedLedgers,ledgerProvider,initialized。
initialized = true
openedLedgers = make(map[string]ledger.PeerLedger)
provider, err := kvledger.NewProvider()
ledgerProvider = provider
initialized 表示是否初始化,openedLedgers 存放 peer 的账本映射,而 ledgerProvider 表示的是账本服务提供者 kvledger,跟踪进去看下它的原型
// NewProvider instantiates a new Provider.
// This is not thread-safe and assumed to be synchronized be the caller
func NewProvider() (ledger.PeerLedgerProvider, error) {
logger.Info("Initializing ledger provider")
// Initialize the ID store (inventory of chainIds/ledgerIds)
idStore := openIDStore(ledgerconfig.GetLedgerProviderPath())
// Initialize the history database (index for history of values by key)
historydbProvider := historyleveldb.NewHistoryDBProvider()
fileLock := leveldbhelper.NewFileLock(ledgerconfig.GetFileLockPath())
if err := fileLock.Lock(); err != nil {
return nil, errors.Wrap(err, "as another peer node command is executing,"+
" wait for that command to complete its execution or terminate it before retrying")
}
logger.Info("ledger provider Initialized")
provider := &Provider{idStore, nil,
nil, historydbProvider, nil, nil, nil, nil, nil, nil, fileLock}
return provider, nil
}
// PeerLedgerProvider provides handle to ledger instances
type PeerLedgerProvider interface {
Initialize(initializer *Initializer) error
Create(genesisBlock *common.Block) (PeerLedger, error)
Open(ledgerID string) (PeerLedger, error)
Exists(ledgerID string) (bool, error)
List() ([]string, error)
Close()
}
// Provider implements interface ledger.PeerLedgerProvider
type Provider struct {
idStore *idStore // ledgerID数据库对象
ledgerStoreProvider *ledgerstorage.Provider // block数据存储对象
vdbProvider privacyenabledstate.DBProvider // 状态数据库存储对象
historydbProvider historydb.HistoryDBProvider // 历史数据存储对象
configHistoryMgr confighistory.Mgr
stateListeners []ledger.StateListener
bookkeepingProvider bookkeeping.Provider
initializer *ledger.Initializer
collElgNotifier *collElgNotifier
stats *stats
fileLock *leveldbhelper.FileLock
}
kvledger.NewProvider() 函数返回的即是一个 Provider 对象,其中最重要的是注释的几个对象,它们分别代表着不同的数据库,存储着不同的数据库。其中在 kvledger.NewProvider() 中分别初始化了 idStore 以及 historydbProvider 对象,剩下的两个对象会在 Provider 的 Initializer 中初始化
// Initialize implements the corresponding method from interface ledger.PeerLedgerProvider
func (provider *Provider) Initialize(initializer *ledger.Initializer) error {
//..........
// 初始化block数据存储对象
provider.ledgerStoreProvider = ledgerstorage.NewProvider(initializer.MetricsProvider)
// 初始化状态数据存储对象
provider.vdbProvider, err = privacyenabledstate.NewCommonStorageDBProvider(provider.bookkeepingProvider, initializer.MetricsProvider, initializer.HealthCheckRegistry)
// .........
}
从头梳理一下 ledgerProvider 的创建过程哈
- 在 serve 函数中调用了 ledgermgmt.Initialize() 函数
- 在 ledgermgmt.Initialize() 函数中初始化了三个全局变量,openedLedgers,ledgerProvider,initialized,而 ledgerProvider主要就是账本服务的提供者
- 通过 kvledger.NewProvider() 函数创建了一个 provider,它其中主要有四个数据库存储对象,此时已经完成了idStore 以及 historydbProvider 这两个数据库对象的创建,之后调用 provider 的 Initializer 方法进行初始化,在其中创建了ledgerStoreProvider 与 vdbProvider 这两个数据库存储对象
- ledgerStoreProvider 中的主要四个数据库存储对象创建完成
下面来看下这几个对象创建的时候都做了什么吧
idStore
该对象用于存储 ledgerID,它用 openIDStore 函数创建
idStore := openIDStore(ledgerconfig.GetLedgerProviderPath())
// idStora对象
type idStore struct {
db *leveldbhelper.DB
}
// GetLedgerProviderPath returns the filesystem path for storing ledger ledgerProvider contents
func GetLedgerProviderPath() string {
return filepath.Join(GetRootPath(), confLedgerProvider)
}
// GetLedgerProviderPath 函数返回的是ledgerProvider目录,路径如下:
// /var/hyperledger/production/ledgersData/ledgerProvider
func openIDStore(path string) *idStore {
db := leveldbhelper.CreateDB(&leveldbhelper.Conf{DBPath: path})
db.Open()
return &idStore{db}
}
// DB - a wrapper on an actual store
type DB struct {
conf *Conf
db *leveldb.DB // levelDB对象
dbState dbState
mux sync.Mutex
readOpts *opt.ReadOptions
writeOptsNoSync *opt.WriteOptions
writeOptsSync *opt.WriteOptions
}
// CreateDB constructs a `DB`
func CreateDB(conf *Conf) *DB {
readOpts := &opt.ReadOptions{}
writeOptsNoSync := &opt.WriteOptions{}
writeOptsSync := &opt.WriteOptions{}
writeOptsSync.Sync = true
return &DB{
conf: conf,
dbState: closed,
readOpts: readOpts,
writeOptsNoSync: writeOptsNoSync,
writeOptsSync: writeOptsSync}
}
// Open opens the underlying db
func (dbInst *DB) Open() {
dbInst.mux.Lock()
defer dbInst.mux.Unlock()
if dbInst.dbState == opened {
return
}
dbOpts := &opt.Options{}
dbPath := dbInst.conf.DBPath
var err error
var dirEmpty bool
// 如果目录不存在则创建
if dirEmpty, err = util.CreateDirIfMissing(dbPath); err != nil {
panic(fmt.Sprintf("Error creating dir if missing: %s", err))
}
dbOpts.ErrorIfMissing = !dirEmpty
// 初始化 levelDB 对象
if dbInst.db, err = leveldb.OpenFile(dbPath, dbOpts); err != nil {
panic(fmt.Sprintf("Error opening leveldb: %s", err))
}
dbInst.dbState = opened
}
该函数很简单,它调用 CreateDB 函数创建了一个 idStore 对象,该对象包含了一个 leveldbhelper.DB 对象,里面封装了 levelDB 对象(最终操作数据库的对象),并调用了 Open 函数打开数据库文件,将数据信息存储到内存中的对象里(可以理解为 MySQL 的连接到数据库)
historydbProvider
该对象用于存储历史数据
// Initialize the history database (index for history of values by key)
historydbProvider := historyleveldb.NewHistoryDBProvider()
// HistoryDBProvider implements interface HistoryDBProvider
type HistoryDBProvider struct {
dbProvider *leveldbhelper.Provider
}
// leveldbhelper.Provider
type Provider struct {
db *DB // 这个即leveldbhelper.DB
dbHandles map[string]*DBHandle
mux sync.Mutex
}
// NewHistoryDBProvider instantiates HistoryDBProvider
func NewHistoryDBProvider() *HistoryDBProvider {
// dbPath = /var/hyperledger/production/ledgersData/historyLeveldb
dbPath := ledgerconfig.GetHistoryLevelDBPath()
logger.Debugf("constructing HistoryDBProvider dbPath=%s", dbPath)
dbProvider := leveldbhelper.NewProvider(&leveldbhelper.Conf{DBPath: dbPath})
return &HistoryDBProvider{dbProvider}
}
// NewProvider constructs a Provider
func NewProvider(conf *Conf) *Provider {
db := CreateDB(conf)
db.Open()
return &Provider{db, make(map[string]*DBHandle), sync.Mutex{}}
}
historydbProvider 与 idStore 的结构稍有不同,它底层是一个 leveldbhelper.Provider 结构,比起 idStore,除了拥有 leveldbhelper.DB 外,还拥有一些别的变量。除了这个区别,其余其实没有什么差别,调用了 CreateDB 函数,并调用 Open 函数,最终返回了一个 leveldbhelper.Provider 。
ledgerStoreProvider
该对象存储的账本数据,包括区块数据与私密数据,这里我们暂时不讨论私密数据(其实是这块我还没看到=_=,之后我会专门搞一篇文章分析分析私密数据)。
provider.ledgerStoreProvider = ledgerstorage.NewProvider(initializer.MetricsProvider)
// Provider encapusaltes two providers 1) block store provider and 2) and pvt data store provider
type Provider struct {
blkStoreProvider blkstorage.BlockStoreProvider // 区块数据存储
pvtdataStoreProvider pvtdatastorage.Provider // 私密数据存储
}
// NewProvider returns the handle to the provider
func NewProvider(metricsProvider metrics.Provider) *Provider {
// Initialize the block storage
// 第一部分,创建 indexConfig 对象
indexConfig := &blkstorage.IndexConfig{AttrsToIndex: attrsToIndex}
// 第二部分,创建 DumpConf 对象
dumpConf := &datadump.DumpConf{
Enabled: ledgerconfig.IsDataDumpEnabled(),
DumpDir: ledgerconfig.GetDataDumpPath(),
LoadDir: ledgerconfig.GetDataLoadPath(),
MaxFileLimit: ledgerconfig.GetDataDumpFileLimit(),
DumpCron: ledgerconfig.GetDataDumpCron(),
DumpInterval: ledgerconfig.GetDataDumpInterval(),
LoadRetryTimes: ledgerconfig.GetDataLoadRetryTimes(),
}
// 第三部分,创建 Conf 对象
blockStoreProvider := fsblkstorage.NewProvider(
fsblkstorage.NewConf(ledgerconfig.GetBlockStorePath(), ledgerconfig.GetMaxBlockfileSize(), dumpConf),
indexConfig,
metricsProvider)
// 这部分不管
pvtStoreProvider := pvtdatastorage.NewProvider()
return &Provider{blockStoreProvider, pvtStoreProvider}
}
ledgerStoreProvider 在创建的时候就比较复杂了,首先 ledgerstorage.Provider 对象包含了区块的 provider 与私密数据的 provider,我们暂时不看私密数据。在 NewProvider 函数中,第一步创建了一个 IndexConfig 对象,来看下这部分相关的代码
// IndexableAttr represents an indexable attribute
type IndexableAttr string
// constants for indexable attributes
const (
IndexableAttrBlockNum = IndexableAttr("BlockNum")
IndexableAttrBlockHash = IndexableAttr("BlockHash")
IndexableAttrTxID = IndexableAttr("TxID")
IndexableAttrBlockNumTranNum = IndexableAttr("BlockNumTranNum")
IndexableAttrBlockTxID = IndexableAttr("BlockTxID")
IndexableAttrTxValidationCode = IndexableAttr("TxValidationCode")
)
// IndexConfig - a configuration that includes a list of attributes that should be indexed
type IndexConfig struct {
AttrsToIndex []IndexableAttr
}
// 下面这些 string 即上面那些 const 常量
var attrsToIndex = []blkstorage.IndexableAttr{
blkstorage.IndexableAttrBlockHash,
blkstorage.IndexableAttrBlockNum,
blkstorage.IndexableAttrTxID,
blkstorage.IndexableAttrBlockNumTranNum,
//BlockTxID index is necessary to detect duplicateTxID during rollback
blkstorage.IndexableAttrBlockTxID,
blkstorage.IndexableAttrTxValidationCode,
}
IndexConfig 表示块索引配置对象,用于存储块索引字段值,目前存储的索引即为上面 const 定义的常量。可以这么理解,IndexConfig 的目的是让数据库表中为哪些字段建立索引,因而在此记录一下。
NewProvider 的第二部分创建了一个 DumpConf 对象,这是数据 Dump 的配置,具体的配置信息都是在 core.yaml
配置文件中配置的,我拿部分出来看一下
ledger:
blockchain:
dataDump:
enabled: false
dumpDir: /var/hyperledger/production/ledgersData/chains/chains/
loadDir: /var/hyperledger/production/ledgersData/chains/chains/
maxFileLimit: 4096
dumpCron:
- 0 */5 * * * ?
- 0 0 * * * ?
dumpInterval: 5m #24h
loadRetryTimes: 5
NewProvider 的第三部分创建了一个 Conf 对象,来看一下相关代码
// Conf encapsulates all the configurations for `FsBlockStore`
type Conf struct {
blockStorageDir string // block数据存储provider使用的路径
maxBlockfileSize int // 存储文件的大小
dumpConf *datadump.DumpConf
}
func NewConf(blockStorageDir string, maxBlockfileSize int, dumpConf *datadump.DumpConf) *Conf {
if maxBlockfileSize <= 0 {
maxBlockfileSize = defaultMaxBlockfileSize
}
return &Conf{blockStorageDir, maxBlockfileSize, dumpConf}
}
// ledgerconfig.GetBlockStorePath() 得到的是:
// /var/hyperledger/production/ledgersData/chains
// ledgerconfig.GetMaxBlockfileSize() 得到的是64M,表示的是一个区块文件的最大大小
blockStoreProvider := fsblkstorage.NewProvider(
fsblkstorage.NewConf(ledgerconfig.GetBlockStorePath(), ledgerconfig.GetMaxBlockfileSize(), dumpConf),
indexConfig,
metricsProvider)
// FsBlockstoreProvider provides handle to block storage - this is not thread-safe
type FsBlockstoreProvider struct {
conf *Conf
indexConfig *blkstorage.IndexConfig
leveldbProvider *leveldbhelper.Provider // leveldbhelper.Provider
stats *stats
}
// NewProvider constructs a filesystem based block store provider
func NewProvider(conf *Conf, indexConfig *blkstorage.IndexConfig, metricsProvider metrics.Provider) blkstorage.BlockStoreProvider {
// 即创建了 leveldbhelper.Provider,与historyProvider中的那个一样
p := leveldbhelper.NewProvider(&leveldbhelper.Conf{DBPath: conf.getIndexDir()})
// create stats instance at provider level and pass to newFsBlockStore
stats := newStats(metricsProvider)
return &FsBlockstoreProvider{conf, indexConfig, p, stats}
}
type stats struct {
blockchainHeight metrics.Gauge
blockstorageCommitTime metrics.Histogram
}
func newStats(metricsProvider metrics.Provider) *stats {
stats := &stats{}
stats.blockchainHeight = metricsProvider.NewGauge(blockchainHeightOpts)
stats.blockstorageCommitTime = metricsProvider.NewHistogram(blockstorageCommitTimeOpts)
return stats
}
fsblkstorage.NewProvider 函数最终返回了一个 FsBlockstoreProvider 对象,其包含了一个 leveldbhelper.Provider 对象,与 historyProvider 中的那个一样,另外还创建了一个 stats 对象,它传的是一个 metricsProvider 参数,这个参数类型从一开始初始化的时候就一路传进来,我一直没解释,到这个地方才用到。这个其实是一个指标服务提供者,fabric 本身的一些指标设置,比如一些性能指标等等。我对这块了解的暂时不多,之后会看下官方的文档,这里稍做了解就可以。所以 stats 对象我在这里也不过多解释了。
到这整个 ledgerStoreProvider 的创建就完成了。
vdbProvider
该对象用于存储状态,这些状态数据就是当前通道上的指定键的最新值,这也就是常说的世界状态。
provider.vdbProvider, err = privacyenabledstate.NewCommonStorageDBProvider(provider.bookkeepingProvider, initializer.MetricsProvider, initializer.HealthCheckRegistry)
// CommonStorageDBProvider implements interface DBProvider
type CommonStorageDBProvider struct {
statedb.VersionedDBProvider
HealthCheckRegistry ledger.HealthCheckRegistry
bookkeepingProvider bookkeeping.Provider
}
// NewCommonStorageDBProvider constructs an instance of DBProvider
func NewCommonStorageDBProvider(bookkeeperProvider bookkeeping.Provider, metricsProvider metrics.Provider, healthCheckRegistry ledger.HealthCheckRegistry) (DBProvider, error) {
var vdbProvider statedb.VersionedDBProvider
var err error
// couchDB 相关
if ledgerconfig.IsCouchDBEnabled() {
if vdbProvider, err = statecouchdb.NewVersionedDBProvider(metricsProvider); err != nil {
return nil, err
}
} else {
vdbProvider = stateleveldb.NewVersionedDBProvider()
}
// 创建dbprovider
dbProvider := &CommonStorageDBProvider{vdbProvider, healthCheckRegistry, bookkeeperProvider}
// 创建好了以后执行 RegisterHealthChecker ,注册一个HealthChecker
err = dbProvider.RegisterHealthChecker()
if err != nil {
return nil, err
}
return dbProvider, nil
}
可以看到关于状态数据库的创建很简单,如果忽略掉 ConchDB 相关的,只是简单的赋值而已。需要注意的是,该函数最终返回的是一个 DBProvider 接口,而 CommonStorageDBProvider 结构则实现了这个接口,因此,我们来看下这个接口都实现了哪些方法
// DBProvider provides handle to a PvtVersionedDB
type DBProvider interface {
// GetDBHandle returns a handle to a PvtVersionedDB
GetDBHandle(id string) (DB, error)
// Close closes all the PvtVersionedDB instances and releases any resources held by VersionedDBProvider
Close()
}
// DB extends VersionedDB interface. This interface provides additional functions for managing private data state
type DB interface {
statedb.VersionedDB
IsBulkOptimizable() bool
LoadCommittedVersionsOfPubAndHashedKeys(pubKeys []*statedb.CompositeKey, hashedKeys []*HashedCompositeKey) error
GetCachedKeyHashVersion(namespace, collection string, keyHash []byte) (*version.Height, bool)
ClearCachedVersions()
GetChaincodeEventListener() cceventmgmt.ChaincodeLifecycleEventListener
GetPrivateData(namespace, collection, key string) (*statedb.VersionedValue, error)
GetValueHash(namespace, collection string, keyHash []byte) (*statedb.VersionedValue, error)
GetKeyHashVersion(namespace, collection string, keyHash []byte) (*version.Height, error)
GetPrivateDataMultipleKeys(namespace, collection string, keys []string) ([]*statedb.VersionedValue, error)
GetPrivateDataRangeScanIterator(namespace, collection, startKey, endKey string) (statedb.ResultsIterator, error)
GetStateMetadata(namespace, key string) ([]byte, error)
GetPrivateDataMetadataByHash(namespace, collection string, keyHash []byte) ([]byte, error)
ExecuteQueryOnPrivateData(namespace, collection, query string) (statedb.ResultsIterator, error)
ApplyPrivacyAwareUpdates(updates *UpdateBatch, height *version.Height) error
}
GetDBHandle 方法返回了一个 DB 接口,提供的方法如上,另外 DB 接口中还有一个子接口 statedb.VersionedDB
// VersionedDB lists methods that a db is supposed to implement
type VersionedDB interface {
// GetState gets the value for given namespace and key. For a chaincode, the namespace corresponds to the chaincodeId
GetState(namespace string, key string) (*VersionedValue, error)
// GetVersion gets the version for given namespace and key. For a chaincode, the namespace corresponds to the chaincodeId
GetVersion(namespace string, key string) (*version.Height, error)
// GetStateMultipleKeys gets the values for multiple keys in a single call
GetStateMultipleKeys(namespace string, keys []string) ([]*VersionedValue, error)
// GetStateRangeScanIterator returns an iterator that contains all the key-values between given key ranges.
// startKey is inclusive
// endKey is exclusive
// The returned ResultsIterator contains results of type *VersionedKV
GetStateRangeScanIterator(namespace string, startKey string, endKey string) (ResultsIterator, error)
// GetStateRangeScanIteratorWithMetadata returns an iterator that contains all the key-values between given key ranges.
// startKey is inclusive
// endKey is exclusive
// metadata is a map of additional query parameters
// The returned ResultsIterator contains results of type *VersionedKV
GetStateRangeScanIteratorWithMetadata(namespace string, startKey string, endKey string, metadata map[string]interface{}) (QueryResultsIterator, error)
// ExecuteQuery executes the given query and returns an iterator that contains results of type *VersionedKV.
ExecuteQuery(namespace, query string) (ResultsIterator, error)
// ExecuteQueryWithMetadata executes the given query with associated query options and
// returns an iterator that contains results of type *VersionedKV.
// metadata is a map of additional query parameters
ExecuteQueryWithMetadata(namespace, query string, metadata map[string]interface{}) (QueryResultsIterator, error)
// ApplyUpdates applies the batch to the underlying db.
// height is the height of the highest transaction in the Batch that
// a state db implementation is expected to ues as a save point
ApplyUpdates(batch *UpdateBatch, height *version.Height) error
// GetLatestSavePoint returns the height of the highest transaction upto which
// the state db is consistent
GetLatestSavePoint() (*version.Height, error)
// ValidateKeyValue tests whether the key and value is supported by the db implementation.
// For instance, leveldb supports any bytes for the key while the couchdb supports only valid utf-8 string
// TODO make the function ValidateKeyValue return a specific error say ErrInvalidKeyValue
// However, as of now, the both implementations of this function (leveldb and couchdb) are deterministic in returing an error
// i.e., an error is returned only if the key-value are found to be invalid for the underlying db
ValidateKeyValue(key string, value []byte) error
// BytesKeySupported returns true if the implementation (underlying db) supports the any bytes to be used as key.
// For instance, leveldb supports any bytes for the key while the couchdb supports only valid utf-8 string
BytesKeySupported() bool
// Open opens the db
Open() error
// Close closes the db
Close()
}
账本存储目录结构
最后再来看一下账本存储的目录结构,账本存储的目录结构是由 core.yaml
文件定义的 peer.fileSystemPath
指定的,一般是 /var/hyperledger/production/ledgersData
目录
# tree /var/hyperledger/production/ledgersData
/var/hyperledger/production/ledgersData
|-- bookkeeper // 隐私数据持有者数据库
| |-- 000001.log
| |-- CURRENT
| |-- LOCK
| |-- LOG
| `-- MANIFEST-000000
|-- chains //block块存储数据库目录
| |-- chains // 存储各个账本ID,账本1,账本2。。。
| | `-- mychannel // 账本mychannel,实际上是通道名
| | `-- blockfile_000000
| `-- index //block索引数据库目录
| |-- 000001.log
| |-- CURRENT
| |-- LOCK
| |-- LOG
| `-- MANIFEST-000000
|-- configHistory // 历史配置数据库
| |-- 000001.log
| |-- CURRENT
| |-- LOCK
| |-- LOG
| `-- MANIFEST-000000
|-- fileLock // 文件锁目录
| |-- 000001.log
| |-- CURRENT
| |-- LOCK
| |-- LOG
| `-- MANIFEST-000000
|-- historyLeveldb // 历史数据库目录
| |-- 000001.log
| |-- CURRENT
| |-- LOCK
| |-- LOG
| `-- MANIFEST-000000
|-- ledgerProvider // ledgerID数据库目录
| |-- 000001.log
| |-- CURRENT
| |-- LOCK
| |-- LOG
| `-- MANIFEST-000000
|-- pvtdataStore // 私有数据数据库目录
| |-- 000001.log
| |-- CURRENT
| |-- LOCK
| |-- LOG
| `-- MANIFEST-000000
`-- stateLeveldb // 状态数据库目录
|-- 000001.log
|-- CURRENT
|-- LOCK
|-- LOG
`-- MANIFEST-000000
每个子目录下存储的都是 levelDB 相关的文件数据,这里暂时只关心每个子目录具体是做什么的就可以了。