okexchain整体架构分析
今天重读的感受与几个月前完全不同,我想大概的原因,是你的基础储备有多少。
如果你基础储备足够,可能很快就理解了。如果基础储备不够,可能要花很长时间也不理解。我之前是典型的后者。下面进入正文
一个大型程序的运行:
入口-框架-分发到各模块。
入口:在app/app.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
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
文件:
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是管理的框架。
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相当于数据库读写层。
每个模块在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
)