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
)

 

 

posted @ 2020-10-29 09:00  走走停停走走  Views(211)  Comments(0Edit  收藏  举报