venus启动venusdeamon启动时主要调用两个app/node.go的两个方法。其中一个是用(bBuilder)build()生成node对象,这个生成方法会调用(builderRPCBuilder)AddServices(services...RPCService)方法
venus deamon启动时主要调用两个app/node.go
的两个方法。其中一个是用(b *Builder) build()
生成node
对象,这个生成方法会调用(builder *RPCBuilder) AddServices(services ...RPCService)
方法把各种api服务添加到node
对象,后面调用(node *Node) RunRPCAndWait()
方法启动对外服务。服务接口有rest api和rpc两种形式。每个服务都是一个submodule,定义在app/submodule
目录下,***_submodule.go
定义类,***_api.go
类实现接口逻辑。
另一个是Start()
方法来启动syncer,mpool,paychan,network, eth
五个模块。
/ Start boots up the node.
func (node *Node) Start(ctx context.Context) error {
.......
// start syncer module to receive new blocks and start sync to latest height
err = node.syncer.Start(syncCtx)
if err != nil {
return err
}
// Start mpool module to receive new message
err = node.mpool.Start(syncCtx)
if err != nil {
return err
}
err = node.paychan.Start(ctx)
if err != nil {
return err
}
// network should start late,
err = node.network.Start(syncCtx)
if err != nil {
return err
}
if err := node.eth.Start(ctx); err != nil {
return fmt.Errorf("failed to start eth module %v", err)
}
}
其中syncer
的启动会产生一个goroutine,for循环不停的接受订阅的信息,仅仅是block信息,为什么这样设计呐?tipset的同步逻辑放在了别的包来实现。
// Start starts the syncer submodule for a node.
func (syncer *SyncerSubmodule) Start(ctx context.Context) error {
.......
// process incoming blocks
go func() {
for {
received, err := syncer.BlockSub.Next(ctx)
if err != nil {
if ctx.Err() != context.Canceled {
log.Errorf("error reading message from topic %s: %s", syncer.BlockSub.Topic(), err)
}
return
}
if err := syncer.handleIncomingBlocks(ctx, received); err != nil {
handlerName := runtime.FuncForPC(reflect.ValueOf(syncer.handleIncomingBlocks).Pointer()).Name()
if err != context.Canceled {
log.Debugf("error in handler %s for topic %s: %s", handlerName, syncer.BlockSub.Topic(), err)
}
}
}
}()
err = syncer.ChainModule.Start(ctx)
if err != nil {
return err
}
return syncer.ChainSyncManager.Start(ctx)
}
而其中的ChainModule.Start()
方法最主要调用一个(c *ChainFork) preMigrationWorker()
来提前进行状态数据的迁移。作为一个刚刚从以太坊系转过来的人,感觉好惊讶,每次启动都先检查一次状态迁移(状态数据修改)。
syncer.ChainSyncManager.Start(ctx)
而这个方法会启动两个gorotine来处理信息的同步。
// Start launches the business logic for the syncing subsystem.
func (d *Dispatcher) Start(syncingCtx context.Context) {
go d.processIncoming(syncingCtx)
go d.syncWorker(syncingCtx)
}
具体的sync协议我们另外一篇来讲解。
mpool.Start()
方法会订阅msg,验证msg合法后放进mpool。
node.paychan.Start(ctx)
这个方法好像不是开启一个goroutine不停的处理事情,也许和chan不能一直存在的原因。
node.network.Start(syncCtx)
方法主要作用是连接peer,是通过(pmgr *PeerMgr) doExpand(ctx context.Context)
方法来实现的。
node.eth.Start(ctx)
启动两个goroutine。
func (em *EthSubModule) Start(_ context.Context) error {
if err := em.ethEventAPI.Start(em.ctx); err != nil {
return err
}
return em.ethAPIAdapter.start(em.ctx)
}
一个用于事件订阅的goroutine,一个是开启msg接受的goroutine,接收到消息后放入mpool也许?需要更多确认。
venus-miner
服务获得出块权后会通过http请求来调用app/submodule/app/submodule/ming/ming_api.go: (miningAPI *MiningAPI) minerCreateBlock()
接口。这个接口主要是调用pkg/statemanager/state_manager.go:(s *Stmgr) RunStateTransition()
来执行父区块,得到父区块的receipt root和state root。最主要进行状态迁移的函数是:pkg/consensus/expected.go:(c *Expected) RunStateTransition()
方法,这个方法会调用pkg/consensus/processor.go:(p *DefaultProcessor) ApplyBlocks()
来执行区块,方法的内部再调用pkg/fvm/fvm.go:(fvm *FVM) ApplyMessage()
来执行区块内的交易,真正执行交易的是extern/filecoin-ffi/fvm.go:(f *FVM) ApplyMessage()
方法,这个方法会通过cgo调用rust的执行方法。但是这里要小心,cgo调用还是不是ref-fvm库的方法,还是filecoin-ffi库中的rust方法。
以上也只是把父区块执行一下而已,后面还需进行gas费用计算,reward奖励,签名的事情,才能完整的产生一个block。主要这里是不需要从mpool里面选择交易进行区块填充的,在venus-miner调用minerCreateBlock
接口前,已经通过调用app/submodule/mpool/mpool_api.go:(a *MessagePoolAPI) MpoolSelects()
接口从venus node选择了要打包的msg。为什么要这样设计啊?直接创建区块时,一起选择交易不是更有效率吗?就不需要来回传输了?
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!