参考资料:
什么是账本?
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(ini