Skip to content

Conversation

@Chralt98
Copy link
Member

@Chralt98 Chralt98 commented Nov 28, 2025

What does it do?

Updates the integration tests to verify the relay chain timestamp now storage item has a valid value.

What important points should reviewers know?

Is there something left for follow-up PRs?

What alternative implementations were considered?

Are there relevant PRs or issues?

References

zeitgeistpm/polkadot-sdk@87971b3...83df353

paritytech/polkadot-sdk#9262

moonbeam-foundation/polkadot-sdk@ececbc6

Summary by CodeRabbit

  • Tests

    • Enhanced runtime-upgrade tests with an explicit authorize-and-apply flow, on-chain verification after upgrade, richer diagnostics, and a new relay-timestamp consistency test.
  • Chores

    • Added a Polkadot utility to dev dependencies and updated local integration test endpoints and environments.
  • Documentation

    • Added docs and an environment entry for a battery-station upgrade scenario; updated zombienet endpoint ports.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 28, 2025

Walkthrough

Adds a new zombienet battery-station upgrade environment and docs, introduces @polkadot/util dev dep, enhances zombienet runtime-upgrade tests to perform an authorized upgrade flow (authorize → validate → sudo apply) with on-chain wasm verification, and adds RelayTimestampNow monotonicity checks.

Changes

Cohort / File(s) Summary
Docs & Commands
integration-tests/README.md
Added zombienet_battery_station_upgrade test block and corresponding battery-station command sequences mirroring the existing zeitgeist upgrade flow.
Environments / Configs
integration-tests/moonwall.config.json, integration-tests/configs/zombieZeitgeist.json, integration-tests/configs/zombieBatteryStation.json
Added new zombienet_battery_station_upgrade environment (uses ./configs/zombieBatteryStation.json), updated relay/parachain ws endpoints and ports for existing zombienet environment, and adjusted parachain IDs/chain paths and ports in zombie config(s).
Dev Dependencies
integration-tests/package.json
Added @polkadot/util@13.5.2 to devDependencies and pnpm overrides to align @polkadot package resolution.
Runtime Upgrade Tests
integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts
Reworked test to implement authorized runtime-upgrade flow: import hash/timestamp helpers, compute wasm hash, discover authorize/apply calls, add txStatus helper with dispatchError handling, submit system.authorizeUpgrade, wait for validation, call applyAuthorizedUpgrade via sudo, verify on-chain :code equals new wasm, and add RelayTimestampNow monotonicity/storage checks across blocks.
Spec & Scripts
integration-tests/scripts/build-zeitgeist-spec.sh, integration-tests/scripts/download-polkadot.sh
build-zeitgeist-spec.sh: new robust dev-chain-based spec generation and JSON transformation, optional runtime wasm extraction. download-polkadot.sh: added POLKADOT_SKIP_DOWNLOAD early-exit when binaries exist.
Other Configs
integration-tests/configs/hydradx.yml
Changed endpoint from a single string to a YAML list of four WebSocket endpoints; other fields unchanged.

Sequence Diagram(s)

sequenceDiagram
    participant Test as Integration Test
    participant Collator as Collator/Node
    participant Parachain as Parachain Chain
    participant Sudo as Sudo/Root

    Note over Test: prepare wasm bytes & compute wasm_hash
    Test->>Collator: submit signed `system.authorizeUpgrade(wasm_hash)`
    Collator->>Parachain: include authorizeUpgrade extrinsic
    Parachain-->>Test: emit events/logs (authorization recorded)
    Note right of Test: wait for validation marker (e.g., storage/event)
    Test->>Sudo: submit `sudo(applyAuthorizedUpgrade(wasm_bytes))`
    Sudo->>Collator: include sudo extrinsic
    Collator->>Parachain: execute applyAuthorizedUpgrade → runtime updated
    Parachain-->>Test: emit events/logs (upgrade applied)
    Test->>Parachain: query `:code` storage
    Parachain-->>Test: return runtime `:code` → Test compares to expected wasm
    Note over Test,Parachain: Test also reads `RelayTimestampNow` across blocks and verifies monotonic increase
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay extra attention to:
    • integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts — txStatus helper, dispatchError handling, discovery of authorize/apply calls, wasm hash computation, sudo-wrapped apply flow, on-chain :code comparison, and RelayTimestampNow decoding/validation.
    • integration-tests/scripts/build-zeitgeist-spec.sh — JSON transformation logic and optional wasm extraction.
    • integration-tests/moonwall.config.json & integration-tests/configs/* — consistency of new environment, IDs, ports, and endpoints with CI/local setups.

Possibly related PRs

Suggested labels

s:accepted

Suggested reviewers

  • robhyrk

Poem

🐰 I nibble bytes and polish hops,
I hash the wasm and mind the clocks,
Authorize, then sudo call,
Timestamps climb — I watch them all,
Nodes hum new code as confetti drops.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title accurately summarizes the main change: adding an integration test to verify relay chain timestamp validity in parachain storage, which aligns with the new T03 test case and timestamp verification logic added to the test file.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chralt98-relay-timestamp-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Chralt98 Chralt98 self-assigned this Nov 28, 2025
@Chralt98 Chralt98 added the s:in-progress The pull requests is currently being worked on label Nov 28, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 20807c7 and e3f521b.

⛔ Files ignored due to path filters (1)
  • integration-tests/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • integration-tests/README.md (1 hunks)
  • integration-tests/moonwall.config.json (2 hunks)
  • integration-tests/package.json (1 hunks)
  • integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (10)
integration-tests/moonwall.config.json (1)

7-23: LGTM! Configuration correctly aligned to battery-station runtime.

The environment name, runtime upgrade path, and zombie spec configuration are all consistently updated to reference battery-station runtime.

integration-tests/package.json (1)

15-15: LGTM! Dependency addition is correct and consistent.

The @polkadot/util package (version 13.5.2) is correctly added and matches the versions of related @PolkaDot packages, with proper override configuration already in place.

integration-tests/README.md (1)

89-92: LGTM! Documentation correctly updated.

The documentation accurately reflects the new battery-station runtime test environment and command, matching the configuration changes.

integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (7)

27-28: LGTM! Appropriate utilities imported.

The @polkadot/util and @polkadot/util-crypto imports are correctly used for storage key construction and wasm hashing in the authorized upgrade flow and relay timestamp verification.


88-93: LGTM! Enhanced logging for debugging.

The specVersion and rtUpgradePath logging provides valuable context for understanding the upgrade process.


148-181: LGTM! Dynamic call discovery provides flexibility.

The findCall helper and upgrade call discovery logic allows the test to adapt to different runtime configurations where upgrade calls might be in different pallets. The logging provides good visibility into the discovered sections.


220-236: LGTM! Comprehensive upgrade verification.

The verification logic properly confirms that:

  • Block production continued after upgrade
  • Runtime code changed to the expected wasm
  • Both conditions are logged for debugging

257-277: Verify the exact timestamp equality expectation.

The test assumes exact equality between tsPara1 and tsRelay1 (line 277). This is a strict assertion that might fail if there are any rounding differences or if the parachain applies any transformation to the relay timestamp.

Please confirm that the parachain timestamp is expected to exactly mirror the relay timestamp with no transformation. If there's any documentation or specification for this feature, it would help verify this assumption.


279-293: LGTM! Monotonicity check is appropriate.

The verification that the relay timestamp increases across blocks (line 291) is a good sanity check for the relay proof mechanism. The test properly waits for new blocks and verifies both relay and parachain timestamps advance together.


244-255: Based on my verification of the codebase context, I can now provide the rewritten review comment:


The manual storage key construction using xxhashAsU8a is the correct and necessary approach for accessing relay chain storage.

RelayTimestampNow is relay chain state that must be accessed via low-level storage queries since polkadot.js does not expose query APIs for relay chain pallets to parachains. The pattern of combining xxhashAsU8a("runtime", 128) with xxhashAsU8a("RelayTimestampNow", 128) is standard Substrate storage key derivation and is the only way to access this storage from the parachain. The u64 type assumption aligns with the standard Timestamp pallet type used elsewhere in the test (e.g., paraApi.query.timestamp.now()). The code properly validates the storage exists before decoding, so no changes are needed.

Comment on lines +120 to +146
const txStatus = async (tx: any, label: string) =>
new Promise<void>((resolve, reject) => {
let unsubscribe: (() => void) | undefined;
tx.signAndSend(alice, (result: any) => {
if (result.dispatchError) {
// Dispatch errors won't throw, so surface them explicitly.
const errText = result.dispatchError.toString();
log(`${label} dispatchError=${errText}`);
reject(new Error(`${label} failed: ${errText}`));
unsubscribe?.();
return;
}
log(
`${label} status=${result.status?.type ?? "unknown"}, events=${result.events
?.map((ev: any) => `${ev.event.section}.${ev.event.method}`)
.join(",")}`
);
if (result.status?.isInBlock || result.status?.isFinalized) {
unsubscribe?.();
resolve();
}
})
.then((unsub: () => void) => {
unsubscribe = unsub;
})
.catch(reject);
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add timeout protection to prevent indefinite hangs.

The txStatus helper lacks timeout protection. If a transaction never reaches isInBlock or isFinalized status (due to network issues, dropped transactions, etc.), the promise will hang indefinitely, causing the test to timeout at the suite level rather than providing a clear failure reason.

Consider adding a timeout mechanism:

 const txStatus = async (tx: any, label: string) =>
   new Promise<void>((resolve, reject) => {
     let unsubscribe: (() => void) | undefined;
+    const timeout = setTimeout(() => {
+      unsubscribe?.();
+      reject(new Error(`${label} timed out after 60s`));
+    }, 60000);
     tx.signAndSend(alice, (result: any) => {
       if (result.dispatchError) {
         const errText = result.dispatchError.toString();
         log(`${label} dispatchError=${errText}`);
+        clearTimeout(timeout);
         reject(new Error(`${label} failed: ${errText}`));
         unsubscribe?.();
         return;
       }
       log(
         `${label} status=${result.status?.type ?? "unknown"}, events=${result.events
           ?.map((ev: any) => `${ev.event.section}.${ev.event.method}`)
           .join(",")}`
       );
       if (result.status?.isInBlock || result.status?.isFinalized) {
+        clearTimeout(timeout);
         unsubscribe?.();
         resolve();
       }
     })
       .then((unsub: () => void) => {
         unsubscribe = unsub;
       })
-      .catch(reject);
+      .catch((err) => {
+        clearTimeout(timeout);
+        reject(err);
+      });
   });
🤖 Prompt for AI Agents
In
integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts
around lines 120-146, the txStatus promise can hang indefinitely because it
never rejects if a transaction never becomes InBlock/Finalized; add timeout
protection by starting a timer when the promise is created that rejects with a
descriptive timeout error after a configurable duration (e.g., 30s), ensure the
timer is cleared on resolve/reject, and call unsubscribe before rejecting on
timeout so the subscription is cleaned up; also ensure any caught errors clear
the timer and unsubscribe as well.

Comment on lines +183 to +214
if (upgradeAvailable) {
// Zeitgeist runtime blocks `setCode`, so use the authorized upgrade flow.
const wasmHash = blake2AsHex(wasm);
log("Authorizing runtime upgrade via system.authorizeUpgrade");
const authorizeTx =
authorizeUpgrade.meta.args.length === 1
? authorizeUpgrade(wasmHash)
: authorizeUpgrade(wasmHash, true);
log(
`authorizeUpgrade located in section=${upgradeCallLocation.authorize}, args=${authorizeUpgrade.meta.args.length}`
);
await txStatus(
paraApi.tx.sudo.sudo(authorizeTx),
"authorizeUpgrade"
);

log("Waiting for validation function approval");
await context.waitBlock(2);

log("Enacting authorized upgrade");
log(
`applyAuthorizedUpgrade located in section=${upgradeCallLocation.enact}, args=${applyAuthorizedUpgrade.meta.args.length}`
);
await txStatus(
paraApi.tx.sudo.sudo(applyAuthorizedUpgrade(rtHex)),
"applyAuthorizedUpgrade"
);
} else {
throw new Error(
"Runtime upgrade calls missing in metadata; expected system.authorizeUpgrade/applyAuthorizedUpgrade"
);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for authorizeUpgrade and applyAuthorizedUpgrade definitions in runtime

# First, find runtime-related directories
echo "=== Locating runtime directories ==="
fd -t d "runtime" | head -20

echo -e "\n=== Searching for authorizeUpgrade definitions ==="
rg -n "authorizeUpgrade\|applyAuthorizedUpgrade" \
  --type rust \
  -B 2 -A 5 \
  2>/dev/null | head -100

Repository: zeitgeistpm/zeitgeist

Length of output: 338


🏁 Script executed:

#!/bin/bash
# Read the test file to understand the full context

echo "=== Reading test file (lines 150-240 for context) ==="
sed -n '150,240p' integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts

echo -e "\n=== Checking Cargo.toml for SDK version ==="
find . -name "Cargo.toml" -path "*/integration-tests/*" | head -1 | xargs cat | grep -A 5 -B 5 "polkadot"

Repository: zeitgeistpm/zeitgeist

Length of output: 3696


🏁 Script executed:

#!/bin/bash
# Find integration-tests Cargo.toml and check SDK version

echo "=== Finding integration-tests Cargo.toml ==="
find . -path "*/integration-tests/Cargo.toml" -exec cat {} \;

echo -e "\n=== Searching for other meta.args.length patterns ==="
rg -n "meta\.args\.length" --type typescript --type javascript -B 2 -A 2

echo -e "\n=== Searching for waitBlock patterns to understand timing ==="
rg -n "waitBlock" --type typescript -B 2 -A 2 | head -50

Repository: zeitgeistpm/zeitgeist

Length of output: 314


🏁 Script executed:

#!/bin/bash
# Better approach to find integration-tests files

echo "=== Integration-tests directory structure ==="
ls -la integration-tests/ 2>/dev/null | head -20

echo -e "\n=== Finding Cargo.toml files ==="
find integration-tests -name "Cargo.toml" 2>/dev/null

echo -e "\n=== Search for meta.args.length (no type filter) ==="
rg -n "meta\.args\.length" -B 2 -A 2 | head -60

echo -e "\n=== Search for waitBlock (no type filter) ==="
rg -n "waitBlock" -B 2 -A 2 | head -60

Repository: zeitgeistpm/zeitgeist

Length of output: 5027


🏁 Script executed:

#!/bin/bash
# Check package.json for SDK versions and find waitBlock implementation

echo "=== Integration-tests package.json ==="
cat integration-tests/package.json

echo -e "\n=== Search for context.waitBlock definition/implementation ==="
rg -n "waitBlock" --type typescript -A 5 -B 2 | grep -A 10 "function\|const.*waitBlock\|=>" | head -40

echo -e "\n=== Search for moonwall context/framework files ==="
rg -n "class.*Context\|function.*context" integration-tests -B 2 -A 5 | head -60

Repository: zeitgeistpm/zeitgeist

Length of output: 1621


🏁 Script executed:

#!/bin/bash
# Search for test-zombienet-runtime-upgrade.ts full content to understand context better

echo "=== Full test file content (part 1 - imports and setup) ==="
head -100 integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts

echo -e "\n=== Check for comments about timing or block requirements ==="
rg -n "block\|timing\|wait" integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts -i | head -40

echo -e "\n=== Search for authorizeUpgrade in Polkadot docs or comments ==="
rg -n "authorizeUpgrade\|applyAuthorizedUpgrade\|SDK\|version" integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts -i -B 1 -A 1

Repository: zeitgeistpm/zeitgeist

Length of output: 3789


🏁 Script executed:

#!/bin/bash
# Search for more context about the meta.args check and what it's handling

echo "=== Full context around meta.args.length check (lines 170-220) ==="
sed -n '170,220p' integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts

echo -e "\n=== Search for any SDK or version-related comments in test ==="
rg -n "SDK\|version\|meta\|args" integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts | head -30

Repository: zeitgeistpm/zeitgeist

Length of output: 2331


🌐 Web query:

Polkadot SDK 16.5.2 authorizeUpgrade signature args parameters

💡 Result:

Summary (@polkadot/api v16.5.2)

  • Extrinsic: api.tx.system.authorizeUpgrade(codeHash: H256)

    • Single argument: codeHash of type H256 (the runtime code hash).
    • Origin required: Root. [1][2]
  • Related extrinsics:

    • api.tx.system.authorizeUpgradeWithoutChecks(codeHash: H256) — same arg but skips safety checks. [1][2]
    • api.tx.system.applyAuthorizedUpgrade(code: Bytes) — provides the runtime WASM preimage (Bytes). [3]

Sources:

  • Polkadot JS extrinsics docs (system.authorizeUpgrade / authorizeUpgradeWithoutChecks). [1]
  • Substrate / Polkadot extrinsics listing (applyAuthorizedUpgrade / authorizeUpgrade). [3]
  • @polkadot/api (v16.5.2) package listing. [4]

🏁 Script executed:

#!/bin/bash
# Search for authorizeUpgrade definition in battery-station runtime

echo "=== Searching for authorizeUpgrade in battery-station runtime ==="
rg -n "authorizeUpgrade" runtime/battery-station -B 3 -A 3 --type rust

echo -e "\n=== Checking runtime/zeitgeist as well ==="
rg -n "authorizeUpgrade" runtime/zeitgeist -B 3 -A 3 --type rust

echo -e "\n=== Search for pallet_system config or extensions ==="
rg -n "pallet_system\|System" runtime/battery-station/src/lib.rs -B 2 -A 10 | head -80

Repository: zeitgeistpm/zeitgeist

Length of output: 229


🏁 Script executed:

#!/bin/bash
# Check battery-station runtime configuration

echo "=== Reading battery-station runtime/lib.rs ==="
wc -l runtime/battery-station/src/lib.rs

echo -e "\n=== Grep for System pallet config ==="
rg -n "impl.*frame_system\|frame_system::Config\|System" runtime/battery-station/src/lib.rs -B 2 -A 5 | head -100

echo -e "\n=== Check for any comments about upgrade or args ==="
rg -n "upgrade\|args\|variant" runtime/battery-station/src/lib.rs -i | head -30

Repository: zeitgeistpm/zeitgeist

Length of output: 243


🏁 Script executed:

#!/bin/bash
# Read the full battery-station runtime lib.rs

echo "=== Full battery-station/src/lib.rs ==="
cat -n runtime/battery-station/src/lib.rs

echo -e "\n=== Check runtime structure for other files ==="
find runtime/battery-station/src -type f -name "*.rs" | head -20

Repository: zeitgeistpm/zeitgeist

Length of output: 7574


🌐 Web query:

Polkadot runtime upgrade authorizeUpgrade applyAuthorizedUpgrade timing blocks requirements

💡 Result:

Short answer — how authorizeUpgrade + applyAuthorizedUpgrade work, timing and requirements:

  • authorizeUpgrade(code_hash) must be called with Root origin and simply records an authorized code hash (two‑phase flow useful when the WASM preimage is large or sent later). [1][2]
  • applyAuthorizedUpgrade(code) can be called by any origin; it supplies the WASM preimage for a previously authorized hash. If the authorization required version checks, applyAuthorizedUpgrade enforces that the spec_name is unchanged and spec_version increases. [1][2]
  • Timing: whether the new runtime is applied immediately in the same block or scheduled for a later block depends on the runtime’s OnSetCode implementation (T::OnSetCode). In many chains (including Polkadot/parachains) the OnSetCode implementation may schedule the actual set_code to a future block (so applyAuthorizedUpgrade may only enqueue the change). The system::set_code path likewise delegates to T::OnSetCode::set_code. [2][3]
  • Parachain note: parachains generally must use the authorize/apply_authorized two‑step flow to coordinate upgrades with the relay chain’s expectations (hence the authorize step is required for parachain upgrade workflows). [1][3]
  • Practical checks/requirements: authorizeUpgrade requires Root; applyAuthorizedUpgrade validates the preimage matches an authorized hash and (unless using the WithoutChecks variants) enforces spec/version checks; there are also set_code and without_checks variants if you need single‑step or skip safety checks. [1][2]

Sources: polkadot.js extrinsics docs (system.authorizeUpgrade / applyAuthorizedUpgrade). [1] Substrate frame_system implementation and OnSetCode behavior (set_code / set_code_without_checks / OnSetCode trait). [2][3]


The meta.args.length check is dead code; 2-block wait is reasonable but undocumented.

1. Lines 189-190 — Unnecessary conditional: The check for authorizeUpgrade.meta.args.length === 1 will always be true with Polkadot SDK v16.5.2, which defines authorizeUpgrade(codeHash: H256) with only one argument. The else branch (authorizeUpgrade(wasmHash, true)) is dead code. This appears to be legacy compatibility logic for older SDK versions. While not a functional issue, it creates unnecessary complexity and could confuse future maintainers.

2. Line 200 — 2-block wait is reasonable: The waitBlock(2) timing is appropriate for parachain upgrade workflows, as the OnSetCode implementation may schedule the actual code change for a future block. However, there is no explicit documentation in the code explaining why 2 blocks is chosen. The subsequent verification at lines 220–225 properly validates that the upgrade succeeded, so the wait is effective.

Consider:

  • Simplifying the conditional to remove the dead else branch (or add a comment explaining the version compatibility intent if intentional).
  • Adding a comment on line 199 explaining why 2 blocks is sufficient (e.g., "wait for OnSetCode scheduling and block finalization").
🤖 Prompt for AI Agents
integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts
lines 183-214: the conditional that checks authorizeUpgrade.meta.args.length and
the else branch with authorizeUpgrade(wasmHash, true) is dead/legacy code for
current Polkadot SDK and should be simplified to always call
authorizeUpgrade(wasmHash); remove the unused else branch; additionally,
annotate the waitBlock(2) call (around line 199) with a brief comment like "wait
for OnSetCode scheduling and block finalization (2 blocks)" so the choice is
documented for future maintainers.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (2)

120-146: Timeout protection still missing.

As previously noted, the txStatus helper lacks timeout protection and can hang indefinitely if a transaction never reaches isInBlock or isFinalized status.

Please see the previous review comment for the suggested timeout implementation.


183-214: Conditional check is still dead code; 2-block wait needs documentation.

As previously noted:

  1. The authorizeUpgrade.meta.args.length === 1 check will always be true with Polkadot SDK v16.5.2, making the else branch dead code.
  2. The waitBlock(2) call (line 200) lacks documentation explaining why 2 blocks is sufficient.

Please refer to the previous review comment for the recommended simplification and documentation.

🧹 Nitpick comments (2)
integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (2)

27-28: Remove unused imports.

The imports u8aConcat and xxhashAsU8a are not used in this file. Only blake2AsHex is used (line 185).

Apply this diff to remove the unused imports:

-import { u8aConcat } from "@polkadot/util";
-import { blake2AsHex, xxhashAsU8a } from "@polkadot/util-crypto";
+import { blake2AsHex } from "@polkadot/util-crypto";

247-254: Stricter error handling for storage decoding.

Line 253 uses rawHex ?? 0 as a fallback, which could mask decoding failures. If rawHex is undefined (but raw is not null), the function silently returns 0 instead of surfacing the actual issue.

Apply this diff for clearer error handling:

 const readRelayTs = async (): Promise<number> => {
   const raw = await paraApi.rpc.state.getStorage(relayTsStorageKey);
   expect(raw, "RelayTimestampNow storage should exist").to.not.be.null;
   const rawHex = raw?.toHex();
-  expect(rawHex, "RelayTimestampNow should decode to hex").to.not.be.undefined;
+  if (!rawHex) {
+    throw new Error("RelayTimestampNow failed to decode to hex");
+  }
   log(`RelayTimestampNow raw=${rawHex}`);
-  return paraApi.createType("u64", rawHex ?? 0).toNumber();
+  return paraApi.createType("u64", rawHex).toNumber();
 };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e3f521b and 297f2d6.

📒 Files selected for processing (1)
  • integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (6)
integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (6)

88-93: Good observability addition.

The specVersion and upgrade path logging will help with debugging runtime upgrade flows.


148-156: LGTM!

The findCall helper correctly locates calls across API sections and returns both the call and its section for later use.


158-182: LGTM!

The upgrade call discovery logic defensively locates the required calls and provides useful logging for troubleshooting.


220-235: LGTM!

The verification logic is thorough, with detailed logging and strong assertions ensuring the runtime upgrade succeeded.


276-280: Exact timestamp equality is appropriate for this test.

The test correctly validates that the parachain timestamp exactly mirrors the relay timestamp, which is the expected behavior when the parachain reads timestamps from relay chain proofs.

Also applies to: 290-295


244-246: Add a comment documenting the hardcoded storage key instead of computing it dynamically.

The storage key 0x54dbd40f5201dbc18b0eed4b2ecd9cc67e2cdf745d68eeb295336330e3a1a063 is correctly hardcoded and appropriate for this integration test. For tests running against a specific runtime version, hardcoding known storage keys is standard practice. Attempting to compute it dynamically adds unnecessary complexity without improving reliability. Simply document the key's derivation:

+        // Storage key for RelayTimestampNow: xxhash128("runtime") + xxhash128("RelayTimestampNow")
         const relayTsStorageKey =
           "0x54dbd40f5201dbc18b0eed4b2ecd9cc67e2cdf745d68eeb295336330e3a1a063";

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (1)

244-245: Hardcoded storage key is fragile and undocumented.

The raw storage key 0x54dbd40f... will silently break if the pallet or storage item name changes. Consider computing it dynamically or documenting its derivation.

Option 1 - Compute dynamically using the imported xxhashAsU8a:

import { u8aToHex } from "@polkadot/util";

// RelayTimestampNow storage key = xxhash128("RelayTimestampNow") twice (module + item)
const moduleHash = xxhashAsU8a("RelayTimestampNow", 128);
const itemHash = xxhashAsU8a("RelayTimestampNow", 128);
const relayTsStorageKey = u8aToHex(u8aConcat(moduleHash, itemHash));

Option 2 - At minimum, add a comment explaining the derivation:

// Storage key for pallet "RelayTimestampNow", item "RelayTimestampNow"
// Computed as: xxhash128("RelayTimestampNow") || xxhash128("RelayTimestampNow")
const relayTsStorageKey = "0x54dbd40f5201dbc18b0eed4b2ecd9cc67e2cdf745d68eeb295336330e3a1a063";
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 297f2d6 and 720edbf.

📒 Files selected for processing (1)
  • integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (6)
integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (6)

88-93: LGTM!

Good diagnostic logging for debugging test failures. The specVersion and upgrade path information will be helpful when investigating issues.


148-156: LGTM!

Clean helper for dynamically locating extrinsics across sections. The undefined return for missing calls is properly handled downstream.


158-176: LGTM!

The dynamic discovery of upgrade calls provides good SDK version compatibility. The upgradeAvailable check ensures both required calls exist before proceeding.


178-181: LGTM!

Useful diagnostic logging to understand the available upgrade-related transaction sections.


256-269: LGTM!

Good retry pattern with bounded attempts and clear logging. The subsequent assertion at line 276 will provide a clear failure message if the timestamp never populates.


277-280: Assertion is correct—parachain timestamp is architecturally designed to derive from relay timestamp.

Zeitgeist's runtime explicitly configures the parachain's pallet_timestamp to use RelayTimestamp as its time source. The RelayTimestampNow storage value is populated from relay chain state proofs via ConsensusHookWrapperForRelayTimestamp, so exact equality is the correct expectation. The test validates this integral design.

Comment on lines +220 to +235
const codeAfter = (await paraApi.rpc.state.getStorage(":code"))?.toString();
log(
`Before: #${blockNumberBefore}, After: #${blockNumberAfter}, code changed=${
codeAfter !== codeString
}`
);
log(
`Code (before): ${codeString.slice(0, 10)}...${codeString.slice(-10)}, code (after): ${
codeAfter ? codeAfter.slice(0, 10) + "..." + codeAfter.slice(-10) : "undefined"
}`
);
expect(
blockNumberAfter,
"Block number did not increase"
).to.be.greaterThan(blockNumberBefore);
expect(codeAfter, "Runtime code should match upgraded wasm").to.equal(rtHex);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add explicit null check before code comparison.

The optional chaining at line 220 could result in codeAfter being undefined. If storage retrieval fails, the assertion at line 235 would produce a confusing failure message ("Runtime code should match upgraded wasm") rather than indicating that the storage read failed.

         const codeAfter = (await paraApi.rpc.state.getStorage(":code"))?.toString();
+        expect(codeAfter, "Failed to read :code storage after upgrade").to.not.be.undefined;
         log(
           `Before: #${blockNumberBefore}, After: #${blockNumberAfter}, code changed=${
             codeAfter !== codeString
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const codeAfter = (await paraApi.rpc.state.getStorage(":code"))?.toString();
log(
`Before: #${blockNumberBefore}, After: #${blockNumberAfter}, code changed=${
codeAfter !== codeString
}`
);
log(
`Code (before): ${codeString.slice(0, 10)}...${codeString.slice(-10)}, code (after): ${
codeAfter ? codeAfter.slice(0, 10) + "..." + codeAfter.slice(-10) : "undefined"
}`
);
expect(
blockNumberAfter,
"Block number did not increase"
).to.be.greaterThan(blockNumberBefore);
expect(codeAfter, "Runtime code should match upgraded wasm").to.equal(rtHex);
const codeAfter = (await paraApi.rpc.state.getStorage(":code"))?.toString();
expect(codeAfter, "Failed to read :code storage after upgrade").to.not.be.undefined;
log(
`Before: #${blockNumberBefore}, After: #${blockNumberAfter}, code changed=${
codeAfter !== codeString
}`
);
log(
`Code (before): ${codeString.slice(0, 10)}...${codeString.slice(-10)}, code (after): ${
codeAfter ? codeAfter.slice(0, 10) + "..." + codeAfter.slice(-10) : "undefined"
}`
);
expect(
blockNumberAfter,
"Block number did not increase"
).to.be.greaterThan(blockNumberBefore);
expect(codeAfter, "Runtime code should match upgraded wasm").to.equal(rtHex);
🤖 Prompt for AI Agents
In
integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts
around lines 220 to 235, the optional chaining when reading storage can yield
undefined and the subsequent equality assertion will give a misleading message;
add an explicit null/undefined check for codeAfter immediately after retrieving
it and fail with a clear error if storage read returned undefined (e.g.,
expect(codeAfter, "Failed to read :code storage after
upgrade").to.not.be.undefined), then proceed to compare the non-null codeAfter
value to rtHex using the existing equality assertion.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (3)

184-215: Simplify dead conditional and document waitBlock timing.

This issue was flagged in a previous review but remains unaddressed:

1. Lines 189-191 — Dead code: The check for authorizeUpgrade.meta.args.length === 1 will always be true with Polkadot SDK v16.5.2, which defines authorizeUpgrade(codeHash: H256) with a single argument. The else branch (authorizeUpgrade(wasmHash, true)) is dead code for current SDK versions and creates unnecessary complexity.

2. Line 200 — Undocumented wait: The waitBlock(2) call lacks an explanatory comment. While 2 blocks is appropriate for parachain upgrade workflows (OnSetCode implementation may schedule code change for a future block), this should be documented for future maintainers.

Apply this diff to simplify the dead conditional and add documentation:

         if (upgradeAvailable) {
           // Zeitgeist runtime blocks `setCode`, so use the authorized upgrade flow.
           const wasmHash = blake2AsHex(wasm);
           log("Authorizing runtime upgrade via system.authorizeUpgrade");
-          const authorizeTx =
-            authorizeUpgrade.meta.args.length === 1
-              ? authorizeUpgrade(wasmHash)
-              : authorizeUpgrade(wasmHash, true);
+          const authorizeTx = authorizeUpgrade(wasmHash);
           log(
             `authorizeUpgrade located in section=${upgradeCallLocation.authorize}, args=${authorizeUpgrade.meta.args.length}`
           );
           await txStatus(
             paraApi.tx.sudo.sudo(authorizeTx),
             "authorizeUpgrade"
           );
 
-          log("Waiting for validation function approval");
+          // Wait for OnSetCode scheduling and validation function approval (2 blocks)
+          log("Waiting for validation function approval (2 blocks)");
           await context.waitBlock(2);

121-147: Still missing timeout protection to prevent indefinite hangs.

This issue was flagged in a previous review but remains unaddressed. The txStatus helper lacks timeout protection. If a transaction never reaches isInBlock or isFinalized status (due to network issues, dropped transactions, etc.), the promise will hang indefinitely, causing the test to timeout at the suite level rather than providing a clear failure reason.

Consider adding a timeout mechanism:

 const txStatus = async (tx: any, label: string) =>
   new Promise<void>((resolve, reject) => {
     let unsubscribe: (() => void) | undefined;
+    const timeout = setTimeout(() => {
+      unsubscribe?.();
+      reject(new Error(`${label} timed out after 60s`));
+    }, 60000);
     tx.signAndSend(alice, (result: any) => {
       if (result.dispatchError) {
         const errText = result.dispatchError.toString();
         log(`${label} dispatchError=${errText}`);
+        clearTimeout(timeout);
         reject(new Error(`${label} failed: ${errText}`));
         unsubscribe?.();
         return;
       }
       log(
         `${label} status=${result.status?.type ?? "unknown"}, events=${result.events
           ?.map((ev: any) => `${ev.event.section}.${ev.event.method}`)
           .join(",")}`
       );
       if (result.status?.isInBlock || result.status?.isFinalized) {
+        clearTimeout(timeout);
         unsubscribe?.();
         resolve();
       }
     })
       .then((unsub: () => void) => {
         unsubscribe = unsub;
       })
-      .catch(reject);
+      .catch((err) => {
+        clearTimeout(timeout);
+        reject(err);
+      });
   });

221-236: Add explicit undefined check before code comparison.

This issue was flagged in a previous review but remains unaddressed. The optional chaining at line 221 could result in codeAfter being undefined if storage retrieval fails. The assertion at line 236 would then produce a confusing failure message ("Runtime code should match upgraded wasm") rather than indicating that the storage read failed.

Apply this diff to add an explicit undefined check:

         const codeAfter = (await paraApi.rpc.state.getStorage(":code"))?.toString();
+        expect(codeAfter, "Failed to read :code storage after upgrade").to.not.be.undefined;
         log(
           `Before: #${blockNumberBefore}, After: #${blockNumberAfter}, code changed=${
             codeAfter !== codeString
           }`
         );
🧹 Nitpick comments (4)
integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (4)

27-28: Remove unused imports.

u8aConcat and xxhashAsU8a are imported but never used in this file.

Apply this diff to remove the unused imports:

-import { u8aConcat } from "@polkadot/util";
-import { blake2AsHex, xxhashAsU8a } from "@polkadot/util-crypto";
+import { blake2AsHex } from "@polkadot/util-crypto";

245-246: Document the hardcoded storage key.

The hardcoded hex string for RelayTimestampNow storage key lacks documentation. If storage layout changes or a reader needs to verify this key is correct, there's no context about what it represents or how it was derived (e.g., twox128("RelayTimestampNow") or similar).

Consider adding a comment explaining the key derivation:

+        // Storage key for RelayTimestampNow - derived from twox128 hash of storage item name
         const relayTsStorageKey =
           "0x54dbd40f5201dbc18b0eed4b2ecd9cc67e2cdf745d68eeb295336330e3a1a063";

Alternatively, compute the key dynamically if the storage item is queryable via the API.


259-272: Improve retry failure messaging.

The retry loop attempts to read a non-zero relay timestamp up to 5 times, but if all retries are exhausted and tsRelay1 remains zero, the test continues to line 279 where the assertion will fail without explaining the retry context. This makes debugging failures more difficult.

Consider adding an explicit check after the retry loop:

         let tsRelay1 = 0n;
         let retries = 0;
         while (tsRelay1 === 0n && retries < 5) {
           log(`Attempt ${retries + 1}: reading RelayTimestampNow`);
           tsRelay1 = await readRelayTs();
           const rawDirect = await paraApi.rpc.state.getStorage(
             relayTsStorageKey
           );
           log(`RelayTimestampNow direct RPC read: ${rawDirect?.toHex() ?? "null"}`);
           if (tsRelay1 === 0n) {
             await context.waitBlock(1);
           }
           retries++;
         }
+        
+        if (tsRelay1 === 0n) {
+          throw new Error(
+            `RelayTimestampNow remained zero after ${retries} attempts; relay proofs may not be arriving`
+          );
+        }

280-298: Exact timestamp equality may be fragile.

Lines 283 and 298 assert that the parachain timestamp exactly equals the relay timestamp. This is a strong assumption that could be fragile if there's any clock skew, rounding differences, or timing variations in how the relay proof is processed. While this may work in the current implementation, consider whether approximate equality (within a small tolerance) would be more robust for long-term test maintenance.

If exact equality is a strict requirement of the implementation (i.e., parachain timestamp must precisely mirror relay timestamp), consider adding a comment explaining this requirement. Otherwise, consider using a tolerance-based check:

// If using approximate equality:
expect(
  Math.abs(Number(tsPara1 - tsRelay1)),
  "Parachain timestamp should be close to relay timestamp"
).to.be.lessThan(1000); // within 1 second
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 720edbf and 8d81301.

📒 Files selected for processing (1)
  • integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (3)
integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (3)

89-94: LGTM!

The specVersion logging provides useful diagnostic context before the upgrade.


149-157: LGTM!

The findCall helper provides a clean way to dynamically locate calls in the API metadata.


159-183: LGTM!

The discovery logic for upgrade calls is well-structured and includes helpful logging for debugging.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (3)
integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (3)

121-147: Missing timeout protection can cause indefinite hangs.

The txStatus helper still lacks timeout protection (as flagged in previous review). If a transaction never reaches isInBlock or isFinalized status, the promise will hang indefinitely.

As previously suggested, add timeout mechanism:

 const txStatus = async (tx: any, label: string) =>
   new Promise<void>((resolve, reject) => {
     let unsubscribe: (() => void) | undefined;
+    const timeout = setTimeout(() => {
+      unsubscribe?.();
+      reject(new Error(`${label} timed out after 60s`));
+    }, 60000);
     tx.signAndSend(alice, (result: any) => {
       if (result.dispatchError) {
         const errText = result.dispatchError.toString();
         log(`${label} dispatchError=${errText}`);
+        clearTimeout(timeout);
         reject(new Error(`${label} failed: ${errText}`));
         unsubscribe?.();
         return;
       }
       log(
         `${label} status=${result.status?.type ?? "unknown"}, events=${result.events
           ?.map((ev: any) => `${ev.event.section}.${ev.event.method}`)
           .join(",")}`
       );
       if (result.status?.isInBlock || result.status?.isFinalized) {
+        clearTimeout(timeout);
         unsubscribe?.();
         resolve();
       }
     })
       .then((unsub: () => void) => {
         unsubscribe = unsub;
       })
-      .catch(reject);
+      .catch((err) => {
+        clearTimeout(timeout);
+        reject(err);
+      });
   });

184-215: Dead code and undocumented timing remain unresolved.

Two issues from previous review are still present:

  1. Lines 189-190: The meta.args.length === 1 check is dead code. Polkadot SDK v16.5.2 defines authorizeUpgrade(codeHash: H256) with exactly one argument. The else branch (authorizeUpgrade(wasmHash, true)) is unreachable.

  2. Line 201: The waitBlock(2) call lacks documentation explaining why 2 blocks is necessary (OnSetCode scheduling for parachain upgrades).

Apply these changes:

-        const authorizeTx =
-          authorizeUpgrade.meta.args.length === 1
-            ? authorizeUpgrade(wasmHash)
-            : authorizeUpgrade(wasmHash, true);
+        const authorizeTx = authorizeUpgrade(wasmHash);
         log(
           `authorizeUpgrade located in section=${upgradeCallLocation.authorize}, args=${authorizeUpgrade.meta.args.length}`
         );
         await txStatus(
           paraApi.tx.sudo.sudo(authorizeTx),
           "authorizeUpgrade"
         );

-        log("Waiting for validation function approval");
+        log("Waiting for OnSetCode scheduling and block finalization (2 blocks)");
         await context.waitBlock(2);

221-236: Missing null check before code comparison.

The optional chaining at line 221 can result in codeAfter being undefined. If storage retrieval fails, the assertion at line 236 produces a misleading error message.

Add explicit null check as previously suggested:

         const codeAfter = (await paraApi.rpc.state.getStorage(":code"))?.toString();
+        expect(codeAfter, "Failed to read :code storage after upgrade").to.not.be.undefined;
         log(
           `Before: #${blockNumberBefore}, After: #${blockNumberAfter}, code changed=${
             codeAfter !== codeString
🧹 Nitpick comments (2)
integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (2)

259-272: Zero timestamp assumption may be incorrect.

The retry loop at line 268 assumes tsRelay1 === 0n indicates an invalid/missing timestamp. However, if the relay chain uses epoch-based timestamps, zero could theoretically be a valid (albeit unlikely) value. The current retry logic is pragmatic but could mask edge cases.

Consider logging when zero is encountered to aid debugging:

         if (tsRelay1 === 0n) {
+          log(`Warning: RelayTimestampNow is zero at attempt ${retries + 1}`);
           await context.waitBlock(1);
         }

274-302: Suggest helper for drift calculation.

The drift calculation and assertion logic is duplicated at lines 280-284 and 299-302. Extracting this into a helper would improve maintainability.

const checkDrift = (tsPara: bigint, tsRelay: bigint, label: string) => {
  const drift = tsPara > tsRelay ? tsPara - tsRelay : tsRelay - tsPara;
  const driftLimitMs = 5_000n;
  expect(drift, `${label}: Parachain timestamp should be close to relay timestamp`).to.be.lte(driftLimitMs);
  return drift;
};

// Usage:
checkDrift(tsPara1, tsRelay1, "First check");
// ... wait blocks ...
checkDrift(tsPara2, tsRelay2, "Second check");
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d81301 and 5614e63.

📒 Files selected for processing (3)
  • integration-tests/configs/zombieZeitgeist.json (1 hunks)
  • integration-tests/moonwall.config.json (2 hunks)
  • integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • integration-tests/moonwall.config.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (5)
integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (2)

89-94: LGTM: Enhanced upgrade diagnostics.

Logging the specVersion and upgrade path provides valuable context for debugging upgrade workflows.


149-157: LGTM: findCall utility is well-structured.

The utility cleanly locates runtime calls and returns both the call and its section, which is useful for diagnostics and dynamic call discovery.

integration-tests/configs/zombieZeitgeist.json (3)

31-32: LGTM: Collator port updates are consistent and aligned.

The collator RPC port (9954) matches the parachain endpoint configured in moonwall.config.json (line 68: "ws://127.0.0.1:9954"). The P2P port (33059) and RPC port follow the consistent offset pattern.


13-13: LGTM: RPC port 9957 correctly aligns with moonwall configuration.

The charlie node RPC port on line 13 is correctly set to 9957, which matches the ws://127.0.0.1:9957 endpoint in moonwall.config.json for the zombienet_zeitgeist_upgrade environment. Similarly, the bob node RPC port on line 18 (9960) is consistent with the configuration.

Also applies to: 18-18


25-26: No action needed. The parachain ID 2092 and chain name "zeitgeist" are correctly configured throughout the codebase:

  • Runtime constant in node/src/main.rs:38 defines POLKADOT_PARACHAIN_ID: u32 = 2092
  • XCM configuration in runtime/zeitgeist/src/xcm_config/config.rs:63 confirms ID: u32 = 2092

These values are consistent with the integration test configuration.

Comment on lines +245 to +246
const relayTsStorageKey =
"0x54dbd40f5201dbc18b0eed4b2ecd9cc67e2cdf745d68eeb295336330e3a1a063";
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Hardcoded storage key is fragile.

The storage key 0x54dbd40f5201dbc18b0eed4b2ecd9cc67e2cdf745d68eeb295336330e3a1a063 is hardcoded, making the code brittle to storage layout changes. Use the Polkadot API to derive the key dynamically.

Replace the hardcoded key with dynamic derivation:

-        const relayTsStorageKey =
-          "0x54dbd40f5201dbc18b0eed4b2ecd9cc67e2cdf745d68eeb295336330e3a1a063";
+        // Derive storage key: twox_128("RelayDataProvider") + twox_128("RelayTimestampNow")
+        const relayTsStorageKey = u8aConcat(
+          xxhashAsU8a("RelayDataProvider", 128),
+          xxhashAsU8a("RelayTimestampNow", 128)
+        );

This explains the previously imported but unused u8aConcat and xxhashAsU8a utilities.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const relayTsStorageKey =
"0x54dbd40f5201dbc18b0eed4b2ecd9cc67e2cdf745d68eeb295336330e3a1a063";
// Derive storage key: twox_128("RelayDataProvider") + twox_128("RelayTimestampNow")
const relayTsStorageKey = u8aConcat(
xxhashAsU8a("RelayDataProvider", 128),
xxhashAsU8a("RelayTimestampNow", 128)
);
🤖 Prompt for AI Agents
In
integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts
around lines 245-246, replace the hardcoded storage key with a dynamic
derivation using the Polkadot API/utility functions: derive the storage key by
hashing the pallet name and storage item name (and any key params) with
xxhashAsU8a and concatenating with u8aConcat (or simply call
api.query.<pallet>.<storage>.key(...)) to produce the correct storage key at
runtime; update the code to use the previously imported u8aConcat and
xxhashAsU8a (or api.query.*.key) so the key adapts to storage layout changes
instead of remaining hardcoded.

@Chralt98 Chralt98 changed the title Patch Polkadot SDK to include additional relay keys Add integration test for valid relay chain timestamp in parachain storage Dec 1, 2025
@Chralt98 Chralt98 changed the title Add integration test for valid relay chain timestamp in parachain storage Update integration test for valid relay chain timestamp in parachain storage Dec 1, 2025
@Chralt98 Chralt98 added s:review-needed The pull request requires reviews and removed s:in-progress The pull requests is currently being worked on labels Dec 1, 2025
@Chralt98 Chralt98 requested a review from robhyrk December 1, 2025 14:26
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (5)
integration-tests/configs/zombieZeitgeist.json (1)

7-7: Past relaychain mismatch issue resolved.

The relaychain is now correctly configured as rococo-local, which aligns with the test expectation at line 51 of test-zombienet-runtime-upgrade.ts that checks for "rococo" in the relay network specName.

integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (4)

188-190: Simplify the conditional—the else branch is dead code.

According to Polkadot SDK v16.5.2 documentation, authorizeUpgrade accepts a single argument (codeHash: H256). The meta.args.length === 1 check will always be true, making the else branch (authorizeUpgrade(wasmHash, true)) unreachable legacy code.

Apply this diff to remove the dead branch:

-        const authorizeTx =
-          authorizeUpgrade.meta.args.length === 1
-            ? authorizeUpgrade(wasmHash)
-            : authorizeUpgrade(wasmHash, true);
+        const authorizeTx = authorizeUpgrade(wasmHash);

244-245: Replace hardcoded storage key with dynamic derivation.

The hardcoded storage key 0x54dbd40f5201dbc18b0eed4b2ecd9cc67e2cdf745d68eeb295336330e3a1a063 is fragile and will break if the storage layout changes. Use the Polkadot API to derive the key dynamically or document the derivation.

Consider using the API's query interface to derive the key at runtime, or at minimum add a comment explaining the key derivation (e.g., "twox_128('RelayDataProvider') + twox_128('RelayTimestampNow')").


120-146: Add timeout protection to prevent indefinite hangs.

The txStatus helper lacks timeout protection. If a transaction never reaches isInBlock or isFinalized (due to network issues or dropped transactions), the promise will hang indefinitely, causing unclear test failures.

Consider adding a timeout mechanism as suggested in previous reviews.


220-235: Add explicit null check before code comparison.

The optional chaining at line 220 could result in codeAfter being undefined. The assertion at line 235 would then produce a misleading failure message rather than indicating that storage retrieval failed.

Apply this diff:

         const codeAfter = (await paraApi.rpc.state.getStorage(":code"))?.toString();
+        expect(codeAfter, "Failed to read :code storage after upgrade").to.not.be.undefined;
         log(
🧹 Nitpick comments (1)
integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (1)

199-200: Document the 2-block wait rationale.

While the 2-block wait is appropriate for parachain upgrade workflows (allowing OnSetCode scheduling and finalization), the code lacks an explanatory comment.

Add a brief comment:

         log("Waiting for validation function approval");
+        // Wait for OnSetCode scheduling and block finalization
         await context.waitBlock(2);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5614e63 and d9ae9d2.

📒 Files selected for processing (6)
  • integration-tests/README.md (1 hunks)
  • integration-tests/configs/hydradx.yml (1 hunks)
  • integration-tests/configs/zombieZeitgeist.json (1 hunks)
  • integration-tests/scripts/build-zeitgeist-spec.sh (1 hunks)
  • integration-tests/scripts/download-polkadot.sh (1 hunks)
  • integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • integration-tests/README.md
🧰 Additional context used
🪛 Shellcheck (0.11.0)
integration-tests/scripts/build-zeitgeist-spec.sh

[warning] 8-8: Quote this to prevent word splitting.

(SC2046)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (4)
integration-tests/scripts/download-polkadot.sh (1)

193-196: LGTM! Useful optimization for CI/dev workflows.

The early exit when POLKADOT_SKIP_DOWNLOAD is set provides a clean way to bypass downloads when binaries are already present, which is valuable for cached CI environments or local development.

integration-tests/configs/hydradx.yml (1)

1-5: LGTM! Improved resilience through endpoint redundancy.

Converting the endpoint to an array of four providers ensures failover capability and better reliability for integration tests.

integration-tests/scripts/build-zeitgeist-spec.sh (1)

11-69: LGTM! Robust approach to spec generation.

The targeted string replacement strategy avoids JSON parsing issues with large integers, and the runtime WASM extraction keeps the upgrade test artifact in sync with genesis code. Good error handling with warning messages when patterns fail.

integration-tests/tests/rt-upgrade-zombienet/test-zombienet-runtime-upgrade.ts (1)

239-303: LGTM! Comprehensive relay timestamp validation.

The new test T03 properly validates that:

  • Relay timestamp storage exists and is non-zero
  • Parachain and relay timestamps are synchronized within a reasonable drift tolerance (5s)
  • Relay timestamp increases monotonically across blocks

The retry logic (lines 258-271) handles transient read issues gracefully, and the u64 little-endian decoding is correct.

set -euo pipefail

# Always run the commands from the "integration-tests" dir
cd $(dirname $0)/..
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Quote the command substitution to prevent word splitting.

Shellcheck flags the unquoted $(dirname $0) which could break if the path contains spaces.

Apply this diff:

-cd $(dirname $0)/..
+cd "$(dirname "$0")/.."

Based on static analysis hints.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cd $(dirname $0)/..
cd "$(dirname "$0")/.."
🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 8-8: Quote this to prevent word splitting.

(SC2046)

🤖 Prompt for AI Agents
In integration-tests/scripts/build-zeitgeist-spec.sh around line 8, the command
substitution cd $(dirname $0)/.. is unquoted which can break when the path
contains spaces; change it to quote the substitution like cd "$(dirname
"$0")/.." (or assign to a variable first) so the resulting path is treated as a
single word and avoid word-splitting.

@codecov-commenter
Copy link

codecov-commenter commented Dec 1, 2025

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.28%. Comparing base (20807c7) to head (d9ae9d2).
⚠️ Report is 1 commits behind head on main.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1456   +/-   ##
=======================================
  Coverage   93.28%   93.28%           
=======================================
  Files         181      181           
  Lines       35049    35049           
=======================================
  Hits        32696    32696           
  Misses       2353     2353           
Flag Coverage Δ
tests 93.28% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

timeout: 120000,
test: async function () {
const relayTsStorageKey =
"0x54dbd40f5201dbc18b0eed4b2ecd9cc67e2cdf745d68eeb295336330e3a1a063";
Copy link

Choose a reason for hiding this comment

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

Is it okay to hardcode this value? relayTsStorageKey

Copy link
Member Author

Choose a reason for hiding this comment

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

It's just a storage key called pallet runtime and storage item name RelayTimestampNow. If it were to change, we would know instantly by this test.

Exactly what we have in /Users/chralt/development/zeitgeist/runtime/common/src/relay_timestamp.rs:

struct RelayTimestampNowPrefix;
impl StorageInstance for RelayTimestampNowPrefix {
    const STORAGE_PREFIX: &'static str = "RelayTimestampNow";

    fn pallet_prefix() -> &'static str {
        "runtime"
    }
}
Screenshot 2025-12-03 at 10 15 02 Screenshot 2025-12-03 at 10 15 24

unsubscribe = unsub;
})
.catch(reject);
});
Copy link

Choose a reason for hiding this comment

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

Is there a risk of indefinite hangs here if the TX fails to reach isInBlock or isFinalized?

Copy link
Member Author

Choose a reason for hiding this comment

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

Should be fixed by: #1459

@Chralt98 Chralt98 added s:accepted This pull request is ready for merge and removed s:review-needed The pull request requires reviews labels Dec 1, 2025
@Chralt98 Chralt98 merged commit 39ad8d6 into main Dec 1, 2025
34 checks passed
@Chralt98 Chralt98 deleted the chralt98-relay-timestamp-tests branch December 1, 2025 18:05
Chralt98 added a commit that referenced this pull request Dec 2, 2025
#1458)

Update integration test for valid relay chain timestamp in parachain storage (#1456)

Co-authored-by: Chralt <chralt.developer@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

s:accepted This pull request is ready for merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants