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

migrate 200 IT file #164

Merged
merged 9 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- 00X_fullchain-Xworkers.js (#158, #159)
- 000_fullchain-5workers-1error.js (#160, #162)
- Clean ToDo (#163)
- 200_fullchain-bot.js (#164)
- Remove `smock` from unit tests:
- IexecEscrow.v8 (#154, #155)
- IexecPocoDelegate (#149, #151)
Expand Down
File renamed without changes.
314 changes: 314 additions & 0 deletions test/200_fullchain-bot.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
// SPDX-FileCopyrightText: 2024 IEXEC BLOCKCHAIN TECH <[email protected]>
// SPDX-License-Identifier: Apache-2.0

import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'hardhat';
import { loadHardhatFixtureDeployment } from '../scripts/hardhat-fixture-deployer';
import { IexecInterfaceNative, IexecInterfaceNative__factory } from '../typechain';
import { OrdersActors, OrdersAssets, OrdersPrices, buildOrders } from '../utils/createOrders';
import {
PocoMode,
TaskStatusEnum,
buildUtf8ResultAndDigest,
getIexecAccounts,
} from '../utils/poco-tools';
import { IexecWrapper } from './utils/IexecWrapper';

const standardDealTag = '0x0000000000000000000000000000000000000000000000000000000000000000';
const appPrice = 1000;
const datasetPrice = 1_000_000;
const workerpoolPrice = 1_000_000_000;

let proxyAddress: string;
let iexecPoco: IexecInterfaceNative;
let iexecWrapper: IexecWrapper;
let [appAddress, workerpoolAddress, datasetAddress]: string[] = [];
let [
requester,
appProvider,
datasetProvider,
scheduler,
anyone,
worker1,
worker2,
worker3,
worker4,
worker5,
]: SignerWithAddress[] = [];
let ordersActors: OrdersActors;
let ordersAssets: OrdersAssets;
let ordersPrices: OrdersPrices;

describe('Integration tests', function () {
beforeEach('Deploy', async () => {
// Deploy all contracts
proxyAddress = await loadHardhatFixtureDeployment();
// Initialize test environment
await loadFixture(initFixture);
});

async function initFixture() {
const accounts = await getIexecAccounts();
({
requester,
appProvider,
datasetProvider,
scheduler,
anyone,
worker1,
worker2,
worker3,
worker4,
worker5,
} = accounts);
iexecWrapper = new IexecWrapper(proxyAddress, accounts);
({ appAddress, datasetAddress, workerpoolAddress } = await iexecWrapper.createAssets());
iexecPoco = IexecInterfaceNative__factory.connect(proxyAddress, anyone);
ordersActors = {
appOwner: appProvider,
datasetOwner: datasetProvider,
workerpoolOwner: scheduler,
requester: requester,
};
ordersAssets = {
app: appAddress,
dataset: datasetAddress,
workerpool: workerpoolAddress,
};
ordersPrices = {
app: appPrice,
dataset: datasetPrice,
workerpool: workerpoolPrice,
};
}

it('Task Lifecycle with BoT Replication and Error Handling', async function () {
const workers = [worker1, worker2, worker3, worker4, worker5];
// Create deal.
const volume = 3;
const tasksAndWorkers: {
[key: number]: {
worker: SignerWithAddress;
useEnclave: boolean;
result: string;
}[];
} = {
0: [
{ worker: worker1, useEnclave: false, result: 'iExec BOT 0' },
{ worker: worker2, useEnclave: false, result: 'iExec BOT 0' },
],
1: [
{ worker: worker2, useEnclave: true, result: 'iExec BOT 1' },
{ worker: worker3, useEnclave: true, result: 'iExec BOT 1' },
],
2: [
{ worker: worker1, useEnclave: false, result: 'iExec BOT 2' },
{ worker: worker3, useEnclave: false, result: '<timeout reached>' },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{ worker: worker3, useEnclave: false, result: '<timeout reached>' },
{ worker: worker3, useEnclave: false, result: '<reveal timeout reached>' },

?
To make it more precise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it's not releated to a reveal timeout. Since it's a bad contribution when the trust contribution is reached the reveal of bad result would failed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not <bad contribution> then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect, makes sense

{ worker: worker2, useEnclave: true, result: 'iExec BOT 2' },
{ worker: worker4, useEnclave: true, result: 'iExec BOT 2' },
{ worker: worker5, useEnclave: true, result: 'iExec BOT 2' },
],
};
const orders = buildOrders({
assets: ordersAssets,
prices: ordersPrices,
requester: requester.address,
tag: standardDealTag,
volume,
trust: 4,
});
const { dealId, dealPrice, schedulerStakePerDeal } = await iexecWrapper.signAndMatchOrders(
...orders.toArray(),
);
const taskPrice = appPrice + datasetPrice + workerpoolPrice;
const schedulerStakePerTask = schedulerStakePerDeal / volume;
const workerRewardPerTask = await iexecWrapper.computeWorkerRewardPerTask(
dealId,
PocoMode.CLASSIC,
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const workerRewardPerTask = await iexecWrapper.computeWorkerRewardPerTask(
dealId,
PocoMode.CLASSIC,
);
const workersRewardPerTask = await iexecWrapper.computeWorkersRewardPerTask(
dealId,
PocoMode.CLASSIC,
);

We probably need to rename it in other files.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will rename it in the next PR that will concern the 000 fullchain test file thanks

const schedulerRewardPerTask = workerpoolPrice - workerRewardPerTask;
// Save frozens
const accounts = [requester, scheduler, appProvider, datasetProvider];
const accountsInitialFrozens = await getInitialFrozens([...accounts, ...workers]);
// Track initial scores
// Finalize each task and check balance changes.
const workerStakePerTask = await iexecPoco
.viewDeal(dealId)
.then((deal) => deal.workerStake.toNumber());
for (let taskIndex = 0; taskIndex < volume; taskIndex++) {
const taskId = await iexecWrapper.initializeTask(dealId, taskIndex);
const initialScores = await getInitialScores(workers);
const contributions = tasksAndWorkers[taskIndex];
for (const contribution of contributions) {
const { worker, useEnclave, result } = contribution;
const { resultDigest } = buildUtf8ResultAndDigest(result);

if (useEnclave) {
await iexecWrapper.contributeToTeeTask(dealId, taskIndex, resultDigest, worker);
} else {
await iexecWrapper.contributeToTask(dealId, taskIndex, resultDigest, worker);
}
}
// Reveal contributions for all workers
for (const contribution of contributions) {
const { worker, result } = contribution;
const { resultDigest } = buildUtf8ResultAndDigest(result);
if (result !== '<timeout reached>') {
await iexecPoco
.connect(worker)
.reveal(taskId, resultDigest)
.then((tx) => tx.wait());
}
}
const { results } = buildUtf8ResultAndDigest(tasksAndWorkers[taskIndex][0].result);
const finalizeTx = await iexecPoco.connect(scheduler).finalize(taskId, results, '0x');
await finalizeTx.wait();
expect((await iexecPoco.viewTask(taskId)).status).to.equal(TaskStatusEnum.COMPLETED);
// Verify token balance changes
const winningWorkers = contributions
.filter(
(contribution) => contribution.result === 'iExec BOT ' + taskIndex.toString(),
)
.map((contribution) => contribution.worker);
const loosingWorkers = contributions
.filter(
(contribution) => contribution.result !== 'iExec BOT ' + taskIndex.toString(),
)
.map((contribution) => contribution.worker);
const nonParticipantWorkers = workers.filter(
(worker) => !winningWorkers.includes(worker) && !loosingWorkers.includes(worker),
);
const participantWorkers = workers.filter(
(worker) => !nonParticipantWorkers.includes(worker),
);
const totalWorkerPoolReward =
workerpoolPrice + workerStakePerTask * loosingWorkers.length; // bad wrokers lose their stake and add it to the pool price
// compute expected worker reward for current task
const workerRewardPerTask = await computeWorkerRewardForCurrentTask(
totalWorkerPoolReward,
dealId,
);
zguesmi marked this conversation as resolved.
Show resolved Hide resolved
const expectedWorkerBalanceChange =
zguesmi marked this conversation as resolved.
Show resolved Hide resolved
workerStakePerTask + workerRewardPerTask / winningWorkers.length;
// compute expected scheduler reward for current task
const schedulerRewardPerTask = totalWorkerPoolReward - workerRewardPerTask;
const expectedSchedulerBalanceChange = schedulerStakePerTask + schedulerRewardPerTask;

const expectedProxyBalanceChange = -(
taskPrice +
workerStakePerTask * participantWorkers.length +
schedulerStakePerTask
);

await expect(finalizeTx).to.changeTokenBalances(
iexecPoco,
[
proxyAddress,
...accounts,
...winningWorkers,
...loosingWorkers,
...nonParticipantWorkers,
],
[
expectedProxyBalanceChange, // Proxy
-0, // Requester
expectedSchedulerBalanceChange, // Scheduler
appPrice, // AppProvider
datasetPrice, // DatasetProvider
...winningWorkers.map(() => expectedWorkerBalanceChange), // winning workers
...loosingWorkers.map(() => 0), // loosing workers
...nonParticipantWorkers.map(() => 0), // non participant workers
],
);
// Multiply amount by the number of finalized tasks to correctly compute
// stake and reward amounts.
const completedTasks = taskIndex + 1;
// Calculate expected frozen changes
const expectedFrozenChanges = [
0, // Proxy
-taskPrice * completedTasks, // Requester
-schedulerStakePerTask * completedTasks, // Scheduler
0, // AppProvider
0, // DatasetProvider
...workers.map(() => 0), // Add 0 for each worker
];
await checkFrozenChanges(accountsInitialFrozens, expectedFrozenChanges);
await validateScores(
initialScores,
winningWorkers,
loosingWorkers,
nonParticipantWorkers,
);
}
});
async function getInitialFrozens(accounts: SignerWithAddress[]) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function can be extracted in a common file.

let initialFrozens = [
{
address: proxyAddress,
frozen: (await iexecPoco.frozenOf(proxyAddress)).toNumber(),
},
];
for (const account of accounts) {
initialFrozens.push({
address: account.address,
frozen: (await iexecPoco.frozenOf(account.address)).toNumber(),
});
}
return initialFrozens;
}

async function getInitialScores(
workers: SignerWithAddress[],
): Promise<{ [address: string]: number }> {
const scores: { [address: string]: number } = {};
for (const worker of workers) {
scores[worker.address] = (await iexecPoco.viewScore(worker.address)).toNumber();
}
return scores;
}

async function computeWorkerRewardForCurrentTask(totalPoolReward: number, dealId: string) {
const deal = await iexecPoco.viewDeal(dealId);
return (totalPoolReward * (100 - deal.schedulerRewardRatio.toNumber())) / 100;
}
});

async function checkFrozenChanges(
accountsInitialFrozens: { address: string; frozen: number }[],
expectedFrozenChanges: number[],
) {
for (let i = 0; i < accountsInitialFrozens.length; i++) {
const message = `Failed with account at index ${i}`;
expect(await iexecPoco.frozenOf(accountsInitialFrozens[i].address)).to.equal(
accountsInitialFrozens[i].frozen + expectedFrozenChanges[i],
message,
);
}
}

async function validateScores(
initialScores: { [address: string]: number },
winningWorkers: SignerWithAddress[],
loosingWorkers: SignerWithAddress[],
nonParticipantWorkers: SignerWithAddress[],
) {
for (const winningWorker of winningWorkers) {
const currentScore = (await iexecPoco.viewScore(winningWorker.address)).toNumber();
expect(currentScore, `Worker ${winningWorker.address} score mismatch`).to.equal(
initialScores[winningWorker.address] + 1,
);
}
for (const loosingWorker of loosingWorkers) {
const currentScore = (await iexecPoco.viewScore(loosingWorker.address)).toNumber();
expect(currentScore, `Worker ${loosingWorker.address} score mismatch`).to.equal(
initialScores[loosingWorker.address] - 1,
);
}
for (const nonParticipantWorker of nonParticipantWorkers) {
const currentScore = (await iexecPoco.viewScore(nonParticipantWorker.address)).toNumber();
expect(currentScore, `Worker ${nonParticipantWorker.address} score mismatch`).to.equal(
initialScores[nonParticipantWorker.address],
);
}
}
Loading