Browse Source

resurrect tx.Proof method

pull/8094/head
William Banfield 3 years ago
parent
commit
4763b0ee99
No known key found for this signature in database GPG Key ID: EFAD3442BF29E3AC
3 changed files with 131 additions and 20 deletions
  1. +2
    -13
      internal/rpc/core/tx.go
  2. +21
    -7
      types/tx.go
  3. +108
    -0
      types/tx_test.go

+ 2
- 13
internal/rpc/core/tx.go View File

@ -6,7 +6,6 @@ import (
"fmt"
"sort"
"github.com/tendermint/tendermint/crypto/merkle"
tmquery "github.com/tendermint/tendermint/internal/pubsub/query"
"github.com/tendermint/tendermint/internal/state/indexer"
"github.com/tendermint/tendermint/libs/bytes"
@ -40,12 +39,7 @@ func (env *Environment) Tx(ctx context.Context, hash bytes.HexBytes, prove bool)
var proof types.TxProof
if prove {
block := env.BlockStore.LoadBlock(r.Height)
root, proofs := merkle.ProofsFromByteSlices(block.Data.Txs.ToSliceOfBytes())
proof = types.TxProof{
RootHash: root,
Proof: *proofs[int(r.Index)],
Data: block.Data.Txs[int(r.Index)],
}
proof = block.Data.Txs.Proof(int(r.Index))
}
return &coretypes.ResultTx{
@ -130,12 +124,7 @@ func (env *Environment) TxSearch(
var proof types.TxProof
if prove {
block := env.BlockStore.LoadBlock(r.Height)
root, proofs := merkle.ProofsFromByteSlices(block.Data.Txs.ToSliceOfBytes())
proof = types.TxProof{
RootHash: root,
Proof: *proofs[int(r.Index)],
Data: block.Data.Txs[int(r.Index)],
}
proof = block.Data.Txs.Proof(int(r.Index))
}
apiResults = append(apiResults, &coretypes.ResultTx{


+ 21
- 7
types/tx.go View File

@ -34,13 +34,8 @@ type Txs []Tx
// Hash returns the Merkle root hash of the transaction hashes.
// i.e. the leaves of the tree are the hashes of the txs.
func (txs Txs) Hash() []byte {
// These allocations will be removed once Txs is switched to [][]byte,
// ref #2603. This is because golang does not allow type casting slices without unsafe
txBzs := make([][]byte, len(txs))
for i := 0; i < len(txs); i++ {
txBzs[i] = txs[i].Hash()
}
return merkle.HashFromByteSlices(txBzs)
hl := txs.hashList()
return merkle.HashFromByteSlices(hl)
}
// Index returns the index of this transaction in the list, or -1 if not found
@ -63,6 +58,25 @@ func (txs Txs) IndexByHash(hash []byte) int {
return -1
}
func (txs Txs) Proof(i int) TxProof {
hl := txs.hashList()
root, proofs := merkle.ProofsFromByteSlices(hl)
return TxProof{
RootHash: root,
Data: txs[i],
Proof: *proofs[i],
}
}
func (txs Txs) hashList() [][]byte {
hl := make([][]byte, len(txs))
for i := 0; i < len(txs); i++ {
hl[i] = txs[i].Hash()
}
return hl
}
// Txs is a slice of transactions. Sorting a Txs value orders the transactions
// lexicographically.
func (txs Txs) Len() int { return len(txs) }


+ 108
- 0
types/tx_test.go View File

@ -1,13 +1,17 @@
package types
import (
"bytes"
"math/rand"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
ctest "github.com/tendermint/tendermint/internal/libs/test"
tmrand "github.com/tendermint/tendermint/libs/rand"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)
func makeTxs(cnt, size int) Txs {
@ -197,3 +201,107 @@ func TestValidateTxRecordSet(t *testing.T) {
}
})
}
func TestValidTxProof(t *testing.T) {
cases := []struct {
txs Txs
}{
{Txs{{1, 4, 34, 87, 163, 1}}},
{Txs{{5, 56, 165, 2}, {4, 77}}},
{Txs{Tx("foo"), Tx("bar"), Tx("baz")}},
{makeTxs(20, 5)},
{makeTxs(7, 81)},
{makeTxs(61, 15)},
}
for h, tc := range cases {
txs := tc.txs
root := txs.Hash()
// make sure valid proof for every tx
for i := range txs {
tx := []byte(txs[i])
proof := txs.Proof(i)
assert.EqualValues(t, i, proof.Proof.Index, "%d: %d", h, i)
assert.EqualValues(t, len(txs), proof.Proof.Total, "%d: %d", h, i)
assert.EqualValues(t, root, proof.RootHash, "%d: %d", h, i)
assert.EqualValues(t, tx, proof.Data, "%d: %d", h, i)
assert.EqualValues(t, txs[i].Hash(), proof.Leaf(), "%d: %d", h, i)
assert.Nil(t, proof.Validate(root), "%d: %d", h, i)
assert.NotNil(t, proof.Validate([]byte("foobar")), "%d: %d", h, i)
// read-write must also work
var (
p2 TxProof
pb2 tmproto.TxProof
)
pbProof := proof.ToProto()
bin, err := pbProof.Marshal()
require.NoError(t, err)
err = pb2.Unmarshal(bin)
require.NoError(t, err)
p2, err = TxProofFromProto(pb2)
if assert.NoError(t, err, "%d: %d: %+v", h, i, err) {
assert.Nil(t, p2.Validate(root), "%d: %d", h, i)
}
}
}
}
func TestTxProofUnchangable(t *testing.T) {
// run the other test a bunch...
for i := 0; i < 40; i++ {
testTxProofUnchangable(t)
}
}
func testTxProofUnchangable(t *testing.T) {
// make some proof
txs := makeTxs(randInt(2, 100), randInt(16, 128))
root := txs.Hash()
i := randInt(0, len(txs)-1)
proof := txs.Proof(i)
// make sure it is valid to start with
assert.Nil(t, proof.Validate(root))
pbProof := proof.ToProto()
bin, err := pbProof.Marshal()
require.NoError(t, err)
// try mutating the data and make sure nothing breaks
for j := 0; j < 500; j++ {
bad := ctest.MutateByteSlice(bin)
if !bytes.Equal(bad, bin) {
assertBadProof(t, root, bad, proof)
}
}
}
// This makes sure that the proof doesn't deserialize into something valid.
func assertBadProof(t *testing.T, root []byte, bad []byte, good TxProof) {
var (
proof TxProof
pbProof tmproto.TxProof
)
err := pbProof.Unmarshal(bad)
if err == nil {
proof, err = TxProofFromProto(pbProof)
if err == nil {
err = proof.Validate(root)
if err == nil {
// XXX Fix simple merkle proofs so the following is *not* OK.
// This can happen if we have a slightly different total (where the
// path ends up the same). If it is something else, we have a real
// problem.
assert.NotEqual(t, proof.Proof.Total, good.Proof.Total, "bad: %#v\ngood: %#v", proof, good)
}
}
}
}
func randInt(low, high int) int {
off := rand.Int() % (high - low)
return low + off
}

Loading…
Cancel
Save