fabric2.3版本源码记录_3
fabrci源码记录3
writer:布羽
链码的命名空间
请参考官方文档https://hyperledger-fabric.readthedocs.io/zh_CN/release-2.2/developapps/chaincodenamespace.html?highlight=%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4,
或者
https://blog.csdn.net/Nemoosi/article/details/104727037/
简单来说,
- 一般来说每个链码只有一个智能合约
- 如果多个智能合约关系非常紧密,那么它们应该放在同一个链码中,通常这只在它们共享同一个world state情况下
- 链码命名空间为不同的world state提供了隔离,要注意的是并不能自选链码命名空间,它是由Hyperledger Fabric分配的,并直接映射到链码名称
- 链码对链码的调用使用invokeChaincode()API接口,两个链码必须安装在同一个节点上
- 如果是查询world state,那么调用可以在与调用方链码不同的通道中进行,如果是更新world state,那么调用必须和调用方链码在同一通道中。
账本管理2
ledgermgmt.Initializer结构体,封装ledger模块所需要的外部依赖,在建立新的账本管理器的时候作为传入参数
core/ledger/ledgermgmt/ledger_mgmt.go
type Initializer struct {
//自定义交易处理器
CustomTxProcessors map[common.HeaderType]ledger.CustomTxProcessor
//状态监听器列表
StateListeners []ledger.StateListener
//部署链码信息提供者
DeployedChaincodeInfoProvider ledger.DeployedChaincodeInfoProvider
//成员关系信息提供者
MembershipInfoProvider ledger.MembershipInfoProvider
//链码生命周期信息提供者
ChaincodeLifecycleEventProvider ledger.ChaincodeLifecycleEventProvider
//度量性能提供者
MetricsProvider metrics.Provider
//健康检查注册,对于需要检查健康状况的组件进行注册
//github.com/hyperledger/fabric-lib-go/healthz/checker.go
HealthCheckRegistry ledger.HealthCheckRegistry
Config *ledger.Config
HashProvider ledger.HashProvider
EbMetadataProvider MetadataProvider
}
启发:在fabric项目中,似乎各个模块会用各自的Initializer来封装外部依赖参数。
里面的自定义交易处理器和状态监听器在后面会讲
新建账本管理器和账本提供者
core/ledger/ledgermgmt/ledger_mgmt.go
ledgermgmt.NewLedgerMgr这个方法总共有三步:
- addListenerForCCEventsHandler
- kvledger.NewProvider
- cceventmgmt.Initialize
其中一三步是有关事件监听的,着重关注第二个,用于新建账本提供者。
core/ledger/kvledger/kv_ledger_provider.go
NewProvider 的作用基本就是初始化各种类型的数据库的提供者。
- 文件上锁检查,若被上锁则报错返回,提示“等待其他peer指令执行完成或终止”
- 初始化账本IDstore
- 初始化区块存储提供者
- 初始化私有数据存储提供者
- 初始化历史数据库存储提供者
- 初始化配置历史提供者
- 初始化CollElgNotifier
- 初始化状态监听者
- 初始化stateDB
- 初始化账本数据统计工具
- 初始化快照目录
注意:
- 在core/ledger/kvledger/ledger_filepath.go这个文件里可以看到各个数据库被保存在哪个目录下
- 在上述操作中,普遍地使用了fabric/common/ledger/util/leveldbhelper来进行文件上锁检查、创建leveldb数据库(上述初始化函数,直接或间接地调用了leveldbhelper.CreateDB来创建数据库)
自定义交易处理器
// CustomTxProcessor allows to generate simulation results during commit time for custom transactions.
// A custom processor may represent the information in a propriety fashion and can use this process to translate
// the information into the form of `TxSimulationResults`. Because, the original information is signed in a
// custom representation, an implementation of a `Processor` should be cautious that the custom representation
// is used for simulation in an deterministic fashion and should take care of compatibility cross fabric versions.
// 'initializingLedger' true indicates that either the transaction being processed is from the genesis block or the ledger is
// synching the state (which could happen during peer startup if the statedb is found to be lagging behind the blockchain).
// In the former case, the transactions processed are expected to be valid and in the latter case, only valid transactions
// are reprocessed and hence any validation can be skipped.
/*CustomTxProcessor允许在提交自定义事务时生成模拟结果。
自定义处理器可以以适当的方式表示信息,并使用这个过程将信息转换为“TxSimulationResults”的形式。因为原始信息是用自定义表示进行签名的,所以“处理器”的实现应该谨慎使用自定义表示以确定的方式进行模拟,并且应该注意跨fabric版本的兼容性。
'initializingLedger' true表示正在处理的事务来自genesis block或者账本正在同步状态(这可能发生在对等启动时,如果statedb被发现落后于区块链)。在前一种情况下,所处理的事务应该是有效的,在后一种情况下,只有有效的事务才会被重新处理,因此可以跳过任何验证。*/
type CustomTxProcessor interface {
GenerateSimulationResults(txEnvelop *common.Envelope, simulator TxSimulator, initializingLedger bool) error
}
如注释所言,自定义交易处理器在提交自定义的交易时会读取配置信息为其产生模拟的读写结果。
实现这个接口GenerateSimulationResults方法的结构体为ConfigTxProcessor,以下是方法实现
core/peer/configtx_processor.go
// This implementation processes CONFIG transactions which simply stores the config-envelope-bytes
func (tp *ConfigTxProcessor) GenerateSimulationResults(txEnv *common.Envelope, simulator ledger.TxSimulator, initializingLedger bool) error {
payload := protoutil.UnmarshalPayloadOrPanic(txEnv.Payload)
channelHdr := protoutil.UnmarshalChannelHeaderOrPanic(payload.Header.ChannelHeader)
txType := common.HeaderType(channelHdr.GetType())
switch txType {
case common.HeaderType_CONFIG:
peerLogger.Debugf("Processing CONFIG")
if payload.Data == nil {
return fmt.Errorf("channel config found nil")
}
return simulator.SetState(peerNamespace, channelConfigKey, payload.Data)
default:
return fmt.Errorf("tx type [%s] is not expected", txType)
}
}
可以看到,目前仅支持处理配置类型的交易。因为配置交易没有准备好的读写集,所以要在这里生成模拟结果。
在生成模拟结果的时候,使用了ledger包的接口文件中定义的simulator接口,实现其方法的结构体为txSimulator
路径为core/ledger/kvledger/txmgmt/txmgr/tx_simulator.go
状态监听器
当状态发生变化时,状态监听器会进行数据更新(它支持自定义代码,当状态发生变化时,执行希望它进行的动作),每个区块一次,在commit前执行。若发生错误,commit操作终止。
它的初始化函数仅在打开账本时被调用一次
core/ledger/ledger_interface.go
// StateListener allows a custom code for performing additional stuff upon state change
// for a particular namespace against which the listener is registered.
// This helps to perform custom tasks other than the state updates.
// A ledger implementation is expected to invoke Function `HandleStateUpdates` once per block and
// the `stateUpdates` parameter passed to the function captures the state changes caused by the block
// for the namespace. The actual data type of stateUpdates depends on the data model enabled.
// For instance, for KV data model, the actual type would be proto message
// `github.com/hyperledger/fabric-protos-go/ledger/rwset/kvrwset.KVWrite`
// Function `HandleStateUpdates` is expected to be invoked before block is committed and if this
// function returns an error, the ledger implementation is expected to halt block commit operation
// and result in a panic.
// The function Initialize is invoked only once at the time of opening the ledger.
type StateListener interface {
Name() string
Initialize(ledgerID string, qe SimpleQueryExecutor) error
//状态监听器命名空间
InterestedInNamespaces() []string
//处理状态更新
HandleStateUpdates(trigger *StateUpdateTrigger) error
//状态提交完成
StateCommitDone(channelID string)
}
HandleStateUpdates的传入参数StateUpdateTrigger,它封装了状态监听器所需要的信息和工具
type StateUpdateTrigger struct {
LedgerID string
//状态更新
StateUpdates StateUpdates
CommittingBlockNum uint64
//已提交状态查询执行器
CommittedStateQueryExecutor SimpleQueryExecutor
PostCommitQueryExecutor SimpleQueryExecutor
}
// StateUpdates encapsulates the state updates
type StateUpdates map[string]*KVStateUpdates
简单查询执行器的接口定义如下
type SimpleQueryExecutor interface {
//根据命名空间和健获取值。对于一个链码来说,命名空间对应了chaincodeId
GetState(namespace string, key string) ([]byte, error)
//范围查询,返回迭代器
GetStateRangeScanIterator(namespace string, startKey string, endKey string) (commonledger.ResultsIterator, error)
//获得私有数据的哈希值,只有私有数据有权者才能查询私有数据,其他peer可以查询hash
GetPrivateDataHash(namespace, collection, key string) ([]byte, error)
}
目前主要的实行状态监听器接口的有KVLedgerLSCCStateListener,collElgNotifier和Mgr(其他还有,例如Cache,core/chaincode/lifecycle/cache.go)
// KVLedgerLSCCStateListener listens for state changes for chaincode lifecycle
//监听链码生命周期
// Mgr manages the history of configurations such as chaincode's collection configurations.
// It should be registered as a state listener. The state listener builds the history.
//监听配置历史
// collElgNotifier listens for the chaincode events and determines whether the peer has become eligible(合格的) for one or more existing
// private data collections and notifies(通知) the registered listener
//监听私有数据状态
创建账本
core/ledger/ledgermgmt/ledger_mgmt.go
func (m *LedgerMgr) CreateLedger(id string, genesisBlock *common.Block)(ledger.PeerLedger, error){ }
- 为了保证原子性,在这个函数开始前会对账本管理器上锁,结束后解锁(可能可以改进,并不是每一个涉及账本的操作都需要把整个账本管理器上锁)
- 创建的账本id为创世块中的通道id(这意味着,一个通道一个账本)
- 调用账本提供者的CreateFromGenesisBlock创建账本
- 若创建成功,在openedLedgers列表中加入新建的账本项(意味着创建的账本刚开始是open状态)
另外还有CreateLedgerFromSnapshot,是从快照文件中创建账本
core/ledger/kvledger/kv_ledger_provider.go
func (p *Provider) CreateFromGenesisBlock(genesisBlock *common.Block) (ledger.PeerLedger, error) { }
- 从初始块中解析出id
- 在idstore中创建记录,id为键,元数据为值,元数据中的状态设置为tatus_UNDER_CONSTRUCTION
- 调用open这个函数,open函数打开存储文件(blockStore和pvtdataStore),注册监听器,获取数据库(状态数据库和历史数据库)的句柄,打包(使用lgrInitializer),调用newKVLedger
- 在新建的账本中commit创世区块(对应的函数为CommitLegacy)
- 若都没问题,更新idstore,把状态设置为Status_ACTIVE
- 若出现问题,则会调用deleteUnderConstructionLedger
里面第三步包含了比较多的细节,最关键的是newKVLedger,真实实现账本的创建,后面再讲。
删除在建账本
若账本部分地被创建(即在完全创建所有所需的数据库时,遭到中断),这时需要调用deleteUnderConstructionLedgers来删除部分建立的账本
core/ledger/kvledger/kv_ledger_provider.go
func (p *Provider) deleteUnderConstructionLedgers() error
{
获取idStore数据库的迭代器,
循环迭代器,解码每一个账本的元数据
若元数据中的状态码为Status_UNDER_CONSTRUCTION则删除对应账本(调用runCleanup)
}
这个函数会在NewProvider这个函数的最后被调用
以及另外有个功能类似的函数deleteUnderConstructionLedger,它用于删除指定id的在建账本,通常在CreateFromGenesisBlock失败时被调用
打开账本
账本创建都会在idstore里面有记录,有些账本可能会因为某些原因被关闭。如果需要可以再通过账本id打开账本
调用链为
func (m *LedgerMgr) OpenLedger(id string) (ledger.PeerLedger, error)
{
检查是不是不在openedLedgers列表中,不在则继续
调用ledgerProvider.Open(id){
检查是不是在idsotre中,以及状态为Status_ACTIVE的账本才能open
调用open
}
更新openedLedgers列表
}
可以发现,在创建账本或者打开账本过程中,都会调用账本提供者的open函数
区别是,打开账本会把idstore中保存的元数据传给open函数,而创建账本则传入的是nil
账本id的获取和账本的关闭
前者业务逻辑比较简单,不再赘述。
后者调用链如下
//下面这个是账本管理器的Close
func (m *LedgerMgr) Close() {//这个函数的作用是关闭所有打开的账本
遍历opendLedgers,调用ledger.Close()
调用账本提供者的Close(),关闭相关的数据库服务提供者
}
比较需要注意的是第一个函数
// Close closes `KVLedger`.
// Currently this function is only used by test code. The caller should make sure no in-progress commit
// or snapshot generation before calling this function. Otherwise, the ledger may have unknown behavior
// and cause panic.
func (l *kvLedger) Close() {
l.blockStore.Shutdown()
l.txmgr.Shutdown()
l.snapshotMgr.shutdown()
}
正如注释所说,有bug,暂时只用于测试。(疑问,并没有处理状态数据库和历史状态数据库)
那么实际被调用的是下面这个函数
// closableLedger extends from actual validated ledger and overwrites the Close method
type closableLedger struct {
ledgerMgr *LedgerMgr
id string
ledger.PeerLedger
}
// Close closes the actual ledger and removes the entries from opened ledgers map
func (l *closableLedger) Close() {
l.ledgerMgr.closeLedger(l.id)
}
可以看到,账本其实被稍微封装了下,调用了账本管理器的closeLedger
func (m *LedgerMgr) closeLedger(ledgerID string) {
m.lock.Lock()
defer m.lock.Unlock()
l, ok := m.openedLedgers[ledgerID]
if ok {
l.Close()
delete(m.openedLedgers, ledgerID)
}
}
结果又调用了前面说到可能有bug的Close....???
账本ID存储idStore
idStore的创建过程如下
NewProvider调用initLedgerIDInventory
func (p *Provider) initLedgerIDInventory() error {
idStore, err := openIDStore(LedgerProviderPath(p.initializer.Config.RootFSPath))
if err != nil {
return err
}
p.idStore = idStore
return nil
}
initLedgerIDInventory调用openIDStore来创建数据库
openIDStore的作用是创建IDStore数据库,
并且插入格式键db.Put(formatKey, expectedFormatBytes, true)
其中,expectedFormatBytes := []byte(dataformat.CurrentFormat)
然后会检查格式是否是期待的格式bytes.Equal(format, expectedFormatBytes)
注意:这里也很离谱,自己插入了format=expectedFormatBytes,怎么可能会不一样?
格式定义在common/ledger/dataformat/dataformats.go
const (
// PreviousFormat specifies the data format in previous fabric version
PreviousFormat = ""
// CurrentFormat specifies the data format in current fabric version
CurrentFormat = "2.0"
)
在账本提供者的包中,另外还有些关于idstore的创建插入数据、删除数据、查询数据的函数,不再赘述。
idStore中的数据如下
1.格式版本,即前面提到的formatKey->expectedFormatBytes
2.id及创世块,id构造的元数据键->创世块
3.id及元数据,id构造的元数据键->元数据
// genesisBlkKeyPrefix is the prefix for each ledger key in idStore db
genesisBlkKeyPrefix = []byte{'l'}
// genesisBlkKeyStop is the end key when querying idStore db by ledger key
genesisBlkKeyStop = []byte{'l' + 1}
// metadataKeyPrefix is the prefix for each metadata key in idStore db
metadataKeyPrefix = []byte{'s'}
// metadataKeyStop is the end key when querying idStore db by metadata key
metadataKeyStop = []byte{'s' + 1}
// formatKey
formatKey = []byte("f")
元数据结构体定义如下
core/ledger/kvledger/msgs/ledger_metadata.pb.go
type LedgerMetadata struct {
Status Status `protobuf:"varint,1,opt,name=status,proto3,enum=msgs.Status" json:"status,omitempty"`
BootSnapshotMetadata *BootSnapshotMetadata `protobuf:"bytes,2,opt,name=boot_snapshot_metadata,json=bootSnapshotMetadata,proto3" json:"boot_snapshot_metadata,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
账本存储提供者
在fabric中,账本以文件形式存储,主要包含两部分,blkStore和pvtdataStore
blkStoreProvider *blkstorage.BlockStoreProvider
pvtdataStoreProvider *pvtdatastorage.Provider
在账本提供者的NewProvider中,调用以下两个函数来初始化
initBlockStoreProvider()
initPvtDataStoreProvider()
blkStoreProvider
提供区块主体数据和存储状态信息的相关结构
首先看一些帮助理解的前置定义
common/ledger/blkstorage/blockstore_provider.go
// 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")
)
// IndexConfig - a configuration that includes a list of attributes that should be indexed
type IndexConfig struct {
AttrsToIndex []IndexableAttr
}
而在账本提供者的文件中,一开始定义了如下常量
core/ledger/kvledger/kv_ledger_provider.go
attrsToIndex = []blkstorage.IndexableAttr{//其实就是个字符串列表
blkstorage.IndexableAttrBlockHash,
blkstorage.IndexableAttrBlockNum,
blkstorage.IndexableAttrTxID,
blkstorage.IndexableAttrBlockNumTranNum,
}
blkStoreProvider的初始化函数如下
core/ledger/kvledger/kv_ledger_provider.go
func (p *Provider) initBlockStoreProvider() error {
indexConfig := &blkstorage.IndexConfig{AttrsToIndex: attrsToIndex}//索引配置,由前面的解释,可知是个字符串列表
blkStoreProvider, err := blkstorage.NewProvider(//调用了blkstorage的提供者新建函数
blkstorage.NewConf(//包装了两个参数,根文件目录,最大区块文件大小
BlockStorePath(p.initializer.Config.RootFSPath),
maxBlockFileSize,//,maxBlockFileSize = 64 * 1024 * 1024,64MB
),
indexConfig,//区块的存储状态信息,具体有什么参考上面讲的
p.initializer.MetricsProvider,//监控服务相关
)
...
}
接下来是它的NewProvider函数
// NewProvider constructs a filesystem based block store provider
func NewProvider(conf *Conf, indexConfig *IndexConfig, metricsProvider metrics.Provider) (*BlockStoreProvider, error){
封装dbConf,包含索引文件目录路径(根文件目录/chains/index),以及期待的格式(PreviousFormat或者CurrentFormat)
调用leveldbhelper.NewProvider(dbConf),这个函数会创建索引目录和索引数据库
创建存放区块链的文件目录(根文件目录/chains/chains)
新建一个监控服务提供者
将上述数据综合封装为BlockStoreProvider并返回
}
所以从上面可以得出结论,在初始化blkStoreProvider后,会在chains目录下创建两个子目录,index子目录用于存放索引数据库,chains子目录用于存放区块文件,其中chains子目录里再根据账本id建立不同的文件夹去存放区块数据(一个块的文件路径举例:根文件目录/chains/chains/mychannel/blockfile_000001)
传入的参数indexConfig表示索引数据库需要存储的键值。
可以看到,通过这个提供者,就能对该数据做一些操作了。
其中Open函数是最重要的,当创建或者打开账本的时候,会调用这个Open函数,通过Open函数,可以获得blockStoreProvider的索引数据库的句柄DBHandel,通过这个句柄,就能对数据库进行增删改查的操作。
实际中,Open返回的并不是句柄,而是将句柄和其他一些id、配置信息等作为参数传入newBlockStore这个函数,将得到的BlockStore这个结构体返回。
在上面这个结构体中,最重要的成员就是fielMgr,即区块文件管理器。
type blockfileMgr struct {
//区块文件存储的根目录
rootDir string
//配置信息,存储路径和区块文件最大size
conf *Conf
//数据库句柄
db *leveldbhelper.DBHandle
//索引管理服务,可以通过它查询区块,例如给出区块哈希值或者交易id等,返回对应文件指针
//在里面还可以配置是否启用哪些索引,默认是全部启用的
index *blockIndex
//检查点信息,在2.0版本被改名了,实际作用没有变化
blockfilesInfo *blockfilesInfo
//引导快照信息,存了最后一个块的块号、哈希和上一个块的哈希,如果这个变量不为空,则会被用来赋值给bcInfo
bootstrappingSnapshotInfo *BootstrappingSnapshotInfo
blkfilesInfoCond *sync.Cond
//区块文件写作器,每个区块文件都会生成一个文件写作器
currentFileWriter *blockfileWriter
//保存区块高度,当前区块哈希,上一个区块哈希
bcInfo atomic.Value
}
上面的检查点信息结构体定义如下
// blockfilesInfo maintains the summary about the blockfiles
type blockfilesInfo struct {
latestFileNumber int
latestFileSize int
noBlockFiles bool//是否为空
lastPersistedBlock uint64//最后一个区块编号
}
在newBlockStore中会调用newBlockfileMgr,因为newBlockfileMgr这个函数比较长,文字描述其作用:
( 1 ) 检查区块链文件存储目录 rootDir 是否存在, 若不存在, 则创建该路径。
(2) 构造区块文件管理器 blockfileMgr。
(3 ) 从 indexdb 中获取最新检查点信息, 键为 “ blkMgrlnfoKey”。 若检查点信息不
存在, 则扫描文件系统 blockfile, 获取后缀编号最大的文件构造检查点( 区块文件命名
方式为 “ blockfile_” + cpInfo.latestFileChunkSufFixNum ), 并将其存储到 index 数据库中。
检查点信息包括最新区块文件后缀编号、 最新区块文件大小、 是否空链、 最后一块区块编
号四个域。 第一次创建账本时还没有任何区块文件, 所以检查点信息默认为 (0,0,true,0) 。
若检查点信息存在, 则根据当前区块文件信息对 index 库中保存的检查点信息进行更新。
(4) 为当前区块新建区块文件写作器(包含文件路径和文件指针的结构体)。
(5 ) 新建索引写作器, 返回结果是 blockIndex 结构体,包括区块编号、 区块哈希、 文件指针、 偏移量、 区块元数据。
(6) 更新区块文件管理器的检查点信息、 当前区块文件写作器、 检查点事件变量。
(7) 更新区块文件管理器的区块链信息到 index 数据库中, 当区块链为空时, 默认初
始化为长度 0, 当前区块哈希, 上一个区块哈希为空; 若区块链不为空, 获取最后一个区
块的头信息, 从中读取当前区块哈希和上一个区块哈希, 链长为检查点最后区块编号+1。
总结下,该索引数据库存储了以下两类数据。
索引数据。以下是文件指针的结构示意,索引的键为区块号、交易号等等,值就是这种文件指针的序列化结果
数据库中除了索引数据,还记录了区块检查点信息,blkMgrlnfoKey->blockfilesInfo
检查点的作用就是检查索引数据库是否已经是最新的状态了(即和实际的文件系统同步)。
检查方式就是去看检查点之后是否还存在数据。
前面提到的的文件写作器和索引写作器,都会在addBlock这个函数中被使用,前者用来写区块数据,若大小超出限制,则使用moveToNextFile函数调整;后者通过indexBlock这个函数为数据库增加索引数据。
pvtdataStoreProvider
提供区块私有数据的存储,暂时不详细了,理解其作用即可。
路径为core/ledger/pvtdatastorage/store.go
该provider通过OpenStore(ledgerID)来获得Store。
比较需要注意的是私有数据生存策略。
- 当某些私有数据需要在经过一定时间后从网上删除,则可使用私有数据生存策略来处理。设置私有数据生存策略以块为单位。
- 举例,若私有数据在块3上,该私有数据生存策略设置为2,则在块6被提交时,需要清除私有数据。(为0表示永不过期)
- 生存策略保存在bookeeper数据库中。
下面是获取私有数据生存策略的接口
//(BTLPolicy是Store的成员变量之一)
// BTLPolicy BlockToLive policy for the pvt data
type BTLPolicy interface {
// GetBTL returns BlockToLive for a given namespace and collection
//返回BlockToLive
GetBTL(ns string, coll string) (uint64, error)
// GetExpiringBlock returns the block number by which the pvtdata for given namespace,collection, and committingBlock should expire
//返回用committingBlock和BlockToLive相加计算后的区块号
GetExpiringBlock(namesapce string, collection string, committingBlock uint64) (uint64, error)
}
实现上面这个接口的为LSCCBasedBTLPolicy,core/ledger/pvtdatapolicy/btlpolicy.go
启发:在高教联盟链中,如果想要实现删除已提交区块某项数据的,可以考虑把这种数据设为私有数据,修改源码,把生成策略修改为由集合中所有组织的签名触发清理。
版本数据库(世界状态数据库)
kv账本提供者的NewProvider函数通过调用initStateDBProvider函数来初始化版本数据库
initStateDBProvider实例化了bookeeper的提供者,打包了状态数据库的配置以及路径,设置系统命名空间为“lscc”,最后初始化了DBProvider
首先介绍provider,可以看到里面包含了版本数据库的提供者
core/ledger/kvledger/txmgmt/privacyenabledstate/db.go
// DBProvider encapsulates other providers such as VersionedDBProvider and
// BookeepingProvider which are required to create DB for a channel
type DBProvider struct {
VersionedDBProvider statedb.VersionedDBProvider
HealthCheckRegistry ledger.HealthCheckRegistry
bookkeepingProvider *bookkeeping.Provider
}
在NewDBProvider这个函数中,根据stateDBConf.StateDatabase这个变量来决定采用levelDB还是couchDB
NewVersionedDBProvider用于新建版本数据库提供者
历史数据库
历史数据库存的数据,键为链码名+变量名+区块号+交易号,值为字节数组
提交区块的时候,只有合法交易的读写集才会更新到历史数据库
交易管理器
未完待续。。。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南