Hyperledger Fabric从源码分析账本

参考资料:

文章目录

什么是账本?

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")
}

在该函数中,主要初始化了三个全局变量—— openedLedgersledgerProviderinitialized

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 的创建过程哈

  1. 在 serve 函数中调用了 ledgermgmt.Initialize() 函数
  2. 在 ledgermgmt.Initialize() 函数中初始化了三个全局变量,openedLedgersledgerProviderinitialized,而 ledgerProvider主要就是账本服务的提供者
  3. 通过 kvledger.NewProvider() 函数创建了一个 provider,它其中主要有四个数据库存储对象,此时已经完成了idStore 以及 historydbProvider 这两个数据库对象的创建,之后调用 provider 的 Initializer 方法进行初始化,在其中创建了ledgerStoreProvider 与 vdbProvider 这两个数据库存储对象
  4. 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 相关的文件数据,这里暂时只关心每个子目录具体是做什么的就可以了。

上一篇:centos7搭建部署fabric环境


下一篇:基于Hyperledger Fabric 的数据访问控制方法