zoukankan      html  css  js  c++  java
  • okexchain整体架构分析

    今天重读的感受与几个月前完全不同,我想大概的原因,是你的基础储备有多少。

    如果你基础储备足够,可能很快就理解了。如果基础储备不够,可能要花很长时间也不理解。我之前是典型的后者。下面进入正文

    一个大型程序的运行:

    入口-框架-分发到各模块。

    入口:在app/app.go

    appProtocolEngine:okchain/app/protocol/engine.go
    ProtocolVX:okchain/app/protocol_vX.go
    OKChainApp: okchain/app/app.go
    BaseApp:cosmos-sdk/baseapp.go
     
     
    一、总体框架

    核心的总体逻辑在ProtocolV0中,在那里面分别:
    1. 定义了各模块的Keeper(各模块读写store的执行者。注:读写store即读写账本) 2. 设置Manager(管理各模块在InitGenesis/BeginBlocker/EndBlocker中的顺序)
    3. 注册Router(各模块query)
    4. 设置AnteHandler(交易安全的执法者,管理手续费)

    运行入口:

    1,cmd/okexchaind/main.go

    server.AddCommands(ctx, cdc, rootCmd, newApp, exportAppStateAndTMValidators, registerRoutes)

    在56行,这句把核心的内容都加载了。

    server来自cosmos:"github.com/cosmos/cosmos-sdk/server"

    2,app/app.go。newApp方法,调用了 app/app.go的NewOKExChainApp() 方法,定义bApp := baseapp.NewBaseApp(appName, logger, db, nil, baseAppOptions...)

    实例了app := &OKExChainApp { BaseApp: bApp },其中type OKExChainApp struct { *baseapp.BaseApp },

    然后调了两个重要的方法:

    (1)// add new protocol based on new version

    protocol.GetEngine().FillProtocol(app, logger, 0);

    (2)// load the status of current protocol from the store

    isLoaded, current := protocol.GetEngine().LoadCurrentProtocol(app.GetCommitMultiStore().GetKVStore(

    protocol.GetMainStoreKey()))

    (3)在postEndBlocker方法中调用了Activate方法。// hook function for BaseApp's EndBlock(upgrade)

    protocol.GetEngine().Activate(appVersion); // activate the new protocol

    3,app/protocol/engine.go。在GetEngine方法调用了app/protocol/engine.go,详见:

    protocolKeeper := proto.NewProtocolKeeper(GetMainStoreKey())
    protocolsEngine = NewAppProtocolEngine(protocolKeeper)
    protocolsEngine.Add(NewProtocolV0(nil, 0, nil, 0, protocolKeeper)) // add protocolV0

    这里先NewProtocolV0,然后在2中LoadCurrentProtocol和 Activate方法中调用protocol.LoadContext()

    4,app/protocol/engine.go。LoadContext方法

    // LoadContext updates the context for the app after the upgrade of protocol
    func (p *ProtocolV0) LoadContext() {
        p.logger.Debug("Protocol V0: LoadContext")
        p.setCodec() // 设置了4个功能,var cdc = codec.New();ModuleBasics.RegisterCodec(cdc);基础module,各模块都实现。sdk.RegisterCodec(cdc);codec.RegisterCrypto(cdc);codec.RegisterEvidences(cdc)
        p.produceKeepers() // produceKeepers initializes all keepers declared in the ProtocolV0 struct
        p.setManager() // p.mm = module.NewManager方法设置了各模块的xx.NewAppModule()
        p.registerRouters()
        p.setAnteHandler()
    
        p.parent.PushInitChainer(p.InitChainer)
        p.parent.PushBeginBlocker(p.BeginBlocker)
        p.parent.PushEndBlocker(p.EndBlocker)
    }
    

      

    不可思议的是:AppModule里面只有两样东西,AppModuleBasic和keeper。

    // NewAppModule creates a new AppModule object
    func NewAppModule(k Keeper) AppModule {
    	return AppModule{
    		AppModuleBasic: AppModuleBasic{},
    		keeper:         k,
    	}
    }
    

      

    TODO:

    abci.go

    二、时间周期

    链启动:initChain,包括genaccounts, distr, staking, auth, bank, slashing, gov, ...

    BeginBlock:全部执行前(环境准备)order,token,dex,mint,distr,slashing等

    Deliver Tx:

    EndBlock:全部执行后(到期计算,validator更新)crisis,gov,dex,order,staking,backend等

    commit:该区块交易导致的状态改变持久化

    其中:中间的三步在内存中,未持久化。

    后面4步,循环区块高度+1。

    三、交易msg

    位置:okchain/x/模块名/types/msg.go
    所有交易都是sdk.Msg接口的实现类,要分别实现五个方法:
     
    Route方法: 该msg类型进行模块路由的风向标。 说明:OKChain获得该msg后,要知道将它路由到哪个模块去处理。

    Type方法: 该msg类型的字符串说明 说明:用于底层执行交易抛出event时,起记录作用。

    ValidateBasic方法: 节点获得广播的交易后,能否将该msg打包进区块的第一步检查(准入检查)。 说明:一笔交易可能因为携带的数据字面量有误而不被打包进区块,比如:地址为空,金额为负。 不通过ValidateBasic的交易不会被打包进区块,也不会扣手续费。

    GetSignBytes方法: msg实体的编码方法。

    GetSigners方法: 防止冒充身份发交易的方法(双花攻击)。 说明:用以规定发送该交易的签名者需与msg中某地址严格一致。比如:A给B转账,A会对交易签 名,而且规定该转账的msg中的Sender地址必须与签名身份严格一致。

    举例:token模块的MsgSend

    // MsgSend - high level transaction of the coin module
    type MsgSend struct {
    FromAddress sdk.AccAddress `json:"from_address"`
    ToAddress sdk.AccAddress `json:"to_address"`
    Amount sdk.DecCoins `json:"amount"`
    }
    五个接口方法实现:
    func NewMsgTokenSend(from, to sdk.AccAddress, coins sdk.DecCoins) MsgSend {
    return MsgSend{
    FromAddress: from,
    ToAddress: to,
    Amount: coins,
    }
    }

    func (msg MsgSend) Route() string { return RouterKey }

    func (msg MsgSend) Type() string { return "send" }

    func (msg MsgSend) ValidateBasic() sdk.Error {
    if msg.FromAddress.Empty() {
    return sdk.ErrInvalidAddress("failed to check send msg because miss sender address")
    }
    if msg.ToAddress.Empty() {
    return sdk.ErrInvalidAddress("failed to check send msg because miss recipient address")
    }
    if !msg.Amount.IsValid() {
    return sdk.ErrInvalidCoins("failed to check send msg because send amount is invalid: " + msg.Amount.String())
    }
    if !msg.Amount.IsAllPositive() {
    return sdk.ErrInsufficientCoins("failed to check send msg because send amount must be positive")
    }
    return nil
    }

    func (msg MsgSend) GetSignBytes() []byte {
    bz := ModuleCdc.MustMarshalJSON(msg)
    return sdk.MustSortJSON(bz)
    }

    func (msg MsgSend) GetSigners() []sdk.AccAddress {
    return []sdk.AccAddress{msg.FromAddress}
    }

    四、模块:module.go

    位置:okchain/x/模块名/module.go

     
    OKChain在应用层内的运行规则是由各个模块组合而成。所有的模块位于okchain/x下。
    任何一个模块根据业务需求会包含以下部分:

    文件:

    genesis.go:该模块在InitGenesis时的执行逻辑。

    handler.go:每个交易会根据类型被发送至对应模块,并在handler.go中得到具体执行。

    module.go:每个模块都被protocol中的Manager统一管理,module.go是管理的框架。

    文件:

    genesis.go:该模块在InitGenesis时的执行逻辑。

    handler.go:每个交易会根据类型被发送至对应模块,并在handler.go中得到具体执行。

    module.go:每个模块都被protocol中的Manager统一管理,module.go是管理的框架。

    文件夹:
    client: okchaincli命令行(发交易和查询)及对应rest相关逻辑; 说明:指令“okchaincli tx 模块名”下的逻辑在x/模块名/cli/tx.go中,query同理。

    keeper: 该模块细粒度的核心逻辑执行者; 说明:宏观可以认为handler.go是通过调用keeper的类方法来实现交易逻辑。

    types: 该模块keeper和genesis使用到的所有结构体; 说明:types路径可以认为是导包调用的最底层,该package不会调用同级或上级路径中的任何资源。

    legacy: OKChain版本硬升级时,genesis file在不同版本间的转换逻辑。

    1,模块:genesis.go

    InitGenesis方法:在启动链时,从genesis.json中将该模块的数据库状态持久化到本地store中。

    ExportGenesis方法:在任意高度停掉okchain,导出当前store中任意高度的状态视图到genesis.json。

    2,模块:handler.go

    只有在本模块内定义msg的模块,即“okchaincli tx”的子命令包含的模块,才拥有文件handler.go。 NewHandler方法:根据路由过来的msg的类型断言,进行对应交易最终的执行分发。

    注:从Java角度来说,handler.go相当于controller+业务逻辑层,用于分发和逻辑实现,keeper.go相当于数据库读写层。

    每个模块是如何被整个Protocol管理的?
    是通过Protocol中的Manager。
     
    那Manager是如何管理每个模块的?
    通过两个接口AppModuleBasic和AppModule。 注:AppModule是AppModuleBasic的子接口。

    每个模块在module.go中都实现了这两个接口,与Manager产生连接。以dex模块举例:

    (1)AppModuleBasic:

    // AppModuleBasic represents a app module basics object
    type AppModuleBasic struct{}

    // Name returns module name
    func (AppModuleBasic) Name() string {
    return ModuleName
    }

    // RegisterCodec registers module codec
    func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) {
    types.RegisterCodec(cdc)
    }

    // DefaultGenesis returns default genesis state
    func (AppModuleBasic) DefaultGenesis() json.RawMessage {
    return types.ModuleCdc.MustMarshalJSON(DefaultGenesisState())
    }

    // ValidateGenesis validates genesis
    func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error {
    var data GenesisState
    err := types.ModuleCdc.UnmarshalJSON(bz, &data)
    if err != nil {
    return err
    }
    return ValidateGenesis(data)
    }

    // RegisterRESTRoutes registers rest routes
    func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) {
    rest.RegisterRoutes(ctx, rtr)
    }

    // GetTxCmd returns the root tx command of this module
    func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command {
    return cli.GetTxCmd(cdc)
    }

    // GetQueryCmd returns the root query command of this module
    func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command {
    return cli.GetQueryCmd(types.QuerierRoute, cdc)
    }

    (2)AppModule:


    // AppModule represents app module
    type AppModule struct {
    AppModuleBasic
    keeper IKeeper
    supplyKeeper SupplyKeeper
    version ProtocolVersionType
    }

    // NewAppModule creates a new AppModule object
    func NewAppModule(version ProtocolVersionType, keeper IKeeper, supplyKeeper SupplyKeeper) AppModule {
    return AppModule{
    AppModuleBasic: AppModuleBasic{},
    keeper: keeper,
    supplyKeeper: supplyKeeper,
    version: version,
    }
    }

    // RegisterInvariants registers invariants
    func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {
    keeper.RegisterInvariants(ir, am.keeper, am.supplyKeeper)
    }

    // Route returns module message route name
    func (AppModule) Route() string {
    return types.RouterKey
    }

    // NewHandler returns module handler
    func (am AppModule) NewHandler() sdk.Handler {
    return NewHandler(am.keeper)
    }

    // QuerierRoute returns module querier route name
    func (AppModule) QuerierRoute() string {
    return types.QuerierRoute
    }

    // NewQuerierHandler returns module querier
    func (am AppModule) NewQuerierHandler() sdk.Querier {
    return NewQuerier(am.keeper)
    }

    // InitGenesis inits module genesis
    func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate {
    var genesisState GenesisState
    types.ModuleCdc.MustUnmarshalJSON(data, &genesisState)
    InitGenesis(ctx, am.keeper, genesisState)
    return []abci.ValidatorUpdate{}
    }

    // ExportGenesis exports module genesis
    func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage {
    gs := ExportGenesis(ctx, am.keeper)
    return types.ModuleCdc.MustMarshalJSON(gs)
    }

    // BeginBlock returns module begin-block
    func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) {
    }

    // EndBlock returns module end-block
    func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
    EndBlocker(ctx, am.keeper)
    return nil
    }

    3,其他

    以order模块为例

    keeper目录:

    (1)invariant.go

    order模块的常数校验,每个模块都要进行当前状态的常数校验,如果不通过panic;接受crisis模块的监督(不开放)

    (2)querier.go

    查询路由,最终读数据库还是通过keeper,所以该文件在keeper目录下。

    types目录:

    codec.go:注册所有的写操作。

    // RegisterCodec registers concrete types on the Amino codec
    func RegisterCodec(cdc *codec.Codec) {
    cdc.RegisterConcrete(MsgNewOrders{}, "okexchain/order/MsgNew", nil)
    cdc.RegisterConcrete(MsgCancelOrders{}, "okexchain/order/MsgCancel", nil)
    }

    concrete,确实的,具体的(而非想象或猜测的);有形的;实在的

    keys.go:区块链状态最终都会存进k-v数据库,持久化时,不同类型的数据都会使用不同类型的keys。

    msgs.go:模块内自定义msg类型。TODO待续

    params.go:可以修改参数

    const (
    // System param
    DefaultOrderExpireBlocks = 259200 // order will be expired after 86400 blocks.
    DefaultMaxDealsPerBlock = 1000 // deals limit per block

    // Fee param
    DefaultFeeAmountPerBlock = "0" // okt
    DefaultFeeDenomPerBlock = common.NativeToken
    DefaultFeeRateTrade = "0.001" // percentage
    DefaultNewOrderMsgGasUnit = 40000
    DefaultCancelOrderMsgGasUnit = 30000
    )

     

  • 相关阅读:
    腾讯2014 笔试
    iOS 并发编程之 Operation Queues
    iOS 架构
    loadView and viewDidLoad?
    Referring to weak self inside a nested block
    Weakify和strongify探究
    iOS开发的最佳实践
    iOS 书籍
    Object-C非正式协议与正式协议的区别
    解决element-ui中el-menu组件作为vue-router模式在刷新页面后default-active属性与当前路由页面不一致问题的方法
  • 原文地址:https://www.cnblogs.com/zccst/p/13894793.html
Copyright © 2011-2022 走看看