Skip to content

Commit

Permalink
test: add major upgrade test from v1 -> main (latest) (#2814)
Browse files Browse the repository at this point in the history
This PR adds a test which spins up a set of 4 nodes on a random v1
version. It then gets all nodes to upgrade at height 10 and asserts that
the upgrade is successful (that all nodes reach height 12).

This can be helpful to catching any problems with the upgrade (i.e.
missing migrations)
  • Loading branch information
cmwaters authored Nov 20, 2023
1 parent 9b5d025 commit 050790e
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 33 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
VERSION := $(shell echo $(shell git describe --tags 2>/dev/null || git log -1 --format='%h') | sed 's/^v//')
COMMIT := $(shell git log -1 --format='%H')
COMMIT := $(shell git rev-parse --short HEAD)
DOCKER := $(shell which docker)
ALL_VERSIONS := $(shell git tag -l)
DOCKER_BUF := $(DOCKER) run --rm -v $(CURDIR):/workspace --workdir /workspace bufbuild/buf
Expand Down Expand Up @@ -119,7 +119,7 @@ test-short:
## test-e2e: Run end to end tests via knuu. This command requires a kube/config file to configure kubernetes.
test-e2e:
@echo "--> Running end to end tests"
@KNUU_NAMESPACE=test KNUU_TIMEOUT=20m E2E_VERSIONS="$(ALL_VERSIONS)" E2E=true go test ./test/e2e/... -timeout 20m -v
@KNUU_NAMESPACE=test KNUU_TIMEOUT=20m E2E_LATEST_VERSION=$(shell git rev-parse --short main) E2E_VERSIONS="$(ALL_VERSIONS)" E2E=true go test ./test/e2e/... -timeout 20m -v
.PHONY: test-e2e

## test-race: Run tests in race mode.
Expand Down
8 changes: 7 additions & 1 deletion test/e2e/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func NewNode(
startHeight, selfDelegation int64,
peers []string,
signerKey, networkKey, accountKey crypto.PrivKey,
upgradeHeight int64,
) (*Node, error) {
instance, err := knuu.NewInstance(name)
if err != nil {
Expand Down Expand Up @@ -75,7 +76,12 @@ func NewNode(
if err != nil {
return nil, err
}
err = instance.SetArgs("start", fmt.Sprintf("--home=%s", remoteRootDir), "--rpc.laddr=tcp://0.0.0.0:26657")
args := []string{"start", fmt.Sprintf("--home=%s", remoteRootDir), "--rpc.laddr=tcp://0.0.0.0:26657"}
if upgradeHeight != 0 {
args = append(args, fmt.Sprintf("--v2-upgrade-height=%d", upgradeHeight))
}

err = instance.SetArgs(args...)
if err != nil {
return nil, err
}
Expand Down
6 changes: 4 additions & 2 deletions test/e2e/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ Celestia uses the [knuu](https://github.com/celestiaorg/knuu) framework to orche
E2E tests can be simply run through go tests. They are distinguished from unit tets through an environment variable. To run all e2e tests run:

```shell
E2E=true KNUU_NAMESPACE=test go test ./test/e2e/... -timeout 30m
E2E=true KNUU_NAMESPACE=test E2E_LATEST_VERSION="$(git rev-parse --short main)" E2E_VERSIONS="$(git tag -l)" go test ./test/e2e/... -timeout 30m -v
```

You can optionally set a global timeout using `KNUU_TIMEOUT` (default is 60m).

## Observation

Logs of each of the nodes are posted to Grafana and can be accessed through Celestia's dashboard (using the `celestia-app` namespace).
Logs of each of the nodes are posted to Grafana and can be accessed through Celestia's dashboard (using the `test` namespace).
1 change: 1 addition & 0 deletions test/e2e/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ func MakeConfig(node *Node) (*config.Config, error) {
cfg.P2P.PersistentPeers = strings.Join(node.InitialPeers, ",")
cfg.Consensus.TimeoutPropose = time.Second
cfg.Consensus.TimeoutCommit = time.Second
cfg.Instrumentation.Prometheus = true
return cfg, nil
}

Expand Down
20 changes: 14 additions & 6 deletions test/e2e/simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/celestiaorg/celestia-app/app"
"github.com/celestiaorg/celestia-app/app/encoding"
v1 "github.com/celestiaorg/celestia-app/pkg/appconsts/v1"
"github.com/celestiaorg/celestia-app/test/txsim"
"github.com/celestiaorg/celestia-app/test/util/testnode"
"github.com/stretchr/testify/require"
Expand All @@ -26,19 +27,25 @@ func TestE2ESimple(t *testing.T) {
t.Skip("skipping e2e test")
}

if os.Getenv("E2E_VERSIONS") != "" {
versionsStr := os.Getenv("E2E_VERSIONS")
versions := ParseVersions(versionsStr)
if len(versions) > 0 {
latestVersion = versions.GetLatest().String()
if os.Getenv("E2E_LATEST_VERSION") != "" {
latestVersion = os.Getenv("E2E_LATEST_VERSION")
_, isSemVer := ParseVersion(latestVersion)
switch {
case isSemVer:
case latestVersion == "latest":
case len(latestVersion) == 8:
// assume this is a git commit hash (we need to trim the last digit to match the docker image tag)
latestVersion = latestVersion[:7]
default:
t.Fatalf("unrecognised version: %s", latestVersion)
}
}
t.Log("Running simple e2e test", "version", latestVersion)

testnet, err := New(t.Name(), seed)
require.NoError(t, err)
t.Cleanup(testnet.Cleanup)
require.NoError(t, testnet.CreateGenesisNodes(4, latestVersion, 10000000))
require.NoError(t, testnet.CreateGenesisNodes(4, latestVersion, 10000000, 0))

kr, err := testnet.CreateAccount("alice", 1e12)
require.NoError(t, err)
Expand All @@ -61,6 +68,7 @@ func TestE2ESimple(t *testing.T) {

totalTxs := 0
for _, block := range blockchain {
require.Equal(t, v1.Version, block.Version.App)
totalTxs += len(block.Data.Txs)
}
require.Greater(t, totalTxs, 10)
Expand Down
14 changes: 7 additions & 7 deletions test/e2e/testnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,32 +34,32 @@ func New(name string, seed int64) (*Testnet, error) {
}, nil
}

func (t *Testnet) CreateGenesisNode(version string, selfDelegation int64) error {
func (t *Testnet) CreateGenesisNode(version string, selfDelegation, upgradeHeight int64) error {
signerKey := t.keygen.Generate(ed25519Type)
networkKey := t.keygen.Generate(ed25519Type)
accountKey := t.keygen.Generate(secp256k1Type)
node, err := NewNode(fmt.Sprintf("val%d", len(t.nodes)), version, 0, selfDelegation, nil, signerKey, networkKey, accountKey)
node, err := NewNode(fmt.Sprintf("val%d", len(t.nodes)), version, 0, selfDelegation, nil, signerKey, networkKey, accountKey, upgradeHeight)
if err != nil {
return err
}
t.nodes = append(t.nodes, node)
return nil
}

func (t *Testnet) CreateGenesisNodes(num int, version string, selfDelegation int64) error {
for i := -0; i < num; i++ {
if err := t.CreateGenesisNode(version, selfDelegation); err != nil {
func (t *Testnet) CreateGenesisNodes(num int, version string, selfDelegation, upgradeHeight int64) error {
for i := 0; i < num; i++ {
if err := t.CreateGenesisNode(version, selfDelegation, upgradeHeight); err != nil {
return err
}
}
return nil
}

func (t *Testnet) CreateNode(version string, startHeight int64) error {
func (t *Testnet) CreateNode(version string, startHeight, upgradeHeight int64) error {
signerKey := t.keygen.Generate(ed25519Type)
networkKey := t.keygen.Generate(ed25519Type)
accountKey := t.keygen.Generate(secp256k1Type)
node, err := NewNode(fmt.Sprintf("val%d", len(t.nodes)), version, startHeight, 0, nil, signerKey, networkKey, accountKey)
node, err := NewNode(fmt.Sprintf("val%d", len(t.nodes)), version, startHeight, 0, nil, signerKey, networkKey, accountKey, upgradeHeight)
if err != nil {
return err
}
Expand Down
85 changes: 84 additions & 1 deletion test/e2e/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/celestiaorg/celestia-app/app"
"github.com/celestiaorg/celestia-app/app/encoding"
v2 "github.com/celestiaorg/celestia-app/pkg/appconsts/v2"
"github.com/celestiaorg/celestia-app/test/txsim"
"github.com/celestiaorg/knuu/pkg/knuu"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -55,7 +56,7 @@ func TestMinorVersionCompatibility(t *testing.T) {
// each node begins with a random version within the same major version set
v := versions.Random(r).String()
t.Log("Starting node", "node", i, "version", v)
require.NoError(t, testnet.CreateGenesisNode(v, 10000000))
require.NoError(t, testnet.CreateGenesisNode(v, 10000000, 0))
}

kr, err := testnet.CreateAccount("alice", 1e12)
Expand All @@ -64,6 +65,7 @@ func TestMinorVersionCompatibility(t *testing.T) {
require.NoError(t, testnet.Setup())
require.NoError(t, testnet.Start())

// TODO: with upgrade tests we should simulate a far broader range of transactions
sequences := txsim.NewBlobSequence(txsim.NewRange(200, 4000), txsim.NewRange(1, 3)).Clone(5)
sequences = append(sequences, txsim.NewSendSequence(4, 1000, 100).Clone(5)...)

Expand Down Expand Up @@ -121,6 +123,87 @@ func TestMinorVersionCompatibility(t *testing.T) {
require.True(t, errors.Is(err, context.Canceled), err.Error())
}

func TestMajorUpgradeToV2(t *testing.T) {
if os.Getenv("E2E") == "" {
t.Skip("skipping e2e test")
}

if os.Getenv("E2E_LATEST_VERSION") != "" {
latestVersion = os.Getenv("E2E_LATEST_VERSION")
_, isSemVer := ParseVersion(latestVersion)
switch {
case isSemVer:
case latestVersion == "latest":
case len(latestVersion) == 8:
// assume this is a git commit hash (we need to trim the last digit to match the docker image tag)
latestVersion = latestVersion[:7]
default:
t.Fatalf("unrecognised version: %s", latestVersion)
}
}

numNodes := 4
upgradeHeight := int64(10)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

testnet, err := New(t.Name(), seed)
require.NoError(t, err)
t.Cleanup(testnet.Cleanup)

preloader, err := knuu.NewPreloader()
require.NoError(t, err)
t.Cleanup(func() { _ = preloader.EmptyImages() })
err = preloader.AddImage(DockerImageName(latestVersion))
require.NoError(t, err)

for i := 0; i < numNodes; i++ {
t.Log("Starting node", "node", i, "version", latestVersion)
require.NoError(t, testnet.CreateGenesisNode(latestVersion, 10000000, upgradeHeight))
}

kr, err := testnet.CreateAccount("alice", 1e12)
require.NoError(t, err)

require.NoError(t, testnet.Setup())
require.NoError(t, testnet.Start())

// assert that the network is initially running on v1
for i := 0; i < numNodes; i++ {
client, err := testnet.Node(i).Client()
require.NoError(t, err)
resp, err := client.Header(ctx, nil)
require.NoError(t, err)
// FIXME: we are not correctly setting the app version at genesis
require.Equal(t, uint64(0), resp.Header.Version.App, "version mismatch before upgrade")
}

errCh := make(chan error)
encCfg := encoding.MakeConfig(app.ModuleEncodingRegisters...)
opts := txsim.DefaultOptions().WithSeed(seed).SuppressLogs()
sequences := txsim.NewBlobSequence(txsim.NewRange(200, 4000), txsim.NewRange(1, 3)).Clone(5)
sequences = append(sequences, txsim.NewSendSequence(4, 1000, 100).Clone(5)...)
go func() {
errCh <- txsim.Run(ctx, testnet.GRPCEndpoints()[0], kr, encCfg, opts, sequences...)
}()

// wait for all nodes to move past the upgrade height
for i := 0; i < numNodes; i++ {
client, err := testnet.Node(i).Client()
require.NoError(t, err)
require.NoError(t, waitForHeight(ctx, client, upgradeHeight+2, time.Minute))
resp, err := client.Header(ctx, nil)
require.NoError(t, err)
require.Equal(t, v2.Version, resp.Header.Version.App, "version mismatch after upgrade")
}

// end txsim
cancel()

err = <-errCh
require.True(t, errors.Is(err, context.Canceled), err.Error())
}

func getHeight(ctx context.Context, client *http.HTTP, period time.Duration) (int64, error) {
timer := time.NewTimer(period)
ticker := time.NewTicker(100 * time.Millisecond)
Expand Down
41 changes: 27 additions & 14 deletions test/e2e/versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,29 +40,42 @@ func (v Version) IsGreater(v2 Version) bool {

type VersionSet []Version

// ParseVersions takes a string of space-separated versions and returns a
// VersionSet. Invalid versions are ignored.
func ParseVersions(versionStr string) VersionSet {
versions := strings.Split(versionStr, " ")
output := make(VersionSet, 0, len(versions))
for _, v := range versions {
var major, minor, patch, rc uint64
isRC := false
if strings.Contains(v, "rc") {
_, err := fmt.Sscanf(v, "v%d.%d.%d-rc%d", &major, &minor, &patch, &rc)
isRC = true
if err != nil {
continue
}
} else {
_, err := fmt.Sscanf(v, "v%d.%d.%d", &major, &minor, &patch)
if err != nil {
continue
}
version, isValid := ParseVersion(v)
if !isValid {
continue
}
output = append(output, Version{major, minor, patch, isRC, rc})
output = append(output, version)
}
return output
}

// ParseVersion takes a string and returns a Version. If the string is not a
// valid version, the second return value is false.
// Must be of the format v1.0.0 or v1.0.0-rc1 (i.e. following SemVer)
func ParseVersion(version string) (Version, bool) {
var major, minor, patch, rc uint64
isRC := false
if strings.Contains(version, "rc") {
_, err := fmt.Sscanf(version, "v%d.%d.%d-rc%d", &major, &minor, &patch, &rc)
isRC = true
if err != nil {
return Version{}, false
}
} else {
_, err := fmt.Sscanf(version, "v%d.%d.%d", &major, &minor, &patch)
if err != nil {
return Version{}, false
}
}
return Version{major, minor, patch, isRC, rc}, true
}

func (v VersionSet) FilterMajor(majorVersion uint64) VersionSet {
output := make(VersionSet, 0, len(v))
for _, version := range v {
Expand Down

0 comments on commit 050790e

Please sign in to comment.