mirror of
https://github.com/tendermint/tendermint.git
synced 2026-04-19 23:30:38 +00:00
resurrect tx.Proof method
This commit is contained in:
@@ -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{
|
||||
|
||||
28
types/tx.go
28
types/tx.go
@@ -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
types/tx_test.go
108
types/tx_test.go
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user