Skip to content

Commit

Permalink
provider, consumer: use new staking interface
Browse files Browse the repository at this point in the history
  • Loading branch information
MSalopek committed Sep 27, 2023
1 parent a99edd7 commit 4b72097
Show file tree
Hide file tree
Showing 18 changed files with 553 additions and 40 deletions.
6 changes: 4 additions & 2 deletions tests/integration/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (s *CCVTestSuite) consumerCtx() sdk.Context {
return s.consumerChain.GetContext()
}

func (s *CCVTestSuite) providerBondDenom() string {
func (s *CCVTestSuite) providerBondDenom() (string, error) {
return s.providerApp.GetTestStakingKeeper().BondDenom(s.providerCtx())
}

Expand Down Expand Up @@ -90,7 +90,9 @@ func (s *CCVTestSuite) setDefaultValSigningInfo(tmVal tmtypes.Validator) {
}

func getBalance(s *CCVTestSuite, providerCtx sdk.Context, delAddr sdk.AccAddress) math.Int {
return s.providerApp.GetTestBankKeeper().GetBalance(providerCtx, delAddr, s.providerBondDenom()).Amount
denom, err := s.providerBondDenom()
s.Require().NoError(err)
return s.providerApp.GetTestBankKeeper().GetBalance(providerCtx, delAddr, denom).Amount
}

// delegateAndUndelegate delegates bondAmt from delAddr to the first validator
Expand Down
6 changes: 3 additions & 3 deletions testutil/ibc_testing/specific_setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ package ibc_testing
import (
"encoding/json"

db "github.com/cosmos/cosmos-db"
ibctesting "github.com/cosmos/ibc-go/v8/testing"

simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

"cosmossdk.io/log"
tmdb "github.com/cometbft/cometbft-db"
"github.com/cometbft/cometbft/abci/types"

appConsumer "github.com/cosmos/interchain-security/v3/app/consumer"
Expand All @@ -33,15 +33,15 @@ var (
// ProviderAppIniter implements ibctesting.AppIniter for a provider app
func ProviderAppIniter() (ibctesting.TestingApp, map[string]json.RawMessage) {
encoding := appProvider.MakeTestEncodingConfig()
testApp := appProvider.New(log.NewNopLogger(), tmdb.NewMemDB(), nil, true, simtestutil.EmptyAppOptions{})
testApp := appProvider.New(log.NewNopLogger(), db.NewMemDB(), nil, true, simtestutil.EmptyAppOptions{})
return testApp, appProvider.NewDefaultGenesisState(encoding.Codec)
}

// ConsumerAppIniter returns a ibctesting.ValSetAppIniter for a consumer app
func ConsumerAppIniter(initValPowers []types.ValidatorUpdate) AppIniter {
return func() (ibctesting.TestingApp, map[string]json.RawMessage) {
encoding := appConsumer.MakeTestEncodingConfig()
testApp := appConsumer.New(log.NewNopLogger(), tmdb.NewMemDB(), nil, true, simtestutil.EmptyAppOptions{})
testApp := appConsumer.New(log.NewNopLogger(), db.NewMemDB(), nil, true, simtestutil.EmptyAppOptions{})
genesisState := appConsumer.NewDefaultGenesisState(encoding.Codec)
// NOTE ibc-go/v7/testing.SetupWithGenesisValSet requires a staking module
// genesisState or it panics. Feed a minimum one.
Expand Down
4 changes: 3 additions & 1 deletion testutil/integration/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ type DemocConsumerApp interface {
// since integration tests require extra functionality from external keepers.
//

// NOTE: @MSalopek this is a bit confusing to me
// See if this needs a bigger refactor
type TestStakingKeeper interface {
ccvtypes.StakingKeeper
Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt math.Int, tokenSrc types.BondStatus,
Expand All @@ -99,10 +101,10 @@ type TestStakingKeeper interface {
) (ubd types.UnbondingDelegation, found bool)
GetRedelegations(ctx sdk.Context, delegator sdk.AccAddress,
maxRetrieve uint16) (redelegations []types.Redelegation)
// NOTE: @MSalopek this is a bit confusing to me
// BondDenom(ctx sdk.Context) (res string)
// IsValidatorJailed(ctx sdk.Context, addr sdk.ConsAddress) bool
IsValidatorJailed(ctx context.Context, addr sdk.ConsAddress) bool
BondDenom(ctx context.Context) (res string)
GetUnbondingDelegation(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress,
) (ubd types.UnbondingDelegation, found bool)
GetAllValidators(ctx sdk.Context) (validators []types.Validator)
Expand Down
19 changes: 19 additions & 0 deletions testutil/simibc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# simibc

## What is this?

A collection of utilities based on [ibc-go/testing](https://github.com/cosmos/ibc-go/tree/main/testing) which make it easier to write test scenarios involving precise orderings of

- BeginBlock, EndBlock on each IBC connected chain
- Packet delivery
- Updating the client

## Why is this useful?

It is very hard to reason about tests written using vanilla [ibc-go/testing](https://github.com/cosmos/ibc-go/tree/main/testing) because the methods included in that library have many side effects. For example, that library has a notion of global time, so calling EndBlock on one chain will influence the future block times of another chain. As another example, sending a packet from chain A to B will automatically progress the block height on chain A. These behaviors make it very hard to understand, especially if your applications have business logic in BeginBlock or EndBlock.

The utilities in simibc do not have any side effects, making it very easy to understand what is happening. It also makes it very easy to write data driven tests (like table tests, model based tests or property based tests).

## How do I use this?

Please see the function docstrings to get an idea of how you could use this package. This README is intentionally short because it is easier to maintain code and docstrings instead of markdown.
114 changes: 114 additions & 0 deletions testutil/simibc/ordered_outbox.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package simibc

import channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types"

// Ack represents a (sent) ack committed to block state
type Ack struct {
Ack []byte
// The packet to which this ack is a response
Packet channeltypes.Packet
// The number of App.Commits that have occurred since this ack was sent
// For example, if the ack was sent at height h, and the blockchain
// has headers ..., h, h+1, h+2 then Commits = 3
Commits int
}

// Packet represents a (sent) packet committed to block state
type Packet struct {
Packet channeltypes.Packet
// The number of App.Commits that have occurred since this packet was sent
// For example, if the ack was sent at height h, and the blockchain
// has headers ..., h, h+1, h+2 then Commits = 3
Commits int
}

// OrderedOutbox is a collection of ORDERED packets and acks that have been sent
// by different chains, but have not yet been delivered to their target.
// The methods take care of bookkeeping, making it easier to simulate
// a real relayed IBC connection.
//
// Each sent packet or ack can be added here. When a sufficient number of
// block commits have followed each sent packet or ack, they can be consumed:
// delivered to their target. Since the sequences are ordered, this is useful
// for testing ORDERED ibc channels.
//
// NOTE: OrderedOutbox MAY be used independently of the rest of simibc.
type OrderedOutbox struct {
// An ordered sequence of packets from each sender
OutboxPackets map[string][]Packet
// An ordered sequence of acks from each sender
OutboxAcks map[string][]Ack
}

// MakeOrderedOutbox creates a new empty OrderedOutbox.
func MakeOrderedOutbox() OrderedOutbox {
return OrderedOutbox{
OutboxPackets: map[string][]Packet{},
OutboxAcks: map[string][]Ack{},
}
}

// AddPacket adds an outbound packet from the sender.
func (n OrderedOutbox) AddPacket(sender string, packet channeltypes.Packet) {
n.OutboxPackets[sender] = append(n.OutboxPackets[sender], Packet{packet, 0})
}

// AddAck adds an outbound ack from the sender. The ack is a response to the packet.
func (n OrderedOutbox) AddAck(sender string, ack []byte, packet channeltypes.Packet) {
n.OutboxAcks[sender] = append(n.OutboxAcks[sender], Ack{ack, packet, 0})
}

// ConsumePackets returns the first num packets with 2 or more commits. Returned
// packets are removed from the outbox and will not be returned again (consumed).
func (n OrderedOutbox) ConsumePackets(sender string, num int) []Packet {
ret := []Packet{}
sz := len(n.OutboxPackets[sender])
if sz < num {
num = sz
}
for _, p := range n.OutboxPackets[sender][:num] {
if 1 < p.Commits {
ret = append(ret, p)
} else {
break
}
}
n.OutboxPackets[sender] = n.OutboxPackets[sender][len(ret):]
return ret
}

// ConsumerAcks returns the first num packets with 2 or more commits. Returned
// acks are removed from the outbox and will not be returned again (consumed).
func (n OrderedOutbox) ConsumeAcks(sender string, num int) []Ack {
ret := []Ack{}
sz := len(n.OutboxAcks[sender])
if sz < num {
num = sz
}
for _, a := range n.OutboxAcks[sender][:num] {
if 1 < a.Commits {
ret = append(ret, a)
} else {
break
}
}
n.OutboxAcks[sender] = n.OutboxAcks[sender][len(ret):]
return ret
}

// Commit marks a block commit, increasing the commit count for all
// packets and acks in the sender's outbox.
// When a packet or ack has 2 or more commits, it is available for
// delivery to the counterparty chain.
// Note that 2 commits are necessary instead of 1:
// - 1st commit is necessary for the packet to included in the block
// - 2nd commit is necessary because in practice the ibc light client
// needs to have block h + 1 to be able to verify the packet in block h.
func (n OrderedOutbox) Commit(sender string) {
for i := range n.OutboxPackets[sender] {
n.OutboxPackets[sender][i].Commits += 1
}
for i := range n.OutboxAcks[sender] {
n.OutboxAcks[sender][i].Commits += 1
}
}
179 changes: 179 additions & 0 deletions testutil/simibc/relay_util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package simibc

import (
clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types"
channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types"
host "github.com/cosmos/ibc-go/v8/modules/core/24-host"
ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint"
ibctesting "github.com/cosmos/ibc-go/v8/testing"
simapp "github.com/cosmos/ibc-go/v8/testing/simapp"
"github.com/stretchr/testify/require"

errorsmod "cosmossdk.io/errors"

sdk "github.com/cosmos/cosmos-sdk/types"

tmtypes "github.com/cometbft/cometbft/types"
)

// UpdateReceiverClient DELIVERs a header to the receiving endpoint
// and update the respective client of the receiving chain.
//
// The header is a header of the sender chain. The receiver chain
// must have a client of the sender chain that it can update.
//
// NOTE: this function MAY be used independently of the rest of simibc.
func UpdateReceiverClient(sender, receiver *ibctesting.Endpoint, header *ibctmtypes.Header) (err error) {
err = augmentHeader(sender.Chain, receiver.Chain, receiver.ClientID, header)

if err != nil {
return err
}

msg, err := clienttypes.NewMsgUpdateClient(
receiver.ClientID, header,
receiver.Chain.SenderAccount.GetAddress().String(),
)

require.NoError(receiver.Chain.T, err)

_, _, err = simapp.SignAndDeliver(
receiver.Chain.T,
receiver.Chain.TxConfig,
receiver.Chain.App.GetBaseApp(),
receiver.Chain.GetContext().BlockHeader(),
[]sdk.Msg{msg},
receiver.Chain.ChainID,
[]uint64{receiver.Chain.SenderAccount.GetAccountNumber()},
[]uint64{receiver.Chain.SenderAccount.GetSequence()},
true, true, receiver.Chain.SenderPrivKey,
)

if err != nil {
return err
}

err = receiver.Chain.SenderAccount.SetSequence(receiver.Chain.SenderAccount.GetSequence() + 1)

if err != nil {
return err
}

return nil
}

// TryRecvPacket will try once to DELIVER a packet from sender to receiver. If successful,
// it will return the acknowledgement bytes.
//
// The packet must be sent from the sender chain to the receiver chain, and the
// receiver chain must have a client for the sender chain which has been updated
// to a recent height of the sender chain so that it can verify the packet.
func TryRecvPacket(sender, receiver *ibctesting.Endpoint, packet channeltypes.Packet) (ack []byte, err error) {
packetKey := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence())
proof, proofHeight := sender.Chain.QueryProof(packetKey)

RPmsg := channeltypes.NewMsgRecvPacket(packet, proof, proofHeight, receiver.Chain.SenderAccount.GetAddress().String())

_, resWithAck, err := simapp.SignAndDeliver(
receiver.Chain.T,
receiver.Chain.TxConfig,
receiver.Chain.App.GetBaseApp(),
receiver.Chain.GetContext().BlockHeader(),
[]sdk.Msg{RPmsg},
receiver.Chain.ChainID,
[]uint64{receiver.Chain.SenderAccount.GetAccountNumber()},
[]uint64{receiver.Chain.SenderAccount.GetSequence()},
true, true, receiver.Chain.SenderPrivKey,
)
if err != nil {
return nil, err
}

err = receiver.Chain.SenderAccount.SetSequence(receiver.Chain.SenderAccount.GetSequence() + 1)

if err != nil {
return nil, err
}

ack, err = ibctesting.ParseAckFromEvents(resWithAck.GetEvents())

if err != nil {
return nil, err
}

return ack, nil
}

// TryRecvAck will try once to DELIVER an ack from sender to receiver.
//
// The ack must have been sent from the sender to the receiver, in response
// to packet which was previously delivered from the receiver to the sender.
// The receiver chain must have a client for the sender chain which has been
// updated to a recent height of the sender chain so that it can verify the packet.
func TryRecvAck(sender, receiver *ibctesting.Endpoint, packet channeltypes.Packet, ack []byte) (err error) {
p := packet
packetKey := host.PacketAcknowledgementKey(p.GetDestPort(), p.GetDestChannel(), p.GetSequence())
proof, proofHeight := sender.Chain.QueryProof(packetKey)

ackMsg := channeltypes.NewMsgAcknowledgement(p, ack, proof, proofHeight, receiver.Chain.SenderAccount.GetAddress().String())

_, _, err = simapp.SignAndDeliver(
receiver.Chain.T,
receiver.Chain.TxConfig,
receiver.Chain.App.GetBaseApp(),
receiver.Chain.GetContext().BlockHeader(),
[]sdk.Msg{ackMsg},
receiver.Chain.ChainID,
[]uint64{receiver.Chain.SenderAccount.GetAccountNumber()},
[]uint64{receiver.Chain.SenderAccount.GetSequence()},
true, true, receiver.Chain.SenderPrivKey,
)

if err != nil {
return err
}

err = receiver.Chain.SenderAccount.SetSequence(receiver.Chain.SenderAccount.GetSequence() + 1)

if err != nil {
return err
}

return nil
}

// augmentHeader is a helper that augments the header with the height and validators that are most recently trusted
// by the receiver chain. If there is an error, the header will not be modified.
func augmentHeader(sender, receiver *ibctesting.TestChain, clientID string, header *ibctmtypes.Header) error {
trustedHeight := receiver.GetClientState(clientID).GetLatestHeight().(clienttypes.Height)

var (
tmTrustedVals *tmtypes.ValidatorSet
ok bool
)
// Once we get TrustedHeight from client, we must query the validators from the counterparty chain
// If the LatestHeight == LastHeader.Height, then TrustedValidators are current validators
// If LatestHeight < LastHeader.Height, we can query the historical validator set from HistoricalInfo
if trustedHeight == sender.LastHeader.GetHeight() {
tmTrustedVals = sender.Vals
} else {
// NOTE: We need to get validators from counterparty at height: trustedHeight+1
// since the last trusted validators for a header at height h
// is the NextValidators at h+1 committed to in header h by
// NextValidatorsHash
tmTrustedVals, ok = sender.GetValsAtHeight(int64(trustedHeight.RevisionHeight + 1))
if !ok {
return errorsmod.Wrapf(ibctmtypes.ErrInvalidHeaderHeight, "could not retrieve trusted validators at trustedHeight: %d", trustedHeight)
}
}
trustedVals, err := tmTrustedVals.ToProto()
if err != nil {
return err
}
// inject trusted fields into last header
// for now assume revision number is 0
header.TrustedHeight = trustedHeight
header.TrustedValidators = trustedVals

return nil
}
Loading

0 comments on commit 4b72097

Please sign in to comment.