Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: add verify proof test cases for celestia da client #6220

Draft
wants to merge 3 commits into
base: feat/celestia-da-client
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 = `{
"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,
},
// TODO: query blob.Proof from celestia-node API and plug in here
// {
// "failure: with proofs from celestia-node blob.GetProof api",
// func() {
// },
// 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")
}
})
}
}
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
Loading