package mock import ( "context" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/internal/proxy" "github.com/tendermint/tendermint/libs/bytes" "github.com/tendermint/tendermint/rpc/client" "github.com/tendermint/tendermint/rpc/coretypes" "github.com/tendermint/tendermint/types" ) // ABCIApp will send all abci related request to the named app, // so you can test app behavior from a client without needing // an entire tendermint node type ABCIApp struct { App abci.Application } var ( _ client.ABCIClient = ABCIApp{} _ client.ABCIClient = ABCIMock{} _ client.ABCIClient = (*ABCIRecorder)(nil) ) func (a ABCIApp) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, error) { return &coretypes.ResultABCIInfo{Response: a.App.Info(proxy.RequestInfo)}, nil } func (a ABCIApp) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) (*coretypes.ResultABCIQuery, error) { return a.ABCIQueryWithOptions(ctx, path, data, client.DefaultABCIQueryOptions) } func (a ABCIApp) ABCIQueryWithOptions( ctx context.Context, path string, data bytes.HexBytes, opts client.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { q := a.App.Query(abci.RequestQuery{ Data: data, Path: path, Height: opts.Height, Prove: opts.Prove, }) return &coretypes.ResultABCIQuery{Response: q}, nil } // NOTE: Caller should call a.App.Commit() separately, // this function does not actually wait for a commit. // TODO: Make it wait for a commit and set res.Height appropriately. func (a ABCIApp) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) { res := coretypes.ResultBroadcastTxCommit{} res.CheckTx = a.App.CheckTx(abci.RequestCheckTx{Tx: tx}) if res.CheckTx.IsErr() { return &res, nil } fb := a.App.FinalizeBlock(abci.RequestFinalizeBlock{Txs: [][]byte{tx}}) res.TxResult = *fb.TxResults[0] res.Height = -1 // TODO return &res, nil } func (a ABCIApp) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { c := a.App.CheckTx(abci.RequestCheckTx{Tx: tx}) // and this gets written in a background thread... if !c.IsErr() { go func() { a.App.FinalizeBlock(abci.RequestFinalizeBlock{Txs: [][]byte{tx}}) }() } return &coretypes.ResultBroadcastTx{ Code: c.Code, Data: c.Data, Log: c.Log, Codespace: c.Codespace, Hash: tx.Hash(), }, nil } func (a ABCIApp) BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { c := a.App.CheckTx(abci.RequestCheckTx{Tx: tx}) // and this gets written in a background thread... if !c.IsErr() { go func() { a.App.FinalizeBlock(abci.RequestFinalizeBlock{Txs: [][]byte{tx}}) }() } return &coretypes.ResultBroadcastTx{ Code: c.Code, Data: c.Data, Log: c.Log, Codespace: c.Codespace, Hash: tx.Hash(), }, nil } // ABCIMock will send all abci related request to the named app, // so you can test app behavior from a client without needing // an entire tendermint node type ABCIMock struct { Info Call Query Call BroadcastCommit Call Broadcast Call } func (m ABCIMock) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, error) { res, err := m.Info.GetResponse(nil) if err != nil { return nil, err } return &coretypes.ResultABCIInfo{Response: res.(abci.ResponseInfo)}, nil } func (m ABCIMock) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) (*coretypes.ResultABCIQuery, error) { return m.ABCIQueryWithOptions(ctx, path, data, client.DefaultABCIQueryOptions) } func (m ABCIMock) ABCIQueryWithOptions( ctx context.Context, path string, data bytes.HexBytes, opts client.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { res, err := m.Query.GetResponse(QueryArgs{path, data, opts.Height, opts.Prove}) if err != nil { return nil, err } resQuery := res.(abci.ResponseQuery) return &coretypes.ResultABCIQuery{Response: resQuery}, nil } func (m ABCIMock) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) { res, err := m.BroadcastCommit.GetResponse(tx) if err != nil { return nil, err } return res.(*coretypes.ResultBroadcastTxCommit), nil } func (m ABCIMock) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { res, err := m.Broadcast.GetResponse(tx) if err != nil { return nil, err } return res.(*coretypes.ResultBroadcastTx), nil } func (m ABCIMock) BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { res, err := m.Broadcast.GetResponse(tx) if err != nil { return nil, err } return res.(*coretypes.ResultBroadcastTx), nil } // ABCIRecorder can wrap another type (ABCIApp, ABCIMock, or Client) // and record all ABCI related calls. type ABCIRecorder struct { Client client.ABCIClient Calls []Call } func NewABCIRecorder(client client.ABCIClient) *ABCIRecorder { return &ABCIRecorder{ Client: client, Calls: []Call{}, } } type QueryArgs struct { Path string Data bytes.HexBytes Height int64 Prove bool } func (r *ABCIRecorder) addCall(call Call) { r.Calls = append(r.Calls, call) } func (r *ABCIRecorder) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, error) { res, err := r.Client.ABCIInfo(ctx) r.addCall(Call{ Name: "abci_info", Response: res, Error: err, }) return res, err } func (r *ABCIRecorder) ABCIQuery( ctx context.Context, path string, data bytes.HexBytes, ) (*coretypes.ResultABCIQuery, error) { return r.ABCIQueryWithOptions(ctx, path, data, client.DefaultABCIQueryOptions) } func (r *ABCIRecorder) ABCIQueryWithOptions( ctx context.Context, path string, data bytes.HexBytes, opts client.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { res, err := r.Client.ABCIQueryWithOptions(ctx, path, data, opts) r.addCall(Call{ Name: "abci_query", Args: QueryArgs{path, data, opts.Height, opts.Prove}, Response: res, Error: err, }) return res, err } func (r *ABCIRecorder) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) { res, err := r.Client.BroadcastTxCommit(ctx, tx) r.addCall(Call{ Name: "broadcast_tx_commit", Args: tx, Response: res, Error: err, }) return res, err } func (r *ABCIRecorder) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { res, err := r.Client.BroadcastTxAsync(ctx, tx) r.addCall(Call{ Name: "broadcast_tx_async", Args: tx, Response: res, Error: err, }) return res, err } func (r *ABCIRecorder) BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) { res, err := r.Client.BroadcastTxSync(ctx, tx) r.addCall(Call{ Name: "broadcast_tx_sync", Args: tx, Response: res, Error: err, }) return res, err }