Skip to content

Commit

Permalink
Integration test: Set scheduled enclave (#2664)
Browse files Browse the repository at this point in the history
* Integration test: Set scheduled enclave

* omit listening the event

* set alice as admin

* cleanup

* increase expected block number
  • Loading branch information
Traf333 committed Apr 15, 2024
1 parent 81b0d0d commit d28fea0
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 22 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
28 changes: 28 additions & 0 deletions tee-worker/docker/lit-scheduled-enclave-multiworker-test.yml
Original file line number Diff line number Diff line change
@@ -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
118 changes: 117 additions & 1 deletion tee-worker/ts-tests/integration-tests/common/transactions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { hexToU8a } from '@polkadot/util';
import type { IntegrationTestContext } from './common-types';

import { FrameSystemEventRecord } from 'parachain-api';
import {
AddressOrPair,
ApiPromise,
ApiTypes,
FrameSystemEventRecord,
Keyring,
SubmittableExtrinsic,
} from 'parachain-api';

// for DI-test
export const subscribeToEventsWithExtHash = async (
Expand Down Expand Up @@ -46,3 +54,111 @@ export const subscribeToEventsWithExtHash = async (
});
});
};

// for II-test
export const subscribeToEvents = async (
section: string,
method: string,
api: ApiPromise
): Promise<FrameSystemEventRecord[]> => {
return new Promise<FrameSystemEventRecord[]>((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<void>((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.`));
}
});
});
}

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');

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<ApiTypes>, 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<ApiTypes>) {
const chain = (await api.rpc.system.chain()).toString().toLowerCase();
if (chain != 'rococo-dev') {
const threshold = api.createType('Compact<u32>', 1);
const call = api.createType('Call', tx);
return api.tx.council.propose(threshold, call, api.createType('Compact<u32>', 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');

const tx = api.tx.teebag.setScheduledEnclave('Identity', block, hexToU8a(`0x${mrenclave}`));

console.log('Schedule Enclave Extrinsic sent');
return signAndSend(tx, alice);
}
35 changes: 15 additions & 20 deletions tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ 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 { type CorePrimitivesIdentity } from 'parachain-api';
import { assert } from 'chai';
import { createJsonRpcRequest, nextRequestId } from './common/helpers';
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);

Expand All @@ -22,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 () {
Expand Down Expand Up @@ -53,22 +55,15 @@ 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() + 5;
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';

// valid mutation
const validParams = [20, '97f516a61ff59c5eab74b8a9b1b7273d6986b9c0e6c479a4010e22402ca7cee6'];
await callRPC(buildRequest('state_setScheduledEnclave', validParams));
await setScheduledEnclave(context.api, expectedBlockNumber, validMrEnclave);
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,
Expand All @@ -78,7 +73,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}`);
});
});

0 comments on commit d28fea0

Please sign in to comment.