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

e2e with IBC tx and test #1216

Merged
merged 13 commits into from
Apr 8, 2022
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,11 @@ benchmark:
docker-build-debug:
@docker build -t osmolabs/osmosisd-e2e --build-arg IMG_TAG=debug -f e2e.Dockerfile .

# TODO: Push this to the Cosmos Dockerhub so we don't have to keep building it
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we are already not building this image in ci. Instead, we grab it from here: https://github.com/orgs/cosmos/packages/container/package/hermes-e2e

I think we should push the image to our own docker hub or github packages and use that instead

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nikever can we link on getting this on our docker hub as well?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also like to learn about how to get access to our docker hub. Please let me know if it's possible to get permissions for that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good idea. It's very easy to push to our org. All you need is a github PAT (personal access token) and then you login with that and just push it. Note, just make sure the package set to public.

# in CI.
docker-build-hermes:
@cd tests/e2e/docker; docker build -t cosmos/hermes-e2e:latest -f hermes.Dockerfile .
czarcas7ic marked this conversation as resolved.
Show resolved Hide resolved

###############################################################################
### Linting ###
###############################################################################
Expand Down
4 changes: 2 additions & 2 deletions tests/e2e/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ type chain struct {
validators []*validator
}

func newChain(name string) (*chain, error) {
func newChain(id string) (*chain, error) {
tmpDir, err := ioutil.TempDir("", "osmosis-e2e-testnet-")
if err != nil {
return nil, err
}

return &chain{
id: name,
id: id,
dataDir: tmpDir,
}, nil
}
Expand Down
12 changes: 12 additions & 0 deletions tests/e2e/docker/hermes.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM informalsystems/hermes:0.12.0 AS hermes-builder
czarcas7ic marked this conversation as resolved.
Show resolved Hide resolved

FROM debian:buster-slim
USER root

COPY --chown=0:0 --from=hermes-builder /usr/lib/x86_64-linux-gnu/libssl.so.1.1 /usr/lib/x86_64-linux-gnu/libssl.so.1.1
COPY --chown=0:0 --from=hermes-builder /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1
COPY --from=hermes-builder /usr/bin/hermes /usr/local/bin/
RUN chmod +x /usr/local/bin/hermes

EXPOSE 3031
ENTRYPOINT ["hermes", "start"]
czarcas7ic marked this conversation as resolved.
Show resolved Hide resolved
123 changes: 110 additions & 13 deletions tests/e2e/e2e_setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"path/filepath"
"strconv"
"strings"
Expand All @@ -31,12 +35,12 @@ const (
stakeDenom = "stake"
minGasPrice = "0.00001"
// chainA
chainAName = "osmo-test-a"
chainAID = "osmo-test-a"
osmoBalanceA = 200000000000
stakeBalanceA = 110000000000
stakeAmountA = 100000000000
// chainB
chainBName = "osmo-test-b"
chainBID = "osmo-test-b"
osmoBalanceB = 500000000000
stakeBalanceB = 440000000000
stakeAmountB = 400000000000
Expand All @@ -54,12 +58,13 @@ var (
type IntegrationTestSuite struct {
suite.Suite

tmpDirs []string
chainA *chain
chainB *chain
dkrPool *dockertest.Pool
dkrNet *dockertest.Network
valResources map[string][]*dockertest.Resource
tmpDirs []string
chainA *chain
chainB *chain
dkrPool *dockertest.Pool
dkrNet *dockertest.Network
hermesResource *dockertest.Resource
valResources map[string][]*dockertest.Resource
}

func TestIntegrationTestSuite(t *testing.T) {
Expand All @@ -70,10 +75,10 @@ func (s *IntegrationTestSuite) SetupSuite() {
s.T().Log("setting up e2e integration test suite...")

var err error
s.chainA, err = newChain(chainAName)
s.chainA, err = newChain(chainAID)
s.Require().NoError(err)

s.chainB, err = newChain(chainBName)
s.chainB, err = newChain(chainBID)
s.Require().NoError(err)

s.dkrPool, err = dockertest.NewPool("")
Expand Down Expand Up @@ -101,6 +106,8 @@ func (s *IntegrationTestSuite) SetupSuite() {
s.initGenesis(s.chainB)
s.initValidatorConfigs(s.chainB)
s.runValidators(s.chainB, 10)

s.runIBCRelayer()
}

func (s *IntegrationTestSuite) TearDownSuite() {
Expand All @@ -115,6 +122,8 @@ func (s *IntegrationTestSuite) TearDownSuite() {

s.T().Log("tearing down e2e integration test suite...")

s.Require().NoError(s.dkrPool.Purge(s.hermesResource))

for _, vr := range s.valResources {
for _, r := range vr {
s.Require().NoError(s.dkrPool.Purge(r))
Expand All @@ -137,11 +146,11 @@ func (s *IntegrationTestSuite) initNodes(c *chain) {
// initialize a genesis file for the first validator
val0ConfigDir := c.validators[0].configDir()
for _, val := range c.validators {
if c.id == chainAName {
if c.id == chainAID {
s.Require().NoError(
addGenesisAccount(val0ConfigDir, "", initBalanceStrA, val.keyInfo.GetAddress()),
)
} else if c.id == chainBName {
} else if c.id == chainBID {
s.Require().NoError(
addGenesisAccount(val0ConfigDir, "", initBalanceStrB, val.keyInfo.GetAddress()),
)
Expand Down Expand Up @@ -197,7 +206,7 @@ func (s *IntegrationTestSuite) initGenesis(c *chain) {
genTxs := make([]json.RawMessage, len(c.validators))
for i, val := range c.validators {
stakeAmountCoin := stakeAmountCoinA
if c.id != chainAName {
if c.id != chainAID {
stakeAmountCoin = stakeAmountCoinB
}
createValmsg, err := val.buildCreateValidatorMsg(stakeAmountCoin)
Expand Down Expand Up @@ -340,6 +349,94 @@ func (s *IntegrationTestSuite) runValidators(c *chain, portOffset int) {
)
}

func (s *IntegrationTestSuite) runIBCRelayer() {
s.T().Log("starting Hermes relayer container...")

tmpDir, err := ioutil.TempDir("", "gaia-e2e-testnet-hermes-")
s.Require().NoError(err)
s.tmpDirs = append(s.tmpDirs, tmpDir)

gaiaAVal := s.chainA.validators[0]
gaiaBVal := s.chainB.validators[0]
hermesCfgPath := path.Join(tmpDir, "hermes")

s.Require().NoError(os.MkdirAll(hermesCfgPath, 0755))
_, err = copyFile(
filepath.Join("./scripts/", "hermes_bootstrap.sh"),
filepath.Join(hermesCfgPath, "hermes_bootstrap.sh"),
)
s.Require().NoError(err)

s.hermesResource, err = s.dkrPool.RunWithOptions(
&dockertest.RunOptions{
Name: fmt.Sprintf("%s-%s-relayer", s.chainA.id, s.chainB.id),
Repository: "ghcr.io/cosmos/hermes-e2e",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we are depending on an image that is not under our control. Let's push the image to our own repository and switch to using that

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nikever do you know how to add this as an image to our repo? I'm not positive of the process

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid blocking this PR, let's create an issue to address this separately?

It should be fine to use the cosmos image temporarily, The main concern about depending on external images, especially with the latest tag, is that whenever they make a change, our tests might get broken

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The credentials for the osmobot Docker account are saved as GitHub Secrets. You can use them to build the Image via CI as in https://github.com/osmosis-labs/osmosis/blob/main/.github/workflows/docker.yml#L24-L29

I will also create personal accounts for both of you on our Docker account.

Lets use a new repository osmolabs/osmosis-e2e for all these new images

Tag: "latest",
NetworkID: s.dkrNet.Network.ID,
Mounts: []string{
fmt.Sprintf("%s/:/root/hermes", hermesCfgPath),
},
PortBindings: map[docker.Port][]docker.PortBinding{
"3031/tcp": {{HostIP: "", HostPort: "3031"}},
},
Env: []string{
fmt.Sprintf("OSMO_A_E2E_CHAIN_ID=%s", s.chainA.id),
fmt.Sprintf("OSMO_B_E2E_CHAIN_ID=%s", s.chainB.id),
fmt.Sprintf("OSMO_A_E2E_VAL_MNEMONIC=%s", gaiaAVal.mnemonic),
fmt.Sprintf("OSMO_B_E2E_VAL_MNEMONIC=%s", gaiaBVal.mnemonic),
fmt.Sprintf("OSMO_A_E2E_VAL_HOST=%s", s.valResources[s.chainA.id][0].Container.Name[1:]),
fmt.Sprintf("OSMO_B_E2E_VAL_HOST=%s", s.valResources[s.chainB.id][0].Container.Name[1:]),
},
Entrypoint: []string{
"sh",
"-c",
"chmod +x /root/hermes/hermes_bootstrap.sh && /root/hermes/hermes_bootstrap.sh",
},
},
noRestart,
)
s.Require().NoError(err)

endpoint := fmt.Sprintf("http://%s/state", s.hermesResource.GetHostPort("3031/tcp"))
s.Require().Eventually(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add something similar to the setup of validators?

As we discussed offline - simply keep querying balances endpoint until we get a 200 response. I think this exact s.Require().Eventually(...) can be copied where we simply replace the endpoint.

The goal is to make sure that HTTP server is available before we start running actual tests.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validators? You mean the node containers themselves? I believe we already have a health check for that. Are you suggesting that we implement a test that queries for validators?

func() bool {
resp, err := http.Get(endpoint)
if err != nil {
return false
}

defer resp.Body.Close()

bz, err := io.ReadAll(resp.Body)
if err != nil {
return false
}

var respBody map[string]interface{}
if err := json.Unmarshal(bz, &respBody); err != nil {
return false
}

status := respBody["status"].(string)
result := respBody["result"].(map[string]interface{})

return status == "success" && len(result["chains"].([]interface{})) == 2
},
5*time.Minute,
time.Second,
"hermes relayer not healthy",
)

s.T().Logf("started Hermes relayer container: %s", s.hermesResource.Container.ID)

// XXX: Give time to both networks to start, otherwise we might see gRPC
// transport errors.
time.Sleep(10 * time.Second)

// create the client, connection and channel between the two Gaia chains
s.connectIBCChains()
}

func noRestart(config *docker.HostConfig) {
// in this case we don't want the nodes to restart on failure
config.RestartPolicy = docker.RestartPolicy{
Expand Down
123 changes: 81 additions & 42 deletions tests/e2e/e2e_test.go
Original file line number Diff line number Diff line change
@@ -1,70 +1,71 @@
package e2e

import (
"errors"
"fmt"
"io"
"net/http"
"strings"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
)

func (s *IntegrationTestSuite) TestQueryBalances() {
var (
expectedDenoms = []string{osmoDenom, stakeDenom}
expectedBalancesA = []uint64{osmoBalanceA, stakeBalanceA - stakeAmountA}
expectedBalancesB = []uint64{osmoBalanceB, stakeBalanceB - stakeAmountB}
)

chainAAPIEndpoint := fmt.Sprintf("http://%s", s.valResources[s.chainA.id][0].GetHostPort("1317/tcp"))
balancesA, err := queryBalances(chainAAPIEndpoint, s.chainA.validators[0].keyInfo.GetAddress().String())
s.Require().NoError(err)
s.Require().NotNil(balancesA)
s.Require().Equal(2, len(balancesA))

chainBAPIEndpoint := fmt.Sprintf("http://%s", s.valResources[s.chainB.id][0].GetHostPort("1317/tcp"))
balancesB, err := queryBalances(chainBAPIEndpoint, s.chainB.validators[0].keyInfo.GetAddress().String())
s.Require().NoError(err)
s.Require().NotNil(balancesB)
s.Require().Equal(2, len(balancesB))

actualDenomsA := make([]string, 0, 2)
actualBalancesA := make([]uint64, 0, 2)
actualDenomsB := make([]string, 0, 2)
actualBalancesB := make([]uint64, 0, 2)

for _, balanceA := range balancesA {
actualDenomsA = append(actualDenomsA, balanceA.GetDenom())
actualBalancesA = append(actualBalancesA, balanceA.Amount.Uint64())
}

for _, balanceB := range balancesB {
actualDenomsB = append(actualDenomsB, balanceB.GetDenom())
actualBalancesB = append(actualBalancesB, balanceB.Amount.Uint64())
}

s.Require().ElementsMatch(expectedDenoms, actualDenomsA)
s.Require().ElementsMatch(expectedBalancesA, actualBalancesA)
s.Require().ElementsMatch(expectedDenoms, actualDenomsB)
s.Require().ElementsMatch(expectedBalancesB, actualBalancesB)
}
// func (s *IntegrationTestSuite) TestQueryBalances() {
czarcas7ic marked this conversation as resolved.
Show resolved Hide resolved
// var (
// expectedDenoms = []string{osmoDenom, stakeDenom}
// expectedBalancesA = []uint64{osmoBalanceA, stakeBalanceA - stakeAmountA}
// expectedBalancesB = []uint64{osmoBalanceB, stakeBalanceB - stakeAmountB}
// )

// chainAAPIEndpoint := fmt.Sprintf("http://%s", s.valResources[s.chainA.id][0].GetHostPort("1317/tcp"))
// balancesA, err := queryBalances(chainAAPIEndpoint, s.chainA.validators[0].keyInfo.GetAddress().String())
// s.Require().NoError(err)
// s.Require().NotNil(balancesA)
// // s.Require().Equal(2, len(balancesA))

// chainBAPIEndpoint := fmt.Sprintf("http://%s", s.valResources[s.chainB.id][0].GetHostPort("1317/tcp"))
// balancesB, err := queryBalances(chainBAPIEndpoint, s.chainB.validators[0].keyInfo.GetAddress().String())
// s.Require().NoError(err)
// s.Require().NotNil(balancesB)
// // s.Require().Equal(2, len(balancesB))

// actualDenomsA := make([]string, 0, 2)
// actualBalancesA := make([]uint64, 0, 2)
// actualDenomsB := make([]string, 0, 2)
// actualBalancesB := make([]uint64, 0, 2)

// for _, balanceA := range balancesA {
// actualDenomsA = append(actualDenomsA, balanceA.GetDenom())
// actualBalancesA = append(actualBalancesA, balanceA.Amount.Uint64())
// }

// for _, balanceB := range balancesB {
// actualDenomsB = append(actualDenomsB, balanceB.GetDenom())
// actualBalancesB = append(actualBalancesB, balanceB.Amount.Uint64())
// }

// s.Require().ElementsMatch(expectedDenoms, actualDenomsA)
// s.Require().ElementsMatch(expectedBalancesA, actualBalancesA)
// s.Require().ElementsMatch(expectedDenoms, actualDenomsB)
// s.Require().ElementsMatch(expectedBalancesB, actualBalancesB)
// }

func queryBalances(endpoint, addr string) (sdk.Coins, error) {
path := fmt.Sprintf(
"%s/cosmos/bank/v1beta1/balances/%s",
endpoint, addr,
)
resp, err := http.Get(path)
var err error
var resp *http.Response
retriesLeft := 5
for {
resp, err = http.Get(path)

if resp.StatusCode == http.StatusServiceUnavailable {
retriesLeft--
if retriesLeft == 0 {
return nil, errors.New(fmt.Sprintf("exceeded retry limit of %d with %d", retriesLeft, http.StatusServiceUnavailable))
return nil, fmt.Errorf("exceeded retry limit of %d with %d", retriesLeft, http.StatusServiceUnavailable)
}
time.Sleep(10 * time.Second)
} else {
Expand All @@ -90,3 +91,41 @@ func queryBalances(endpoint, addr string) (sdk.Coins, error) {

return balancesResp.GetBalances(), nil
}

func (s *IntegrationTestSuite) TestIBCTokenTransfer() {
var ibcStakeDenom string

s.Run("send_uosmo_to_chainB", func() {
recipient := s.chainB.validators[0].keyInfo.GetAddress().String()
token := sdk.NewInt64Coin(osmoDenom, 3300000000) // 3,300uosmo
s.sendIBC(s.chainA.id, s.chainB.id, recipient, token)

chainBAPIEndpoint := fmt.Sprintf("http://%s", s.valResources[s.chainB.id][0].GetHostPort("1317/tcp"))

// require the recipient account receives the IBC tokens (IBC packets ACKd)
var (
balances sdk.Coins
err error
)
s.Require().Eventually(
func() bool {
balances, err = queryBalances(chainBAPIEndpoint, recipient)
s.Require().NoError(err)

return balances.Len() == 3
},
time.Minute,
5*time.Second,
)

for _, c := range balances {
if strings.Contains(c.Denom, "ibc/") {
ibcStakeDenom = c.Denom
s.Require().Equal(token.Amount.Int64(), c.Amount.Int64())
break
}
}

s.Require().NotEmpty(ibcStakeDenom)
})
}
Loading