From f293766fe9cac1a372de72e9e43212d2cafb0028 Mon Sep 17 00:00:00 2001 From: Gonza Montiel Date: Wed, 5 Nov 2025 17:06:01 +0100 Subject: [PATCH] fix: safe mode test (#275) ## Fix safe mode test - Fix test isolation so safe mode is exited gracefully and doesn't affect other tests execution - Refactored test for readability adding a helper to check for events in block - Added some test scenarios entering and exiting safe mode --- .../common/test-block/test-block-safe-mode.ts | 192 +++++++++++++----- 1 file changed, 146 insertions(+), 46 deletions(-) diff --git a/test/datahaven/suites/dev/common/test-block/test-block-safe-mode.ts b/test/datahaven/suites/dev/common/test-block/test-block-safe-mode.ts index 5cde0875..8520e461 100644 --- a/test/datahaven/suites/dev/common/test-block/test-block-safe-mode.ts +++ b/test/datahaven/suites/dev/common/test-block/test-block-safe-mode.ts @@ -1,4 +1,4 @@ -import { beforeAll, beforeEach, describeSuite, expect } from "@moonwall/cli"; +import { afterEach, beforeAll, beforeEach, describeSuite, expect } from "@moonwall/cli"; import type { ApiPromise } from "@polkadot/api"; describeSuite({ @@ -12,13 +12,8 @@ describeSuite({ api = context.polkadotJs(); }); - async function getSubstrateBlockNumber(): Promise { - const blockNumber = await api.query.system.number(); - return blockNumber.toNumber(); - } - beforeEach(async () => { - // Check if safe mode is already active from genesis + // Ensure safe mode is active let enteredUntil = (await api.query.safeMode.enteredUntil()) as any; if (!enteredUntil.isSome) { @@ -26,12 +21,67 @@ describeSuite({ const sudoTx = api.tx.sudo.sudo(enterSafeModeCall); await context.createBlock(sudoTx); - enteredUntil = (await api.query.safeMode.enteredUntil()) as any; - } + await context.createBlock(); - expect(enteredUntil.isSome, "Safe mode should be active").to.be.true; + enteredUntil = (await api.query.safeMode.enteredUntil()) as any; + expect(enteredUntil.isSome, "Safe mode should be active after entering").to.be.true; + } }); + afterEach(async () => { + // Exit safe mode and verify + const exitBlockBefore = await getSubstrateBlockNumber(); + const exitSafeModeCall = api.tx.safeMode.forceExit(); + const exitSudoTx = api.tx.sudo.sudo(exitSafeModeCall); + + const blockHash = await context.createBlock(exitSudoTx); + + const safeModeExited = await checkEvent("safeMode.Exited", blockHash); + const sudoExecuted = await checkEvent("sudo.Sudid", blockHash); + const extrinsicFailed = await checkEvent("system.ExtrinsicFailed", blockHash); + + expect(safeModeExited, "SafeMode.Exited event should be present").to.be.true; + expect(sudoExecuted, "Sudo.Sudid event should be present").to.be.true; + expect(extrinsicFailed, "Extrinsic should not have failed").to.be.false; + + const apiAtBlock = await getApiAtBlock(blockHash); + const enteredUntilAtExitBlock = (await apiAtBlock.query.safeMode.enteredUntil()) as any; + + expect(!enteredUntilAtExitBlock.isSome, "Safe mode should be deactivated.").to.be.true; + + await context.createBlock(); + const exitBlockAfter = await getSubstrateBlockNumber(); + expect(exitBlockAfter, "Should be able to create blocks after exit").to.be.greaterThan( + exitBlockBefore + ); + }); + + async function getSubstrateBlockNumber(): Promise { + const blockNumber = await api.query.system.number(); + return blockNumber.toNumber(); + } + + async function getApiAtBlock( + blockHash?: string | Awaited> + ) { + const blockHashStr = + typeof blockHash === "string" ? blockHash : (await api.rpc.chain.getBlockHash()).toString(); + return await api.at(blockHashStr); + } + + async function checkEvent( + eventName: string, + blockHash?: string | Awaited> + ): Promise { + const [section, method] = eventName.split("."); + const apiAtBlock = await getApiAtBlock(blockHash); + const events = await apiAtBlock.query.system.events(); + return events.some((record: any) => { + const { event } = record; + return event.section === section && event.method === method; + }); + } + it({ id: "T01", title: "should produce blocks while in safe mode", @@ -50,25 +100,6 @@ describeSuite({ blocksToCreate, "Blocks should continue to be produced in safe mode" ); - - const exitBlockBefore = await getSubstrateBlockNumber(); - const exitSafeModeCall = api.tx.safeMode.forceExit(); - const exitSudoTx = api.tx.sudo.sudo(exitSafeModeCall); - - await context.createBlock(exitSudoTx); - - // Verify the exit block was created (ensures state is updated) - const exitBlockAfter = await getSubstrateBlockNumber(); - expect(exitBlockAfter, "Exit block should have been created").to.be.greaterThan( - exitBlockBefore - ); - - const enteredUntilAfterExit = (await api.query.safeMode.enteredUntil()) as any; - expect(!enteredUntilAfterExit.isSome, "Safe mode should be deactivated").to.be.true; - - await context.createBlock(); - const finalBlock = await getSubstrateBlockNumber(); - expect(finalBlock).to.be.greaterThan(currentBlock); } }); @@ -78,34 +109,103 @@ describeSuite({ test: async () => { const startBlock = await getSubstrateBlockNumber(); - // Create a block - this implicitly includes timestamp extrinsic - // The fact that this succeeds proves Timestamp is whitelisted await context.createBlock(); - // Verify the block was created (contains valid timestamp) const block = await context.viem().getBlock({ blockTag: "latest" }); expect(Number(block.timestamp)).to.be.greaterThan(0); - // Verify block number increased const currentBlock = await getSubstrateBlockNumber(); expect(currentBlock).to.be.greaterThan(startBlock); + } + }); - // Exit safe mode - const exitBlockBefore = await getSubstrateBlockNumber(); - const exitSafeModeCall = api.tx.safeMode.forceExit(); - const exitSudoTx = api.tx.sudo.sudo(exitSafeModeCall); + it({ + id: "T03", + title: "should allow system.remark calls in safe mode", + test: async () => { + const remarkData = "0x48656c6c6f"; // "Hello" in hex + const remarkTx = api.tx.system.remarkWithEvent(remarkData); - await context.createBlock(exitSudoTx); + const blockHash = await context.createBlock(remarkTx); - // Verify the exit block was created (ensures state is updated) - const exitBlockAfter = await getSubstrateBlockNumber(); - expect(exitBlockAfter, "Exit block should have been created").to.be.greaterThan( - exitBlockBefore - ); + const remarkExecuted = await checkEvent("system.Remarked", blockHash); + const extrinsicSuccess = await checkEvent("system.ExtrinsicSuccess", blockHash); - // Verify we exited safe mode - const enteredUntilAfterExit = (await api.query.safeMode.enteredUntil()) as any; - expect(!enteredUntilAfterExit.isSome, "Safe mode should be deactivated").to.be.true; + expect(remarkExecuted, "System.Remarked event should be present").to.be.true; + expect(extrinsicSuccess, "Extrinsic should have succeeded").to.be.true; + } + }); + + it({ + id: "T04", + title: "should allow preimage.notePreimage calls in safe mode", + test: async () => { + const preimageData = api.tx.system.remarkWithEvent("0x1234").method.toHex(); + const notePreimageTx = api.tx.preimage.notePreimage(preimageData); + + const blockHash = await context.createBlock(notePreimageTx); + + const preimageNoted = await checkEvent("preimage.Noted", blockHash); + const extrinsicSuccess = await checkEvent("system.ExtrinsicSuccess", blockHash); + + expect(preimageNoted, "Preimage.Noted event should be present").to.be.true; + expect(extrinsicSuccess, "Extrinsic should have succeeded").to.be.true; + } + }); + + it({ + id: "T05", + title: "should allow scheduler calls in safe mode", + test: async () => { + const currentBlock = await getSubstrateBlockNumber(); + const scheduleAtBlock = currentBlock + 10; + + const taskId = new Uint8Array(32).fill(0); + taskId.set(new TextEncoder().encode("testTask"), 0); + const call = api.tx.system.remarkWithEvent("0xabcd"); + const scheduleTx = api.tx.scheduler.scheduleNamed(taskId, scheduleAtBlock, null, 0, call); + const sudoTx = api.tx.sudo.sudo(scheduleTx); + const blockHash = await context.createBlock(sudoTx); + + const sudoExecuted = await checkEvent("sudo.Sudid", blockHash); + const scheduled = await checkEvent("scheduler.Scheduled", blockHash); + + expect(sudoExecuted, "Sudo.Sudid event should be present").to.be.true; + expect(scheduled, "Scheduler.Scheduled event should be present").to.be.true; + } + }); + + it({ + id: "T06", + title: "should allow txPause.pause calls in safe mode via sudo", + test: async () => { + const pauseCall = api.tx.txPause.pause(["System", "remark_with_event"]); + const sudoTx = api.tx.sudo.sudo(pauseCall); + const blockHash = await context.createBlock(sudoTx); + const callPaused = await checkEvent("txPause.CallPaused", blockHash); + expect(callPaused, "System.remark_with_event should have been paused").to.be.true; + + const remarkTx = api.tx.system.remarkWithEvent("0xpaused"); + let remarkFailed = false; + try { + await context.createBlock(remarkTx); + } catch (error: any) { + remarkFailed = error.message.includes("Transaction call is not expected"); + } + expect(remarkFailed, "Remark call should have been rejected due to pause").to.be.true; + + // Unpause the call + const unpauseCall = api.tx.txPause.unpause(["System", "remark_with_event"]); + const unpauseSudoTx = api.tx.sudo.sudo(unpauseCall); + const unpauseBlockHash = await context.createBlock(unpauseSudoTx); + const callUnpaused = await checkEvent("txPause.CallUnpaused", unpauseBlockHash); + expect(callUnpaused, "System.remark_with_event should have been unpaused").to.be.true; + + // Verify remark now works after unpausing + const remarkTx2 = api.tx.system.remarkWithEvent("0xunpaused"); + const remarkBlockHash = await context.createBlock(remarkTx2); + const remarkSuccess = await checkEvent("system.ExtrinsicSuccess", remarkBlockHash); + expect(remarkSuccess, "Remark call should succeed after unpausing").to.be.true; } }); }