介绍
ABCI 是 Tendermint 中定义的一套 Core 和 App 交互的接口,它把共识引擎和应用层的逻辑进行解绑,可以帮助实现基于同一引擎上的高度定制化的 App 应用程序。
由于这种出色的架构设计把共识引擎独立出来,因此整个 tendermint 可以作为许多开源项目中一个可替代的底层共识机制来运作,大大的增加了其应用场景。
本文主要介绍 ABCI 的设计,以及 tendermint core 与 之的关系
下面是一段官方简介
Tendermint Core would be responsible for
- Sharing blocks and transactions between nodes
- Establishing a canonical/immutable order of transactions (the blockchain)
The application will be responsible for
- Maintaining the UTXO database
- Validating cryptographic signatures of transactions
- Preventing transactions from spending non-existent transactions
- Allowing clients to query the UTXO database.
翻译成中文就是
Core 所做的事
- 节点之间共享 blocks 和 transactions 信息
- 建立不可更改的交易顺序
Application 所做的事
- 维护 UTXO database
- 验证签名的有效性
- 验证交易的合法性
- 运行 client 去查询 UTXO database
总结来说
Core 是共识引擎,负责进行交易的共识
Application 则是状态机,记录交易开始的状态和最终 Commit 的状态
App 发出一笔交易,把状态(交易)信息发给共识引擎,共识引擎完成共识过程之后,把共识结果发回给 App,由 App 最终写入持久化。
ABCI 主要以下几个要点:
(1) 消息协议 (message protocol)
- protobuf,
- consensus makes requests, application responds
(2)Server/Client
- consensus engine runs client
- application runs server
- two implementations: async raw bytes, grpc
(3) Blockchain protocol
Tendermint Core 主要维护 3 类连接:
- mempool connection: CheckTx
- consensus connnections: 只有 commited 的 transactions 才会被 executed。
- query connections: only uses Query and Info
Note: The mempool and consensus logic act as clients, and each maintains an open ABCI connection with the application, which hosts an ABCI server.
ABCI Server
要在不同的语言中使用 ABCI,这个语言必须要实现一个 ABCI server。
简单来说,需要实现:
- a socket server
- a handler for ABCI Messages
具体如下:
(1)ABCI server
1. Socket Server
func (s *SocketServer) OnStart() error {
s.BaseService.OnStart()
ln, err := net.Listen(s.proto, s.addr)
if err != nil {
return err
}
s.listener = ln
go s.acceptConnectionsRoutine()
return nil
}
注:这里我们可以既通过 socket RPC 来实现(效率高),也可以通过 gRPC 来实现(效率低)。
- Asynchronous raw byte server (比如 Tendermint Socket Protocol,Known as TMSP or Teaspoon)
- GRPC
2. ABCI Server Implementation
在 ABCI Server 中,源码在 abci/server/
目录下:
server/
├── grpc_server.go
├── server.go
└── socket_server.go
比如,对于普通的 RPC 来说,在 socket_server.go 中的 handleRequest() 函数中,会根据 types.Request 的类型来调用 application 接口中的相关方法。
// Pull responses from 'responses' and write them to conn.
func (s *SocketServer) handleResponses(closeConn chan error, responses <-chan *types.Response, conn net.Conn) {
var count int
var bufWriter = bufio.NewWriter(conn)
for {
var res = <-responses
err := types.WriteMessage(res, bufWriter)
if err != nil {
closeConn <- fmt.Errorf("Error writing message: %v", err.Error())
return
}
if _, ok := res.Value.(*types.Response_Flush); ok {
err = bufWriter.Flush()
if err != nil {
closeConn <- fmt.Errorf("Error flushing write buffer: %v", err.Error())
return
}
}
count++
}
}
func (s *SocketServer) handleRequest(req *types.Request, responses chan<- *types.Response) {
switch r := req.Value.(type) {
case *types.Request_DeliverTx:
res := s.app.DeliverTx(r.DeliverTx.Tx)
responses <- types.ToResponseDeliverTx(res.Code, res.Data, res.Log)
case *types.Request_CheckTx:
res := s.app.CheckTx(r.CheckTx.Tx)
responses <- types.ToResponseCheckTx(res.Code, res.Data, res.Log)
case *types.Request_Commit:
res := s.app.Commit()
responses <- types.ToResponseCommit(res.Code, res.Data, res.Log)
...
}
}
注意这里 handleRequest 的处理方式是写 channel,而不是直接 response,response 是在 handleResponse() 函数中根据 channel 中获得值进行处理的
// Pull responses from 'responses' and write them to conn.
func (s *SocketServer) handleResponses(closeConn chan error, responses <-chan *types.Response, conn net.Conn) {
var count int
var bufWriter = bufio.NewWriter(conn)
for {
var res = <-responses
err := types.WriteMessage(res, bufWriter)
if err != nil {
closeConn <- fmt.Errorf("Error writing message: %v", err.Error())
return
}
if _, ok := res.Value.(*types.Response_Flush); ok {
err = bufWriter.Flush()
if err != nil {
closeConn <- fmt.Errorf("Error flushing write buffer: %v", err.Error())
return
}
}
count++
}
}
(2)Application
1. App Interface
作为一个 ABCI server,其必须实现 Application 接口,这个接口的声明在 tpes/application.go
中,如下:
type Application interface {
// Info/Query Connection
Info() ResponseInfo // Return application info
SetOption(key string, value string) (log string) // Set application option
Query(reqQuery RequestQuery) ResponseQuery // Query for state
// Mempool Connection
CheckTx(tx []byte) Result // Validate a tx for the mempool
// Consensus Connection
InitChain(validators []*Validator) // Initialize blockchain with validators from TendermintCore
BeginBlock(hash []byte, header *Header) // Signals the beginning of a block
DeliverTx(tx []byte) Result // Deliver a tx for full processing
EndBlock(height uint64) ResponseEndBlock // Signals the end of a block, returns changes to the validator set
Commit() Result // Commit the state and return the application Merkle root hash
}
ABCI Messages 所使用的 消息类型都是通过 Protobuf 协议定义的,在 types/types.proto
文件中声明(包括 Validator,Request, Response 等消息格式)。
enum CodeType {
OK = 0;
InternalError = 1;
EncodingError = 2;
...
}
message Request {
oneof value{
RequestEcho echo = 1;
RequestFlush flush = 2;
RequestInfo info = 3;
RequestSetOption set_option = 4;
RequestDeliverTx deliver_tx = 5;
RequestCheckTx check_tx = 6;
RequestCommit commit = 7;
...
}
}
message Response {
oneof value{
ResponseException exception = 1;
ResponseEcho echo = 2;
ResponseFlush flush = 3;
ResponseInfo info = 4;
ResponseSetOption set_option = 5;
...
}
}
message Validator {
bytes pubKey = 1;
uint64 power = 2;
}
2. App Implementation
Application 中声明的接口方法,既可以在具体的 application 代码中实现,tendermint 提供了 abci server 的两个简单实现,dummy 和 counter,侦听端口是 46658。
dummy 在 abci/example/dummy/dummy.go 中实现,而 counter 则在 abci/example/counter/counter.go 中实现。
ABCI Message 中最重要的就是 deliver_tx, check_tx, and commit 三类消息。
tendermint engine 的角色是作为 ABCI Client,因此任何一个 Validator 节点(也即 Tendermint Node)都是作为 ABCI Client,启动 Tendermint Node 之后就可以连接到 ABCI Server 了。
Tendermint 还提供了一个简易的 ABCI 客户端,即 abci-cli,通过 go get -u github.com/tendermint/abci/cmd/... 获得。
这样,就可以通过这个 abci-cli 给 ABCI Server 发消息了。
比如:
$ abci-cli echo hello
$ abci-cli info
abci/abci-cli/abci-cli.go
提供的是 command,而 client 代码的具体实现全部在 abci/client/
目录下
client/
├── client.go
├── grpc_client.go
├── local_client.go
└── socket_client.go
if client == nil {
var err error
client, err = abcicli.NewClient(c.GlobalString("address"), c.GlobalString("abci"), false)
...
}
具体的 client 类型是通过 transport 类型来指定的(默认是 socket)
func NewClient(addr, transport string, mustConnect bool) (client Client, err error) {
switch transport {
case "socket":
client = NewSocketClient(addr, mustConnect)
case "grpc":
client = NewGRPCClient(addr, mustConnect)
default:
err = fmt.Errorf("Unknown abci transport %s", transport)
}
return
}
默认的 transport 类型是 socket
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "address",
Value: "tcp://127.0.0.1:46658",
Usage: "address of application socket",
},
cli.StringFlag{
Name: "abci",
Value: "socket",
Usage: "socket or grpc",
},
cli.BoolFlag{
Name: "verbose",
Usage: "print the command and results as if it were a console session",
},
}
启动 Server
srv, _ := server.NewServer("tcp://0.0.0.0:46658", "socket", app)
if _, err := srv.Start(); err != nil {
os.Exit(1)
}
解释:
这里通过调用 server.NewServer()
来创建一个 Server,三个参数分别是,侦听地址,transport 类型(socket/grpc),app 实现。返回类型是 cmn.Service
。
具体的启动逻辑在 srv.Start()
里面。 SocketServer 内置了 BaseService 结构体成员变量,其实现了 Service 声明的方法。
type Service interface {
Start() (bool, error)
OnStart() error
Stop() bool
OnStop()
Reset() (bool, error)
...
}
可见,最终是通过调用 OnStart() 来启动的,其过程很简单,简单来说就是调用 net.Listen()
和 go s.acceptConnectionsRoutine()
来启动侦听和接受连接。
func (s *SocketServer) OnStart() error {
s.BaseService.OnStart()
ln, err := net.Listen(s.proto, s.addr)
if err != nil {
return err
}
s.listener = ln
go s.acceptConnectionsRoutine()
return nil
}
全文完
网友评论