From 6e065affe582998d826e86b33549f40a0c2dd8ec Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 13 Apr 2017 16:04:20 -0400 Subject: [PATCH] rpc: /tx allows height+hash --- rpc/core/tx.go | 50 ++++++++++++++++++++++++++++--------- rpc/core/types/responses.go | 10 ++++---- rpc/test/client_test.go | 13 +++++----- types/tx.go | 10 ++++++++ 4 files changed, 60 insertions(+), 23 deletions(-) diff --git a/rpc/core/tx.go b/rpc/core/tx.go index e578a25bf..e7c416da5 100644 --- a/rpc/core/tx.go +++ b/rpc/core/tx.go @@ -5,16 +5,30 @@ import ( abci "github.com/tendermint/abci/types" ctypes "github.com/tendermint/tendermint/rpc/core/types" + "github.com/tendermint/tendermint/state/tx/indexer" "github.com/tendermint/tendermint/types" ) func Tx(hash []byte, height, index int, prove bool) (*ctypes.ResultTx, error) { - var deliverTx abci.ResponseDeliverTx - if len(hash) > 0 { - if height != 0 || index != 0 { - return nil, fmt.Errorf("Invalid args. If hash is provided, height and index should not be") - } + // if index is disabled, we need a height + _, indexerDisabled := txIndexer.(*indexer.Null) + if indexerDisabled && height == 0 { + return nil, fmt.Errorf("TxIndexer is disabled. Please specify a height to search for the tx by hash or index") + } + + // hash and index must not be passed together + if len(hash) > 0 && index != 0 { + return nil, fmt.Errorf("Invalid args. Only one of hash and index may be provided") + } + + // results + var txResult abci.ResponseDeliverTx + var tx types.Tx + + // if indexer is enabled and we have a hash, + // fetch the tx result and set the height and index + if !indexerDisabled && len(hash) > 0 { r, err := txIndexer.Tx(hash) if err != nil { return nil, err @@ -26,19 +40,31 @@ func Tx(hash []byte, height, index int, prove bool) (*ctypes.ResultTx, error) { height = int(r.Height) // XXX index = int(r.Index) - deliverTx = r.DeliverTx + txResult = r.DeliverTx } + // height must be valid if height <= 0 || height > blockStore.Height() { return nil, fmt.Errorf("Invalid height (%d) for blockStore at height %d", height, blockStore.Height()) } block := blockStore.LoadBlock(height) + // index must be valid if index < 0 || index >= len(block.Data.Txs) { return nil, fmt.Errorf("Index (%d) is out of range for block (%d) with %d txs", index, height, len(block.Data.Txs)) } - tx := block.Data.Txs[index] + + // if indexer is disabled and we have a hash, + // search for it in the list of txs + if indexerDisabled && len(hash) > 0 { + index = block.Data.Txs.IndexByHash(hash) + if index < 0 { + return nil, fmt.Errorf("Tx hash %X not found in block %d", hash, height) + } + + } + tx = block.Data.Txs[index] var proof types.TxProof if prove { @@ -46,10 +72,10 @@ func Tx(hash []byte, height, index int, prove bool) (*ctypes.ResultTx, error) { } return &ctypes.ResultTx{ - Height: height, - Index: index, - DeliverTx: deliverTx, - Tx: tx, - Proof: proof, + Height: height, + Index: index, + TxResult: txResult, + Tx: tx, + Proof: proof, }, nil } diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index 1449164fd..6979e0e6c 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -94,11 +94,11 @@ type ResultBroadcastTxCommit struct { } type ResultTx struct { - Height int `json:"height"` - Index int `json:"index"` - DeliverTx abci.ResponseDeliverTx `json:"deliver_tx"` - Tx types.Tx `json:"tx"` - Proof types.TxProof `json:"proof,omitempty"` + Height int `json:"height"` + Index int `json:"index"` + TxResult abci.ResponseDeliverTx `json:"tx_result"` + Tx types.Tx `json:"tx"` + Proof types.TxProof `json:"proof,omitempty"` } type ResultUnconfirmedTxs struct { diff --git a/rpc/test/client_test.go b/rpc/test/client_test.go index 3a7ea796b..2b06f77fe 100644 --- a/rpc/test/client_test.go +++ b/rpc/test/client_test.go @@ -165,8 +165,9 @@ func testTx(t *testing.T, client rpc.HTTPClient) { // first we broadcast a tx tmResult := new(ctypes.TMResult) - tx := types.Tx(randBytes(t)) - _, err := client.Call("broadcast_tx_commit", map[string]interface{}{"tx": tx}, tmResult) + txBytes := randBytes(t) + tx := types.Tx(txBytes) + _, err := client.Call("broadcast_tx_commit", map[string]interface{}{"tx": txBytes}, tmResult) require.Nil(err) res := (*tmResult).(*ctypes.ResultBroadcastTxCommit) @@ -195,10 +196,10 @@ func testTx(t *testing.T, client rpc.HTTPClient) { // on proper hash match {true, 0, 0, tx.Hash(), false}, {true, 0, 0, tx.Hash(), true}, - {false, res.Height, 0, tx.Hash(), false}, // TODO: or shall we allow this???? - {false, res.Height, 0, tx.Hash(), true}, // TODO: or shall we allow this???? + {true, res.Height, 0, tx.Hash(), false}, + {true, res.Height, 0, tx.Hash(), true}, + {true, 100, 0, tx.Hash(), false}, // with indexer disabled, height is overwritten // with extra data is an error - {false, 10, 0, tx.Hash(), false}, {false, 0, 2, tx.Hash(), true}, {false, 0, 0, []byte("jkh8y0fw"), false}, {false, 0, 0, nil, true}, @@ -229,7 +230,7 @@ func testTx(t *testing.T, client rpc.HTTPClient) { assert.Equal(tx, res2.Tx, idx) assert.Equal(res.Height, res2.Height, idx) assert.Equal(0, res2.Index, idx) - assert.Equal(abci.CodeType_OK, res2.DeliverTx.Code, idx) + assert.Equal(abci.CodeType_OK, res2.TxResult.Code, idx) // time to verify the proof proof := res2.Proof if tc.prove && assert.Equal(tx, proof.Data, idx) { diff --git a/types/tx.go b/types/tx.go index c47774b8f..91f6fbc01 100644 --- a/types/tx.go +++ b/types/tx.go @@ -45,6 +45,16 @@ func (txs Txs) Index(tx Tx) int { return -1 } +// Index returns the index of this transaction hash in the list, or -1 if not found +func (txs Txs) IndexByHash(hash []byte) int { + for i := range txs { + if bytes.Equal(txs[i].Hash(), hash) { + return i + } + } + return -1 +} + // Proof returns a simple merkle proof for this node. // // Panics if i < 0 or i >= len(txs)