Michael.W谈hyperledger Fabric第17期-详细带读Fabric的源码2-Manager接口及通道相关源码
上个帖子中:Michael.W谈hyperledger Fabric第16期-详细带读Fabric的源码1-orderer模块入口
发现orderer模块的main函数中所有接口的实现都离不开这样的一个multichain.Manager实例化对象。那本帖继续深度挖掘这个Manager接口为何许人也。
1 Manager接口
接下来是对Manager进行探索。
Manager接口是用来协调通道的产生和进入的。
type Manager interface {
// 获取链对象。需要输入链ID,注意这地方返回的不是链对象,而是ChainSupport接口对象
GetChain(chainID string) (ChainSupport, bool)
// 获取系统通道名
SystemChannelID() string
// 生成新通道的配置。可能是新建,也可能是更新。
NewChannelConfig(envConfigUpdate *cb.Envelope) (configtxapi.Manager, error)
}
什么是系统通道?
客户端想建立一条新通道,它将这个请求包裹成一条交易发送给orderer节点。之前说过每一笔交易都会被分割到某一条通道上,这是就需要一条系统通道,用来引导其他通道的生成。顺着往下看:
// 配置资源。每个通道都有自己的配置,那这些配置就都包裹在这个配置资源里。
type configResources struct {
configtxapi.Manager
}
// 获取跟orderer节点相关的配置
func (cr *configResources) SharedConfig() config.Orderer {
oc, ok := cr.OrdererConfig()
if !ok {
logger.Panicf("[channel %s] has no orderer configuration", cr.ChainID())
}
return oc
}
看一下返回值config.Orderer:
type Orderer interface {
// 返回共识类型,包括solo和kafka
ConsensusType() string
// 每个区块包含交易消息的最大数量
BatchSize() *ab.BatchSize
// 每个区块等待打包的超时时间
BatchTimeout() time.Duration
// 一个排序网络中最大通道数量
MaxChannelsCount() uint64
// 如果选择kafka机制,还会有kafkaBroker的地址
KafkaBrokers() []string
// 接受排序服务的组织
Organizations() map[string]Org
}
回到上一级:
// 账本资源类
type ledgerResources struct {
// 前面所说的配置资源
*configResources
// 账本的读写对象,可以把这个当做对账本操作的入口
ledger ledger.ReadWriter
}
// manager的实现类
type multiLedger struct {
// 包含的所有通道的对象集合
chains map[string]*chainSupport
// 我自己为成员变量chains而自行添加的互斥锁。因为go中的map是并发不安全的。只需要在每次有关chains的读写操作之前加锁,然后解锁即可。
lock sync.Mutex
// 所支持的共识机制
consenters map[string]Consenter
// 账本读写工厂
ledgerFactory ledger.Factory
// 签名对象
signer crypto.LocalSigner
// 系统通道名
systemChannelID string
// 系统通道对象,这地方没使用chainSupport的接口,而是使用了其实例化对象。
systemChannel *chainSupport
}
// 获取通道上最新配置交易
func getConfigTx(reader ledger.Reader) *cb.Envelope {
// 获取该通道最新的一个区块
lastBlock := ledger.GetBlock(reader, reader.Height()-1)
// 根据该通道最新的一个区块的元数据信息找到最新的配置交易所在的区块的索引
index, err := utils.GetLastConfigIndexFromBlock(lastBlock)
if err != nil {
logger.Panicf("Chain did not have appropriately encoded last config in its latest block: %s", err)
}
// 根据索引得到该区块
configBlock := ledger.GetBlock(reader, index)
if configBlock == nil {
logger.Panicf("Config block does not exist")
}
// 一般配置交易是在区块中的第一个交易,所以索引值为0
return utils.ExtractEnvelopeOrPanic(configBlock, 0)
}
// 实例化Manager接口
func NewManagerImpl(ledgerFactory ledger.Factory, consenters map[string]Consenter, signer crypto.LocalSigner) Manager {
// 将用参数创建一个multiLedger对象
ml := &multiLedger{
chains: make(map[string]*chainSupport),
ledgerFactory: ledgerFactory,
consenters: consenters,
signer: signer,
}
// 读取本地存储的通道的ID
existingChains := ledgerFactory.ChainIDs()
// 开始遍历本地的所有通道ID
for _, chainID := range existingChains {
// 利用账本工厂借助通道ID实例化账本读的对象
rl, err := ledgerFactory.GetOrCreate(chainID) //rl是read ledger的缩写
// 虽然GetOrCreate()返回的是一个ReadWriter对象,但是只会用到reader
if err != nil {
logger.Panicf("Ledger factory reported chainID %s but could not retrieve it: %s", chainID, err)
}
// 获取通道上最新配置交易(前面已讲过)
configTx := getConfigTx(rl)
if configTx == nil {
logger.Panic("Programming error, configTx should never be nil here")
}
// 将配置交易和账本的读写对象绑定在一起
ledgerResources := ml.newLedgerResources(configTx)
chainID := ledgerResources.ChainID()
// 接下来是读取该通道上是否有联盟配置,一般只有系统通道上会有联盟配置。系统通道的特点就是可以创建其他链。
if _, ok := ledgerResources.ConsortiumsConfig(); ok {
//如果存在系统通道
if ml.systemChannelID != "" {
// 如果此时ml对象中的系统通道的ID不为空,表明现在已经存在系统通道,会报错
logger.Panicf("There appear to be two system chains %s and %s", ml.systemChannelID, chainID)
}
// 实例化chainSupport的对象
chain := newChainSupport(createSystemChainFilters(ml, ledgerResources),
ledgerResources,
consenters,
signer)
logger.Infof("Starting with system channel %s and orderer type %s", chainID, chain.SharedConfig().ConsensusType())
// 依次给ml对象中的成员赋值
ml.lock.Lock() //为ml.chains加锁(map并发不安全),自己添加
ml.chains[chainID] = chain
ml.lock.Unlock() //为ml.chains解锁(map并发不安全),自己添加
ml.systemChannelID = chainID
ml.systemChannel = chain
// 该函数释放前(所有的通道都启动完成后)启动该通道
defer chain.start()
} else {
// 如果不存在系统通道
logger.Debugf("Starting chain: %s", chainID)
// 创建一个标准的通道对象
chain := newChainSupport(createStandardFilters(ledgerResources),
ledgerResources,
consenters,
signer)
ml.lock.Lock() //为ml.chains加锁(map并发不安全),自己添加
ml.chains[chainID] = chain
ml.lock.Unlock() //为ml.chains解锁(map并发不安全),自己添加
// 启动该通道
chain.start()
}
}
// 在遍历完所有本地存储的通道之后,会统一检查一下ml对象中systemChannelID是否还是空,即遍历一遍后要确保在所有的通道中有系统通道
if ml.systemChannelID == "" {
// 如果没有系统通道,报错
logger.Panicf("No system chain found. If bootstrapping, does your system channel contain a consortiums group definition?")
}
// 返回这个multiLedger对象ml。multiLedger为Manager接口的实现类
return ml
}
// multiLedger类中systemChannelID外部不可见,于是使用方法让外部可读
func (ml *multiLedger) SystemChannelID() string {
// 系统通道ID
return ml.systemChannelID
}
// 通过某个chainID找到对应的chainSupport对象
func (ml *multiLedger) GetChain(chainID string) (ChainSupport, bool) {
ml.lock.Lock() //为ml.chains加锁(map并发不安全),自己添加
defer ml.lock.Unlock() //为ml.chains解锁(map并发不安全),自己添加
cs, ok := ml.chains[chainID] //map的读操作
return cs, ok
}
// 实例化一个账本资源对象。参数是一个配置交易
func (ml *multiLedger) newLedgerResources(configTx *cb.Envelope) *ledgerResources {
initializer := configtx.NewInitializer()
// 利用传进来的配置交易,生成一个configManager
configManager, err := configtx.NewManagerImpl(configTx, initializer, nil)
if err != nil {
logger.Panicf("Error creating configtx manager and handlers: %s", err)
}
// 得到这个生成一个configManager的chainID
chainID := configManager.ChainID()
// 根据chainID创建一个账本对象
ledger, err := ml.ledgerFactory.GetOrCreate(chainID)
if err != nil {
logger.Panicf("Error getting ledger for %s", chainID)
}
// 返回一个经过初始化的ledgerResources内部类对象,前面看到过ledgerResources为账本资源类
return &ledgerResources{
configResources: &configResources{Manager: configManager},
ledger: ledger,
}
}
2 通道相关
下面这个函数要好好看看
// 根据传入的配置交易生成一个新通道
func (ml *multiLedger) newChain(configtx *cb.Envelope) {
// 根据传入的配置交易生成一个账本资源对象
ledgerResources := ml.newLedgerResources(configtx)
// 将传入进来的配置交易组装成一个区块,然后通过Append函数将这个区块连接到账本(链)中
ledgerResources.ledger.Append(ledger.CreateNextBlock(ledgerResources.ledger, []*cb.Envelope{configtx}))
// 也就是说配置交易是每个账本(每条链)上的第一个区块-创世区块
// 防止并发读写侵占,而创建的一个map。相当于把ml中的chains(存放所有通道对象的map)重新在函数中创建一个副本,在进行相关操作。因为go中的map是并发不安全的。如果在multiLedger中添加一个互斥锁后,该副本的生成过程可以省略。
/*newChains := make(map[string]*chainSupport)
for key, value := range ml.chains {
newChains[key] = value
}*/
// 创建一个新的chainSupport对象(普通的通道,即记录交易的通道)
cs := newChainSupport(createStandardFilters(ledgerResources), ledgerResources, ml.consenters, ml.signer)
chainID := ledgerResources.ChainID()
logger.Infof("Created and starting new chain %s", chainID)
//newChains[string(chainID)] = cs
//启动该通道
cs.start()
/*由于自定义加锁而注释掉的语句
ml.chains = newChains*/
//将生成的普通交易链对象加入到ml.chains中
ml.lock.Lock()
ml.chains[chainID] = cs
ml.lock.Unlock()
}
// 当前这个ml管理了多少个通道,即返回ml.chains中的键值对个数
func (ml *multiLedger) channelsCount() int {
return len(ml.chains)
}
// 生成新通道的配置
func (ml *multiLedger) NewChannelConfig(envConfigUpdate *cb.Envelope) (configtxapi.Manager, error) {
// 该函数比较长,都是一些检查性的工作。就不一一列举了。
...
// 最后返回一个用来配置manager的实例化对象
return configtx.NewManagerImpl(templateConfig, initializer, nil)
}
ps:
本人热爱图灵,热爱中本聪,热爱V神,热爱一切被梨花照过的姑娘。
以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。
同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下!
如果需要转发,麻烦注明作者。十分感谢!
公众号名称:后现代泼痞浪漫主义奠基人