Skip to content

Commit

Permalink
test: add initial success test case
Browse files Browse the repository at this point in the history
  • Loading branch information
damiannolan committed Apr 24, 2024
1 parent 690f7c8 commit 501d6f1
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 22 deletions.
2 changes: 1 addition & 1 deletion modules/light-clients/07-celestia/client_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (cs *ClientState) VerifyMembership(ctx sdk.Context, clientStore storetypes.
return errorsmod.Wrapf(commitmenttypes.ErrInvalidProof, "could not unmarshal share proof: %v", err)
}

shareProof, err := shareProofFromProto(&shareProofProto)
shareProof, err := ShareProofFromProto(&shareProofProto)
if err != nil {
return err
}
Expand Down
133 changes: 133 additions & 0 deletions modules/light-clients/07-celestia/client_state_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package celestia_test

import (
"encoding/hex"
"encoding/json"

celestia "github.com/cosmos/ibc-go/modules/light-clients/07-celestia"
"github.com/cosmos/ibc-go/v8/modules/core/23-commitment/types"
"github.com/cosmos/ibc-go/v8/modules/core/exported"
ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint"
ibctesting "github.com/cosmos/ibc-go/v8/testing"
)

// dataHash queried from commit at height=10000
// https://public-celestia-rpc.numia.xyz/header?height=10000
const dataHash string = "694F52677DDA82F3148D0A170ECC2A6A74A72563CC3F042BA7277AF3C1558127"

Check failure on line 16 in modules/light-clients/07-celestia/client_state_test.go

View workflow job for this annotation

GitHub Actions / lint

G101: Potential hardcoded credentials (gosec)

// shareProofJSON for shares [0,1] queried at height=10000
// https://public-celestia-rpc.numia.xyz/prove_shares?height=10000&startShare=0&endShare=1
var shareProofJSON string = `{

Check warning on line 20 in modules/light-clients/07-celestia/client_state_test.go

View workflow job for this annotation

GitHub Actions / lint

var-declaration: should omit type string from declaration of var shareProofJSON; it will be inferred from the right-hand side (revive)
"data": [
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAAENAAAACbsAgrAAQq9AQopL2liYy5hcHBsaWNhdGlvbnMudHJhbnNmZXIudjEuTXNnVHJhbnNmZXISjwEKCHRyYW5zZmVyEgljaGFubmVsLTIaEQoEdXRpYRIJMjU3MDAwMDAwIi9jZWxlc3RpYTF3ZWU2MjRscG53Y2NkcHB5dDJrZGZ5czZydnI5eHl2dThmbWVhZSorb3NtbzF3ZWU2MjRscG53Y2NkcHB5dDJrZGZ5czZydnI5eHl2dTdjZWUzeDIHCAEQ+p3mBRJlCk4KRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDQTeSPV//mL4658YJPa9fwwXHbo0zygD9Q0FWgyFN/QwSBAoCCH8SEwoNCgR1dGlhEgUyNzYyORCftwgaQBQg6ZFAoqJz3duveBY3NuDk7llRaGu/e6PQPTaoz4ZGNW5qZkFjgYhkglPXMEdKdktaNO/VEU+RZztQcIwE49DRAgqlAQqiAQojL2Nvc21vcy5zdGFraW5nLnYxYmV0YTEuTXNnRGVsZWdhdGUSewovY2VsZXN0aWExemVnN2xycXNjNjh5NW1nZHBnbnp5ZGUza3E3eDJ0cnE3NnZraDkSNmNlbGVzdGlhdmE="
],
"share_proofs": [
{
"end": 1,
"nodes": [
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVCj8zV410D/2gZY+0iDX7AS/snewF0EQGkuuPWq24Te",
"/////////////////////////////////////////////////////////////////////////////7kDYk5RAaznnwDfIPmd6Iyw03FvBjhlOrAQEMY6dnNd"
]
}
],
"namespace_id": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ==",
"row_proof": {
"row_roots": [
"00000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000001D3C808F4BA0439B72D803F35FC92ED42B86A12C1121CD3A72EC07BE00FAF0DAD"
],
"proofs": [
{
"total": 8,
"index": 0,
"leaf_hash": "Oj+WbIzd1NCo4e4ptcPkyCR3vRW1eI28Fu+GfvCi1hk=",
"aunts": [
"Dm+pi7IQIqqeDq5sA6aDRl29AddOikyIMsNKTs3TOvQ=",
"EN4RBE6ZgyzZAOYbzCPaNgUuxZv1F9a3Av0oqQ7VPEo=",
"ibs0Ape4CNv+qCosdw8W/m4ADHIt6HyqLRVMyNF5FqE="
]
}
],
"start_row": 0,
"end_row": 0
},
"namespace_version": 0
}`

func (suite *CelestiaTestSuite) TestVerifyMembership() {
var (
height exported.Height
path *ibctesting.Path
proof []byte
)

testCases := []struct {
name string
malleate func()
expError error
}{
{
"success: with share proof queried from celestia-core",
func() {
// convert ShareProof json to protobuf encoded bytes
var shareProofInternal celestia.ShareProofInternal
err := json.Unmarshal([]byte(shareProofJSON), &shareProofInternal)
suite.Require().NoError(err)

shareProofProto := shareProofInternal.ToProto()

bz, err := suite.chainA.App.AppCodec().Marshal(&shareProofProto)
suite.Require().NoError(err)

proof = bz // assign proof bytes to ShareProof proto

// overwrite the client consensus state data root to the stub dataHash from queried Height
consensusState := path.EndpointA.GetConsensusState(height)
tmConsensusState, ok := consensusState.(*ibctm.ConsensusState)
suite.Require().True(ok)

root, err := hex.DecodeString(dataHash)
suite.Require().NoError(err)

// assign consensus state root as data availability root
tmConsensusState.Root = types.NewMerkleRoot(root)

path.EndpointA.SetConsensusState(tmConsensusState, height)
},
nil,
},
{
"failure: with proofs from celestia-node blob.GetProof api",
func() {
// TODO: query blob.Proof from celestia-node API and plug in here
},
nil,
},
}

for _, tc := range testCases {
tc := tc
suite.Run(tc.name, func() {
suite.SetupTest()

path = ibctesting.NewPath(suite.chainA, suite.chainB)

clientID := suite.CreateClient(path.EndpointA)

lightClientModule, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(clientID)
suite.Require().True(found)

height = path.EndpointA.GetClientLatestHeight()

tc.malleate()

err := lightClientModule.VerifyMembership(suite.chainA.GetContext(), clientID, height, 0, 0, proof, nil, nil)

expPass := tc.expError == nil
if expPass {
suite.Require().NoError(err)
} else {
suite.Require().ErrorIs(err, tc.expError, "failed verify membership")
}
})
}
}
6 changes: 3 additions & 3 deletions modules/light-clients/07-celestia/light_client_module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,9 +427,9 @@ func (suite *CelestiaTestSuite) TestUpdateStateOnMisbehaviour() {
}
}

func (*CelestiaTestSuite) TestVerifyMembership() {
// TODO
}
// func (*CelestiaTestSuite) TestVerifyMembership() {
// // TODO
// }

func (suite *CelestiaTestSuite) TestRecoverClient() {
var subjectClientID, substituteClientID string
Expand Down
12 changes: 6 additions & 6 deletions modules/light-clients/07-celestia/row_proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

// RowProof is a Merkle proof that a set of rows exist in a Merkle tree with a
// given data root.
type rowProof struct {
type RowProofInternal struct {
// RowRoots are the roots of the rows being proven.
RowRoots []tmbytes.HexBytes `json:"row_roots"`
// Proofs is a list of Merkle proofs where each proof proves that a row
Expand All @@ -23,7 +23,7 @@ type rowProof struct {
// Validate performs checks on the fields of this RowProof. Returns an error if
// the proof fails validation. If the proof passes validation, this function
// attempts to verify the proof. It returns nil if the proof is valid.
func (rp rowProof) Validate(root []byte) error {
func (rp RowProofInternal) Validate(root []byte) error {
// HACKHACK performing subtraction with unsigned integers is unsafe.
if int(rp.EndRow-rp.StartRow+1) != len(rp.RowRoots) {
return fmt.Errorf("the number of rows %d must equal the number of row roots %d", int(rp.EndRow-rp.StartRow+1), len(rp.RowRoots))
Expand All @@ -40,7 +40,7 @@ func (rp rowProof) Validate(root []byte) error {

// VerifyProof verifies that all the row roots in this RowProof exist in a
// Merkle tree with the given root. Returns true if all proofs are valid.
func (rp rowProof) VerifyProof(root []byte) bool {
func (rp RowProofInternal) VerifyProof(root []byte) bool {
for i, proof := range rp.Proofs {
err := proof.Verify(root, rp.RowRoots[i])
if err != nil {
Expand All @@ -50,9 +50,9 @@ func (rp rowProof) VerifyProof(root []byte) bool {
return true
}

func rowProofFromProto(p *RowProof) rowProof {
func RowProofFromProto(p *RowProof) RowProofInternal {
if p == nil {
return rowProof{}
return RowProofInternal{}
}
rowRoots := make([]tmbytes.HexBytes, len(p.RowRoots))
rowProofs := make([]*merkle.Proof, len(p.Proofs))
Expand All @@ -66,7 +66,7 @@ func rowProofFromProto(p *RowProof) rowProof {
}
}

return rowProof{
return RowProofInternal{
RowRoots: rowRoots,
Proofs: rowProofs,
StartRow: p.StartRow,
Expand Down
24 changes: 12 additions & 12 deletions modules/light-clients/07-celestia/share_proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

// ShareProof is an NMT proof that a set of shares exist in a set of rows and a
// Merkle proof that those rows exist in a Merkle tree with a given data root.
type shareProof struct {
type ShareProofInternal struct {
// Data are the raw shares that are being proven.
Data [][]byte `json:"data"`
// ShareProofs are NMT proofs that the shares in Data exist in a set of
Expand All @@ -22,12 +22,12 @@ type shareProof struct {
// NamespaceID is the namespace id of the shares being proven. This
// namespace id is used when verifying the proof. If the namespace id doesn't
// match the namespace of the shares, the proof will fail verification.
NamespaceID []byte `json:"namespace_id"`
RowProof rowProof `json:"row_proof"`
NamespaceVersion uint32 `json:"namespace_version"`
NamespaceID []byte `json:"namespace_id"`
RowProof RowProofInternal `json:"row_proof"`
NamespaceVersion uint32 `json:"namespace_version"`
}

func (sp shareProof) ToProto() ShareProof {
func (sp ShareProofInternal) ToProto() ShareProof {
// TODO consider extracting a ToProto function for RowProof
rowRoots := make([][]byte, len(sp.RowProof.RowRoots))
rowProofs := make([]*crypto.Proof, len(sp.RowProof.Proofs))
Expand All @@ -51,15 +51,15 @@ func (sp shareProof) ToProto() ShareProof {
return pbtp
}

// shareProofFromProto creates a ShareProof from a proto message.
// ShareProofFromProto creates a ShareProof from a proto message.
// Expects the proof to be pre-validated.
func shareProofFromProto(pb *ShareProof) (shareProof, error) {
func ShareProofFromProto(pb *ShareProof) (ShareProofInternal, error) {
if pb == nil {
return shareProof{}, fmt.Errorf("nil share proof protobuf")
return ShareProofInternal{}, fmt.Errorf("nil share proof protobuf")
}

return shareProof{
RowProof: rowProofFromProto(pb.RowProof),
return ShareProofInternal{
RowProof: RowProofFromProto(pb.RowProof),
Data: pb.Data,
ShareProofs: pb.ShareProofs,
NamespaceID: pb.NamespaceId,
Expand All @@ -71,7 +71,7 @@ func shareProofFromProto(pb *ShareProof) (shareProof, error) {
// It returns nil if the proof is valid. Otherwise, it returns a sensible error.
// The `root` is the block data root that the shares to be proven belong to.
// Note: these proofs are tested on the app side.
func (sp shareProof) Validate(root []byte) error {
func (sp ShareProofInternal) Validate(root []byte) error {
numberOfSharesInProofs := int32(0)
for _, proof := range sp.ShareProofs {
// the range is not inclusive from the left.
Expand Down Expand Up @@ -105,7 +105,7 @@ func (sp shareProof) Validate(root []byte) error {
return nil
}

func (sp shareProof) VerifyProof() bool {
func (sp ShareProofInternal) VerifyProof() bool {
cursor := int32(0)
for i, proof := range sp.ShareProofs {
nmtProof := nmt.NewInclusionProof(
Expand Down

0 comments on commit 501d6f1

Please sign in to comment.