From cf8f966718babfc746fe801942358cb2f1bddc02 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 28 Apr 2026 17:05:52 +0200 Subject: [PATCH] test(integration): cover attested IBC commits --- .github/workflows/migration_test.yml | 8 +- tests/integration/docker/Dockerfile.gm | 1 + .../patches/app-wiring/patch-app-wiring.sh | 12 ++ tests/integration/gm_gaia_health_test.go | 116 +++++++++++++++++- 4 files changed, 132 insertions(+), 5 deletions(-) diff --git a/.github/workflows/migration_test.yml b/.github/workflows/migration_test.yml index a5d9c478..a22d1922 100644 --- a/.github/workflows/migration_test.yml +++ b/.github/workflows/migration_test.yml @@ -58,7 +58,7 @@ jobs: steps: - uses: actions/checkout@v5 with: - fetch-depth: 0 + fetch-depth: 1 - name: Log in to GHCR uses: docker/login-action@v4 @@ -107,7 +107,7 @@ jobs: steps: - uses: actions/checkout@v5 with: - fetch-depth: 0 + fetch-depth: 1 - name: Log in to GHCR uses: docker/login-action@v4 @@ -152,7 +152,7 @@ jobs: steps: - uses: actions/checkout@v5 with: - fetch-depth: 0 + fetch-depth: 1 - name: Set up Go uses: actions/setup-go@v6 @@ -194,7 +194,7 @@ jobs: steps: - uses: actions/checkout@v5 with: - fetch-depth: 0 + fetch-depth: 1 - name: Set up Go uses: actions/setup-go@v6 diff --git a/tests/integration/docker/Dockerfile.gm b/tests/integration/docker/Dockerfile.gm index 88cfc173..13c023a5 100644 --- a/tests/integration/docker/Dockerfile.gm +++ b/tests/integration/docker/Dockerfile.gm @@ -34,6 +34,7 @@ RUN chmod +x /workspace/patch-app-wiring.sh && \ # Align module versions like in CI RUN go mod edit -replace github.com/evstack/ev-node=github.com/evstack/ev-node@${EVNODE_VERSION} \ && go mod edit -replace github.com/evstack/ev-abci=../ev-abci \ + && go mod edit -replace github.com/bytedance/sonic=github.com/bytedance/sonic@v1.15.0 \ && go mod tidy # Build gmd binary diff --git a/tests/integration/docker/patches/app-wiring/patch-app-wiring.sh b/tests/integration/docker/patches/app-wiring/patch-app-wiring.sh index 3235d06f..1f391e80 100755 --- a/tests/integration/docker/patches/app-wiring/patch-app-wiring.sh +++ b/tests/integration/docker/patches/app-wiring/patch-app-wiring.sh @@ -189,6 +189,18 @@ func (app *App) GetNetworkKeeper() networkkeeper.Keeper { EOF fi +# Add BlockID provider setter (required by ev-abci server wiring) +if ! grep -q "SetNetworkKeeperBlockIDProvider" "$APP_GO"; then + echo "[patch-app-wiring] Adding SetNetworkKeeperBlockIDProvider method" + add_import "$APP_GO" $'\tnetworktypes "github.com/evstack/ev-abci/modules/network/types"' + cat >>"$APP_GO" <<'EOF' + +func (app *App) SetNetworkKeeperBlockIDProvider(p networktypes.BlockIDProvider) { + app.NetworkKeeper.SetBlockIDProvider(p) +} +EOF +fi + echo "[patch-app-wiring] Step 5: Final validation" # Validate critical components diff --git a/tests/integration/gm_gaia_health_test.go b/tests/integration/gm_gaia_health_test.go index 243ab1e5..7054c4a4 100644 --- a/tests/integration/gm_gaia_health_test.go +++ b/tests/integration/gm_gaia_health_test.go @@ -2,8 +2,10 @@ package integration_test import ( "context" + "encoding/base64" "encoding/json" "fmt" + "strings" "testing" "time" @@ -16,6 +18,8 @@ import ( "github.com/celestiaorg/tastora/framework/testutil/sdkacc" "github.com/celestiaorg/tastora/framework/testutil/wait" "github.com/celestiaorg/tastora/framework/types" + cmted25519 "github.com/cometbft/cometbft/crypto/ed25519" + cmttypes "github.com/cometbft/cometbft/types" "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module/testutil" @@ -94,6 +98,53 @@ func (s *DockerIntegrationTestSuite) TestAttesterSystem() { require.NoError(s.T(), err) s.T().Log("Attester node started successfully") + // Wait for the attester to attest some blocks and LastAttestedHeight to advance. + s.T().Log("Waiting for attestations to reach quorum...") + var targetHeight int64 = 10 + err = wait.ForCondition(ctx, 2*time.Minute, 2*time.Second, func() (bool, error) { + node := gmChain.GetNodes()[0] + rpcClient, _ := node.GetRPCClient() + if rpcClient == nil { + return false, nil + } + status, statusErr := rpcClient.Status(ctx) + if statusErr != nil { + return false, nil + } + return status.SyncInfo.LatestBlockHeight >= targetHeight, nil + }) + s.Require().NoError(err, "chain did not reach target height %d", targetHeight) + + // Fetch /commit for the target height and assert VerifyCommitLight passes. + { + node := gmChain.GetNodes()[0] + rpcClient, err := node.GetRPCClient() + s.Require().NoError(err) + commitResp, err := rpcClient.Commit(ctx, &targetHeight) + s.Require().NoError(err, "fetch commit at height %d", targetHeight) + + privValJSONBz, err := node.ReadFile(ctx, "config/priv_validator_key.json") + s.Require().NoError(err) + var pv struct { + PubKey struct { + Type string `json:"type"` + Value string `json:"value"` + } `json:"pub_key"` + } + s.Require().NoError(json.Unmarshal(privValJSONBz, &pv)) + pkBytes, err := base64.StdEncoding.DecodeString(pv.PubKey.Value) + s.Require().NoError(err) + cmtPub := cmted25519.PubKey(pkBytes) + valSet := cmttypes.NewValidatorSet([]*cmttypes.Validator{cmttypes.NewValidator(cmtPub, 1)}) + + commit := commitResp.SignedHeader.Commit + s.Require().NoError( + valSet.VerifyCommitLight("gm", commit.BlockID, targetHeight, commit), + "reconstructed commit must pass 07-tendermint light-client verification", + ) + s.T().Logf("commit at height %d passes VerifyCommitLight with %d signatures", targetHeight, len(commit.Signatures)) + } + hermes, err := relayer.NewHermes(ctx, s.dockerClient, s.T().Name(), s.networkID, 0, s.logger) require.NoError(s.T(), err, "failed to create hermes relayer") @@ -256,7 +307,7 @@ func (s *DockerIntegrationTestSuite) getGmChain(ctx context.Context) *cosmos.Cha "--log_level", "*:info", ). WithNode(cosmos.NewChainNodeConfigBuilder(). - WithPostInit(AddSingleSequencer, writePasshraseFile("12345678")). + WithPostInit(AddSingleSequencer, AddGenesisAttester, writePasshraseFile("12345678")). Build()). Build(ctx) require.NoError(s.T(), err) @@ -305,6 +356,69 @@ func AddSingleSequencer(ctx context.Context, node *cosmos.ChainNode) error { return node.WriteFile(ctx, "config/genesis.json", updatedGenesis) } +// AddGenesisAttester populates app_state.network.attester_infos with a single +// attester entry derived from the node's priv_validator_key.json and the +// operator address of the "validator" keyring entry. +func AddGenesisAttester(ctx context.Context, node *cosmos.ChainNode) error { + genesisBz, err := node.ReadFile(ctx, "config/genesis.json") + if err != nil { + return fmt.Errorf("read genesis: %w", err) + } + + pubKey, err := getPubKey(ctx, node) + if err != nil { + return fmt.Errorf("get consensus pubkey: %w", err) + } + + // Consensus address (cosmosvalcons1... derived from ed25519 Address()) + consensusAddress := sdk.ConsAddress(pubKey.Address()).String() + + // Operator address: run `gmd keys show validator -a` inside the node container. + stdout, stderr, err := node.Exec(ctx, []string{ + node.BinaryName, + "keys", "show", "validator", "-a", + "--keyring-backend", "test", + "--home", node.HomeDir(), + }, nil) + if err != nil { + return fmt.Errorf("query validator operator address (stderr=%q): %w", string(stderr), err) + } + authority := strings.TrimSpace(string(stdout)) + if authority == "" { + return fmt.Errorf("empty operator address for validator keyring entry") + } + + attesterInfo := map[string]interface{}{ + "authority": authority, + "pubkey": map[string]interface{}{ + "@type": "/cosmos.crypto.ed25519.PubKey", + "key": base64.StdEncoding.EncodeToString(pubKey.Bytes()), + }, + "joined_height": 0, + "consensus_address": consensusAddress, + } + + var genDoc map[string]interface{} + if err := json.Unmarshal(genesisBz, &genDoc); err != nil { + return fmt.Errorf("parse genesis: %w", err) + } + appState, ok := genDoc["app_state"].(map[string]interface{}) + if !ok { + return fmt.Errorf("genesis has no app_state object") + } + network, ok := appState["network"].(map[string]interface{}) + if !ok { + return fmt.Errorf("genesis has no app_state.network object") + } + network["attester_infos"] = []interface{}{attesterInfo} + + updatedBz, err := json.MarshalIndent(genDoc, "", " ") + if err != nil { + return fmt.Errorf("marshal genesis: %w", err) + } + return node.WriteFile(ctx, "config/genesis.json", updatedBz) +} + // setupIBCConnection establishes a complete IBC connection and channel func setupIBCConnection(t *testing.T, ctx context.Context, chainA, chainB types.Chain, hermes *relayer.Hermes) (ibc.Connection, ibc.Channel) { err := hermes.CreateClients(ctx, chainA, chainB)