Skip to content

Commit

Permalink
add tests for proof verification
Browse files Browse the repository at this point in the history
  • Loading branch information
lumtis committed Apr 9, 2024
1 parent 599dac2 commit 1e862e2
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 14 deletions.
7 changes: 4 additions & 3 deletions pkg/testdata/testdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ var ethFiles embed.FS
//go:embed *
var testDataFiles embed.FS

// readHeader reads a header from a file.
// ReadEthHeader reads a header from a file.
// TODO: centralize test data
// https://github.com/zeta-chain/node/issues/1874
func ReadEthHeader() (header types.Header, err error) {
Expand All @@ -38,7 +38,7 @@ func ReadEthHeader() (header types.Header, err error) {
return header, err
}

// readReceipt reads a receipt from a file.
// ReadEthReceipt reads a receipt from a file.
// TODO: centralize test data
// https://github.com/zeta-chain/node/issues/1874
func ReadEthReceipt(index int) (receipt types.Receipt, err error) {
Expand All @@ -55,7 +55,7 @@ func ReadEthReceipt(index int) (receipt types.Receipt, err error) {
return receipt, err
}

// readTx reads a tx from a file.
// ReadEthTx reads a tx from a file.
// TODO: centralize test data
// https://github.com/zeta-chain/node/issues/1874
func ReadEthTx(index int) (tx types.Transaction, err error) {
Expand Down Expand Up @@ -85,6 +85,7 @@ type Blocks struct {
Blocks []Block `json:"blocks"`
}

// LoadTestBlocks loads test blocks from a file.
// TODO: centralize test data
// https://github.com/zeta-chain/node/issues/1874
func LoadTestBlocks(t *testing.T) Blocks {
Expand Down
20 changes: 20 additions & 0 deletions x/lightclient/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,24 @@ func TestGenesis(t *testing.T) {
nullify.Fill(got)
require.Equal(t, genesisState, *got)
})

t.Run("can export genesis with empty state", func(t *testing.T) {
// Export genesis with empty state
k, ctx, _, _ := keepertest.LightclientKeeper(t)
got := lightclient.ExportGenesis(ctx, *k)
require.NotNil(t, got)

// Compare genesis after export
expected := types.GenesisState{
VerificationFlags: types.VerificationFlags{
EthTypeChainEnabled: false,
BtcTypeChainEnabled: false,
},
BlockHeaders: []proofs.BlockHeader{},
ChainStates: []types.ChainState{},
}
nullify.Fill(got)
nullify.Fill(expected)
require.Equal(t, expected, *got)
})
}
134 changes: 134 additions & 0 deletions x/lightclient/keeper/grpc_query_prove_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package keeper_test

import (
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
"github.com/zeta-chain/zetacore/pkg/proofs"
keepertest "github.com/zeta-chain/zetacore/testutil/keeper"
"github.com/zeta-chain/zetacore/testutil/sample"
"github.com/zeta-chain/zetacore/x/lightclient/types"
)

// TODO: Add test for Bitcoin proof verification
// https://github.com/zeta-chain/node/issues/1994

func TestKeeper_Prove(t *testing.T) {
t.Run("should error if req is nil", func(t *testing.T) {
k, ctx, _, _ := keepertest.LightclientKeeper(t)
wctx := sdk.WrapSDKContext(ctx)

_, err := k.Prove(wctx, nil)
require.Error(t, err)
})

t.Run("should error if block hash is invalid", func(t *testing.T) {
k, ctx, _, _ := keepertest.LightclientKeeper(t)
wctx := sdk.WrapSDKContext(ctx)

proof, _, _, txIndex, _, hash := generateProof(t)

_, err := k.Prove(wctx, &types.QueryProveRequest{
ChainId: 1000,
TxHash: hash.Hex(),
Proof: proof,
BlockHash: "invalid",
TxIndex: txIndex,
})
require.ErrorContains(t, err, "cannot convert hash to bytes for chain")
})

t.Run("should error if block header not found", func(t *testing.T) {
k, ctx, _, _ := keepertest.LightclientKeeper(t)
wctx := sdk.WrapSDKContext(ctx)

proof, _, blockHash, txIndex, chainID, hash := generateProof(t)

_, err := k.Prove(wctx, &types.QueryProveRequest{
ChainId: chainID,
TxHash: hash.Hex(),
Proof: proof,
BlockHash: blockHash,
TxIndex: txIndex,
})
require.ErrorContains(t, err, "block header not found")
})

t.Run("should returns response with proven false if invalid proof", func(t *testing.T) {
k, ctx, _, _ := keepertest.LightclientKeeper(t)
wctx := sdk.WrapSDKContext(ctx)

proof, blockHeader, blockHash, txIndex, chainID, hash := generateProof(t)

k.SetBlockHeader(ctx, blockHeader)

res, err := k.Prove(wctx, &types.QueryProveRequest{
ChainId: chainID,
TxHash: hash.Hex(),
Proof: proof,
BlockHash: blockHash,
TxIndex: txIndex + 1, // change txIndex to make it invalid
})
require.NoError(t, err)
require.False(t, res.Valid)
})

t.Run("should returns response with proven true if valid proof", func(t *testing.T) {
k, ctx, _, _ := keepertest.LightclientKeeper(t)
wctx := sdk.WrapSDKContext(ctx)

proof, blockHeader, blockHash, txIndex, chainID, hash := generateProof(t)

k.SetBlockHeader(ctx, blockHeader)

res, err := k.Prove(wctx, &types.QueryProveRequest{
ChainId: chainID,
TxHash: hash.Hex(),
Proof: proof,
BlockHash: blockHash,
TxIndex: txIndex,
})
require.NoError(t, err)
require.True(t, res.Valid)
})

t.Run("should error if error during proof verification", func(t *testing.T) {
k, ctx, _, _ := keepertest.LightclientKeeper(t)
wctx := sdk.WrapSDKContext(ctx)

proof, blockHeader, blockHash, txIndex, chainID, hash := generateProof(t)

// corrupt the block header
blockHeader.Header = proofs.HeaderData{}

k.SetBlockHeader(ctx, blockHeader)

_, err := k.Prove(wctx, &types.QueryProveRequest{
ChainId: chainID,
TxHash: hash.Hex(),
Proof: proof,
BlockHash: blockHash,
TxIndex: txIndex,
})
require.Error(t, err)
})

t.Run("should error if tx hash mismatch", func(t *testing.T) {
k, ctx, _, _ := keepertest.LightclientKeeper(t)
wctx := sdk.WrapSDKContext(ctx)

proof, blockHeader, blockHash, txIndex, chainID, _ := generateProof(t)

k.SetBlockHeader(ctx, blockHeader)

_, err := k.Prove(wctx, &types.QueryProveRequest{
ChainId: chainID,
TxHash: sample.Hash().Hex(), // change tx hash to make it invalid
Proof: proof,
BlockHash: blockHash,
TxIndex: txIndex,
})
require.ErrorContains(t, err, "tx hash mismatch")
})
}
9 changes: 0 additions & 9 deletions x/lightclient/keeper/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,6 @@ func (k Keeper) VerifyProof(ctx sdk.Context, proof *proofs.Proof, chainID int64,
return nil, err
}

// chain must support header-based merkle proof verification
senderChain := chains.GetChainFromChainID(chainID)
if senderChain == nil {
return nil, cosmoserror.Wrapf(types.ErrChainNotSupported, "chain id %d doesn't exist", chainID)
}
if !senderChain.SupportMerkleProof() {
return nil, cosmoserror.Wrapf(types.ErrChainNotSupported, "chain id %d doesn't support merkle proof", chainID)
}

// get block header from the store
hashBytes, err := chains.StringToHash(chainID, blockHash)
if err != nil {
Expand Down
76 changes: 74 additions & 2 deletions x/lightclient/keeper/proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,55 @@ package keeper_test
import (
"testing"

ethcommon "github.com/ethereum/go-ethereum/common"

ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/require"
"github.com/zeta-chain/zetacore/pkg/chains"
"github.com/zeta-chain/zetacore/pkg/proofs"
"github.com/zeta-chain/zetacore/pkg/proofs/ethereum"
"github.com/zeta-chain/zetacore/pkg/testdata"
keepertest "github.com/zeta-chain/zetacore/testutil/keeper"
"github.com/zeta-chain/zetacore/testutil/sample"
"github.com/zeta-chain/zetacore/x/lightclient/types"
)

// generateProof generates a proof and block header
// returns the proof, block header, block hash, tx index, chain id, and tx hash
func generateProof(t *testing.T) (*proofs.Proof, proofs.BlockHeader, string, int64, int64, ethcommon.Hash) {
header, err := testdata.ReadEthHeader()
require.NoError(t, err)
b, err := rlp.EncodeToBytes(&header)
require.NoError(t, err)

var txs ethtypes.Transactions
for i := 0; i < testdata.TxsCount; i++ {
tx, err := testdata.ReadEthTx(i)
require.NoError(t, err)
txs = append(txs, &tx)
}
txsTree := ethereum.NewTrie(txs)

// choose 2 as the index of the tx to prove
txIndex := 2
proof, err := txsTree.GenerateProof(txIndex)
require.NoError(t, err)

chainID := chains.SepoliaChain().ChainId
ethProof := proofs.NewEthereumProof(proof)
ethHeader := proofs.NewEthereumHeader(b)
blockHeader := proofs.BlockHeader{
Height: header.Number.Int64(),
Hash: header.Hash().Bytes(),
ParentHash: header.ParentHash.Bytes(),
ChainId: chainID,
Header: ethHeader,
}
txHash := txs[txIndex].Hash()
return ethProof, blockHeader, header.Hash().Hex(), int64(txIndex), chainID, txHash
}

func TestKeeper_VerifyProof(t *testing.T) {
t.Run("should error if verification flags not found", func(t *testing.T) {
k, ctx, _, _ := keepertest.LightclientKeeper(t)
Expand Down Expand Up @@ -79,6 +120,37 @@ func TestKeeper_VerifyProof(t *testing.T) {
require.ErrorIs(t, err, types.ErrBlockHeaderNotFound)
})

// TODO: Add test with successful verification
// and ErrProofVerificationFailed
t.Run("should fail if proof can't be verified", func(t *testing.T) {
k, ctx, _, _ := keepertest.LightclientKeeper(t)

proof, blockHeader, blockHash, txIndex, chainID, _ := generateProof(t)

k.SetVerificationFlags(ctx, types.VerificationFlags{
EthTypeChainEnabled: true,
BtcTypeChainEnabled: true,
})

k.SetBlockHeader(ctx, blockHeader)

// providing wrong tx index
_, err := k.VerifyProof(ctx, proof, chainID, blockHash, txIndex+1)
require.ErrorIs(t, err, types.ErrProofVerificationFailed)
})

t.Run("can verify a proof", func(t *testing.T) {
k, ctx, _, _ := keepertest.LightclientKeeper(t)

proof, blockHeader, blockHash, txIndex, chainID, _ := generateProof(t)

k.SetVerificationFlags(ctx, types.VerificationFlags{
EthTypeChainEnabled: true,
BtcTypeChainEnabled: true,
})

k.SetBlockHeader(ctx, blockHeader)

txBytes, err := k.VerifyProof(ctx, proof, chainID, blockHash, txIndex)
require.NoError(t, err)
require.NotNil(t, txBytes)
})
}
1 change: 1 addition & 0 deletions x/lightclient/keeper/verification_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func (k Keeper) GetVerificationFlags(ctx sdk.Context) (val types.VerificationFla
}

// CheckVerificationFlagsEnabled checks for a specific chain if the verification flags are enabled
// It returns an error if the chain is not enabled
func (k Keeper) CheckVerificationFlagsEnabled(ctx sdk.Context, chainID int64) error {
verificationFlags, found := k.GetVerificationFlags(ctx)
if !found {
Expand Down

0 comments on commit 1e862e2

Please sign in to comment.