From ec78aa3a81205baae2fcac5b95166d92e475760e Mon Sep 17 00:00:00 2001 From: Maurice Le Cordier Date: Mon, 7 Feb 2022 23:16:29 +0200 Subject: [PATCH] Tx Method implementation (#272) --- CHANGELOG-PENDING.md | 2 ++ go.mod | 2 +- rpc/client/client.go | 33 +++++++++++++++++++++++-- rpc/client/client_test.go | 51 +++++++++++++++++++++++++++++++++++++++ types/tx.go | 36 +++++++++++++++++++++++++++ 5 files changed, 121 insertions(+), 3 deletions(-) diff --git a/CHANGELOG-PENDING.md b/CHANGELOG-PENDING.md index e4e3f644228..51b0b893c69 100644 --- a/CHANGELOG-PENDING.md +++ b/CHANGELOG-PENDING.md @@ -23,8 +23,10 @@ Month, DD, YYYY - [rpc] [Implement BlockResults #263](https://github.com/celestiaorg/optimint/pull/263) [@tzdybal](https://github.com/tzdybal/) - [store,indexer] [Replace tm-db dependency with store package #268](https://github.com/celestiaorg/optimint/pull/268) [@tzdybal](https://github.com/tzdybal/) - [rpc] [Implement ConsensusState/DumpConsensusState #273](https://github.com/celestiaorg/optimint/pull/273) [@tzdybal](https://github.com/tzdybal/) +- [rpc] [Implement Tx Method #272](https://github.com/celestiaorg/optimint/pull/272) [@mauriceLC92](https://github.com/mauriceLC92) ### BUG FIXES + - [store] [Use KeyCopy instead of Key in BadgerIterator #274](https://github.com/celestiaorg/optimint/pull/274) [@tzdybal](https://github.com/tzdybal/) - [go package] (Link to PR) Description @username diff --git a/go.mod b/go.mod index 27890ce003e..954f92078b0 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/golang/protobuf v1.5.2 github.com/google/orderedcode v0.0.1 github.com/gorilla/rpc v1.2.0 + github.com/gorilla/websocket v1.4.2 github.com/ipfs/go-log v1.0.5 github.com/libp2p/go-libp2p v0.15.1 github.com/libp2p/go-libp2p-core v0.9.0 @@ -54,7 +55,6 @@ require ( github.com/google/gopacket v1.1.19 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f // indirect - github.com/gorilla/websocket v1.4.2 // indirect github.com/gtank/merlin v0.1.1 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect diff --git a/rpc/client/client.go b/rpc/client/client.go index 6320bbd0e83..911ada1c65d 100644 --- a/rpc/client/client.go +++ b/rpc/client/client.go @@ -398,8 +398,37 @@ func (c *Client) Validators(ctx context.Context, height *int64, page, perPage *i } func (c *Client) Tx(ctx context.Context, hash []byte, prove bool) (*ctypes.ResultTx, error) { - // needs block store, tx index (?) - panic("Tx - not implemented!") + res, err := c.node.TxIndexer.Get(hash) + if err != nil { + return nil, err + } + + if res == nil { + return nil, fmt.Errorf("tx (%X) not found", hash) + } + + height := res.Height + index := res.Index + + var proof types.TxProof + if prove { + block, _ := c.node.Store.LoadBlock(uint64(height)) + blockProof := block.Data.Txs.Proof(int(index)) // XXX: overflow on 32-bit machines + proof = types.TxProof{ + RootHash: blockProof.RootHash, + Data: types.Tx(blockProof.Data), + Proof: blockProof.Proof, + } + } + + return &ctypes.ResultTx{ + Hash: hash, + Height: height, + Index: index, + TxResult: res.Result, + Tx: res.Tx, + Proof: proof, + }, nil } func (c *Client) TxSearch(ctx context.Context, query string, prove bool, pagePtr, perPagePtr *int, orderBy string) (*ctypes.ResultTxSearch, error) { diff --git a/rpc/client/client_test.go b/rpc/client/client_test.go index b571e704c04..ed49a1d748b 100644 --- a/rpc/client/client_test.go +++ b/rpc/client/client_test.go @@ -242,6 +242,57 @@ func TestGetBlockByHash(t *testing.T) { require.NoError(err) } +func TestTx(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + mockApp := &mocks.Application{} + mockApp.On("InitChain", mock.Anything).Return(abci.ResponseInitChain{}) + key, _, _ := crypto.GenerateEd25519Key(crand.Reader) + node, err := node.NewNode(context.Background(), config.NodeConfig{ + DALayer: "mock", + Aggregator: true, + BlockManagerConfig: config.BlockManagerConfig{ + BlockTime: 200 * time.Millisecond, + }}, + key, proxy.NewLocalClientCreator(mockApp), + &tmtypes.GenesisDoc{ChainID: "test"}, + log.TestingLogger()) + require.NoError(err) + require.NotNil(node) + + rpc := NewClient(node) + require.NotNil(rpc) + mockApp.On("BeginBlock", mock.Anything).Return(abci.ResponseBeginBlock{}) + mockApp.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{}) + mockApp.On("Commit", mock.Anything).Return(abci.ResponseCommit{}) + mockApp.On("DeliverTx", mock.Anything).Return(abci.ResponseDeliverTx{}) + mockApp.On("CheckTx", mock.Anything).Return(abci.ResponseCheckTx{}) + + err = rpc.node.Start() + require.NoError(err) + + tx1 := tmtypes.Tx("tx1") + res, err := rpc.BroadcastTxSync(context.Background(), tx1) + assert.NoError(err) + assert.NotNil(res) + + time.Sleep(1 * time.Second) + + resTx, errTx := rpc.Tx(context.Background(), res.Hash, true) + assert.NoError(errTx) + assert.NotNil(resTx) + assert.EqualValues(tx1, resTx.Tx) + assert.EqualValues(res.Hash, resTx.Hash) + + tx2 := tmtypes.Tx("tx2") + assert.Panics(func() { + resTx, errTx := rpc.Tx(context.Background(), tx2.Hash(), true) + assert.Nil(resTx) + assert.Error(errTx) + }) +} + func TestUnconfirmedTxs(t *testing.T) { tx1 := tmtypes.Tx("tx1") tx2 := tmtypes.Tx("another tx") diff --git a/types/tx.go b/types/tx.go index 2ede78da288..46eef82e863 100644 --- a/types/tx.go +++ b/types/tx.go @@ -1,7 +1,43 @@ package types +import ( + "github.com/tendermint/tendermint/crypto/merkle" + "github.com/tendermint/tendermint/crypto/tmhash" + tmbytes "github.com/tendermint/tendermint/libs/bytes" +) + // Tx represents transactoin. type Tx []byte // Txs represents a slice of transactions. type Txs []Tx + +// Hash computes the TMHASH hash of the wire encoded transaction. +func (tx Tx) Hash() []byte { + return tmhash.Sum(tx) +} + +// Proof returns a simple merkle proof for this node. +// Panics if i < 0 or i >= len(txs) +// TODO: optimize this! +func (txs Txs) Proof(i int) TxProof { + l := len(txs) + bzs := make([][]byte, l) + for i := 0; i < l; i++ { + bzs[i] = txs[i].Hash() + } + root, proofs := merkle.ProofsFromByteSlices(bzs) + + return TxProof{ + RootHash: root, + Data: txs[i], + Proof: *proofs[i], + } +} + +// TxProof represents a Merkle proof of the presence of a transaction in the Merkle tree. +type TxProof struct { + RootHash tmbytes.HexBytes `json:"root_hash"` + Data Tx `json:"data"` + Proof merkle.Proof `json:"proof"` +}