tendermint, local & remote client
Tendermint supports both local and remote clients. Local client means using tendermint as a library, thus we will only hvae a single os process running and there is no RPC at all.
Remote client is the opposite where ABCI is invoked via RPC, socket or grpc depending on which remote client is implemented.
clientCreator
The point is how to create tendermint node. It must be provided to tendermint a clientCreator which is an interface with method NewABCIClient(). This method will be used by tendermit to create the proxy client later.
// NewABCIClient returns newly connected client
type ClientCreator interface {
NewABCIClient() (abcicli.Client, error)
}
There are two kinds of clientCreator implemented in tendermint:
//----------------------------------------------------
// local proxy uses a mutex on an in-proc app
type localClientCreator struct {
mtx *sync.Mutex
app types.Application
}
func NewLocalClientCreator(app types.Application) ClientCreator {
return &localClientCreator{
mtx: new(sync.Mutex),
app: app,
}
}
func (l *localClientCreator) NewABCIClient() (abcicli.Client, error) {
return abcicli.NewLocalClient(l.mtx, l.app), nil
}
//---------------------------------------------------------------
// remote proxy opens new connections to an external app process
type remoteClientCreator struct {
addr string
transport string
mustConnect bool
}
func NewRemoteClientCreator(addr, transport string, mustConnect bool) ClientCreator {
return &remoteClientCreator{
addr: addr,
transport: transport,
mustConnect: mustConnect,
}
}
func (r *remoteClientCreator) NewABCIClient() (abcicli.Client, error) {
remoteApp, err := abcicli.NewClient(r.addr, r.transport, r.mustConnect)
if err != nil {
return nil, errors.Wrap(err, "Failed to connect to proxy")
}
return remoteApp, nil
}
createAndStartProxyAppConns
NewNode() will create the app using createAndStartProxyAppConns(). This will create a NewMultiAppConn as a service to be started later.
func createAndStartProxyAppConns(clientCreator proxy.ClientCreator, logger log.Logger) (proxy.AppConns, error) {
proxyApp := proxy.NewAppConns(clientCreator)
proxyApp.SetLogger(logger.With("module", "proxy"))
if err := proxyApp.Start(); err != nil {
return nil, fmt.Errorf("error starting proxy app connections: %v", err)
}
return proxyApp, nil
}
func NewAppConns(clientCreator ClientCreator) AppConns {
return NewMultiAppConn(clientCreator)
}
// Make all necessary abci connections to the application
func NewMultiAppConn(clientCreator ClientCreator) *multiAppConn {
multiAppConn := &multiAppConn{
clientCreator: clientCreator,
}
multiAppConn.BaseService = *service.NewBaseService(nil, "multiAppConn", multiAppConn)
return multiAppConn
}
multiAppConn
It actually hold the appMempoolConn, appConnConsensus and queryConn for communicating between tendermint and app.
// multiAppConn implements AppConns
// a multiAppConn is made of a few appConns (mempool, consensus, query)
// and manages their underlying abci clients
// TODO: on app restart, clients must reboot together
type multiAppConn struct {
service.BaseService
mempoolConn *appConnMempool
consensusConn *appConnConsensus
queryConn *appConnQuery
clientCreator ClientCreator
}
When multiAppConn is started, multiAppConn will create all 3 connections to App. Here it is going to use clientCreator.NewABCIClient() to set up the connection.
func (app *multiAppConn) OnStart() error {
// query connection
querycli, err := app.clientCreator.NewABCIClient()
if err != nil {
return errors.Wrap(err, "Error creating ABCI client (query connection)")
}
querycli.SetLogger(app.Logger.With("module", "abci-client", "connection", "query"))
if err := querycli.Start(); err != nil {
return errors.Wrap(err, "Error starting ABCI client (query connection)")
}
app.queryConn = NewAppConnQuery(querycli)
// mempool connection
memcli, err := app.clientCreator.NewABCIClient()
if err != nil {
return errors.Wrap(err, "Error creating ABCI client (mempool connection)")
}
memcli.SetLogger(app.Logger.With("module", "abci-client", "connection", "mempool"))
if err := memcli.Start(); err != nil {
return errors.Wrap(err, "Error starting ABCI client (mempool connection)")
}
app.mempoolConn = NewAppConnMempool(memcli)
// consensus connection
concli, err := app.clientCreator.NewABCIClient()
if err != nil {
return errors.Wrap(err, "Error creating ABCI client (consensus connection)")
}
concli.SetLogger(app.Logger.With("module", "abci-client", "connection", "consensus"))
if err := concli.Start(); err != nil {
return errors.Wrap(err, "Error starting ABCI client (consensus connection)")
}
app.consensusConn = NewAppConnConsensus(concli)
return nil
}
Local Client
Simple as the code shown:
func (l *localClientCreator) NewABCIClient() (abcicli.Client, error) {
return abcicli.NewLocalClient(l.mtx, l.app), nil
}
func NewLocalClient(mtx *sync.Mutex, app types.Application) *localClient {
if mtx == nil {
mtx = new(sync.Mutex)
}
cli := &localClient{
mtx: mtx,
Application: app,
}
cli.BaseService = *service.NewBaseService(nil, "localClient", cli)
return cli
}
Taking Burrow as an example:
github.com/tendermint/tendermint/abci/client.NewLocalClient at local_client.go:25
github.com/tendermint/tendermint/proxy.(*localClientCreator).NewABCIClient at client.go:35
github.com/tendermint/tendermint/proxy.(*multiAppConn).OnStart at multi_app_conn.go:66
github.com/tendermint/tendermint/libs/service.(*BaseService).Start at service.go:139
github.com/tendermint/tendermint/node.createAndStartProxyAppConns at node.go:224
github.com/tendermint/tendermint/node.NewNode at node.go:574
github.com/hyperledger/burrow/consensus/tendermint.NewNode at tendermint.go:69
github.com/hyperledger/burrow/core.(*Kernel).LoadTendermintFromConfig at config.go:112
github.com/hyperledger/burrow/core.LoadKernelFromConfig at config.go:155
github.com/hyperledger/burrow/cmd/burrow/commands.Start.func1.1 at start.go:26
github.com/jawher/mow.cli/internal/flow.(*Step).callDo at flow.go:55
github.com/jawher/mow.cli/internal/flow.(*Step).Run at flow.go:25
github.com/jawher/mow.cli/internal/flow.(*Step).Run at flow.go:29
github.com/jawher/mow.cli/internal/flow.(*Step).Run at flow.go:29
github.com/jawher/mow.cli/internal/flow.(*Step).Run at flow.go:29
github.com/jawher/mow%2ecli.(*Cmd).parse at commands.go:681
github.com/jawher/mow%2ecli.(*Cmd).parse at commands.go:695
github.com/jawher/mow%2ecli.(*Cli).parse at cli.go:76
github.com/jawher/mow%2ecli.(*Cli).Run at cli.go:105
main.main at main.go:15
runtime.main at proc.go:203
runtime.goexit at asm_amd64.s:1357
- Async stack trace
runtime.rt0_go at asm_amd64.s:220
Remote Client
It will actually create RPC connection towards App.
func (r *remoteClientCreator) NewABCIClient() (abcicli.Client, error) {
remoteApp, err := abcicli.NewClient(r.addr, r.transport, r.mustConnect)
if err != nil {
return nil, errors.Wrap(err, "Failed to connect to proxy")
}
return remoteApp, nil
}
// NewClient returns a new ABCI client of the specified transport type.
// It returns an error if the transport is not "socket" or "grpc"
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
}
Call stack:
github.com/tendermint/tendermint/abci/client.NewSocketClient at socket_client.go:46
github.com/tendermint/tendermint/abci/client.NewClient at client.go:59
github.com/tendermint/tendermint/proxy.(*remoteClientCreator).NewABCIClient at client.go:56
github.com/tendermint/tendermint/proxy.(*multiAppConn).OnStart at multi_app_conn.go:88
github.com/tendermint/tendermint/libs/service.(*BaseService).Start at service.go:139
github.com/tendermint/tendermint/node.createAndStartProxyAppConns at node.go:226
github.com/tendermint/tendermint/node.NewNode at node.go:583
github.com/tendermint/tendermint/node.DefaultNewNode at node.go:114
github.com/tendermint/tendermint/cmd/tendermint/commands.NewRunNodeCmd.func1 at run_node.go:106
github.com/spf13/cobra.(*Command).execute at command.go:838
github.com/spf13/cobra.(*Command).ExecuteC at command.go:943
github.com/spf13/cobra.(*Command).Execute at command.go:883
github.com/tendermint/tendermint/libs/cli.Executor.Execute at setup.go:89
main.main at main.go:48
runtime.main at proc.go:203
runtime.goexit at asm_amd64.s:1357
- Async stack trace
runtime.rt0_go at asm_amd64.s:220