baseapp.go

Loc: baseapp/baseapp.go

ABCI应用的基本结构

// The ABCI application
type BaseApp struct {
// initialized on creation
Logger log.Logger
name string // application name from abci.Info
db dbm.DB // common DB backend
cms sdk.CommitMultiStore // Main (uncached) state
router Router // handle any kind of message
codespacer *sdk.Codespacer // handle module codespacing
// must be set
txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx
anteHandler sdk.AnteHandler // ante handler for fee and auth
// may be nil
initChainer sdk.InitChainer // initialize state with validators and state blob
beginBlocker sdk.BeginBlocker // logic to run before any txs
endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes
//--------------------
// Volatile
// checkState is set on initialization and reset on Commit.
// deliverState is set in InitChain and BeginBlock and cleared on Commit.
// See methods setCheckState and setDeliverState.
// .valUpdates accumulate in DeliverTx and are reset in BeginBlock.
// QUESTION: should we put valUpdates in the deliverState.ctx?
checkState *state // for CheckTx
deliverState *state // for DeliverTx
valUpdates []abci.Validator // cached validator changes from DeliverTx
}
  • name 就是应用的名称

  • router 路由选择正确的handler进行处理

  • anteHandler 每一笔交易都会执行手续费和验证的handler

  • beginBlocker 和 endBlocker就是 对应的abci接口,一个在所有交易之前执行,一个是在交易执行之后执行。

  • checkState 和 deliverState表示的是当前处理的交易进入了什么阶段。

  • valUpdates 是缓存验证人集的更新,一般在endblocker的时候更新

ABCI 几个典型接口

// Implements ABCI
func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) {
// Decode the Tx.
var result sdk.Result
var tx, err = app.txDecoder(txBytes)
if err != nil {
result = err.Result()
} else {
result = app.runTx(false, txBytes, tx)
}
// After-handler hooks.
if result.IsOK() {
app.valUpdates = append(app.valUpdates, result.ValidatorUpdates...)
} else {
// Even though the Result.Code is not OK, there are still effects,
// namely fee deductions and sequence incrementing.
}
// Tell the blockchain engine (i.e. Tendermint).
return abci.ResponseDeliverTx{
Code: uint32(result.Code),
Data: result.Data,
Log: result.Log,
GasWanted: result.GasWanted,
GasUsed: result.GasUsed,
Tags: result.Tags,
}
}

上面就是一个DeliverTx的grpc调用的接口实现。 消息是通过go-amino序列化过所以他先用app.txDecoder(txBytes)解码,再调用runTx, 其实大部分交易tx 都是在runTx中处理的,然后返回处理结果。 app.valUpdates = append(...添加改变的验证人,最后把abci.ResponseDeliverTx返回给tendermint core

这时候大家肯定很好奇runTx里面到底做了什么? 让我继续跟踪吧(因为runtx函数源码很长所以我删去了一部分对理解没有关系的代码)

// txBytes may be nil in some cases, eg. in tests.
// Also, in the future we may support "internal" transactions.
func (app *BaseApp) runTx(isCheckTx bool, txBytes []byte, tx sdk.Tx) (result sdk.Result) {
// Handle any panics.
defer func() {...}()
// Get the Msg.
var msg = tx.GetMsg()
...
// Validate the Msg.
err := msg.ValidateBasic()
...
// Get the context
var ctx sdk.Context
if isCheckTx {
ctx = app.checkState.ctx.WithTxBytes(txBytes)
} else {
ctx = app.deliverState.ctx.WithTxBytes(txBytes)
}
// Run the ante handler.
if app.anteHandler != nil {
newCtx, result, abort := app.anteHandler(ctx, tx)
...
}
// Match route.
msgType := msg.Type()
handler := app.router.Route(msgType)
...
// Get the correct cache
var msCache sdk.CacheMultiStore
if isCheckTx == true {
// CacheWrap app.checkState.ms in case it fails.
msCache = app.checkState.CacheMultiStore()
ctx = ctx.WithMultiStore(msCache)
} else {
// CacheWrap app.deliverState.ms in case it fails.
msCache = app.deliverState.CacheMultiStore()
ctx = ctx.WithMultiStore(msCache)
}
result = handler(ctx, msg)
// If result was successful, write to app.checkState.ms or app.deliverState.ms
if result.IsOK() {
msCache.Write()
}
return result
}

输入参数isCheckTx代表了这个runtx是checktx调用的还是delivertx调用的,因为这两个接口,有些操作和资源需求的是不一样的。但设计的时候delivertx和checktx都会走runtx。 GetMsg()获取msgmsg.validateBasic()验证交易合法性,ctx就相当于处理tx所需要的所有资源。msg.type()获取交易消息的类型。 handler := app.router.Route(msgType)这个就是根据交易信息类型选择handler,这个Route的具体应用就在这里,根据模块路由消息到对应的handler。handler(ctx,msg)开始调用你编写的handler.go里面的代码了(其实对应的handler肯定有一个参数会判别这个消息是checkt还是delivertx,因为上面说过他们两走一个,后面源码分析也会看到有一个变量决定了),返回消息处理的结果。所以在app.go对baseapp.go二次封装的时候,必定要要把自己模块的handler注册到router上。

注意 大家有没有注意到 antehandler,这个是不管是那个模块对应的交易消息都会执行的handler,我猜以后会实现手续费啊之类的。

总结

baseapp.go 定义了一个基本的abci应用的结构和接口。 用户具体的app.go(gaia)都是对baseapp.go的二次封装,添加注册模块。通过route来调用不同的模块的handler,各个模块相互独立。