Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration test: Set scheduled enclave #2664

Merged
merged 6 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}`);
});
});