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

simibc: improves package documentation for simibc #662

Merged
merged 25 commits into from
Feb 7, 2023
Merged
Show file tree
Hide file tree
Changes from 19 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
10 changes: 5 additions & 5 deletions tests/difference/core/driver/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ func (s *CoreSuite) consumerSlash(val sdk.ConsAddress, h int64, isDowntime bool)
if e.Type == channeltypes.EventTypeSendPacket {
packet, err := ibctestingcore.ReconstructPacketFromEvent(e)
s.Require().NoError(err)
s.simibc.Link.AddPacket(s.chainID(C), packet)
s.simibc.Outboxes.AddPacket(s.chainID(C), packet)
}
}
}
Expand Down Expand Up @@ -422,10 +422,10 @@ func (s *CoreSuite) TestAssumptions() {
s.Require().Equal(s.offsetHeight, s.height(C)-1)

// Network is empty
s.Require().Empty(s.simibc.Link.OutboxPackets[P])
s.Require().Empty(s.simibc.Link.OutboxPackets[C])
s.Require().Empty(s.simibc.Link.OutboxAcks[P])
s.Require().Empty(s.simibc.Link.OutboxAcks[C])
s.Require().Empty(s.simibc.Outboxes.OutboxPackets[P])
s.Require().Empty(s.simibc.Outboxes.OutboxPackets[C])
s.Require().Empty(s.simibc.Outboxes.OutboxAcks[P])
s.Require().Empty(s.simibc.Outboxes.OutboxAcks[C])
}

// Test a set of traces
Expand Down
4 changes: 2 additions & 2 deletions tests/difference/core/driver/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import (

type Builder struct {
suite *suite.Suite
link simibc.OrderedLink
link simibc.OrderedOutbox
path *ibctesting.Path
coordinator *ibctesting.Coordinator
clientHeaders map[string][]*ibctmtypes.Header
Expand Down Expand Up @@ -485,7 +485,7 @@ func (b *Builder) createConsumerGenesis(tmConfig *ibctesting.TendermintConfig) *
}

func (b *Builder) createLink() {
b.link = simibc.MakeOrderedLink()
b.link = simibc.MakeOrderedOutbox()
// init utility data structures
b.mustBeginBlock = map[string]bool{P: true, C: true}
b.clientHeaders = map[string][]*ibctmtypes.Header{}
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.
13 changes: 11 additions & 2 deletions testutil/simibc/chain_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import (
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)

// BeginBlock updates the current header and calls the app.BeginBlock() method.
// BeginBlock updates the current header and calls the app.BeginBlock method.
// The new block height is the previous block height + 1.
// The new block time is the previous block time + dt.
//
// NOTE: this method may be used independently of the rest of simibc.
func BeginBlock(c *ibctesting.TestChain, dt time.Duration) {

c.CurrentHeader = tmproto.Header{
Expand All @@ -28,7 +30,14 @@ func BeginBlock(c *ibctesting.TestChain, dt time.Duration) {
_ = c.App.BeginBlock(abci.RequestBeginBlock{Header: c.CurrentHeader})
}

// EndBlock and calls the preCommitCallback before the app.Commit() is called.
// EndBlock calls app.EndBlock and executes preCommitCallback BEFORE calling app.Commit
// The callback is useful for testing purposes to execute arbitrary code before the
// chain sdk context is cleared in .Commit().
// For example, app.EndBlock may lead to a new state, which you would like to query
// to check that it is correct. However, the sdk context is cleared after .Commit(),
// so you can query the state inside the callback.
//
// NOTE: this method may be used independently of the rest of simibc.
func EndBlock(c *ibctesting.TestChain, preCommitCallback func()) (*ibctmtypes.Header, []channeltypes.Packet) {
ebRes := c.App.EndBlock(abci.RequestEndBlock{Height: c.CurrentHeader.Height})

Expand Down
4 changes: 4 additions & 0 deletions testutil/simibc/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/*
simibc is a collection of utilities wrapping the ibc-go testing framework which make is easier to write test scenarios involving precise orders of packet and ack delivery and calls to BeginBlock and EndBlock.
*/
package simibc
93 changes: 0 additions & 93 deletions testutil/simibc/ordered_link.go

This file was deleted.

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/v3/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
}
}
Loading