Skip to content

Commit 2cc7e0b

Browse files
chattontac0turtle
andauthored
test: add basic docker E2E which writes headers to DA network (#2378)
<!-- Please read and fill out this form before submitting your PR. Please make sure you have reviewed our contributors guide before submitting your first PR. NOTE: PR titles should follow semantic commits: https://www.conventionalcommits.org/en/v1.0.0/ --> ## Overview This PR adds a basic test which - spins up a celestia-app chain - spins up a bridge node - spins up a rollkit test app and verifies liveness. <!-- Please provide an explanation of the PR, including the appropriate context, background, goal, and rationale. If there is an issue with this information, please provide a tl;dr and link the issue. Ex: Closes #<issue number> --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced comprehensive Docker-based end-to-end tests for blockchain components, enabling automated integration testing in multiple environments. - Added a dedicated test suite for validating Celestia and Rollkit node interactions within Docker containers. - **Chores** - Updated workflows to build and push multi-platform Docker images (linux/amd64 and linux/arm64). - Implemented a new workflow job to run Docker end-to-end tests automatically. - Updated .gitignore to exclude the vendor directory from version control. - **Tests** - Added new Makefile target for running Docker end-to-end tests with extended timeout. - Added new Go module and dependencies to support Docker-based testing. - Enhanced test scripts to allow exclusion of specific directories from test runs. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Marko <marko@baricevic.me>
1 parent 8b970af commit 2cc7e0b

8 files changed

Lines changed: 1685 additions & 2 deletions

File tree

.github/workflows/test.yml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,23 @@ jobs:
3333
with:
3434
context: .
3535
push: true
36+
platforms: linux/amd64,linux/arm64
3637
tags: ghcr.io/${{ github.repository_owner }}/rollkit:${{ inputs.image-tag }}
3738

38-
upgrade-tests:
39+
docker-tests:
40+
name: Docker E2E Tests
3941
needs: build-docker-image
4042
runs-on: ubuntu-latest
4143
steps:
42-
- run: exit 0 # TODO: add upgrade test uses the image built in the build-docker-image step
44+
- uses: actions/checkout@v4
45+
- name: set up go
46+
uses: actions/setup-go@v5
47+
with:
48+
go-version-file: ./test/docker-e2e/go.mod
49+
- name: Run Docker E2E Tests
50+
run: make test-docker-e2e
51+
env:
52+
ROLLKIT_IMAGE_TAG: ${{ inputs.image-tag }}
4353

4454
build_all-apps:
4555
name: Build All Rollkit Binaries

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ proto/tendermint
44
types/pb/tendermint
55
.vscode/launch.json
66
.vscode/settings.json
7+
vendor
78
*/**.html
89
*.idea
910
*.env

scripts/test.mk

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,8 @@ test-cover:
3838
test-evm:
3939
@echo "--> Running EVM tests"
4040
@cd execution/evm && go test -mod=readonly -failfast -timeout=15m ./... -tags=evm
41+
42+
## test-docker-e2e: Running Docker E2E tests
43+
test-docker-e2e:
44+
@echo "--> Running Docker E2E tests"
45+
@cd test/docker-e2e && go test -mod=readonly -failfast -timeout=30m ./...

scripts/test_cover.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@ import (
1010
"os"
1111
"os/exec"
1212
"path/filepath"
13+
"slices"
1314
"strings"
1415
)
1516

1617
func main() {
1718
rootDir := "."
19+
20+
excludeDirs := []string{filepath.ToSlash("test/docker-e2e")}
21+
1822
var coverFiles []string
1923
var testFailures bool
2024

@@ -37,6 +41,11 @@ func main() {
3741
fullCoverProfilePath := filepath.Join(modDir, "cover.out")
3842
relativeCoverProfileArg := "cover.out"
3943

44+
if slices.Contains(excludeDirs, modDir) {
45+
fmt.Printf("--> Skipping tests in: %s\n as they are marked as excluded", modDir)
46+
return nil
47+
}
48+
4049
fmt.Printf("--> Running tests with coverage in: %s (profile: %s)\n", modDir, relativeCoverProfileArg)
4150
cmd := exec.Command("go", "test", "./...", "-race", "-coverprofile="+relativeCoverProfileArg, "-covermode=atomic")
4251
cmd.Dir = modDir

test/docker-e2e/base_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package docker_e2e
2+
3+
import (
4+
"context"
5+
"github.com/celestiaorg/tastora/framework/types"
6+
"testing"
7+
)
8+
9+
func (s *DockerTestSuite) TestBasicDockerE2E() {
10+
ctx := context.Background()
11+
s.SetupDockerResources()
12+
13+
var (
14+
bridgeNode types.DANode
15+
)
16+
17+
s.T().Run("start celestia chain", func(t *testing.T) {
18+
err := s.celestia.Start(ctx)
19+
s.Require().NoError(err)
20+
})
21+
22+
s.T().Run("start bridge node", func(t *testing.T) {
23+
genesisHash := s.getGenesisHash(ctx)
24+
25+
celestiaNodeHostname, err := s.celestia.GetNodes()[0].GetInternalHostName(ctx)
26+
s.Require().NoError(err)
27+
28+
bridgeNode = s.daNetwork.GetBridgeNodes()[0]
29+
30+
s.StartBridgeNode(ctx, bridgeNode, testChainID, genesisHash, celestiaNodeHostname)
31+
})
32+
33+
s.T().Run("fund da wallet", func(t *testing.T) {
34+
daWallet, err := bridgeNode.GetWallet()
35+
s.Require().NoError(err)
36+
s.T().Logf("da node celestia address: %s", daWallet.GetFormattedAddress())
37+
38+
s.FundWallet(ctx, daWallet, 100_000_000_00)
39+
})
40+
41+
s.T().Run("start rollkit chain node", func(t *testing.T) {
42+
s.StartRollkitNode(ctx, bridgeNode, s.rollkitChain.GetNodes()[0])
43+
})
44+
}

test/docker-e2e/docker_test.go

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
package docker_e2e
2+
3+
import (
4+
"context"
5+
"cosmossdk.io/math"
6+
"encoding/hex"
7+
"fmt"
8+
"github.com/celestiaorg/go-square/v2/share"
9+
tastoradocker "github.com/celestiaorg/tastora/framework/docker"
10+
"github.com/celestiaorg/tastora/framework/testutil/sdkacc"
11+
"github.com/celestiaorg/tastora/framework/testutil/toml"
12+
tastoratypes "github.com/celestiaorg/tastora/framework/types"
13+
sdk "github.com/cosmos/cosmos-sdk/types"
14+
"github.com/cosmos/cosmos-sdk/types/module/testutil"
15+
"github.com/cosmos/cosmos-sdk/x/auth"
16+
"github.com/cosmos/cosmos-sdk/x/bank"
17+
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
18+
"github.com/stretchr/testify/suite"
19+
"go.uber.org/zap/zaptest"
20+
"os"
21+
"strings"
22+
"testing"
23+
)
24+
25+
const (
26+
// testChainID is the chain ID used for testing.
27+
// it must be the string "test" as it is handled explicitly in app/node.
28+
testChainID = "test"
29+
)
30+
31+
func init() {
32+
sdkConf := sdk.GetConfig()
33+
sdkConf.SetBech32PrefixForAccount("celestia", "celestiapub")
34+
sdkConf.Seal()
35+
}
36+
37+
func TestDockerSuite(t *testing.T) {
38+
if testing.Short() {
39+
t.Skip("skipping due to short mode")
40+
}
41+
suite.Run(t, new(DockerTestSuite))
42+
}
43+
44+
type DockerTestSuite struct {
45+
suite.Suite
46+
provider tastoratypes.Provider
47+
celestia tastoratypes.Chain
48+
daNetwork tastoratypes.DataAvailabilityNetwork
49+
rollkitChain tastoratypes.RollkitChain
50+
}
51+
52+
// ConfigOption is a function type for modifying tastoradocker.Config
53+
type ConfigOption func(*tastoradocker.Config)
54+
55+
// CreateDockerProvider creates a new tastoratypes.Provider with optional configuration modifications
56+
func (s *DockerTestSuite) CreateDockerProvider(opts ...ConfigOption) tastoratypes.Provider {
57+
t := s.T()
58+
encConfig := testutil.MakeTestEncodingConfig(auth.AppModuleBasic{}, bank.AppModuleBasic{})
59+
numValidators := 1
60+
numFullNodes := 0
61+
client, network := tastoradocker.DockerSetup(t)
62+
63+
cfg := tastoradocker.Config{
64+
Logger: zaptest.NewLogger(t),
65+
DockerClient: client,
66+
DockerNetworkID: network,
67+
ChainConfig: &tastoradocker.ChainConfig{
68+
ConfigFileOverrides: map[string]any{
69+
"config/app.toml": appOverrides(),
70+
"config/config.toml": configOverrides(),
71+
},
72+
Type: "celestia",
73+
Name: "celestia",
74+
Version: "v4.0.0-rc6",
75+
NumValidators: &numValidators,
76+
NumFullNodes: &numFullNodes,
77+
ChainID: testChainID,
78+
Images: []tastoradocker.DockerImage{
79+
{
80+
Repository: "ghcr.io/celestiaorg/celestia-app",
81+
Version: "v4.0.0-rc6",
82+
UIDGID: "10001:10001",
83+
},
84+
},
85+
Bin: "celestia-appd",
86+
Bech32Prefix: "celestia",
87+
Denom: "utia",
88+
CoinType: "118",
89+
GasPrices: "0.025utia",
90+
GasAdjustment: 1.3,
91+
EncodingConfig: &encConfig,
92+
AdditionalStartArgs: []string{
93+
"--force-no-bbr",
94+
"--grpc.enable",
95+
"--grpc.address",
96+
"0.0.0.0:9090",
97+
"--rpc.grpc_laddr=tcp://0.0.0.0:9098",
98+
"--timeout-commit", "1s",
99+
},
100+
},
101+
DataAvailabilityNetworkConfig: &tastoradocker.DataAvailabilityNetworkConfig{
102+
BridgeNodeCount: 1,
103+
Image: tastoradocker.DockerImage{
104+
Repository: "ghcr.io/celestiaorg/celestia-node",
105+
Version: "pr-4283",
106+
UIDGID: "10001:10001",
107+
},
108+
},
109+
RollkitChainConfig: &tastoradocker.RollkitChainConfig{
110+
ChainID: "rollkit-test",
111+
Bin: "testapp",
112+
AggregatorPassphrase: "12345678",
113+
NumNodes: 1,
114+
Image: getRollkitImage(),
115+
},
116+
}
117+
118+
for _, opt := range opts {
119+
opt(&cfg)
120+
}
121+
122+
return tastoradocker.NewProvider(cfg, t)
123+
}
124+
125+
// getGenesisHash returns the genesis hash of the given chain node.
126+
func (s *DockerTestSuite) getGenesisHash(ctx context.Context) string {
127+
node := s.celestia.GetNodes()[0]
128+
c, err := node.GetRPCClient()
129+
s.Require().NoError(err, "failed to get node client")
130+
131+
first := int64(1)
132+
block, err := c.Block(ctx, &first)
133+
s.Require().NoError(err, "failed to get block")
134+
135+
genesisHash := block.Block.Header.Hash().String()
136+
s.Require().NotEmpty(genesisHash, "genesis hash is empty")
137+
return genesisHash
138+
}
139+
140+
// SetupDockerResources creates a new provider and chain using the given configuration options.
141+
// none of the resources are started.
142+
func (s *DockerTestSuite) SetupDockerResources(opts ...ConfigOption) {
143+
s.provider = s.CreateDockerProvider(opts...)
144+
s.celestia = s.CreateChain()
145+
s.daNetwork = s.CreateDANetwork()
146+
s.rollkitChain = s.CreateRollkitChain()
147+
}
148+
149+
// CreateChain creates a chain using the provider.
150+
func (s *DockerTestSuite) CreateChain() tastoratypes.Chain {
151+
ctx := context.Background()
152+
153+
chain, err := s.provider.GetChain(ctx)
154+
s.Require().NoError(err)
155+
156+
return chain
157+
}
158+
159+
// CreateDANetwork creates a DA network using the provider
160+
func (s *DockerTestSuite) CreateDANetwork() tastoratypes.DataAvailabilityNetwork {
161+
ctx := context.Background()
162+
163+
daNetwork, err := s.provider.GetDataAvailabilityNetwork(ctx)
164+
s.Require().NoError(err)
165+
166+
return daNetwork
167+
}
168+
169+
// CreateRollkitChain creates a Rollkit chain using the provider
170+
func (s *DockerTestSuite) CreateRollkitChain() tastoratypes.RollkitChain {
171+
ctx := context.Background()
172+
173+
rollkitChain, err := s.provider.GetRollkitChain(ctx)
174+
s.Require().NoError(err)
175+
176+
return rollkitChain
177+
}
178+
179+
// StartBridgeNode initializes and starts a bridge node within the data availability network using the given parameters.
180+
func (s *DockerTestSuite) StartBridgeNode(ctx context.Context, bridgeNode tastoratypes.DANode, chainID string, genesisHash string, celestiaNodeHostname string) {
181+
s.Require().Equal(tastoratypes.BridgeNode, bridgeNode.GetType())
182+
err := bridgeNode.Start(ctx,
183+
tastoratypes.WithChainID(chainID),
184+
tastoratypes.WithAdditionalStartArguments("--p2p.network", chainID, "--core.ip", celestiaNodeHostname, "--rpc.addr", "0.0.0.0"),
185+
tastoratypes.WithEnvironmentVariables(
186+
map[string]string{
187+
"CELESTIA_CUSTOM": tastoratypes.BuildCelestiaCustomEnvVar(chainID, genesisHash, ""),
188+
"P2P_NETWORK": chainID,
189+
},
190+
),
191+
)
192+
s.Require().NoError(err)
193+
}
194+
195+
// FundWallet transfers the specified amount of utia from the faucet wallet to the target wallet.
196+
func (s *DockerTestSuite) FundWallet(ctx context.Context, wallet tastoratypes.Wallet, amount int64) {
197+
fromAddress, err := sdkacc.AddressFromWallet(s.celestia.GetFaucetWallet())
198+
s.Require().NoError(err)
199+
200+
toAddress, err := sdk.AccAddressFromBech32(wallet.GetFormattedAddress())
201+
s.Require().NoError(err)
202+
203+
bankSend := banktypes.NewMsgSend(fromAddress, toAddress, sdk.NewCoins(sdk.NewCoin("utia", math.NewInt(amount))))
204+
_, err = s.celestia.BroadcastMessages(ctx, s.celestia.GetFaucetWallet(), bankSend)
205+
s.Require().NoError(err)
206+
}
207+
208+
// StartRollkitNode initializes and starts a Rollkit node.
209+
func (s *DockerTestSuite) StartRollkitNode(ctx context.Context, bridgeNode tastoratypes.DANode, rollkitNode tastoratypes.RollkitNode) {
210+
err := rollkitNode.Init(ctx)
211+
s.Require().NoError(err)
212+
213+
bridgeNodeHostName, err := bridgeNode.GetInternalHostName()
214+
s.Require().NoError(err)
215+
216+
authToken, err := bridgeNode.GetAuthToken()
217+
s.Require().NoError(err)
218+
219+
daAddress := fmt.Sprintf("http://%s:26658", bridgeNodeHostName)
220+
err = rollkitNode.Start(ctx,
221+
"--rollkit.da.address", daAddress,
222+
"--rollkit.da.gas_price", "0.025",
223+
"--rollkit.da.auth_token", authToken,
224+
"--rollkit.rpc.address", "0.0.0.0:7331", // bind to 0.0.0.0 so rpc is reachable from test host.
225+
"--rollkit.da.namespace", generateValidNamespaceHex(),
226+
)
227+
s.Require().NoError(err)
228+
}
229+
230+
// getRollkitImage returns the Docker image configuration for Rollkit
231+
// Uses ROLLKIT_IMAGE_REPO and ROLLKIT_IMAGE_TAG environment variables if set
232+
func getRollkitImage() tastoradocker.DockerImage {
233+
repo := strings.TrimSpace(os.Getenv("ROLLKIT_IMAGE_REPO"))
234+
if repo == "" {
235+
repo = "ghcr.io/rollkit/rollkit"
236+
}
237+
238+
tag := strings.TrimSpace(os.Getenv("ROLLKIT_IMAGE_TAG"))
239+
if tag == "" {
240+
tag = "latest"
241+
}
242+
243+
return tastoradocker.DockerImage{
244+
Repository: repo,
245+
Version: tag,
246+
UIDGID: "10001:10001",
247+
}
248+
}
249+
250+
func generateValidNamespaceHex() string {
251+
return hex.EncodeToString(share.RandomBlobNamespaceID())
252+
}
253+
254+
// appOverrides enables indexing of transactions so Broadcasting of transactions works
255+
func appOverrides() toml.Toml {
256+
return toml.Toml{
257+
"tx-index": toml.Toml{
258+
"indexer": "kv",
259+
},
260+
}
261+
}
262+
263+
// configOverrides enables indexing of transactions so Broadcasting of transactions works
264+
func configOverrides() toml.Toml {
265+
return toml.Toml{
266+
"tx_index": toml.Toml{
267+
"indexer": "kv",
268+
},
269+
}
270+
}

0 commit comments

Comments
 (0)