TxResult includes Tx. /tx only works if indexer active

This commit is contained in:
Ethan Buchman
2017-04-18 19:56:41 -04:00
parent d572bb0c5d
commit f4d0076344
12 changed files with 68 additions and 117 deletions

View File

@ -23,8 +23,9 @@ import (
rpccore "github.com/tendermint/tendermint/rpc/core" rpccore "github.com/tendermint/tendermint/rpc/core"
grpccore "github.com/tendermint/tendermint/rpc/grpc" grpccore "github.com/tendermint/tendermint/rpc/grpc"
sm "github.com/tendermint/tendermint/state" sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/state/tx" "github.com/tendermint/tendermint/state/txindex"
txindexer "github.com/tendermint/tendermint/state/tx/indexer" "github.com/tendermint/tendermint/state/txindex/kv"
"github.com/tendermint/tendermint/state/txindex/null"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
"github.com/tendermint/tendermint/version" "github.com/tendermint/tendermint/version"
@ -53,7 +54,7 @@ type Node struct {
consensusReactor *consensus.ConsensusReactor // for participating in the consensus consensusReactor *consensus.ConsensusReactor // for participating in the consensus
proxyApp proxy.AppConns // connection to the application proxyApp proxy.AppConns // connection to the application
rpcListeners []net.Listener // rpc servers rpcListeners []net.Listener // rpc servers
txIndexer tx.Indexer txIndexer txindex.TxIndexer
} }
func NewNodeDefault(config cfg.Config) *Node { func NewNodeDefault(config cfg.Config) *Node {
@ -88,13 +89,13 @@ func NewNode(config cfg.Config, privValidator *types.PrivValidator, clientCreato
state = sm.LoadState(stateDB) state = sm.LoadState(stateDB)
// Transaction indexing // Transaction indexing
var txIndexer tx.Indexer var txIndexer txindex.TxIndexer
switch config.GetString("tx_indexer") { switch config.GetString("tx_indexer") {
case "kv": case "kv":
store := dbm.NewDB("tx_indexer", config.GetString("db_backend"), config.GetString("db_dir")) store := dbm.NewDB("tx_indexer", config.GetString("db_backend"), config.GetString("db_dir"))
txIndexer = txindexer.NewKV(store) txIndexer = kv.NewTxIndex(store)
default: default:
txIndexer = &txindexer.Null{} txIndexer = &null.TxIndex{}
} }
state.TxIndexer = txIndexer state.TxIndexer = txIndexer

View File

@ -160,11 +160,9 @@ func (c *HTTP) Commit(height int) (*ctypes.ResultCommit, error) {
return (*tmResult).(*ctypes.ResultCommit), nil return (*tmResult).(*ctypes.ResultCommit), nil
} }
func (c *HTTP) Tx(hash []byte, height, index int, prove bool) (*ctypes.ResultTx, error) { func (c *HTTP) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
tmResult := new(ctypes.TMResult) tmResult := new(ctypes.TMResult)
query := map[string]interface{}{ query := map[string]interface{}{
"height": height,
"index": index,
"hash": hash, "hash": hash,
"prove": prove, "prove": prove,
} }

View File

@ -44,7 +44,7 @@ type SignClient interface {
Block(height int) (*ctypes.ResultBlock, error) Block(height int) (*ctypes.ResultBlock, error)
Commit(height int) (*ctypes.ResultCommit, error) Commit(height int) (*ctypes.ResultCommit, error)
Validators() (*ctypes.ResultValidators, error) Validators() (*ctypes.ResultValidators, error)
Tx(hash []byte, height, index int, prove bool) (*ctypes.ResultTx, error) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error)
} }
// HistoryClient shows us data from genesis to now in large chunks. // HistoryClient shows us data from genesis to now in large chunks.

View File

@ -104,6 +104,6 @@ func (c Local) Validators() (*ctypes.ResultValidators, error) {
return core.Validators() return core.Validators()
} }
func (c Local) Tx(hash []byte, height, index int, prove bool) (*ctypes.ResultTx, error) { func (c Local) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
return core.Tx(hash, height, index, prove) return core.Tx(hash, prove)
} }

View File

@ -133,8 +133,8 @@ func TestAppCalls(t *testing.T) {
} }
// make sure we can lookup the tx with proof // make sure we can lookup the tx with proof
// ptx, err := c.Tx(bres.TxID, txh, 0, true) // ptx, err := c.Tx(bres.TxID, true)
ptx, err := c.Tx(bres.TxID, 0, 0, true) ptx, err := c.Tx(bres.TxID, true)
require.Nil(err, "%d: %+v", i, err) require.Nil(err, "%d: %+v", i, err)
assert.Equal(txh, ptx.Height) assert.Equal(txh, ptx.Height)
assert.Equal(types.Tx(tx), ptx.Tx) assert.Equal(types.Tx(tx), ptx.Tx)

View File

@ -7,7 +7,7 @@ import (
p2p "github.com/tendermint/go-p2p" p2p "github.com/tendermint/go-p2p"
"github.com/tendermint/tendermint/consensus" "github.com/tendermint/tendermint/consensus"
"github.com/tendermint/tendermint/proxy" "github.com/tendermint/tendermint/proxy"
"github.com/tendermint/tendermint/state/tx" "github.com/tendermint/tendermint/state/txindex"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
) )
@ -46,7 +46,7 @@ var (
pubKey crypto.PubKey pubKey crypto.PubKey
genDoc *types.GenesisDoc // cache the genesis structure genDoc *types.GenesisDoc // cache the genesis structure
addrBook *p2p.AddrBook addrBook *p2p.AddrBook
txIndexer tx.Indexer txIndexer txindex.TxIndexer
) )
func SetConfig(c cfg.Config) { func SetConfig(c cfg.Config) {
@ -89,6 +89,6 @@ func SetProxyAppQuery(appConn proxy.AppConnQuery) {
proxyAppQuery = appConn proxyAppQuery = appConn
} }
func SetTxIndexer(indexer tx.Indexer) { func SetTxIndexer(indexer txindex.TxIndexer) {
txIndexer = indexer txIndexer = indexer
} }

View File

@ -19,7 +19,7 @@ var Routes = map[string]*rpc.RPCFunc{
"genesis": rpc.NewRPCFunc(GenesisResult, ""), "genesis": rpc.NewRPCFunc(GenesisResult, ""),
"block": rpc.NewRPCFunc(BlockResult, "height"), "block": rpc.NewRPCFunc(BlockResult, "height"),
"commit": rpc.NewRPCFunc(CommitResult, "height"), "commit": rpc.NewRPCFunc(CommitResult, "height"),
"tx": rpc.NewRPCFunc(TxResult, "hash,height,index,prove"), "tx": rpc.NewRPCFunc(TxResult, "hash,prove"),
"validators": rpc.NewRPCFunc(ValidatorsResult, ""), "validators": rpc.NewRPCFunc(ValidatorsResult, ""),
"dump_consensus_state": rpc.NewRPCFunc(DumpConsensusStateResult, ""), "dump_consensus_state": rpc.NewRPCFunc(DumpConsensusStateResult, ""),
"unconfirmed_txs": rpc.NewRPCFunc(UnconfirmedTxsResult, ""), "unconfirmed_txs": rpc.NewRPCFunc(UnconfirmedTxsResult, ""),
@ -100,8 +100,8 @@ func NumUnconfirmedTxsResult() (ctypes.TMResult, error) {
// Tx allow user to query the transaction results. `nil` could mean the // Tx allow user to query the transaction results. `nil` could mean the
// transaction is in the mempool, invalidated, or was not send in the first // transaction is in the mempool, invalidated, or was not send in the first
// place. // place.
func TxResult(hash []byte, height, index int, prove bool) (ctypes.TMResult, error) { func TxResult(hash []byte, prove bool) (ctypes.TMResult, error) {
return Tx(hash, height, index, prove) return Tx(hash, prove)
} }
func BroadcastTxCommitResult(tx []byte) (ctypes.TMResult, error) { func BroadcastTxCommitResult(tx []byte) (ctypes.TMResult, error) {

View File

@ -3,79 +3,41 @@ package core
import ( import (
"fmt" "fmt"
abci "github.com/tendermint/abci/types"
ctypes "github.com/tendermint/tendermint/rpc/core/types" ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/tendermint/tendermint/state/tx/indexer" "github.com/tendermint/tendermint/state/txindex/null"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
) )
func Tx(hash []byte, height, index int, prove bool) (*ctypes.ResultTx, error) { func Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
// if index is disabled, we need a height // if index is disabled, return error
_, indexerDisabled := txIndexer.(*indexer.Null) if _, ok := txIndexer.(*null.TxIndex); ok {
if indexerDisabled && height == 0 { return nil, fmt.Errorf("Transaction indexing is disabled.")
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 r, err := txIndexer.Get(hash)
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 { if err != nil {
return nil, err return nil, err
} }
if r == nil { if r == nil {
return &ctypes.ResultTx{}, fmt.Errorf("Tx (%X) not found", hash) return nil, fmt.Errorf("Tx (%X) not found", hash)
} }
height = int(r.Height) // XXX height := int(r.Height) // XXX
index = int(r.Index) index := int(r.Index)
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))
}
// 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 var proof types.TxProof
if prove { if prove {
block := blockStore.LoadBlock(height)
proof = block.Data.Txs.Proof(index) proof = block.Data.Txs.Proof(index)
} }
return &ctypes.ResultTx{ return &ctypes.ResultTx{
Height: height, Height: height,
Index: index, Index: index,
TxResult: txResult, TxResult: r.Result,
Tx: tx, Tx: r.Tx,
Proof: proof, Proof: proof,
}, nil }, nil
} }

View File

@ -15,7 +15,7 @@ import (
rpc "github.com/tendermint/go-rpc/client" rpc "github.com/tendermint/go-rpc/client"
"github.com/tendermint/tendermint/rpc/core" "github.com/tendermint/tendermint/rpc/core"
ctypes "github.com/tendermint/tendermint/rpc/core/types" ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/tendermint/tendermint/state/tx/indexer" "github.com/tendermint/tendermint/state/txindex/null"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
) )
@ -157,7 +157,7 @@ func testBroadcastTxCommit(t *testing.T, client rpc.HTTPClient) {
func TestURITx(t *testing.T) { func TestURITx(t *testing.T) {
testTx(t, GetURIClient(), true) testTx(t, GetURIClient(), true)
core.SetTxIndexer(&indexer.Null{}) core.SetTxIndexer(&null.TxIndex{})
testTx(t, GetJSONClient(), false) testTx(t, GetJSONClient(), false)
core.SetTxIndexer(node.ConsensusState().GetState().TxIndexer) core.SetTxIndexer(node.ConsensusState().GetState().TxIndexer)
} }
@ -165,7 +165,7 @@ func TestURITx(t *testing.T) {
func TestJSONTx(t *testing.T) { func TestJSONTx(t *testing.T) {
testTx(t, GetJSONClient(), true) testTx(t, GetJSONClient(), true)
core.SetTxIndexer(&indexer.Null{}) core.SetTxIndexer(&null.TxIndex{})
testTx(t, GetJSONClient(), false) testTx(t, GetJSONClient(), false)
core.SetTxIndexer(node.ConsensusState().GetState().TxIndexer) core.SetTxIndexer(node.ConsensusState().GetState().TxIndexer)
} }
@ -188,36 +188,21 @@ func testTx(t *testing.T, client rpc.HTTPClient, withIndexer bool) {
mem := node.MempoolReactor().Mempool mem := node.MempoolReactor().Mempool
require.Equal(0, mem.Size()) require.Equal(0, mem.Size())
txHash := tx.Hash()
txHash2 := types.Tx("a different tx").Hash()
cases := []struct { cases := []struct {
validWithIndexer bool valid bool
validNoIndexer bool
height int
index int
hash []byte hash []byte
prove bool prove bool
}{ }{
// only on proper height, index match // only valid if correct hash provided
{true, true, res.Height, 0, nil, false}, {true, txHash, false},
{true, true, res.Height, 0, nil, true}, {true, txHash, true},
{false, false, res.Height, 1, nil, false}, {false, txHash2, false},
{false, false, res.Height, -7, nil, true}, {false, txHash2, true},
{false, false, -10, -100, nil, false}, {false, nil, false},
{false, false, res.Height + 1, 0, nil, true}, {false, nil, true},
// on proper hash match
{true, false, 0, 0, tx.Hash(), false},
{true, false, 0, 0, tx.Hash(), true},
{true, true, res.Height, 0, tx.Hash(), false},
{true, true, res.Height, 0, tx.Hash(), true},
{true, false, 100, 0, tx.Hash(), false}, // with indexer enabled, height is overwritten
// with extra data is an error
{false, false, 0, 2, tx.Hash(), true},
{false, false, 0, 0, []byte("jkh8y0fw"), false},
{false, false, 0, 0, nil, true},
// missing height and hash fails
{false, false, 0, 0, nil, false},
{false, false, 0, 1, nil, true},
} }
for i, tc := range cases { for i, tc := range cases {
@ -227,13 +212,11 @@ func testTx(t *testing.T, client rpc.HTTPClient, withIndexer bool) {
// since there's only one tx, we know index=0. // since there's only one tx, we know index=0.
tmResult = new(ctypes.TMResult) tmResult = new(ctypes.TMResult)
query := map[string]interface{}{ query := map[string]interface{}{
"height": tc.height,
"index": tc.index,
"hash": tc.hash, "hash": tc.hash,
"prove": tc.prove, "prove": tc.prove,
} }
_, err = client.Call("tx", query, tmResult) _, err = client.Call("tx", query, tmResult)
valid := (withIndexer && tc.validWithIndexer) || (!withIndexer && tc.validNoIndexer) valid := (withIndexer && tc.valid)
if !valid { if !valid {
require.NotNil(err, idx) require.NotNil(err, idx)
} else { } else {

View File

@ -84,7 +84,12 @@ func execBlockOnProxyApp(eventCache types.Fireable, proxyAppConn proxy.AppConnCo
txError = txResult.Code.String() txError = txResult.Code.String()
} }
txResults[txIndex] = &types.TxResult{uint64(block.Height), uint32(txIndex), *txResult} txResults[txIndex] = &types.TxResult{
Height: uint64(block.Height),
Index: uint32(txIndex),
Tx: req.GetDeliverTx().Tx,
Result: *txResult,
}
txIndex++ txIndex++
// NOTE: if we count we can access the tx from the block instead of // NOTE: if we count we can access the tx from the block instead of

View File

@ -18,7 +18,7 @@ func TestTxIndex(t *testing.T) {
indexer := &TxIndex{store: db.NewMemDB()} indexer := &TxIndex{store: db.NewMemDB()}
tx := types.Tx("HELLO WORLD") tx := types.Tx("HELLO WORLD")
txResult := &types.TxResult{1, 1, abci.ResponseDeliverTx{Data: []byte{0}, Code: abci.CodeType_OK, Log: ""}} txResult := &types.TxResult{1, 1, tx, abci.ResponseDeliverTx{Data: []byte{0}, Code: abci.CodeType_OK, Log: ""}}
hash := tx.Hash() hash := tx.Hash()
batch := txindex.NewBatch() batch := txindex.NewBatch()
@ -32,7 +32,8 @@ func TestTxIndex(t *testing.T) {
} }
func benchmarkTxIndex(txsCount int, b *testing.B) { func benchmarkTxIndex(txsCount int, b *testing.B) {
txResult := &types.TxResult{1, 1, abci.ResponseDeliverTx{Data: []byte{0}, Code: abci.CodeType_OK, Log: ""}} tx := types.Tx("HELLO WORLD")
txResult := &types.TxResult{1, 1, tx, abci.ResponseDeliverTx{Data: []byte{0}, Code: abci.CodeType_OK, Log: ""}}
dir, err := ioutil.TempDir("", "tx_indexer_db") dir, err := ioutil.TempDir("", "tx_indexer_db")
if err != nil { if err != nil {

View File

@ -108,5 +108,6 @@ func (tp TxProof) Validate(dataHash []byte) error {
type TxResult struct { type TxResult struct {
Height uint64 `json:"height"` Height uint64 `json:"height"`
Index uint32 `json:"index"` Index uint32 `json:"index"`
DeliverTx abci.ResponseDeliverTx `json:"deliver_tx"` Tx Tx `json:"tx"`
Result abci.ResponseDeliverTx `json:"result"`
} }