package proxy import ( "context" "io" "os" "syscall" "time" "github.com/go-kit/kit/metrics" abciclient "github.com/tendermint/tendermint/abci/client" "github.com/tendermint/tendermint/abci/example/kvstore" "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/service" e2e "github.com/tendermint/tendermint/test/e2e/app" ) // ClientFactory returns a client object, which will create a local // client if addr is one of: 'kvstore', 'persistent_kvstore', 'e2e', // or 'noop', otherwise - a remote client. // // The Closer is a noop except for persistent_kvstore applications, // which will clean up the store. func ClientFactory(logger log.Logger, addr, transport, dbDir string) (abciclient.Client, io.Closer, error) { switch addr { case "kvstore": return abciclient.NewLocalClient(logger, kvstore.NewApplication()), noopCloser{}, nil case "persistent_kvstore": app := kvstore.NewPersistentKVStoreApplication(logger, dbDir) return abciclient.NewLocalClient(logger, app), app, nil case "e2e": app, err := e2e.NewApplication(e2e.DefaultConfig(dbDir)) if err != nil { return nil, noopCloser{}, err } return abciclient.NewLocalClient(logger, app), noopCloser{}, nil case "noop": return abciclient.NewLocalClient(logger, types.NewBaseApplication()), noopCloser{}, nil default: const mustConnect = false // loop retrying client, err := abciclient.NewClient(logger, addr, transport, mustConnect) if err != nil { return nil, noopCloser{}, err } return client, noopCloser{}, nil } } type noopCloser struct{} func (noopCloser) Close() error { return nil } // proxyClient provides the application connection. type proxyClient struct { service.BaseService logger log.Logger client abciclient.Client metrics *Metrics } // New creates a proxy application interface. func New(client abciclient.Client, logger log.Logger, metrics *Metrics) abciclient.Client { conn := &proxyClient{ logger: logger, metrics: metrics, client: client, } conn.BaseService = *service.NewBaseService(logger, "proxyClient", conn) return conn } func (app *proxyClient) OnStop() { tryCallStop(app.client) } func (app *proxyClient) Error() error { return app.client.Error() } func tryCallStop(client abciclient.Client) { if c, ok := client.(interface{ Stop() }); ok { c.Stop() } } func (app *proxyClient) OnStart(ctx context.Context) error { var err error defer func() { if err != nil { tryCallStop(app.client) } }() // Kill Tendermint if the ABCI application crashes. go func() { if !app.client.IsRunning() { return } app.client.Wait() if ctx.Err() != nil { return } if err := app.client.Error(); err != nil { app.logger.Error("client connection terminated. Did the application crash? Please restart tendermint", "err", err) if killErr := kill(); killErr != nil { app.logger.Error("Failed to kill this process - please do so manually", "err", killErr) } } }() return app.client.Start(ctx) } func kill() error { p, err := os.FindProcess(os.Getpid()) if err != nil { return err } return p.Signal(syscall.SIGABRT) } func (app *proxyClient) InitChain(ctx context.Context, req types.RequestInitChain) (*types.ResponseInitChain, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "init_chain", "type", "sync"))() return app.client.InitChain(ctx, req) } func (app *proxyClient) PrepareProposal(ctx context.Context, req types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "prepare_proposal", "type", "sync"))() return app.client.PrepareProposal(ctx, req) } func (app *proxyClient) ProcessProposal(ctx context.Context, req types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "process_proposal", "type", "sync"))() return app.client.ProcessProposal(ctx, req) } func (app *proxyClient) ExtendVote(ctx context.Context, req types.RequestExtendVote) (*types.ResponseExtendVote, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "extend_vote", "type", "sync"))() return app.client.ExtendVote(ctx, req) } func (app *proxyClient) VerifyVoteExtension(ctx context.Context, req types.RequestVerifyVoteExtension) (*types.ResponseVerifyVoteExtension, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "verify_vote_extension", "type", "sync"))() return app.client.VerifyVoteExtension(ctx, req) } func (app *proxyClient) FinalizeBlock(ctx context.Context, req types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "finalize_block", "type", "sync"))() return app.client.FinalizeBlock(ctx, req) } func (app *proxyClient) Commit(ctx context.Context) (*types.ResponseCommit, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "commit", "type", "sync"))() return app.client.Commit(ctx) } func (app *proxyClient) Flush(ctx context.Context) error { defer addTimeSample(app.metrics.MethodTiming.With("method", "flush", "type", "sync"))() return app.client.Flush(ctx) } func (app *proxyClient) CheckTx(ctx context.Context, req types.RequestCheckTx) (*types.ResponseCheckTx, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "check_tx", "type", "sync"))() return app.client.CheckTx(ctx, req) } func (app *proxyClient) Echo(ctx context.Context, msg string) (*types.ResponseEcho, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "echo", "type", "sync"))() return app.client.Echo(ctx, msg) } func (app *proxyClient) Info(ctx context.Context, req types.RequestInfo) (*types.ResponseInfo, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "info", "type", "sync"))() return app.client.Info(ctx, req) } func (app *proxyClient) Query(ctx context.Context, reqQuery types.RequestQuery) (*types.ResponseQuery, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "query", "type", "sync"))() return app.client.Query(ctx, reqQuery) } func (app *proxyClient) ListSnapshots(ctx context.Context, req types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "list_snapshots", "type", "sync"))() return app.client.ListSnapshots(ctx, req) } func (app *proxyClient) OfferSnapshot(ctx context.Context, req types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "offer_snapshot", "type", "sync"))() return app.client.OfferSnapshot(ctx, req) } func (app *proxyClient) LoadSnapshotChunk(ctx context.Context, req types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "load_snapshot_chunk", "type", "sync"))() return app.client.LoadSnapshotChunk(ctx, req) } func (app *proxyClient) ApplySnapshotChunk(ctx context.Context, req types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "apply_snapshot_chunk", "type", "sync"))() return app.client.ApplySnapshotChunk(ctx, req) } // addTimeSample returns a function that, when called, adds an observation to m. // The observation added to m is the number of seconds ellapsed since addTimeSample // was initially called. addTimeSample is meant to be called in a defer to calculate // the amount of time a function takes to complete. func addTimeSample(m metrics.Histogram) func() { start := time.Now() return func() { m.Observe(time.Since(start).Seconds()) } }