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
+}