Skip to content

Commit d28fea0

Browse files
author
Igor Trofimov
authored
Integration test: Set scheduled enclave (#2664)
* Integration test: Set scheduled enclave * omit listening the event * set alice as admin * cleanup * increase expected block number
1 parent 81b0d0d commit d28fea0

File tree

4 files changed

+161
-22
lines changed

4 files changed

+161
-22
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -781,7 +781,7 @@ jobs:
781781
- test_name: lit-di-vc-multiworker-test
782782
- test_name: lit-dr-vc-multiworker-test
783783
- test_name: lit-resume-worker
784-
- test_name: lit-scheduled-enclave-test
784+
- test_name: lit-scheduled-enclave-multiworker-test
785785
steps:
786786
- uses: actions/checkout@v4
787787

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
services:
2+
lit-scheduled-enclave-multiworker-test:
3+
image: litentry/litentry-cli:latest
4+
container_name: litentry-scheduled-enclave-multiworker-test
5+
volumes:
6+
- ../ts-tests:/ts-tests
7+
- ../client-api:/client-api
8+
- ../cli:/usr/local/worker-cli
9+
build:
10+
context: ..
11+
dockerfile: build.Dockerfile
12+
target: deployed-client
13+
depends_on:
14+
litentry-node:
15+
condition: service_healthy
16+
litentry-worker-1:
17+
condition: service_healthy
18+
litentry-worker-2:
19+
condition: service_healthy
20+
litentry-worker-3:
21+
condition: service_healthy
22+
networks:
23+
- litentry-test-network
24+
entrypoint: "bash -c '/usr/local/worker-cli/lit_ts_integration_test.sh scheduled_enclave.test.ts 2>&1' "
25+
restart: "no"
26+
networks:
27+
litentry-test-network:
28+
driver: bridge

tee-worker/ts-tests/integration-tests/common/transactions.ts

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1+
import { hexToU8a } from '@polkadot/util';
12
import type { IntegrationTestContext } from './common-types';
23

3-
import { FrameSystemEventRecord } from 'parachain-api';
4+
import {
5+
AddressOrPair,
6+
ApiPromise,
7+
ApiTypes,
8+
FrameSystemEventRecord,
9+
Keyring,
10+
SubmittableExtrinsic,
11+
} from 'parachain-api';
412

513
// for DI-test
614
export const subscribeToEventsWithExtHash = async (
@@ -46,3 +54,111 @@ export const subscribeToEventsWithExtHash = async (
4654
});
4755
});
4856
};
57+
58+
// for II-test
59+
export const subscribeToEvents = async (
60+
section: string,
61+
method: string,
62+
api: ApiPromise
63+
): Promise<FrameSystemEventRecord[]> => {
64+
return new Promise<FrameSystemEventRecord[]>((resolve, reject) => {
65+
let blocksToScan = 15;
66+
const unsubscribe = api.rpc.chain.subscribeNewHeads(async (blockHeader) => {
67+
const shiftedApi = await api.at(blockHeader.hash);
68+
69+
const allBlockEvents = await shiftedApi.query.system.events();
70+
const allExtrinsicEvents = allBlockEvents.filter(({ phase }) => phase.isApplyExtrinsic);
71+
72+
const matchingEvent = allExtrinsicEvents.filter(({ event, phase }) => {
73+
return event.section === section && event.method === method;
74+
});
75+
76+
if (matchingEvent.length == 0) {
77+
blocksToScan -= 1;
78+
if (blocksToScan < 1) {
79+
reject(new Error(`timed out listening for event ${section}.${method}`));
80+
(await unsubscribe)();
81+
}
82+
return;
83+
}
84+
85+
resolve(matchingEvent);
86+
(await unsubscribe)();
87+
});
88+
});
89+
};
90+
91+
export async function waitForBlock(api: ApiPromise, blockNumber: number, blocksToCheck = 5) {
92+
let count = 0;
93+
94+
return new Promise<void>((resolve, reject) => {
95+
const unsubscribe = api.rpc.chain.subscribeNewHeads(async (header) => {
96+
console.log(`Chain is at block: #${header.number}`);
97+
98+
if (header.number.toNumber() === blockNumber) {
99+
(await unsubscribe)();
100+
resolve();
101+
}
102+
103+
if (++count === blocksToCheck) {
104+
(await unsubscribe)();
105+
reject(new Error(`Timeout: Block #${blockNumber} not reached within ${blocksToCheck} blocks.`));
106+
}
107+
});
108+
});
109+
}
110+
111+
export async function setAliceAsAdmin(api: ApiPromise) {
112+
// Get keyring of Alice, who is also the sudo in dev chain spec
113+
const keyring = new Keyring({ type: 'sr25519' });
114+
const alice = keyring.addFromUri('//Alice');
115+
116+
const tx = await sudoWrapperGC(api, api.tx.teebag.setAdmin('esqZdrqhgH8zy1wqYh1aLKoRyoRWLFbX9M62eKfaTAoK67pJ5'));
117+
118+
console.log(`Setting Alice as Admin for Teebag`);
119+
return signAndSend(tx, alice);
120+
}
121+
122+
export function signAndSend(tx: SubmittableExtrinsic<ApiTypes>, account: AddressOrPair) {
123+
return new Promise<{ block: string }>(async (resolve, reject) => {
124+
await tx.signAndSend(account, (result) => {
125+
console.log(`Current status is ${result.status}`);
126+
if (result.status.isInBlock) {
127+
console.log(`Transaction included at blockHash ${result.status.asInBlock}`);
128+
} else if (result.status.isFinalized) {
129+
console.log(`Transaction finalized at blockHash ${result.status.asFinalized}`);
130+
resolve({
131+
block: result.status.asFinalized.toString(),
132+
});
133+
} else if (result.status.isInvalid) {
134+
reject(`Transaction is ${result.status}`);
135+
}
136+
});
137+
});
138+
}
139+
140+
// After removing the sudo module, we use `EnsureRootOrHalfCouncil` instead of `Sudo`,
141+
// and there are only two council members in litmus-dev/rococo-dev/litentry-dev.
142+
// So only `propose` is required, no vote.
143+
//
144+
// TODO: support to send the `vote extrinsic`, if the number of council members is greater than 2.
145+
export async function sudoWrapperGC(api: ApiPromise, tx: SubmittableExtrinsic<ApiTypes>) {
146+
const chain = (await api.rpc.system.chain()).toString().toLowerCase();
147+
if (chain != 'rococo-dev') {
148+
const threshold = api.createType('Compact<u32>', 1);
149+
const call = api.createType('Call', tx);
150+
return api.tx.council.propose(threshold, call, api.createType('Compact<u32>', tx.length));
151+
} else {
152+
return api.tx.sudo.sudo(tx);
153+
}
154+
}
155+
156+
export async function setScheduledEnclave(api: ApiPromise, block: number, mrenclave: string) {
157+
const keyring = new Keyring({ type: 'sr25519' });
158+
const alice = keyring.addFromUri('//Alice');
159+
160+
const tx = api.tx.teebag.setScheduledEnclave('Identity', block, hexToU8a(`0x${mrenclave}`));
161+
162+
console.log('Schedule Enclave Extrinsic sent');
163+
return signAndSend(tx, alice);
164+
}

tee-worker/ts-tests/integration-tests/scheduled_enclave.test.ts

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@ import { step } from 'mocha-steps';
33
import { initIntegrationTestContext } from './common/utils';
44
import { getTeeShieldingKey } from './common/di-utils';
55
import type { IntegrationTestContext, JsonRpcRequest } from './common/common-types';
6-
import { decodeRpcBytesAsString, sendRequest } from './common/call';
7-
import type { CorePrimitivesIdentity } from 'parachain-api';
6+
import { sendRequest } from './common/call';
7+
import { type CorePrimitivesIdentity } from 'parachain-api';
88
import { assert } from 'chai';
99
import { createJsonRpcRequest, nextRequestId } from './common/helpers';
10+
import { setAliceAsAdmin, setScheduledEnclave, waitForBlock } from './common/transactions';
1011

1112
describe('Scheduled Enclave', function () {
12-
let context: IntegrationTestContext = undefined as any;
13-
let teeShieldingKey: KeyObject = undefined as any;
14-
let aliceSubstrateIdentity: CorePrimitivesIdentity = undefined as any;
13+
let context: IntegrationTestContext;
14+
let teeShieldingKey: KeyObject;
15+
let aliceSubstrateIdentity: CorePrimitivesIdentity;
1516

1617
this.timeout(6000000);
1718

@@ -22,6 +23,7 @@ describe('Scheduled Enclave', function () {
2223
);
2324
teeShieldingKey = await getTeeShieldingKey(context);
2425
aliceSubstrateIdentity = await context.web3Wallets.substrate.Alice.getIdentity(context);
26+
await setAliceAsAdmin(context.api);
2527
});
2628

2729
step('state of scheduled enclave list', async function () {
@@ -53,22 +55,15 @@ describe('Scheduled Enclave', function () {
5355
assert.equal(scheduledEnclaveList.length, 1);
5456

5557
// set new mrenclave
56-
let setEnclaveResponse = await callRPC(
57-
buildRequest('state_setScheduledEnclave', [20, 'some invalid mrenclave'])
58-
);
59-
assert.include(decodeRpcBytesAsString(setEnclaveResponse.value), 'Failed to decode mrenclave');
58+
const lastBlock = await context.api.rpc.chain.getBlock();
59+
const expectedBlockNumber = lastBlock.block.header.number.toNumber() + 5;
60+
console.log(`expected mrenclave block number: ${expectedBlockNumber}`);
6061

61-
setEnclaveResponse = await callRPC(buildRequest('state_setScheduledEnclave', [20, '48656c6c6f20776f726c6421']));
62-
assert.include(
63-
decodeRpcBytesAsString(setEnclaveResponse.value),
64-
'mrenclave len mismatch, expected 32 bytes long'
65-
);
62+
const validMrEnclave = '97f516a61ff59c5eab74b8a9b1b7273d6986b9c0e6c479a4010e22402ca7cee6';
6663

67-
// valid mutation
68-
const validParams = [20, '97f516a61ff59c5eab74b8a9b1b7273d6986b9c0e6c479a4010e22402ca7cee6'];
69-
await callRPC(buildRequest('state_setScheduledEnclave', validParams));
64+
await setScheduledEnclave(context.api, expectedBlockNumber, validMrEnclave);
65+
await waitForBlock(context.api, expectedBlockNumber);
7066

71-
// checking mutated state
7267
response = await callRPC(buildRequest('state_getScheduledEnclave'));
7368
scheduledEnclaveList = context.api.createType('Vec<(u64, [u8; 32])>', response.value).toJSON() as [
7469
number,
@@ -78,7 +73,7 @@ describe('Scheduled Enclave', function () {
7873
assert.equal(scheduledEnclaveList.length, 2);
7974

8075
const [blockNumber, mrEnclave] = scheduledEnclaveList[1];
81-
assert.equal(blockNumber, validParams[0]);
82-
assert.equal(mrEnclave, `0x${validParams[1]}`);
76+
assert.equal(blockNumber, expectedBlockNumber);
77+
assert.equal(mrEnclave, `0x${validMrEnclave}`);
8378
});
8479
});

0 commit comments

Comments
 (0)