diff --git a/lite2/rpc/client.go b/lite2/rpc/client.go index 9155220c9..2a36e7892 100644 --- a/lite2/rpc/client.go +++ b/lite2/rpc/client.go @@ -352,6 +352,11 @@ func (c *Client) TxSearch(query string, prove bool, page, perPage int, orderBy s return c.next.TxSearch(query, prove, page, perPage, orderBy) } +func (c *Client) TxByHeight(height int64, prove bool, orderBy string) ( + *ctypes.ResultTxSearch, error) { + return c.next.TxByHeight(height, prove, orderBy) +} + // Validators fetches and verifies validators. // // WARNING: only full validator sets are verified (when length of validators is diff --git a/rpc/client/http/http.go b/rpc/client/http/http.go index 9af0b6cf4..d1462bc10 100644 --- a/rpc/client/http/http.go +++ b/rpc/client/http/http.go @@ -408,6 +408,21 @@ func (c *baseRPCClient) TxSearch(query string, prove bool, page, perPage int, or return result, nil } +func (c *baseRPCClient) TxByHeight(height int64, prove bool, orderBy string) ( + *ctypes.ResultTxSearch, error) { + result := new(ctypes.ResultTxSearch) + params := map[string]interface{}{ + "height": height, + "prove": prove, + "order_by": orderBy, + } + _, err := c.caller.Call("tx_by_height", params, result) + if err != nil { + return nil, errors.Wrap(err, "TxByHeight") + } + return result, nil +} + func (c *baseRPCClient) Validators(height *int64, page, perPage int) (*ctypes.ResultValidators, error) { result := new(ctypes.ResultValidators) _, err := c.caller.Call("validators", map[string]interface{}{ diff --git a/rpc/client/interface.go b/rpc/client/interface.go index 408d803c8..5b46c6829 100644 --- a/rpc/client/interface.go +++ b/rpc/client/interface.go @@ -70,6 +70,8 @@ type SignClient interface { Validators(height *int64, page, perPage int) (*ctypes.ResultValidators, error) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) TxSearch(query string, prove bool, page, perPage int, orderBy string) (*ctypes.ResultTxSearch, error) + // NOTE It could affect on the performance and it must be used only for `blocks_with_tx_results` + TxByHeight(height int64, prove bool, orderBy string) (*ctypes.ResultTxSearch, error) } // HistoryClient provides access to data from genesis to now in large chunks. diff --git a/rpc/core/routes.go b/rpc/core/routes.go index aa0403f87..0c2a18560 100644 --- a/rpc/core/routes.go +++ b/rpc/core/routes.go @@ -25,6 +25,7 @@ var Routes = map[string]*rpc.RPCFunc{ "commit": rpc.NewRPCFunc(Commit, "height"), "tx": rpc.NewRPCFunc(Tx, "hash,prove"), "tx_search": rpc.NewRPCFunc(TxSearch, "query,prove,page,per_page,order_by"), + "tx_by_height": rpc.NewRPCFunc(TxByHeight, "height,prove,order_by"), "validators": rpc.NewRPCFunc(Validators, "height,page,per_page"), "dump_consensus_state": rpc.NewRPCFunc(DumpConsensusState, ""), "consensus_state": rpc.NewRPCFunc(ConsensusState, ""), diff --git a/rpc/core/tx.go b/rpc/core/tx.go index e7e2582f6..9525b79be 100644 --- a/rpc/core/tx.go +++ b/rpc/core/tx.go @@ -124,3 +124,64 @@ func TxSearch(ctx *rpctypes.Context, query string, prove bool, page, perPage int return &ctypes.ResultTxSearch{Txs: apiResults, TotalCount: totalCount}, nil } + +func TxByHeight(ctx *rpctypes.Context, height int64, prove bool, orderBy string) (*ctypes.ResultTxSearch, error) { + // if index is disabled, return error + if _, ok := txIndexer.(*null.TxIndex); ok { + return nil, errors.New("transaction indexing is disabled") + } + + q, err := tmquery.New(fmt.Sprintf("tx.height=%d", height)) + if err != nil { + return nil, err + } + + results, err := txIndexer.Search(ctx.Context(), q) + if err != nil { + return nil, err + } + + // sort results + switch orderBy { + case "desc": + sort.Slice(results, func(i, j int) bool { + return results[i].Index > results[j].Index + }) + case "asc", "": + sort.Slice(results, func(i, j int) bool { + return results[i].Index < results[j].Index + }) + default: + return nil, errors.New("expected order_by to be either `asc` or `desc` or empty") + } + + totalCount := len(results) + + var block *types.Block + if prove { + block = blockStore.LoadBlock(height) + } + + apiResults := make([]*ctypes.ResultTx, 0, totalCount) + for _, r := range results { + + var proof types.TxProof + if prove { + if block == nil { + panic("block must not be nil") + } + proof = block.Data.Txs.Proof(int(r.Index)) // XXX: overflow on 32-bit machines + } + + apiResults = append(apiResults, &ctypes.ResultTx{ + Hash: r.Tx.Hash(), + Height: r.Height, + Index: r.Index, + TxResult: r.Result, + Tx: r.Tx, + Proof: proof, + }) + } + + return &ctypes.ResultTxSearch{Txs: apiResults, TotalCount: totalCount}, nil +}