Merge branch 'feature/tx-proof' into feature/237-tx-indexing

Conflicts:
	types/tx.go
This commit is contained in:
Ethan Buchman
2017-04-12 18:48:39 -04:00
9 changed files with 269 additions and 90 deletions
+60
View File
@@ -1,6 +1,9 @@
package types
import (
"bytes"
"errors"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/go-merkle"
)
@@ -32,6 +35,63 @@ func (txs Txs) Hash() []byte {
}
}
// Index returns the index of this transaction in the list, or -1 if not found
func (txs Txs) Index(tx Tx) int {
for i := range txs {
if bytes.Equal(txs[i], tx) {
return i
}
}
return -1
}
// 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)
hashables := make([]merkle.Hashable, l)
for i := 0; i < l; i++ {
hashables[i] = txs[i]
}
root, proofs := merkle.SimpleProofsFromHashables(hashables)
return TxProof{
Index: i,
Total: l,
RootHash: root,
Data: txs[i],
Proof: *proofs[i],
}
}
type TxProof struct {
Index, Total int
RootHash []byte
Data Tx
Proof merkle.SimpleProof
}
func (tp TxProof) LeafHash() []byte {
return tp.Data.Hash()
}
// Validate returns nil if it matches the dataHash, and is internally consistent
// otherwise, returns a sensible error
func (tp TxProof) Validate(dataHash []byte) error {
if !bytes.Equal(dataHash, tp.RootHash) {
return errors.New("Proof matches different data hash")
}
valid := tp.Proof.Verify(tp.Index, tp.Total, tp.LeafHash(), tp.RootHash)
if !valid {
return errors.New("Proof is not internally consistent")
}
return nil
}
// TxResult contains results of executing the transaction.
//
// One usage is indexing transaction results.
+122
View File
@@ -0,0 +1,122 @@
package types
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
cmn "github.com/tendermint/go-common"
ctest "github.com/tendermint/go-common/test"
wire "github.com/tendermint/go-wire"
)
func makeTxs(cnt, size int) Txs {
txs := make(Txs, cnt)
for i := 0; i < cnt; i++ {
txs[i] = cmn.RandBytes(size)
}
return txs
}
func randInt(low, high int) int {
off := cmn.RandInt() % (high - low)
return low + off
}
func TestTxIndex(t *testing.T) {
assert := assert.New(t)
for i := 0; i < 20; i++ {
txs := makeTxs(15, 60)
for j := 0; j < len(txs); j++ {
tx := txs[j]
idx := txs.Index(tx)
assert.Equal(j, idx)
}
assert.Equal(-1, txs.Index(nil))
assert.Equal(-1, txs.Index(Tx("foodnwkf")))
}
}
func TestValidTxProof(t *testing.T) {
assert := assert.New(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 {
leaf := txs[i]
leafHash := leaf.Hash()
proof := txs.Proof(i)
assert.Equal(i, proof.Index, "%d: %d", h, i)
assert.Equal(len(txs), proof.Total, "%d: %d", h, i)
assert.Equal(root, proof.RootHash, "%d: %d", h, i)
assert.Equal(leaf, proof.Data, "%d: %d", h, i)
assert.Equal(leafHash, proof.LeafHash(), "%d: %d", h, i)
assert.Nil(proof.Validate(root), "%d: %d", h, i)
assert.NotNil(proof.Validate([]byte("foobar")), "%d: %d", h, i)
// read-write must also work
var p2 TxProof
bin := wire.BinaryBytes(proof)
err := wire.ReadBinaryBytes(bin, &p2)
if assert.Nil(err, "%d: %d: %+v", h, i, err) {
assert.Nil(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) {
assert := assert.New(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(proof.Validate(root))
bin := wire.BinaryBytes(proof)
// 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 make sure the proof doesn't deserialize into something valid
func assertBadProof(t *testing.T, root []byte, bad []byte, good TxProof) {
var proof TxProof
err := wire.ReadBinaryBytes(bad, &proof)
if err == nil {
err = proof.Validate(root)
if err == nil {
// okay, 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.Total, good.Total, "bad: %#v\ngood: %#v", proof, good)
}
}
}