From 942c2e46c54d12a90d573214053e753c1912a68a Mon Sep 17 00:00:00 2001 From: Igor Trofimov Date: Mon, 15 Apr 2024 10:37:24 +0000 Subject: [PATCH 1/5] Integration test: Set scheduled enclave --- .github/workflows/ci.yml | 2 +- ...lit-scheduled-enclave-multiworker-test.yml | 28 ++++++++++ .../integration-tests/common/transactions.ts | 55 +++++++++++++++++- .../scheduled_enclave.test.ts | 56 +++++++++++++------ 4 files changed, 122 insertions(+), 19 deletions(-) create mode 100644 tee-worker/docker/lit-scheduled-enclave-multiworker-test.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3cf2ccfc18..0271e6b296 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -781,7 +781,7 @@ jobs: - test_name: lit-di-vc-multiworker-test - test_name: lit-dr-vc-multiworker-test - test_name: lit-resume-worker - - test_name: lit-scheduled-enclave-test + - test_name: lit-scheduled-enclave-multiworker-test steps: - uses: actions/checkout@v4 diff --git a/tee-worker/docker/lit-scheduled-enclave-multiworker-test.yml b/tee-worker/docker/lit-scheduled-enclave-multiworker-test.yml new file mode 100644 index 0000000000..c0854e4ff1 --- /dev/null +++ b/tee-worker/docker/lit-scheduled-enclave-multiworker-test.yml @@ -0,0 +1,28 @@ +services: + lit-scheduled-enclave-multiworker-test: + image: litentry/litentry-cli:latest + container_name: litentry-scheduled-enclave-multiworker-test + volumes: + - ../ts-tests:/ts-tests + - ../client-api:/client-api + - ../cli:/usr/local/worker-cli + build: + context: .. + dockerfile: build.Dockerfile + target: deployed-client + depends_on: + litentry-node: + condition: service_healthy + litentry-worker-1: + condition: service_healthy + litentry-worker-2: + condition: service_healthy + litentry-worker-3: + condition: service_healthy + networks: + - litentry-test-network + entrypoint: "bash -c '/usr/local/worker-cli/lit_ts_integration_test.sh scheduled_enclave.test.ts 2>&1' " + restart: "no" +networks: + litentry-test-network: + driver: bridge diff --git a/tee-worker/ts-tests/integration-tests/common/transactions.ts b/tee-worker/ts-tests/integration-tests/common/transactions.ts index a57625ced7..5081c41094 100644 --- a/tee-worker/ts-tests/integration-tests/common/transactions.ts +++ b/tee-worker/ts-tests/integration-tests/common/transactions.ts @@ -1,6 +1,6 @@ import type { IntegrationTestContext } from './common-types'; -import { FrameSystemEventRecord } from 'parachain-api'; +import { ApiPromise, FrameSystemEventRecord } from 'parachain-api'; // for DI-test export const subscribeToEventsWithExtHash = async ( @@ -46,3 +46,56 @@ export const subscribeToEventsWithExtHash = async ( }); }); }; + +// for II-test +export const subscribeToEvents = async ( + section: string, + method: string, + api: ApiPromise +): Promise => { + return new Promise((resolve, reject) => { + let blocksToScan = 15; + const unsubscribe = api.rpc.chain.subscribeNewHeads(async (blockHeader) => { + const shiftedApi = await api.at(blockHeader.hash); + + const allBlockEvents = await shiftedApi.query.system.events(); + const allExtrinsicEvents = allBlockEvents.filter(({ phase }) => phase.isApplyExtrinsic); + + const matchingEvent = allExtrinsicEvents.filter(({ event, phase }) => { + return event.section === section && event.method === method; + }); + + if (matchingEvent.length == 0) { + blocksToScan -= 1; + if (blocksToScan < 1) { + reject(new Error(`timed out listening for event ${section}.${method}`)); + (await unsubscribe)(); + } + return; + } + + resolve(matchingEvent); + (await unsubscribe)(); + }); + }); +}; + +export async function waitForBlock(api: ApiPromise, blockNumber: number, blocksToCheck = 5) { + let count = 0; + + return new Promise((resolve, reject) => { + const unsubscribe = api.rpc.chain.subscribeNewHeads(async (header) => { + console.log(`Chain is at block: #${header.number}`); + + if (header.number.toNumber() === blockNumber) { + (await unsubscribe)(); + resolve(); + } + + if (++count === blocksToCheck) { + (await unsubscribe)(); + reject(new Error(`Timeout: Block #${blockNumber} not reached within ${blocksToCheck} blocks.`)); + } + }); + }); +} diff --git a/tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts b/tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts index 85ae3fb599..de781d1087 100644 --- a/tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts +++ b/tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts @@ -3,10 +3,36 @@ import { step } from 'mocha-steps'; import { initIntegrationTestContext } from './common/utils'; import { getTeeShieldingKey } from './common/di-utils'; import type { IntegrationTestContext, JsonRpcRequest } from './common/common-types'; -import { decodeRpcBytesAsString, sendRequest } from './common/call'; -import type { CorePrimitivesIdentity } from 'parachain-api'; +import { sendRequest } from './common/call'; +import { Keyring, type ApiPromise, type CorePrimitivesIdentity } from 'parachain-api'; import { assert } from 'chai'; import { createJsonRpcRequest, nextRequestId } from './common/helpers'; +import { hexToU8a } from '@polkadot/util'; +import { subscribeToEvents, waitForBlock } from './common/transactions'; + +async function setScheduledEnclave(api: ApiPromise, block: number, mrenclave: string) { + const keyring = new Keyring({ type: 'sr25519' }); + const alice = keyring.addFromUri('//Alice'); + + const tx = api.tx.teebag.setScheduledEnclave('Identity', block, hexToU8a(`0x${mrenclave}`)); + + console.log('Schedule Enclave Extrinsic sent'); + return new Promise<{ block: string }>(async (resolve, reject) => { + await tx.signAndSend(alice, (result) => { + console.log(`Current status is ${result.status}`); + if (result.status.isInBlock) { + console.log(`Transaction included at blockHash ${result.status.asInBlock}`); + } else if (result.status.isFinalized) { + console.log(`Transaction finalized at blockHash ${result.status.asFinalized}`); + resolve({ + block: result.status.asFinalized.toString(), + }); + } else if (result.status.isInvalid) { + reject(`Transaction is ${result.status}`); + } + }); + }); +} describe('Scheduled Enclave', function () { let context: IntegrationTestContext = undefined as any; @@ -53,22 +79,18 @@ describe('Scheduled Enclave', function () { assert.equal(scheduledEnclaveList.length, 1); // set new mrenclave - let setEnclaveResponse = await callRPC( - buildRequest('state_setScheduledEnclave', [20, 'some invalid mrenclave']) - ); - assert.include(decodeRpcBytesAsString(setEnclaveResponse.value), 'Failed to decode mrenclave'); + const lastBlock = await context.api.rpc.chain.getBlock(); + const expectedBlockNumber = lastBlock.block.header.number.toNumber() + 2; + console.log(`expected mrenclave block number: ${expectedBlockNumber}`); - setEnclaveResponse = await callRPC(buildRequest('state_setScheduledEnclave', [20, '48656c6c6f20776f726c6421'])); - assert.include( - decodeRpcBytesAsString(setEnclaveResponse.value), - 'mrenclave len mismatch, expected 32 bytes long' - ); + const validMrEnclave = '97f516a61ff59c5eab74b8a9b1b7273d6986b9c0e6c479a4010e22402ca7cee6'; + + await setScheduledEnclave(context.api, expectedBlockNumber, validMrEnclave); + const events = await subscribeToEvents('teebag', 'ScheduledEnclaveSet', context.api); + assert.equal(events.length, 1); - // valid mutation - const validParams = [20, '97f516a61ff59c5eab74b8a9b1b7273d6986b9c0e6c479a4010e22402ca7cee6']; - await callRPC(buildRequest('state_setScheduledEnclave', validParams)); + await waitForBlock(context.api, expectedBlockNumber); - // checking mutated state response = await callRPC(buildRequest('state_getScheduledEnclave')); scheduledEnclaveList = context.api.createType('Vec<(u64, [u8; 32])>', response.value).toJSON() as [ number, @@ -78,7 +100,7 @@ describe('Scheduled Enclave', function () { assert.equal(scheduledEnclaveList.length, 2); const [blockNumber, mrEnclave] = scheduledEnclaveList[1]; - assert.equal(blockNumber, validParams[0]); - assert.equal(mrEnclave, `0x${validParams[1]}`); + assert.equal(blockNumber, expectedBlockNumber); + assert.equal(mrEnclave, `0x${validMrEnclave}`); }); }); From 3b7a4d7c9e8938fbbed3200147787ea7f1136c0d Mon Sep 17 00:00:00 2001 From: Igor Trofimov Date: Mon, 15 Apr 2024 11:12:56 +0000 Subject: [PATCH 2/5] omit listening the event --- .../ts-tests/integration-tests/scheduled_enclave.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts b/tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts index de781d1087..8e445958d3 100644 --- a/tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts +++ b/tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts @@ -86,9 +86,6 @@ describe('Scheduled Enclave', function () { const validMrEnclave = '97f516a61ff59c5eab74b8a9b1b7273d6986b9c0e6c479a4010e22402ca7cee6'; await setScheduledEnclave(context.api, expectedBlockNumber, validMrEnclave); - const events = await subscribeToEvents('teebag', 'ScheduledEnclaveSet', context.api); - assert.equal(events.length, 1); - await waitForBlock(context.api, expectedBlockNumber); response = await callRPC(buildRequest('state_getScheduledEnclave')); From 4702bb18d00308ccf541a977b2c8c408daa9d889 Mon Sep 17 00:00:00 2001 From: Igor Trofimov Date: Mon, 15 Apr 2024 11:23:55 +0000 Subject: [PATCH 3/5] set alice as admin --- .../integration-tests/common/transactions.ts | 66 ++++++++++++++++++- .../scheduled_enclave.test.ts | 26 +------- 2 files changed, 66 insertions(+), 26 deletions(-) diff --git a/tee-worker/ts-tests/integration-tests/common/transactions.ts b/tee-worker/ts-tests/integration-tests/common/transactions.ts index 5081c41094..3b86e2db88 100644 --- a/tee-worker/ts-tests/integration-tests/common/transactions.ts +++ b/tee-worker/ts-tests/integration-tests/common/transactions.ts @@ -1,6 +1,14 @@ +import { hexToU8a } from '@polkadot/util'; import type { IntegrationTestContext } from './common-types'; -import { ApiPromise, FrameSystemEventRecord } from 'parachain-api'; +import { + AddressOrPair, + ApiPromise, + ApiTypes, + FrameSystemEventRecord, + Keyring, + SubmittableExtrinsic, +} from 'parachain-api'; // for DI-test export const subscribeToEventsWithExtHash = async ( @@ -99,3 +107,59 @@ export async function waitForBlock(api: ApiPromise, blockNumber: number, blocksT }); }); } + +async function setAliceAsAdmin(api: ApiPromise) { + // Get keyring of Alice, who is also the sudo in dev chain spec + const keyring = new Keyring({ type: 'sr25519' }); + const alice = keyring.addFromUri('//Alice'); + + const tx = await sudoWrapperGC(api, api.tx.teebag.setAdmin('esqZdrqhgH8zy1wqYh1aLKoRyoRWLFbX9M62eKfaTAoK67pJ5')); + + console.log(`Setting Alice as Admin for Teebag`); + return signAndSend(tx, alice); +} + +export function signAndSend(tx: SubmittableExtrinsic, account: AddressOrPair) { + return new Promise<{ block: string }>(async (resolve, reject) => { + await tx.signAndSend(account, (result) => { + console.log(`Current status is ${result.status}`); + if (result.status.isInBlock) { + console.log(`Transaction included at blockHash ${result.status.asInBlock}`); + } else if (result.status.isFinalized) { + console.log(`Transaction finalized at blockHash ${result.status.asFinalized}`); + resolve({ + block: result.status.asFinalized.toString(), + }); + } else if (result.status.isInvalid) { + reject(`Transaction is ${result.status}`); + } + }); + }); +} + +// After removing the sudo module, we use `EnsureRootOrHalfCouncil` instead of `Sudo`, +// and there are only two council members in litmus-dev/rococo-dev/litentry-dev. +// So only `propose` is required, no vote. +// +// TODO: support to send the `vote extrinsic`, if the number of council members is greater than 2. +export async function sudoWrapperGC(api: ApiPromise, tx: SubmittableExtrinsic) { + const chain = (await api.rpc.system.chain()).toString().toLowerCase(); + if (chain != 'rococo-dev') { + const threshold = api.createType('Compact', 1); + const call = api.createType('Call', tx); + return api.tx.council.propose(threshold, call, api.createType('Compact', tx.length)); + } else { + return api.tx.sudo.sudo(tx); + } +} + +export async function setScheduledEnclave(api: ApiPromise, block: number, mrenclave: string) { + const keyring = new Keyring({ type: 'sr25519' }); + const alice = keyring.addFromUri('//Alice'); + + await setAliceAsAdmin(api); + const tx = api.tx.teebag.setScheduledEnclave('Identity', block, hexToU8a(`0x${mrenclave}`)); + + console.log('Schedule Enclave Extrinsic sent'); + return signAndSend(tx, alice); +} diff --git a/tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts b/tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts index 8e445958d3..53c6f2a068 100644 --- a/tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts +++ b/tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts @@ -8,31 +8,7 @@ import { Keyring, type ApiPromise, type CorePrimitivesIdentity } from 'parachain import { assert } from 'chai'; import { createJsonRpcRequest, nextRequestId } from './common/helpers'; import { hexToU8a } from '@polkadot/util'; -import { subscribeToEvents, waitForBlock } from './common/transactions'; - -async function setScheduledEnclave(api: ApiPromise, block: number, mrenclave: string) { - const keyring = new Keyring({ type: 'sr25519' }); - const alice = keyring.addFromUri('//Alice'); - - const tx = api.tx.teebag.setScheduledEnclave('Identity', block, hexToU8a(`0x${mrenclave}`)); - - console.log('Schedule Enclave Extrinsic sent'); - return new Promise<{ block: string }>(async (resolve, reject) => { - await tx.signAndSend(alice, (result) => { - console.log(`Current status is ${result.status}`); - if (result.status.isInBlock) { - console.log(`Transaction included at blockHash ${result.status.asInBlock}`); - } else if (result.status.isFinalized) { - console.log(`Transaction finalized at blockHash ${result.status.asFinalized}`); - resolve({ - block: result.status.asFinalized.toString(), - }); - } else if (result.status.isInvalid) { - reject(`Transaction is ${result.status}`); - } - }); - }); -} +import { setScheduledEnclave, signAndSend, subscribeToEvents, waitForBlock } from './common/transactions'; describe('Scheduled Enclave', function () { let context: IntegrationTestContext = undefined as any; From 2a5bf185ae4b502ef2e1dcb717309a967659013a Mon Sep 17 00:00:00 2001 From: Igor Trofimov Date: Mon, 15 Apr 2024 11:53:31 +0000 Subject: [PATCH 4/5] cleanup --- .../integration-tests/common/transactions.ts | 3 +-- .../integration-tests/scheduled_enclave.test.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/tee-worker/ts-tests/integration-tests/common/transactions.ts b/tee-worker/ts-tests/integration-tests/common/transactions.ts index 3b86e2db88..e1a1881bc3 100644 --- a/tee-worker/ts-tests/integration-tests/common/transactions.ts +++ b/tee-worker/ts-tests/integration-tests/common/transactions.ts @@ -108,7 +108,7 @@ export async function waitForBlock(api: ApiPromise, blockNumber: number, blocksT }); } -async function setAliceAsAdmin(api: ApiPromise) { +export async function setAliceAsAdmin(api: ApiPromise) { // Get keyring of Alice, who is also the sudo in dev chain spec const keyring = new Keyring({ type: 'sr25519' }); const alice = keyring.addFromUri('//Alice'); @@ -157,7 +157,6 @@ export async function setScheduledEnclave(api: ApiPromise, block: number, mrencl const keyring = new Keyring({ type: 'sr25519' }); const alice = keyring.addFromUri('//Alice'); - await setAliceAsAdmin(api); const tx = api.tx.teebag.setScheduledEnclave('Identity', block, hexToU8a(`0x${mrenclave}`)); console.log('Schedule Enclave Extrinsic sent'); diff --git a/tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts b/tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts index 53c6f2a068..97e56ec6af 100644 --- a/tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts +++ b/tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts @@ -4,16 +4,15 @@ import { initIntegrationTestContext } from './common/utils'; import { getTeeShieldingKey } from './common/di-utils'; import type { IntegrationTestContext, JsonRpcRequest } from './common/common-types'; import { sendRequest } from './common/call'; -import { Keyring, type ApiPromise, type CorePrimitivesIdentity } from 'parachain-api'; +import { type CorePrimitivesIdentity } from 'parachain-api'; import { assert } from 'chai'; import { createJsonRpcRequest, nextRequestId } from './common/helpers'; -import { hexToU8a } from '@polkadot/util'; -import { setScheduledEnclave, signAndSend, subscribeToEvents, waitForBlock } from './common/transactions'; +import { setAliceAsAdmin, setScheduledEnclave, waitForBlock } from './common/transactions'; describe('Scheduled Enclave', function () { - let context: IntegrationTestContext = undefined as any; - let teeShieldingKey: KeyObject = undefined as any; - let aliceSubstrateIdentity: CorePrimitivesIdentity = undefined as any; + let context: IntegrationTestContext; + let teeShieldingKey: KeyObject; + let aliceSubstrateIdentity: CorePrimitivesIdentity; this.timeout(6000000); @@ -24,6 +23,7 @@ describe('Scheduled Enclave', function () { ); teeShieldingKey = await getTeeShieldingKey(context); aliceSubstrateIdentity = await context.web3Wallets.substrate.Alice.getIdentity(context); + await setAliceAsAdmin(context.api); }); step('state of scheduled enclave list', async function () { From d9df608869f208bd40197749102fa3e1466df4c0 Mon Sep 17 00:00:00 2001 From: Igor Trofimov Date: Mon, 15 Apr 2024 12:24:06 +0000 Subject: [PATCH 5/5] increase expected block number --- tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts b/tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts index 97e56ec6af..5ac1d19a33 100644 --- a/tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts +++ b/tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts @@ -56,7 +56,7 @@ describe('Scheduled Enclave', function () { // set new mrenclave const lastBlock = await context.api.rpc.chain.getBlock(); - const expectedBlockNumber = lastBlock.block.header.number.toNumber() + 2; + const expectedBlockNumber = lastBlock.block.header.number.toNumber() + 5; console.log(`expected mrenclave block number: ${expectedBlockNumber}`); const validMrEnclave = '97f516a61ff59c5eab74b8a9b1b7273d6986b9c0e6c479a4010e22402ca7cee6';