Skip to content

Commit

Permalink
feat(tm2): store tx results and add endpoint to query them (gnolang#1546
Browse files Browse the repository at this point in the history
)

## Description

This PR introduces saving transaction results (execution results) in the
node's state DB, and serving them over the node's RPC endpoint (`tx`).

<details><summary>Contributors' checklist...</summary>

- [x] Added new tests, or not needed, or not feasible
- [x] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [x] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
</details>
  • Loading branch information
zivkovicmilos authored and omarsy committed Apr 21, 2024
1 parent 04e282f commit 6600f51
Show file tree
Hide file tree
Showing 16 changed files with 588 additions and 356 deletions.
10 changes: 10 additions & 0 deletions gno.land/pkg/gnoclient/mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ type (
mockStatus func() (*ctypes.ResultStatus, error)
mockUnconfirmedTxs func(limit int) (*ctypes.ResultUnconfirmedTxs, error)
mockNumUnconfirmedTxs func() (*ctypes.ResultUnconfirmedTxs, error)
mockTx func(hash []byte) (*ctypes.ResultTx, error)
)

type mockRPCClient struct {
Expand All @@ -141,6 +142,7 @@ type mockRPCClient struct {
status mockStatus
unconfirmedTxs mockUnconfirmedTxs
numUnconfirmedTxs mockNumUnconfirmedTxs
tx mockTx
}

func (m *mockRPCClient) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
Expand Down Expand Up @@ -282,3 +284,11 @@ func (m *mockRPCClient) NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error
}
return nil, nil
}

func (m *mockRPCClient) Tx(hash []byte) (*ctypes.ResultTx, error) {
if m.tx != nil {
return m.tx(hash)
}

return nil, nil
}
20 changes: 2 additions & 18 deletions tm2/pkg/bft/rpc/client/httpclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,10 @@ func (c *baseRPCClient) Commit(height *int64) (*ctypes.ResultCommit, error) {
return result, nil
}

func (c *baseRPCClient) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
func (c *baseRPCClient) Tx(hash []byte) (*ctypes.ResultTx, error) {
result := new(ctypes.ResultTx)
params := map[string]interface{}{
"hash": hash,
"prove": prove,
"hash": hash,
}
_, err := c.caller.Call("tx", params, result)
if err != nil {
Expand All @@ -320,21 +319,6 @@ func (c *baseRPCClient) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
return result, nil
}

func (c *baseRPCClient) TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) {
result := new(ctypes.ResultTxSearch)
params := map[string]interface{}{
"query": query,
"prove": prove,
"page": page,
"per_page": perPage,
}
_, err := c.caller.Call("tx_search", params, result)
if err != nil {
return nil, errors.Wrap(err, "TxSearch")
}
return result, nil
}

func (c *baseRPCClient) Validators(height *int64) (*ctypes.ResultValidators, error) {
result := new(ctypes.ResultValidators)
params := map[string]interface{}{}
Expand Down
6 changes: 5 additions & 1 deletion tm2/pkg/bft/rpc/client/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ import (
// first synchronously consumes the events from the node's synchronous event
// switch, or reads logged events from the filesystem.
type Client interface {
// service.Service
ABCIClient
HistoryClient
NetworkClient
SignClient
StatusClient
MempoolClient
TxClient
}

// ABCIClient groups together the functionality that principally affects the
Expand Down Expand Up @@ -94,3 +94,7 @@ type MempoolClient interface {
UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error)
NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error)
}

type TxClient interface {
Tx(hash []byte) (*ctypes.ResultTx, error)
}
10 changes: 2 additions & 8 deletions tm2/pkg/bft/rpc/client/localclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,6 @@ func (c *Local) Validators(height *int64) (*ctypes.ResultValidators, error) {
return core.Validators(c.ctx, height)
}

/*
func (c *Local) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
return core.Tx(c.ctx, hash, prove)
func (c *Local) Tx(hash []byte) (*ctypes.ResultTx, error) {
return core.Tx(c.ctx, hash)
}
func (c *Local) TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) {
return core.TxSearch(c.ctx, query, prove, page, perPage)
}
*/
1 change: 1 addition & 0 deletions tm2/pkg/bft/rpc/client/mock/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type Client struct {
client.HistoryClient
client.StatusClient
client.MempoolClient
client.TxClient
service.Service
}

Expand Down
136 changes: 34 additions & 102 deletions tm2/pkg/bft/rpc/client/rpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,130 +365,62 @@ func TestNumUnconfirmedTxs(t *testing.T) {
mempool.Flush()
}

/*
func TestTx(t *testing.T) {
t.Parallel()
// first we broadcast a tx
// Prepare the transaction
// by broadcasting it to the chain
c := getHTTPClient()
_, _, tx := MakeTxKV()
bres, err := c.BroadcastTxCommit(tx)
require.Nil(t, err, "%+v", err)

txHeight := bres.Height
txHash := bres.Hash
response, err := c.BroadcastTxCommit(tx)
require.NoError(t, err)
require.NotNil(t, response)

anotherTxHash := types.Tx("a different tx").Hash()
var (
txHeight = response.Height
txHash = response.Hash
)

cases := []struct {
name string
valid bool
hash []byte
prove bool
}{
// only valid if correct hash provided
{true, txHash, false},
{true, txHash, true},
{false, anotherTxHash, false},
{false, anotherTxHash, true},
{false, nil, false},
{false, nil, true},
{
"tx result found",
true,
txHash,
},
{
"tx result not found",
false,
types.Tx("a different tx").Hash(),
},
}

for i, c := range GetClients() {
for j, tc := range cases {
t.Logf("client %d, case %d", i, j)
for _, c := range GetClients() {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
// now we query for the tx.
// since there's only one tx, we know index=0.
ptx, err := c.Tx(tc.hash)

if !tc.valid {
require.Error(t, err)

return
}

// now we query for the tx.
// since there's only one tx, we know index=0.
ptx, err := c.Tx(tc.hash, tc.prove)
require.NoError(t, err)

if !tc.valid {
require.NotNil(t, err)
} else {
require.Nil(t, err, "%+v", err)
assert.EqualValues(t, txHeight, ptx.Height)
assert.EqualValues(t, tx, ptx.Tx)
assert.Zero(t, ptx.Index)
assert.True(t, ptx.TxResult.IsOK())
assert.EqualValues(t, txHash, ptx.Hash)
// time to verify the proof
proof := ptx.Proof
if tc.prove && assert.EqualValues(t, tx, proof.Data) {
assert.NoError(t, proof.Proof.Verify(proof.RootHash, txHash))
}
}
}
}
}
func TestTxSearch(t *testing.T) {
t.Parallel()
// first we broadcast a tx
c := getHTTPClient()
_, _, tx := MakeTxKV()
bres, err := c.BroadcastTxCommit(tx)
require.Nil(t, err, "%+v", err)
txHeight := bres.Height
txHash := bres.Hash
anotherTxHash := types.Tx("a different tx").Hash()
for i, c := range GetClients() {
t.Logf("client %d", i)
// now we query for the tx.
// since there's only one tx, we know index=0.
result, err := c.TxSearch(fmt.Sprintf("tx.hash='%v'", txHash), true, 1, 30)
require.Nil(t, err, "%+v", err)
require.Len(t, result.Txs, 1)
ptx := result.Txs[0]
assert.EqualValues(t, txHeight, ptx.Height)
assert.EqualValues(t, tx, ptx.Tx)
assert.Zero(t, ptx.Index)
assert.True(t, ptx.TxResult.IsOK())
assert.EqualValues(t, txHash, ptx.Hash)
// time to verify the proof
proof := ptx.Proof
if assert.EqualValues(t, tx, proof.Data) {
assert.NoError(t, proof.Proof.Verify(proof.RootHash, txHash))
}
// query by height
result, err = c.TxSearch(fmt.Sprintf("tx.height=%d", txHeight), true, 1, 30)
require.Nil(t, err, "%+v", err)
require.Len(t, result.Txs, 1)
// query for non existing tx
result, err = c.TxSearch(fmt.Sprintf("tx.hash='%X'", anotherTxHash), false, 1, 30)
require.Nil(t, err, "%+v", err)
require.Len(t, result.Txs, 0)
// query using a tag (see kvstore application)
result, err = c.TxSearch("app.creator='Cosmoshi Netowoko'", false, 1, 30)
require.Nil(t, err, "%+v", err)
if len(result.Txs) == 0 {
t.Fatal("expected a lot of transactions")
})
}
// query using a tag (see kvstore application) and height
result, err = c.TxSearch("app.creator='Cosmoshi Netowoko' AND tx.height<10000", true, 1, 30)
require.Nil(t, err, "%+v", err)
if len(result.Txs) == 0 {
t.Fatal("expected a lot of transactions")
}
// query a non existing tx with page 1 and txsPerPage 1
result, err = c.TxSearch("app.creator='Cosmoshi Neetowoko'", true, 1, 1)
require.Nil(t, err, "%+v", err)
require.Len(t, result.Txs, 0)
}
}
*/

func TestBatchedJSONRPCCalls(t *testing.T) {
c := getHTTPClient()
Expand Down
78 changes: 78 additions & 0 deletions tm2/pkg/bft/rpc/core/mock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package core

import "github.com/gnolang/gno/tm2/pkg/bft/types"

type (
heightDelegate func() int64
loadBlockMetaDelegate func(int64) *types.BlockMeta
loadBlockDelegate func(int64) *types.Block
loadBlockPartDelegate func(int64, int) *types.Part
loadBlockCommitDelegate func(int64) *types.Commit
loadSeenCommitDelegate func(int64) *types.Commit

saveBlockDelegate func(*types.Block, *types.PartSet, *types.Commit)
)

type mockBlockStore struct {
heightFn heightDelegate
loadBlockMetaFn loadBlockMetaDelegate
loadBlockFn loadBlockDelegate
loadBlockPartFn loadBlockPartDelegate
loadBlockCommitFn loadBlockCommitDelegate
loadSeenCommitFn loadSeenCommitDelegate
saveBlockFn saveBlockDelegate
}

func (m *mockBlockStore) Height() int64 {
if m.heightFn != nil {
return m.heightFn()
}

return 0
}

func (m *mockBlockStore) LoadBlockMeta(height int64) *types.BlockMeta {
if m.loadBlockMetaFn != nil {
return m.loadBlockMetaFn(height)
}

return nil
}

func (m *mockBlockStore) LoadBlock(height int64) *types.Block {
if m.loadBlockFn != nil {
return m.loadBlockFn(height)
}

return nil
}

func (m *mockBlockStore) LoadBlockPart(height int64, index int) *types.Part {
if m.loadBlockPartFn != nil {
return m.loadBlockPartFn(height, index)
}

return nil
}

func (m *mockBlockStore) LoadBlockCommit(height int64) *types.Commit {
if m.loadBlockCommitFn != nil {
return m.loadBlockCommitFn(height)
}

return nil
}

func (m *mockBlockStore) LoadSeenCommit(height int64) *types.Commit {
if m.loadSeenCommitFn != nil {
return m.loadSeenCommitFn(height)
}

return nil
}

func (m *mockBlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, seenCommit *types.Commit) {
if m.saveBlockFn != nil {
m.saveBlockFn(block, blockParts, seenCommit)
}
}
19 changes: 9 additions & 10 deletions tm2/pkg/bft/rpc/core/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,15 @@ import (
// NOTE: Amino is registered in rpc/core/types/codec.go.
var Routes = map[string]*rpc.RPCFunc{
// info API
"health": rpc.NewRPCFunc(Health, ""),
"status": rpc.NewRPCFunc(Status, ""),
"net_info": rpc.NewRPCFunc(NetInfo, ""),
"blockchain": rpc.NewRPCFunc(BlockchainInfo, "minHeight,maxHeight"),
"genesis": rpc.NewRPCFunc(Genesis, ""),
"block": rpc.NewRPCFunc(Block, "height"),
"block_results": rpc.NewRPCFunc(BlockResults, "height"),
"commit": rpc.NewRPCFunc(Commit, "height"),
//"tx": rpc.NewRPCFunc(Tx, "hash,prove"),
//"tx_search": rpc.NewRPCFunc(TxSearch, "query,prove,page,per_page"),
"health": rpc.NewRPCFunc(Health, ""),
"status": rpc.NewRPCFunc(Status, ""),
"net_info": rpc.NewRPCFunc(NetInfo, ""),
"blockchain": rpc.NewRPCFunc(BlockchainInfo, "minHeight,maxHeight"),
"genesis": rpc.NewRPCFunc(Genesis, ""),
"block": rpc.NewRPCFunc(Block, "height"),
"block_results": rpc.NewRPCFunc(BlockResults, "height"),
"commit": rpc.NewRPCFunc(Commit, "height"),
"tx": rpc.NewRPCFunc(Tx, "hash"),
"validators": rpc.NewRPCFunc(Validators, "height"),
"dump_consensus_state": rpc.NewRPCFunc(DumpConsensusState, ""),
"consensus_state": rpc.NewRPCFunc(ConsensusState, ""),
Expand Down
Loading

0 comments on commit 6600f51

Please sign in to comment.