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

Add VerifyMembership to 07-tendermint #1297

Merged
merged 9 commits into from
Apr 28, 2022
142 changes: 92 additions & 50 deletions modules/light-clients/07-tendermint/types/client_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,23 +368,18 @@ func (cs ClientState) VerifyPacketCommitment(
sequence uint64,
commitmentBytes []byte,
) error {
merkleProof, consensusState, err := produceVerificationArgs(store, cdc, cs, height, prefix, proof)
commitmentPath := commitmenttypes.NewMerklePath(host.PacketCommitmentPath(portID, channelID, sequence))
merklePath, err := commitmenttypes.ApplyPrefix(prefix, commitmentPath)
if err != nil {
return err
}

// check delay period has passed
if err := verifyDelayPeriodPassed(ctx, store, height, delayTimePeriod, delayBlockPeriod); err != nil {
return err
}

commitmentPath := commitmenttypes.NewMerklePath(host.PacketCommitmentPath(portID, channelID, sequence))
path, err := commitmenttypes.ApplyPrefix(prefix, commitmentPath)
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
path, err := cdc.Marshal(&merklePath)
if err != nil {
return err
return sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "failed to marshal path into commitment merkle path")
}

if err := merkleProof.VerifyMembership(cs.ProofSpecs, consensusState.GetRoot(), path, commitmentBytes); err != nil {
if err := cs.VerifyMembership(ctx, store, cdc, height, delayTimePeriod, delayBlockPeriod, proof, path, commitmentBytes); err != nil {
return err
}

Expand All @@ -407,23 +402,18 @@ func (cs ClientState) VerifyPacketAcknowledgement(
sequence uint64,
acknowledgement []byte,
) error {
merkleProof, consensusState, err := produceVerificationArgs(store, cdc, cs, height, prefix, proof)
ackPath := commitmenttypes.NewMerklePath(host.PacketAcknowledgementPath(portID, channelID, sequence))
merklePath, err := commitmenttypes.ApplyPrefix(prefix, ackPath)
if err != nil {
return err
}

// check delay period has passed
if err := verifyDelayPeriodPassed(ctx, store, height, delayTimePeriod, delayBlockPeriod); err != nil {
return err
}

ackPath := commitmenttypes.NewMerklePath(host.PacketAcknowledgementPath(portID, channelID, sequence))
path, err := commitmenttypes.ApplyPrefix(prefix, ackPath)
path, err := cdc.Marshal(&merklePath)
if err != nil {
return err
return sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "failed to marshal path into acknowledgement merkle path")
}

if err := merkleProof.VerifyMembership(cs.ProofSpecs, consensusState.GetRoot(), path, channeltypes.CommitAcknowledgement(acknowledgement)); err != nil {
if err := cs.VerifyMembership(ctx, store, cdc, height, delayTimePeriod, delayBlockPeriod, proof, path, acknowledgement); err != nil {
return err
}

Expand Down Expand Up @@ -484,25 +474,66 @@ func (cs ClientState) VerifyNextSequenceRecv(
channelID string,
nextSequenceRecv uint64,
) error {
merkleProof, consensusState, err := produceVerificationArgs(store, cdc, cs, height, prefix, proof)
nextSequenceRecvPath := commitmenttypes.NewMerklePath(host.NextSequenceRecvPath(portID, channelID))
merklePath, err := commitmenttypes.ApplyPrefix(prefix, nextSequenceRecvPath)
if err != nil {
return err
}

// check delay period has passed
if err := verifyDelayPeriodPassed(ctx, store, height, delayTimePeriod, delayBlockPeriod); err != nil {
value := sdk.Uint64ToBigEndian(nextSequenceRecv)

path, err := cdc.Marshal(&merklePath)
if err != nil {
return sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "failed to marshal path into acknowledgement merkle path")
}

if err := cs.VerifyMembership(ctx, store, cdc, height, delayTimePeriod, delayBlockPeriod, proof, path, value); err != nil {
return err
}

nextSequenceRecvPath := commitmenttypes.NewMerklePath(host.NextSequenceRecvPath(portID, channelID))
path, err := commitmenttypes.ApplyPrefix(prefix, nextSequenceRecvPath)
if err != nil {
return nil
}

// VerifyMembership is a generic proof verification method which verifies a proof of the existence of a value at a given CommitmentPath at the specified height.
// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24).
func (cs ClientState) VerifyMembership(
ctx sdk.Context,
clientStore sdk.KVStore,
cdc codec.BinaryCodec,
height exported.Height,
delayTimePeriod uint64,
delayBlockPeriod uint64,
proof []byte,
path []byte,
value []byte,
) error {
if cs.GetLatestHeight().LT(height) {
return sdkerrors.Wrapf(
sdkerrors.ErrInvalidHeight,
"client state height < proof height (%d < %d), please ensure the client has been updated", cs.GetLatestHeight(), height,
)
}

if err := verifyDelayPeriodPassed(ctx, clientStore, height, delayTimePeriod, delayBlockPeriod); err != nil {
return err
}

bz := sdk.Uint64ToBigEndian(nextSequenceRecv)
var merkleProof commitmenttypes.MerkleProof
if err := cdc.Unmarshal(proof, &merkleProof); err != nil {
return sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "failed to unmarshal proof into ICS 23 commitment merkle proof")
}

var merklePath commitmenttypes.MerklePath
if err := cdc.Unmarshal(path, &merklePath); err != nil {
return sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "failed to unmarshal path into ICS 23 commitment merkle path")
}

if err := merkleProof.VerifyMembership(cs.ProofSpecs, consensusState.GetRoot(), path, bz); err != nil {
consensusState, found := GetConsensusState(clientStore, cdc, height)
if !found {
return sdkerrors.Wrap(clienttypes.ErrConsensusStateNotFound, "please ensure the proof was constructed against a height that exists on the client")
}

if err := merkleProof.VerifyMembership(cs.ProofSpecs, consensusState.GetRoot(), merklePath, value); err != nil {
return err
}

Expand All @@ -512,30 +543,41 @@ func (cs ClientState) VerifyNextSequenceRecv(
// verifyDelayPeriodPassed will ensure that at least delayTimePeriod amount of time and delayBlockPeriod number of blocks have passed
// since consensus state was submitted before allowing verification to continue.
func verifyDelayPeriodPassed(ctx sdk.Context, store sdk.KVStore, proofHeight exported.Height, delayTimePeriod, delayBlockPeriod uint64) error {
// check that executing chain's timestamp has passed consensusState's processed time + delay time period
processedTime, ok := GetProcessedTime(store, proofHeight)
if !ok {
return sdkerrors.Wrapf(ErrProcessedTimeNotFound, "processed time not found for height: %s", proofHeight)
}
currentTimestamp := uint64(ctx.BlockTime().UnixNano())
validTime := processedTime + delayTimePeriod
// NOTE: delay time period is inclusive, so if currentTimestamp is validTime, then we return no error
if currentTimestamp < validTime {
return sdkerrors.Wrapf(ErrDelayPeriodNotPassed, "cannot verify packet until time: %d, current time: %d",
validTime, currentTimestamp)
}
// check that executing chain's height has passed consensusState's processed height + delay block period
processedHeight, ok := GetProcessedHeight(store, proofHeight)
if !ok {
return sdkerrors.Wrapf(ErrProcessedHeightNotFound, "processed height not found for height: %s", proofHeight)
if delayTimePeriod != 0 {
// check that executing chain's timestamp has passed consensusState's processed time + delay time period
processedTime, ok := GetProcessedTime(store, proofHeight)
if !ok {
return sdkerrors.Wrapf(ErrProcessedTimeNotFound, "processed time not found for height: %s", proofHeight)
}

currentTimestamp := uint64(ctx.BlockTime().UnixNano())
validTime := processedTime + delayTimePeriod

// NOTE: delay time period is inclusive, so if currentTimestamp is validTime, then we return no error
if currentTimestamp < validTime {
return sdkerrors.Wrapf(ErrDelayPeriodNotPassed, "cannot verify packet until time: %d, current time: %d",
validTime, currentTimestamp)
}

}
currentHeight := clienttypes.GetSelfHeight(ctx)
validHeight := clienttypes.NewHeight(processedHeight.GetRevisionNumber(), processedHeight.GetRevisionHeight()+delayBlockPeriod)
// NOTE: delay block period is inclusive, so if currentHeight is validHeight, then we return no error
if currentHeight.LT(validHeight) {
return sdkerrors.Wrapf(ErrDelayPeriodNotPassed, "cannot verify packet until height: %s, current height: %s",
validHeight, currentHeight)

if delayBlockPeriod != 0 {
// check that executing chain's height has passed consensusState's processed height + delay block period
processedHeight, ok := GetProcessedHeight(store, proofHeight)
if !ok {
return sdkerrors.Wrapf(ErrProcessedHeightNotFound, "processed height not found for height: %s", proofHeight)
}

currentHeight := clienttypes.GetSelfHeight(ctx)
validHeight := clienttypes.NewHeight(processedHeight.GetRevisionNumber(), processedHeight.GetRevisionHeight()+delayBlockPeriod)

// NOTE: delay block period is inclusive, so if currentHeight is validHeight, then we return no error
if currentHeight.LT(validHeight) {
return sdkerrors.Wrapf(ErrDelayPeriodNotPassed, "cannot verify packet until height: %s, current height: %s",
validHeight, currentHeight)
}
}

return nil
}

Expand Down
176 changes: 175 additions & 1 deletion modules/light-clients/07-tendermint/types/client_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,181 @@ func (suite *TendermintTestSuite) TestInitialize() {
}
}

func (suite *TendermintTestSuite) TestVerifyMembership() {
var (
testingpath *ibctesting.Path
delayTimePeriod uint64
delayBlockPeriod uint64
proofHeight exported.Height
proof []byte
path []byte
value []byte
)

testCases := []struct {
name string
malleate func()
expPass bool
}{
{
name: "successful ClientState verification",
malleate: func() {
// default proof construction uses ClientState
},
expPass: true,
},
{
name: "successful ConsensusState verification",
malleate: func() {
// TODO
},
expPass: true,
},
{
name: "successful Connection verification",
malleate: func() {
// TODO
},
expPass: true,
},
{
name: "successful Channel verification",
malleate: func() {
// TODO
},
expPass: true,
},
{
"successful PacketCommitment verification", func() {
// send from chainB to chainA since we are proving chainB sent a packet
packet := channeltypes.NewPacket(ibctesting.MockPacketData, 1, testingpath.EndpointB.ChannelConfig.PortID, testingpath.EndpointB.ChannelID, testingpath.EndpointA.ChannelConfig.PortID, testingpath.EndpointA.ChannelID, clienttypes.NewHeight(0, 100), 0)
err := testingpath.EndpointB.SendPacket(packet)
suite.Require().NoError(err)

// make packet commitment proof
key := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence())
merklePath := commitmenttypes.NewMerklePath(string(key))
merklePath, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath)
suite.Require().NoError(err)

path, err = suite.chainB.Codec.Marshal(&merklePath)
suite.Require().NoError(err)

proof, proofHeight = testingpath.EndpointB.QueryProof(key)

value = channeltypes.CommitPacket(suite.chainA.App.GetIBCKeeper().Codec(), packet)
}, true,
damiannolan marked this conversation as resolved.
Show resolved Hide resolved
},
{
name: "successful Acknowledgement verification",
malleate: func() {
// TODO
},
expPass: true,
},
{
name: "successful NextSequenceRecv verification",
malleate: func() {
// TODO
},
expPass: true,
},
{
name: "successful custom type verification",
malleate: func() {
// TODO
damiannolan marked this conversation as resolved.
Show resolved Hide resolved
},
expPass: true,
},
{
name: "delay time period has passed",
malleate: func() {
delayTimePeriod = uint64(time.Second.Nanoseconds())
},
expPass: true,
},
{
name: "delay time period has not passed",
malleate: func() {
delayTimePeriod = uint64(time.Hour.Nanoseconds())
},
expPass: false,
},
{
name: "delay block period has passed",
malleate: func() {
delayBlockPeriod = 1
},
expPass: true,
},
{
name: "delay block period has not passed",
malleate: func() {
delayBlockPeriod = 1000
},
expPass: false,
},
{
"latest client height < height", func() {
proofHeight = testingpath.EndpointA.GetClientState().GetLatestHeight().Increment()
}, false,
},
{
"proof verification failed", func() {
proof = invalidProof
}, false,
},
}

for _, tc := range testCases {
tc := tc

suite.Run(tc.name, func() {
suite.SetupTest() // reset
testingpath = ibctesting.NewPath(suite.chainA, suite.chainB)
suite.coordinator.Setup(testingpath)

// reset time and block delays to 0, malleate may change to a specific non-zero value.
delayTimePeriod = 0
delayBlockPeriod = 0

// create default proof, merklePath, and value which passes
// may be overwritten by malleate()
key := host.FullClientStateKey(testingpath.EndpointB.ClientID)
merklePath := commitmenttypes.NewMerklePath(string(key))
merklePath, err := commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath)
suite.Require().NoError(err)

path, err = suite.chainA.Codec.Marshal(&merklePath)
suite.Require().NoError(err)

proof, proofHeight = suite.chainB.QueryProof(key)

clientState := testingpath.EndpointB.GetClientState().(*types.ClientState)
value, err = suite.chainB.Codec.MarshalInterface(clientState)
suite.Require().NoError(err)

tc.malleate() // make changes as necessary

clientState = testingpath.EndpointA.GetClientState().(*types.ClientState)

ctx := suite.chainA.GetContext()
store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, testingpath.EndpointA.ClientID)

err = clientState.VerifyMembership(
ctx, store, suite.chainA.Codec, proofHeight, delayTimePeriod, delayBlockPeriod,
proof, path, value,
)

if tc.expPass {
suite.Require().NoError(err)
} else {
suite.Require().Error(err)
}
})
}
}

func (suite *TendermintTestSuite) TestVerifyClientConsensusState() {
testCases := []struct {
name string
Expand Down Expand Up @@ -577,7 +752,6 @@ func (suite *TendermintTestSuite) TestVerifyPacketAcknowledgement() {
},
expPass: false,
},

{
"ApplyPrefix failed", func() {
prefix = commitmenttypes.MerklePrefix{}
Expand Down