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

View File

@ -160,13 +160,11 @@ func (c *HTTP) Commit(height int) (*ctypes.ResultCommit, error) {
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)
query := map[string]interface{}{
"height": height,
"index": index,
"hash": hash,
"prove": prove,
"hash": hash,
"prove": prove,
}
_, err := c.rpc.Call("tx", query, tmResult)
if err != nil {

View File

@ -44,7 +44,7 @@ type SignClient interface {
Block(height int) (*ctypes.ResultBlock, error)
Commit(height int) (*ctypes.ResultCommit, 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.

View File

@ -104,6 +104,6 @@ func (c Local) Validators() (*ctypes.ResultValidators, error) {
return core.Validators()
}
func (c Local) Tx(hash []byte, height, index int, prove bool) (*ctypes.ResultTx, error) {
return core.Tx(hash, height, index, prove)
func (c Local) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
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
// ptx, err := c.Tx(bres.TxID, txh, 0, true)
ptx, err := c.Tx(bres.TxID, 0, 0, true)
// ptx, err := c.Tx(bres.TxID, true)
ptx, err := c.Tx(bres.TxID, true)
require.Nil(err, "%d: %+v", i, err)
assert.Equal(txh, ptx.Height)
assert.Equal(types.Tx(tx), ptx.Tx)

View File

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

View File

@ -19,7 +19,7 @@ var Routes = map[string]*rpc.RPCFunc{
"genesis": rpc.NewRPCFunc(GenesisResult, ""),
"block": rpc.NewRPCFunc(BlockResult, "height"),
"commit": rpc.NewRPCFunc(CommitResult, "height"),
"tx": rpc.NewRPCFunc(TxResult, "hash,height,index,prove"),
"tx": rpc.NewRPCFunc(TxResult, "hash,prove"),
"validators": rpc.NewRPCFunc(ValidatorsResult, ""),
"dump_consensus_state": rpc.NewRPCFunc(DumpConsensusStateResult, ""),
"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
// transaction is in the mempool, invalidated, or was not send in the first
// place.
func TxResult(hash []byte, height, index int, prove bool) (ctypes.TMResult, error) {
return Tx(hash, height, index, prove)
func TxResult(hash []byte, prove bool) (ctypes.TMResult, error) {
return Tx(hash, prove)
}
func BroadcastTxCommitResult(tx []byte) (ctypes.TMResult, error) {

View File

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

View File

@ -15,7 +15,7 @@ import (
rpc "github.com/tendermint/go-rpc/client"
"github.com/tendermint/tendermint/rpc/core"
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"
)
@ -157,7 +157,7 @@ func testBroadcastTxCommit(t *testing.T, client rpc.HTTPClient) {
func TestURITx(t *testing.T) {
testTx(t, GetURIClient(), true)
core.SetTxIndexer(&indexer.Null{})
core.SetTxIndexer(&null.TxIndex{})
testTx(t, GetJSONClient(), false)
core.SetTxIndexer(node.ConsensusState().GetState().TxIndexer)
}
@ -165,7 +165,7 @@ func TestURITx(t *testing.T) {
func TestJSONTx(t *testing.T) {
testTx(t, GetJSONClient(), true)
core.SetTxIndexer(&indexer.Null{})
core.SetTxIndexer(&null.TxIndex{})
testTx(t, GetJSONClient(), false)
core.SetTxIndexer(node.ConsensusState().GetState().TxIndexer)
}
@ -188,36 +188,21 @@ func testTx(t *testing.T, client rpc.HTTPClient, withIndexer bool) {
mem := node.MempoolReactor().Mempool
require.Equal(0, mem.Size())
txHash := tx.Hash()
txHash2 := types.Tx("a different tx").Hash()
cases := []struct {
validWithIndexer bool
validNoIndexer bool
height int
index int
hash []byte
prove bool
valid bool
hash []byte
prove bool
}{
// only on proper height, index match
{true, true, res.Height, 0, nil, false},
{true, true, res.Height, 0, nil, true},
{false, false, res.Height, 1, nil, false},
{false, false, res.Height, -7, nil, true},
{false, false, -10, -100, nil, false},
{false, false, res.Height + 1, 0, 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},
// only valid if correct hash provided
{true, txHash, false},
{true, txHash, true},
{false, txHash2, false},
{false, txHash2, true},
{false, nil, false},
{false, nil, true},
}
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.
tmResult = new(ctypes.TMResult)
query := map[string]interface{}{
"height": tc.height,
"index": tc.index,
"hash": tc.hash,
"prove": tc.prove,
"hash": tc.hash,
"prove": tc.prove,
}
_, err = client.Call("tx", query, tmResult)
valid := (withIndexer && tc.validWithIndexer) || (!withIndexer && tc.validNoIndexer)
valid := (withIndexer && tc.valid)
if !valid {
require.NotNil(err, idx)
} else {

View File

@ -84,7 +84,12 @@ func execBlockOnProxyApp(eventCache types.Fireable, proxyAppConn proxy.AppConnCo
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++
// 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()}
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()
batch := txindex.NewBatch()
@ -32,7 +32,8 @@ func TestTxIndex(t *testing.T) {
}
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")
if err != nil {

View File

@ -106,7 +106,8 @@ func (tp TxProof) Validate(dataHash []byte) error {
//
// One usage is indexing transaction results.
type TxResult struct {
Height uint64 `json:"height"`
Index uint32 `json:"index"`
DeliverTx abci.ResponseDeliverTx `json:"deliver_tx"`
Height uint64 `json:"height"`
Index uint32 `json:"index"`
Tx Tx `json:"tx"`
Result abci.ResponseDeliverTx `json:"result"`
}