@prosopo/provider 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/cjs/contracts/captcha/dist/contract-info/captcha.cjs +2 -2
  2. package/dist/tests/accounts.d.ts +11 -0
  3. package/dist/tests/accounts.d.ts.map +1 -0
  4. package/dist/tests/accounts.js +47 -0
  5. package/dist/tests/accounts.js.map +1 -0
  6. package/dist/tests/batch/commitments.test.d.ts +6 -0
  7. package/dist/tests/batch/commitments.test.d.ts.map +1 -0
  8. package/dist/tests/batch/commitments.test.js +217 -0
  9. package/dist/tests/batch/commitments.test.js.map +1 -0
  10. package/dist/tests/contract/helpers.test.d.ts +6 -0
  11. package/dist/tests/contract/helpers.test.d.ts.map +1 -0
  12. package/dist/tests/contract/helpers.test.js +58 -0
  13. package/dist/tests/contract/helpers.test.js.map +1 -0
  14. package/dist/tests/data/captchas.d.ts +37 -0
  15. package/dist/tests/data/captchas.d.ts.map +1 -0
  16. package/dist/tests/data/captchas.js +902 -0
  17. package/dist/tests/data/captchas.js.map +1 -0
  18. package/dist/tests/data/captchas1.json +53 -0
  19. package/dist/tests/data/captchas2.json +69 -0
  20. package/dist/tests/data/captchas3.json +54 -0
  21. package/dist/tests/data/captchas4.json +53 -0
  22. package/dist/tests/dataUtils/DatabaseAccounts.d.ts +35 -0
  23. package/dist/tests/dataUtils/DatabaseAccounts.d.ts.map +1 -0
  24. package/dist/tests/dataUtils/DatabaseAccounts.js +85 -0
  25. package/dist/tests/dataUtils/DatabaseAccounts.js.map +1 -0
  26. package/dist/tests/dataUtils/DatabasePopulator.d.ts +66 -0
  27. package/dist/tests/dataUtils/DatabasePopulator.d.ts.map +1 -0
  28. package/dist/tests/dataUtils/DatabasePopulator.js +264 -0
  29. package/dist/tests/dataUtils/DatabasePopulator.js.map +1 -0
  30. package/dist/tests/dataUtils/dapp-example-contract/dapp.json +648 -0
  31. package/dist/tests/dataUtils/dapp-example-contract/loadFiles.d.ts +4 -0
  32. package/dist/tests/dataUtils/dapp-example-contract/loadFiles.d.ts.map +1 -0
  33. package/dist/tests/dataUtils/dapp-example-contract/loadFiles.js +41 -0
  34. package/dist/tests/dataUtils/dapp-example-contract/loadFiles.js.map +1 -0
  35. package/dist/tests/dataUtils/funds.d.ts +23 -0
  36. package/dist/tests/dataUtils/funds.d.ts.map +1 -0
  37. package/dist/tests/dataUtils/funds.js +102 -0
  38. package/dist/tests/dataUtils/funds.js.map +1 -0
  39. package/dist/tests/dataUtils/populateDatabase.d.ts +15 -0
  40. package/dist/tests/dataUtils/populateDatabase.d.ts.map +1 -0
  41. package/dist/tests/dataUtils/populateDatabase.js +76 -0
  42. package/dist/tests/dataUtils/populateDatabase.js.map +1 -0
  43. package/dist/tests/getUser.d.ts +5 -0
  44. package/dist/tests/getUser.d.ts.map +1 -0
  45. package/dist/tests/getUser.js +18 -0
  46. package/dist/tests/getUser.js.map +1 -0
  47. package/dist/tests/tasks/tasks.test.d.ts +7 -0
  48. package/dist/tests/tasks/tasks.test.d.ts.map +1 -0
  49. package/dist/tests/tasks/tasks.test.js +719 -0
  50. package/dist/tests/tasks/tasks.test.js.map +1 -0
  51. package/dist/tests/util.test.d.ts +2 -0
  52. package/dist/tests/util.test.d.ts.map +1 -0
  53. package/dist/tests/util.test.js +51 -0
  54. package/dist/tests/util.test.js.map +1 -0
  55. package/package.json +10 -10
@@ -0,0 +1,719 @@
1
+ // Copyright (C) 2021-2022 Prosopo (UK) Ltd.
2
+ // This file is part of provider <https://github.com/prosopo/provider>.
3
+ //
4
+ // provider is free software: you can redistribute it and/or modify
5
+ // it under the terms of the GNU General Public License as published by
6
+ // the Free Software Foundation, either version 3 of the License, or
7
+ // (at your option) any later version.
8
+ //
9
+ // provider is distributed in the hope that it will be useful,
10
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ // GNU General Public License for more details.
13
+ //
14
+ // You should have received a copy of the GNU General Public License
15
+ import { AccountKey } from '../dataUtils/DatabaseAccounts.js';
16
+ import { BN, stringToHex, stringToU8a, u8aToHex } from '@polkadot/util';
17
+ import { CaptchaMerkleTree, computeCaptchaSolutionHash, computePendingRequestHash } from '@prosopo/datasets';
18
+ import { CaptchaStatus, DappPayee, Payee } from '@prosopo/captcha-contract';
19
+ import { ContractDeployer, ProsopoContractError, getBlockNumber, getDispatchError, wrapQuery } from '@prosopo/contract';
20
+ import { DappAbiJSON, DappWasm } from '../dataUtils/dapp-example-contract/loadFiles.js';
21
+ import { MockEnvironment } from '@prosopo/env';
22
+ import { PROVIDER, accountAddress, accountContract, accountMnemonic, getSignedTasks } from '../accounts.js';
23
+ import { ProsopoEnvError, getPair, hexHash, i18n } from '@prosopo/common';
24
+ import { afterEach, beforeEach, describe, expect, test } from 'vitest';
25
+ import { at, get } from '@prosopo/util';
26
+ import { captchaData } from '../data/captchas.js';
27
+ import { createType } from '@polkadot/types';
28
+ import { getSendAmount, getStakeAmount, sendFunds } from '../dataUtils/funds.js';
29
+ import { getUser } from '../getUser.js';
30
+ import { parseBlockNumber } from '../../index.js';
31
+ import { randomAsHex, signatureVerify } from '@polkadot/util-crypto';
32
+ import { testConfig } from '@prosopo/config';
33
+ function delay(ms) {
34
+ return new Promise((resolve) => setTimeout(resolve, ms));
35
+ }
36
+ export async function sleep(timeout) {
37
+ await delay(timeout);
38
+ }
39
+ const PROVIDER_PAYEE = Payee.dapp;
40
+ describe.sequential('CONTRACT TASKS', async function () {
41
+ beforeEach(async function (context) {
42
+ context.ss58Format = 42;
43
+ context.pairType = 'sr25519';
44
+ const alicePair = await getPair(context.pairType, context.ss58Format, '//Alice');
45
+ console.log('testConfig', testConfig);
46
+ context.env = new MockEnvironment(alicePair, testConfig);
47
+ try {
48
+ await context.env.isReady();
49
+ }
50
+ catch (e) {
51
+ throw new ProsopoEnvError(e);
52
+ }
53
+ const promiseStakeDefault = wrapQuery(context.env.getContractInterface().query.getProviderStakeThreshold, context.env.getContractInterface().query)();
54
+ context.providerStakeThreshold = new BN((await promiseStakeDefault).toNumber());
55
+ });
56
+ afterEach(async (context) => {
57
+ if (context.env && 'db' in context.env)
58
+ await context.env.db?.close();
59
+ });
60
+ /** Gets some static solved captchas and constructions captcha solutions from them
61
+ * Computes the request hash for these captchas and the dappUser and then stores the request hash in the mock db
62
+ * @return {CaptchaSolution[], string} captchaSolutions and requestHash
63
+ */
64
+ async function createMockCaptchaSolutionsAndRequestHash(env, pairType, ss58Format) {
65
+ // There must exist a dappUser who can receive a captcha
66
+ const dappUserAccount = await getUser(env, AccountKey.dappUsers);
67
+ // There must exist a provider with a dataset for us to get a random dataset with solutions
68
+ const providerAccount = await getUser(env, AccountKey.providersWithStakeAndDataset);
69
+ // There must exist a dapp that is staked who can use the service
70
+ const dappContractAccount = await getUser(env, AccountKey.dappsWithStake);
71
+ const tasks = await getSignedTasks(env, providerAccount);
72
+ const providerDetails = (await tasks.contract.query.getProvider(accountAddress(providerAccount))).value
73
+ .unwrap()
74
+ .unwrap();
75
+ //await sleep(132000)
76
+ const solvedCaptchas = await env.db.getRandomSolvedCaptchasFromSingleDataset(providerDetails.datasetId.toString(), 2);
77
+ const pair = await getPair(pairType, ss58Format, accountMnemonic(dappUserAccount));
78
+ await env.changeSigner(pair);
79
+ const userSalt = randomAsHex();
80
+ const captchaSolutions = solvedCaptchas.map((captcha) => ({
81
+ captchaId: captcha.captchaId,
82
+ salt: userSalt,
83
+ solution: captcha.solution,
84
+ captchaContentId: captcha.captchaContentId,
85
+ }));
86
+ const pendingRequestSalt = randomAsHex();
87
+ const requestHash = computePendingRequestHash(captchaSolutions.map((c) => c.captchaId), accountAddress(dappUserAccount), pendingRequestSalt);
88
+ const blockNumber = (await getBlockNumber(env.getApi())).toNumber();
89
+ if ('storeDappUserPending' in env.db) {
90
+ await env.db.storeDappUserPending(hexHash(accountAddress(dappUserAccount)), requestHash, pendingRequestSalt, 99999999999999, blockNumber);
91
+ }
92
+ const signer = env.keyring.addFromMnemonic(accountMnemonic(dappUserAccount));
93
+ const userSignature = signer.sign(stringToHex(requestHash));
94
+ signatureVerify(stringToHex(requestHash), userSignature, accountAddress(dappUserAccount));
95
+ return {
96
+ dappUserAccount,
97
+ captchaSolutions,
98
+ requestHash,
99
+ providerAccount,
100
+ dappContractAccount,
101
+ userSalt,
102
+ userSignature,
103
+ blockNumber,
104
+ };
105
+ }
106
+ test('Provider registration', async function ({ env, providerStakeThreshold }) {
107
+ const [providerMnemonic, providerAddress] = env.createAccountAndAddToKeyring() || ['', ''];
108
+ const tasks = await getSignedTasks(env, [providerMnemonic, providerAddress]);
109
+ const stakeAmount = getStakeAmount(env, providerStakeThreshold);
110
+ const sendAmount = getSendAmount(env, stakeAmount);
111
+ await sendFunds(env, providerAddress, 'ProsopoPayee', sendAmount);
112
+ const queryResult = await tasks.contract.query.providerRegister(Array.from(stringToU8a(PROVIDER.url + randomAsHex().slice(0, 8))), PROVIDER.fee, PROVIDER_PAYEE);
113
+ if (queryResult.value.err) {
114
+ throw new Error(queryResult.value.err);
115
+ }
116
+ if (queryResult.value.ok?.err) {
117
+ throw new Error(queryResult.value.ok.err);
118
+ }
119
+ const result = await tasks.contract.tx.providerRegister(Array.from(stringToU8a(PROVIDER.url + randomAsHex().slice(0, 8))), PROVIDER.fee, PROVIDER_PAYEE);
120
+ console.log(JSON.stringify(result.error, null, 4));
121
+ expect(result?.error).to.be.undefined;
122
+ }, 8000);
123
+ test('Provider update', async ({ env }) => {
124
+ const providerAccount = await getUser(env, AccountKey.providers);
125
+ const tasks = await getSignedTasks(env, providerAccount);
126
+ const value = new BN((await tasks.contract.query.getProviderStakeThreshold()).value.unwrap().toNumber());
127
+ const result = (await tasks.contract.tx.providerUpdate(Array.from(stringToU8a(PROVIDER.url + randomAsHex().slice(0, 8))), PROVIDER.fee, PROVIDER_PAYEE, { value })).result;
128
+ if (result?.isError && result?.dispatchError) {
129
+ const dispatchError = getDispatchError(result?.dispatchError);
130
+ throw new ProsopoContractError(dispatchError);
131
+ }
132
+ expect(result?.isError).to.be.false;
133
+ }, 8000);
134
+ test('Provider add dataset', async ({ env }) => {
135
+ const providerAccount = await getUser(env, AccountKey.providersWithStake);
136
+ const tasks = await getSignedTasks(env, providerAccount);
137
+ await tasks.providerSetDatasetFromFile(JSON.parse(JSON.stringify(captchaData)));
138
+ }, 8000);
139
+ test('Provider add dataset with too few captchas will fail', async ({ env }) => {
140
+ const providerAccount = await getUser(env, AccountKey.providersWithStake);
141
+ const tasks = await getSignedTasks(env, providerAccount);
142
+ // copy captchaData and remove all but one captcha
143
+ const dataset = { ...captchaData };
144
+ dataset.captchas = dataset.captchas.slice(0, 1);
145
+ try {
146
+ await tasks.providerSetDatasetFromFile(JSON.parse(JSON.stringify(dataset)));
147
+ }
148
+ catch (e) {
149
+ expect(e).to.match(/Number of captchas in dataset is less than configured number of captchas/);
150
+ }
151
+ });
152
+ test('Provider add dataset with too few solutions will fail', async ({ env }) => {
153
+ const providerAccount = await getUser(env, AccountKey.providersWithStake);
154
+ const tasks = await getSignedTasks(env, providerAccount);
155
+ const dataset = { ...captchaData };
156
+ // remove solution field from each captcha
157
+ dataset.captchas = dataset.captchas.map((captcha) => {
158
+ const { solution, ...rest } = captcha;
159
+ return rest;
160
+ });
161
+ try {
162
+ await tasks.providerSetDatasetFromFile(JSON.parse(JSON.stringify(dataset)));
163
+ }
164
+ catch (e) {
165
+ expect(e).to.match(/Number of solutions in dataset is less than configured number of solutions/);
166
+ }
167
+ });
168
+ test('Inactive Provider cannot add dataset', async ({ env }) => {
169
+ const providerAccount = await getUser(env, AccountKey.providers);
170
+ const tasks = await getSignedTasks(env, providerAccount);
171
+ try {
172
+ await tasks.providerSetDatasetFromFile(JSON.parse(JSON.stringify(captchaData)));
173
+ }
174
+ catch (e) {
175
+ expect(e).to.match(/ProviderInactive/);
176
+ }
177
+ });
178
+ test('Provider approve', async ({ env, pairType, ss58Format }) => {
179
+ const { dappUserAccount, captchaSolutions, providerAccount, dappContractAccount, userSignature } = await createMockCaptchaSolutionsAndRequestHash(env, pairType, ss58Format);
180
+ const tasks = await getSignedTasks(env, dappUserAccount);
181
+ const salt = randomAsHex();
182
+ const tree = new CaptchaMerkleTree();
183
+ const captchaSolutionsSalted = captchaSolutions.map((captcha) => ({
184
+ ...captcha,
185
+ salt: salt,
186
+ }));
187
+ const captchasHashed = captchaSolutionsSalted.map((captcha) => computeCaptchaSolutionHash(captcha));
188
+ tree.build(captchasHashed);
189
+ const commitmentId = tree.root.hash;
190
+ const provider = (await tasks.contract.query.getProvider(accountAddress(providerAccount))).value
191
+ .unwrap()
192
+ .unwrap();
193
+ const completedAt = (await env.getApi().rpc.chain.getBlock()).block.header.number.toNumber();
194
+ const requestedAt = completedAt - 1;
195
+ const providerTasks = await getSignedTasks(env, providerAccount);
196
+ const commit = {
197
+ dappContract: accountContract(dappContractAccount),
198
+ datasetId: provider.datasetId,
199
+ id: commitmentId,
200
+ providerAccount: accountAddress(providerAccount),
201
+ userAccount: accountAddress(dappUserAccount),
202
+ status: CaptchaStatus.approved,
203
+ requestedAt,
204
+ completedAt,
205
+ userSignature: [...userSignature],
206
+ };
207
+ const queryResult = await providerTasks.contract.query.providerCommit(commit);
208
+ if (queryResult.value.err) {
209
+ throw new Error(queryResult.value.err);
210
+ }
211
+ const result = await providerTasks.contract.tx.providerCommit(commit);
212
+ if (result.result?.isError && result.result?.dispatchError) {
213
+ const dispatchError = getDispatchError(result.result?.dispatchError);
214
+ throw new ProsopoContractError(dispatchError);
215
+ }
216
+ expect(result.result?.isError).to.be.false;
217
+ if (result.error) {
218
+ throw new ProsopoContractError(result.error.message);
219
+ }
220
+ });
221
+ test('Provider disapprove', async ({ env, pairType, ss58Format }) => {
222
+ const { dappUserAccount, captchaSolutions, providerAccount, dappContractAccount, userSignature } = await createMockCaptchaSolutionsAndRequestHash(env, pairType, ss58Format);
223
+ const tasks = await getSignedTasks(env, dappUserAccount);
224
+ const salt = randomAsHex();
225
+ const tree = new CaptchaMerkleTree();
226
+ const captchaSolutionsSalted = captchaSolutions.map((captcha) => ({
227
+ ...captcha,
228
+ salt: salt,
229
+ }));
230
+ const captchasHashed = captchaSolutionsSalted.map((captcha) => computeCaptchaSolutionHash(captcha));
231
+ tree.build(captchasHashed);
232
+ const commitmentId = tree.root.hash;
233
+ const provider = (await tasks.contract.query.getProvider(accountAddress(providerAccount))).value
234
+ .unwrap()
235
+ .unwrap();
236
+ const completedAt = (await env.getApi().rpc.chain.getBlock()).block.header.number.toNumber();
237
+ const requestedAt = completedAt - 1;
238
+ const providerTasks = await getSignedTasks(env, providerAccount);
239
+ await providerTasks.contract.tx.providerCommit({
240
+ dappContract: accountContract(dappContractAccount),
241
+ datasetId: provider.datasetId.toString(),
242
+ id: commitmentId,
243
+ providerAccount: accountAddress(providerAccount),
244
+ userAccount: accountAddress(dappUserAccount),
245
+ status: CaptchaStatus.disapproved,
246
+ requestedAt,
247
+ completedAt,
248
+ userSignature: [...userSignature],
249
+ });
250
+ });
251
+ test('Timestamps check', async ({ env, pairType, ss58Format }) => {
252
+ const salt = randomAsHex();
253
+ const tree = new CaptchaMerkleTree();
254
+ const { dappUserAccount, captchaSolutions, providerAccount, dappContractAccount, userSignature } = await createMockCaptchaSolutionsAndRequestHash(env, pairType, ss58Format);
255
+ const tasks = await getSignedTasks(env, dappUserAccount);
256
+ const captchaSolutionsSalted = captchaSolutions.map((captcha) => ({
257
+ ...captcha,
258
+ salt: salt,
259
+ }));
260
+ const captchasHashed = captchaSolutionsSalted.map((captcha) => computeCaptchaSolutionHash(captcha));
261
+ tree.build(captchasHashed);
262
+ const commitmentId = tree.root.hash;
263
+ const provider = (await tasks.contract.query.getProvider(accountAddress(providerAccount))).value
264
+ .unwrap()
265
+ .unwrap();
266
+ const completedAt = (await env.getApi().rpc.chain.getBlock()).block.header.number.toNumber();
267
+ const requestedAt = completedAt - 1;
268
+ const providerTasks = await getSignedTasks(env, providerAccount);
269
+ await providerTasks.contract.tx.providerCommit({
270
+ dappContract: accountContract(dappContractAccount),
271
+ datasetId: provider.datasetId.toString(),
272
+ id: commitmentId,
273
+ providerAccount: accountAddress(providerAccount),
274
+ userAccount: accountAddress(dappUserAccount),
275
+ status: CaptchaStatus.approved,
276
+ completedAt,
277
+ requestedAt,
278
+ userSignature: [...userSignature],
279
+ });
280
+ const commitment = (await providerTasks.contract.query.getCommit(commitmentId)).value.unwrap().unwrap();
281
+ // check the timestamp
282
+ const completedAtCheck = parseInt(commitment.completedAt.toString().replace(',', ''));
283
+ expect(completedAtCheck).to.be.above(0);
284
+ // check how much time passed after successful completion
285
+ const lastCorrectCaptcha = (await providerTasks.contract.query.dappOperatorLastCorrectCaptcha(accountAddress(dappUserAccount))).value
286
+ .unwrap()
287
+ .unwrap();
288
+ expect(Number.parseInt(lastCorrectCaptcha.before.toString())).to.be.above(0);
289
+ });
290
+ test('Provider details', async ({ env }) => {
291
+ try {
292
+ const providerAccount = await getUser(env, AccountKey.providersWithStakeAndDataset);
293
+ const tasks = await getSignedTasks(env, providerAccount);
294
+ const result = (await tasks.contract.query.getProvider(accountAddress(providerAccount))).value
295
+ .unwrap()
296
+ .unwrap();
297
+ expect(result).to.have.a.property('status');
298
+ }
299
+ catch (err) {
300
+ throw new ProsopoEnvError(err);
301
+ }
302
+ });
303
+ test('Provider accounts', async ({ env }) => {
304
+ const providerAccount = await getUser(env, AccountKey.providersWithStakeAndDataset);
305
+ const tasks = await getSignedTasks(env, providerAccount);
306
+ const result = (await tasks.contract.query.getAllProviderAccounts()).value.unwrap().unwrap();
307
+ expect(result).to.be.an('array');
308
+ });
309
+ test('Dapp registration', async ({ env, providerStakeThreshold }) => {
310
+ const newAccount = env.createAccountAndAddToKeyring() || ['', ''];
311
+ const tasks = await getSignedTasks(env, newAccount);
312
+ const stakeAmount = getStakeAmount(env, providerStakeThreshold);
313
+ const sendAmount = getSendAmount(env, stakeAmount);
314
+ await sendFunds(env, accountAddress(newAccount), 'Dapp', sendAmount);
315
+ const dappParams = ['1000000000000000000', 1000, env.getContractInterface().address, 65, 1000000];
316
+ const deployer = new ContractDeployer(env.getApi(), await DappAbiJSON(), await DappWasm(), env.pair, dappParams, 0, 0, randomAsHex());
317
+ const deployResult = await deployer.deploy();
318
+ const instantiateEvent = deployResult.events.find((event) => event.event.section === 'contracts');
319
+ const contractAddress = String(get(instantiateEvent?.event.data, 'contract'));
320
+ const result = (await tasks.contract.tx.dappRegister(contractAddress, DappPayee.dapp)).result;
321
+ expect(result?.isError).to.be.false;
322
+ const dapp = (await tasks.contract.query.getDapp(contractAddress)).value.unwrap().unwrap();
323
+ expect(dapp.owner).to.equal(accountAddress(newAccount));
324
+ });
325
+ test('Dapp is active', async ({ env }) => {
326
+ const dappAccount = await getUser(env, AccountKey.dappsWithStake);
327
+ const tasks = await getSignedTasks(env, dappAccount);
328
+ const result = await tasks.dappIsActive(accountContract(dappAccount));
329
+ expect(result).to.equal(true);
330
+ });
331
+ test('Dapp details', async ({ env }) => {
332
+ const dappAccount = await getUser(env, AccountKey.dapps);
333
+ const tasks = await getSignedTasks(env, dappAccount);
334
+ const result = (await tasks.contract.query.getDapp(accountContract(dappAccount))).value.unwrap().unwrap();
335
+ expect(result).to.have.a.property('status');
336
+ });
337
+ test('Dapp fund', async ({ env }) => {
338
+ const dappAccount = await getUser(env, AccountKey.dappsWithStake);
339
+ const tasks = await getSignedTasks(env, dappAccount);
340
+ const value = createType(env.getContractInterface().abi.registry, 'u128', '10');
341
+ const dappContractAddress = accountContract(dappAccount);
342
+ const dappBefore = (await tasks.contract.query.getDapp(dappContractAddress)).value.unwrap().unwrap();
343
+ const result = (await tasks.contract.tx.dappFund(dappContractAddress, { value })).result;
344
+ expect(result?.isError).to.be.false;
345
+ const dappAfter = (await tasks.contract.query.getDapp(dappContractAddress)).value.unwrap().unwrap();
346
+ expect(dappBefore.balance.toNumber() + value.toNumber()).to.equal(dappAfter.balance.toNumber());
347
+ });
348
+ //TODO reinstate when https://github.com/polkadot-js/api/issues/5410 is resolved
349
+ // it.only('Dapp accounts', async ({env}): Promise<void> => {
350
+ // const account = await getUser(env, AccountKey.dapps)
351
+ //
352
+ // const tasks = await changeSigner(env, account)
353
+ //
354
+ // const result = await tasks.contractApi.getDappAccounts()
355
+ // console.log(result)
356
+ //
357
+ // expect(result).to.be.an('array')
358
+ // })
359
+ test('Captchas are correctly formatted before being passed to the API layer', async ({ env }) => {
360
+ const dappUserAccount = await getUser(env, AccountKey.dappUsers);
361
+ const providerAccount = await getUser(env, AccountKey.providersWithStakeAndDataset);
362
+ const dappUserTasks = await getSignedTasks(env, dappUserAccount);
363
+ const provider = (await dappUserTasks.contract.query.getProvider(accountAddress(providerAccount))).value
364
+ .unwrap()
365
+ .unwrap();
366
+ const captchas = await dappUserTasks.getCaptchaWithProof(provider.datasetId.toString(), true, 1);
367
+ expect(captchas[0]).to.have.nested.property('captcha.captchaId');
368
+ expect(captchas[0]).to.have.nested.property('captcha.datasetId', provider.datasetId.toString());
369
+ expect(captchas[0]).to.have.property('proof');
370
+ expect(captchas[0]).to.not.have.property('solution');
371
+ expect(captchas[0]).to.not.have.nested.property('captcha.solution');
372
+ });
373
+ test('Captcha proofs are returned if commitment found and solution is correct', async ({ env, pairType, ss58Format, }) => {
374
+ // Construct a pending request hash between dappUserAccount, providerAccount and dappContractAccount
375
+ const { captchaSolutions, requestHash, dappUserAccount, providerAccount, dappContractAccount, userSignature } = await createMockCaptchaSolutionsAndRequestHash(env, pairType, ss58Format);
376
+ const dappUserTasks = await getSignedTasks(env, dappUserAccount);
377
+ const tree = new CaptchaMerkleTree();
378
+ const captchaSolutionsSalted = captchaSolutions;
379
+ const captchasHashed = captchaSolutionsSalted.map((captcha) => computeCaptchaSolutionHash(captcha));
380
+ tree.build(captchasHashed);
381
+ const commitmentId = tree.root.hash;
382
+ const provider = (await dappUserTasks.contract.query.getProvider(accountAddress(providerAccount))).value
383
+ .unwrap()
384
+ .unwrap();
385
+ const completedAt = (await env.getApi().rpc.chain.getBlock()).block.header.number.toNumber();
386
+ const requestedAt = completedAt - 1;
387
+ // next part contains internal contract calls that must be run by provider
388
+ const providerTasks = await getSignedTasks(env, providerAccount);
389
+ await providerTasks.contract.tx.providerCommit({
390
+ dappContract: accountContract(dappContractAccount),
391
+ datasetId: provider.datasetId.toString(),
392
+ id: commitmentId,
393
+ providerAccount: accountAddress(providerAccount),
394
+ userAccount: accountAddress(dappUserAccount),
395
+ status: CaptchaStatus.approved,
396
+ completedAt,
397
+ requestedAt,
398
+ userSignature: [...userSignature],
399
+ });
400
+ const commitment = (await providerTasks.contract.query.getCommit(commitmentId)).value.unwrap().unwrap();
401
+ // next part contains internal contract calls that must be run by provider
402
+ await env.getApi().rpc.chain.getBlockHash(commitment.completedAt);
403
+ const result = await providerTasks.dappUserSolution(accountAddress(dappUserAccount), accountContract(dappContractAccount), requestHash, JSON.parse(JSON.stringify(captchaSolutionsSalted)), u8aToHex(userSignature));
404
+ expect(result.captchas.length).to.be.eq(2);
405
+ const expectedProof = tree.proof(at(captchaSolutionsSalted, 0).captchaId);
406
+ const filteredResult = at(result.captchas.filter((res) => res.captchaId == at(captchaSolutionsSalted, 0).captchaId), 0);
407
+ expect(filteredResult.proof).to.deep.eq(expectedProof);
408
+ expect(filteredResult.captchaId).to.eq(at(captchaSolutionsSalted, 0).captchaId);
409
+ });
410
+ // test('Dapp User sending an invalid captchas causes error', async ({env}): Promise<void> => {
411
+ // const { requestHash } = await createMockCaptchaSolutionsAndRequestHash( env, pairType, ss58Format );
412
+ //
413
+ // await env.getContractInterface()!.changeSigner(env, provider.mnemonic as string);
414
+ // const providerTasks = new Tasks(env);
415
+ // const captchaSolutions = [
416
+ // { captchaId: 'blah', solution: [21], salt: 'blah' }
417
+ // ];
418
+ // const tree = new CaptchaMerkleTree();
419
+ // const captchasHashed = captchaSolutions.map((captcha) =>
420
+ // computeCaptchaSolutionHash(captcha)
421
+ // );
422
+ //
423
+ // tree.build(captchasHashed);
424
+ // const solutionPromise = providerTasks.dappUserSolution(
425
+ // dappUser.address,
426
+ // dapp.contractAccount as string,
427
+ // requestHash,
428
+ // JSON.parse(JSON.stringify(captchaSolutions)) as JSON
429
+ // );
430
+ //
431
+ // solutionPromise.catch((e) =>
432
+ // e.message.should.match(`/${ERRORS.CAPTCHA.INVALID_CAPTCHA_ID.message}/`)
433
+ // );
434
+ // });
435
+ //
436
+ // test('Dapp User sending solutions without committing to blockchain causes error', async ({env}): Promise<void> => {
437
+ // const { captchaSolutions, requestHash } = await createMockCaptchaSolutionsAndRequestHash( env, pairType, ss58Format );
438
+ //
439
+ // await env.getContractInterface()!.changeSigner(env, provider.mnemonic as string);
440
+ // const providerTasks = new Tasks(env);
441
+ // const tree = new CaptchaMerkleTree();
442
+ // const captchasHashed = captchaSolutions.map((captcha) =>
443
+ // computeCaptchaSolutionHash(captcha)
444
+ // );
445
+ //
446
+ // tree.build(captchasHashed);
447
+ // const solutionPromise = providerTasks.dappUserSolution(
448
+ // dappUser.address,
449
+ // dapp.contractAccount as string,
450
+ // requestHash,
451
+ // JSON.parse(JSON.stringify(captchaSolutions)) as JSON
452
+ // );
453
+ //
454
+ // solutionPromise.catch((e) =>
455
+ // e.message.should.match(
456
+ // `/${ERRORS.CONTRACT.CAPTCHA_SOLUTION_COMMITMENT_DOES_NOT_EXIST.message}/`
457
+ // )
458
+ // );
459
+ // });
460
+ //
461
+ // test('No proofs are returned if commitment found and solution is incorrect', async ({env}): Promise<void> => {
462
+ // const { captchaSolutions, requestHash } = await createMockCaptchaSolutionsAndRequestHash( env, pairType, ss58Format );
463
+ // const captchaSolutionsBad = captchaSolutions.map((original) => ({
464
+ // ...original,
465
+ // solution: [3]
466
+ // }));
467
+ // const tree = new CaptchaMerkleTree();
468
+ // const salt = randomAsHex();
469
+ // // Have to salt the solutions with random salt each time otherwise we end up with the same commitment for
470
+ // // multiple users
471
+ // const captchaSolutionsSalted = captchaSolutionsBad.map((captcha) => ({
472
+ // ...captcha,
473
+ // salt: salt
474
+ // }));
475
+ // const solutionsHashed = captchaSolutionsSalted.map((captcha) =>
476
+ // computeCaptchaSolutionHash(captcha)
477
+ // );
478
+ //
479
+ // tree.build(solutionsHashed);
480
+ // const commitmentId = tree.root!.hash;
481
+ //
482
+ // await env.getContractInterface()!.changeSigner(env, dappUser.mnemonic);
483
+ // const dappUserTasks = new Tasks(env);
484
+ //
485
+ // await ,dappUserTasks.contractApi.dappUserCommtest(
486
+ // dapp.contractAccount as string,
487
+ // datasetId as string,
488
+ // commitmentId,
489
+ // provider.address as string
490
+ // );
491
+ // // next part contains internal contract calls that must be run by provider
492
+ // await env.getContractInterface()!.changeSigner(env, provider.mnemonic as string);
493
+ // const providerTasks = new Tasks(env);
494
+ // const result = await providerTasks.dappUserSolution(
495
+ // dappUser.address,
496
+ // dapp.contractAccount as string,
497
+ // requestHash,
498
+ // JSON.parse(JSON.stringify(captchaSolutionsSalted)) as JSON
499
+ // );
500
+ //
501
+ // expect(result!.length).to.be.eq(0);
502
+ // });
503
+ test('Validates the received captchas length', async ({ env, pairType, ss58Format }) => {
504
+ const providerAccount = await getUser(env, AccountKey.providersWithStakeAndDataset);
505
+ const { captchaSolutions } = await createMockCaptchaSolutionsAndRequestHash(env, pairType, ss58Format);
506
+ const providerTasks = await getSignedTasks(env, providerAccount);
507
+ // All of the captchaIds present in the solutions should be in the database
508
+ expect(async function () {
509
+ await providerTasks.validateReceivedCaptchasAgainstStoredCaptchas(captchaSolutions);
510
+ }).to.not.throw();
511
+ });
512
+ test('Builds the tree and gets the commitment', async ({ env, pairType, ss58Format }) => {
513
+ try {
514
+ const { captchaSolutions, dappUserAccount, userSignature } = await createMockCaptchaSolutionsAndRequestHash(env, pairType, ss58Format);
515
+ const dappAccount = await getUser(env, AccountKey.dappsWithStake);
516
+ const tasks = await getSignedTasks(env, dappUserAccount);
517
+ const initialTree = new CaptchaMerkleTree();
518
+ const captchasHashed = captchaSolutions.map((captcha) => computeCaptchaSolutionHash(captcha));
519
+ initialTree.build(captchasHashed);
520
+ const initialCommitmentId = initialTree.root.hash;
521
+ const providerAccount = await getUser(env, AccountKey.providersWithStakeAndDataset);
522
+ const provider = (await tasks.contract.query.getProvider(accountAddress(providerAccount))).value
523
+ .unwrap()
524
+ .unwrap();
525
+ const providerTasks = await getSignedTasks(env, providerAccount);
526
+ const completedAt = (await env.getApi().rpc.chain.getBlock()).block.header.number.toNumber();
527
+ const requestedAt = completedAt - 1;
528
+ const commit = {
529
+ dappContract: accountContract(dappAccount),
530
+ datasetId: provider.datasetId.toString(),
531
+ id: initialCommitmentId,
532
+ providerAccount: accountAddress(providerAccount),
533
+ userAccount: accountAddress(dappUserAccount),
534
+ status: CaptchaStatus.approved,
535
+ completedAt,
536
+ requestedAt,
537
+ userSignature: [...userSignature],
538
+ };
539
+ const queryResult = await providerTasks.contract.query.providerCommit(commit);
540
+ const error = queryResult.value.err || queryResult.value.ok?.err;
541
+ if (error) {
542
+ throw new Error(error);
543
+ }
544
+ const result = (await providerTasks.contract.tx.providerCommit(commit)).result;
545
+ expect(result?.isError).to.be.false;
546
+ const { commitmentId, tree } = await tasks.buildTreeAndGetCommitmentId(captchaSolutions);
547
+ expect(tree).to.deep.equal(initialTree);
548
+ expect(commitmentId).to.equal(initialCommitmentId);
549
+ const commitment = await wrapQuery(tasks.contract.query.getCommit, tasks.contract.query)(commitmentId);
550
+ expect(commitment).to.not.be.undefined;
551
+ }
552
+ catch (e) {
553
+ console.log(e);
554
+ throw e;
555
+ }
556
+ });
557
+ test('BuildTreeAndGetCommitment throws if commitment does not exist', async ({ env, pairType, ss58Format, }) => {
558
+ const { captchaSolutions, dappUserAccount } = await createMockCaptchaSolutionsAndRequestHash(env, pairType, ss58Format);
559
+ const tasks = await getSignedTasks(env, dappUserAccount);
560
+ const salt = randomAsHex();
561
+ const captchaSolutionsSalted = captchaSolutions.map((captcha) => ({
562
+ ...captcha,
563
+ salt: salt,
564
+ }));
565
+ const commitmentPromise = tasks.buildTreeAndGetCommitmentId(captchaSolutionsSalted);
566
+ commitmentPromise.catch((e) => e.message.should.match(new RegExp(i18n.t('CONTRACT.CAPTCHA_SOLUTION_COMMITMENT_DOES_NOT_EXIST'))));
567
+ });
568
+ test('Validates the Dapp User Solution Request is Pending', async ({ env, pairType, ss58Format, }) => {
569
+ const { dappUserAccount, captchaSolutions, blockNumber } = await createMockCaptchaSolutionsAndRequestHash(env, pairType, ss58Format);
570
+ const tasks = await getSignedTasks(env, dappUserAccount);
571
+ const pendingRequestSalt = randomAsHex();
572
+ const captchaIds = captchaSolutions.map((c) => c.captchaId);
573
+ const requestHash = computePendingRequestHash(captchaIds, accountAddress(dappUserAccount), pendingRequestSalt);
574
+ await env.db.storeDappUserPending(hexHash(accountAddress(dappUserAccount)), requestHash, pendingRequestSalt, 99999999999999, blockNumber);
575
+ const pendingRecord = await env.db.getDappUserPending(requestHash);
576
+ const valid = await tasks.validateDappUserSolutionRequestIsPending(requestHash, pendingRecord, accountAddress(dappUserAccount), captchaIds);
577
+ expect(valid).to.be.true;
578
+ });
579
+ test('Get random captchas and request hash', async ({ env }) => {
580
+ try {
581
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
582
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
583
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
584
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
585
+ // NOTE this test can fail if the contract contains Providers that
586
+ // are not present in the database. It can also fail if the contract
587
+ // contains providers that have loaded a different dataset to the
588
+ // one imported from captchasData (above)
589
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
590
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
591
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
592
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
593
+ const dappUserAccount = await getUser(env, AccountKey.dappUsers);
594
+ const dappAccount = await getUser(env, AccountKey.dappsWithStake);
595
+ // there must be at least one provider in the contract and db
596
+ await getUser(env, AccountKey.providersWithStakeAndDataset);
597
+ const dappUserTasks = await getSignedTasks(env, dappUserAccount);
598
+ const solvedCaptchaCount = env.config.captchas.solved.count;
599
+ const unsolvedCaptchaCount = env.config.captchas.unsolved.count;
600
+ const { provider } = (await dappUserTasks.contract.query.getRandomActiveProvider(accountAddress(dappUserAccount), accountContract(dappAccount))).value
601
+ .unwrap()
602
+ .unwrap();
603
+ const { captchas, requestHash } = await dappUserTasks.getRandomCaptchasAndRequestHash(provider.datasetId.toString(), hexHash(accountAddress(dappUserAccount)));
604
+ expect(captchas.length).to.equal(solvedCaptchaCount + unsolvedCaptchaCount);
605
+ const pendingRequest = env.db?.getDappUserPending(requestHash);
606
+ expect(pendingRequest).to.not.be.null;
607
+ }
608
+ catch (err) {
609
+ throw new ProsopoEnvError(err);
610
+ }
611
+ });
612
+ test('Validate provided captcha dataset', async ({ env }) => {
613
+ const dappAccount = await getUser(env, AccountKey.dappsWithStake);
614
+ const tasks = await getSignedTasks(env, dappAccount);
615
+ const res = (await tasks.contract.query.getRandomActiveProvider(accountContract(dappAccount), accountContract(dappAccount))).value
616
+ .unwrap()
617
+ .unwrap();
618
+ const blockNumberParsed = parseBlockNumber(res.blockNumber.toString());
619
+ await tasks.validateProviderWasRandomlyChosen(accountContract(dappAccount), accountContract(dappAccount), res.provider.datasetId.toString(), blockNumberParsed);
620
+ const valid = await tasks
621
+ .validateProviderWasRandomlyChosen(accountContract(dappAccount), accountContract(dappAccount), res.provider.datasetId.toString(), blockNumberParsed)
622
+ .then(() => true)
623
+ .catch(() => false);
624
+ expect(valid).to.be.true;
625
+ });
626
+ test('Validate provided captcha dataset - fail', async ({ env, providerStakeThreshold }) => {
627
+ const providerAccount = await getUser(env, AccountKey.providers);
628
+ const tasks = await getSignedTasks(env, providerAccount);
629
+ let provider = (await tasks.contract.query.getProvider(accountAddress(providerAccount))).value.unwrap().unwrap();
630
+ const resultProviderUpdate1 = (await tasks.contract.tx.providerUpdate(provider.url, provider.fee, PROVIDER_PAYEE, {
631
+ value: 0,
632
+ })).result;
633
+ expect(resultProviderUpdate1?.isError).to.be.false;
634
+ provider = (await tasks.contract.query.getProvider(accountAddress(providerAccount))).value.unwrap().unwrap();
635
+ expect(provider.status).to.equal('Inactive');
636
+ const resultproviderUpdate2 = (await tasks.contract.tx.providerUpdate(provider.url, provider.fee, PROVIDER_PAYEE, {
637
+ value: providerStakeThreshold,
638
+ })).result;
639
+ expect(resultproviderUpdate2?.isError).to.be.false;
640
+ await tasks.providerSetDatasetFromFile(JSON.parse(JSON.stringify(captchaData)));
641
+ const dappAccount = await getUser(env, AccountKey.dappsWithStake);
642
+ const dappUser = await getUser(env, AccountKey.dappUsers);
643
+ const dappUserTasks = await getSignedTasks(env, dappUser);
644
+ const res = (await dappUserTasks.contract.query.getRandomActiveProvider(accountAddress(dappUser), accountContract(dappAccount))).value
645
+ .unwrap()
646
+ .unwrap();
647
+ const blockNumberParsed = parseBlockNumber(res.blockNumber.toString());
648
+ const valid = await dappUserTasks
649
+ .validateProviderWasRandomlyChosen(accountAddress(dappUser), accountContract(dappAccount), '0x1dc833d14a257f21967feddafb3b3876b75b3fc9b0a2d071f29da9bfebc84f5a', blockNumberParsed)
650
+ .then(() => true)
651
+ .catch(() => false);
652
+ expect(valid).to.be.false;
653
+ });
654
+ test('Provider deregister', async ({ env }) => {
655
+ const providerAccount = await getUser(env, AccountKey.providersWithStake);
656
+ const tasks = await getSignedTasks(env, providerAccount);
657
+ const isError = (await tasks.contract.tx.providerDeregister()).result?.isError;
658
+ expect(isError).to.be.false;
659
+ });
660
+ // TODO find out what is making this fail occasionally
661
+ // test('Calculate captcha solution on the basis of Dapp users provided solutions', async ({env}): Promise<void> => {
662
+ // const providerAccount = await getUser(env, AccountKey.providersWithStakeAndDataset)
663
+ // const providerTasks = await getSignedTasks(env, providerAccount)
664
+ // const providerDetails = await providerTasks.contractApi.getProvider(accountAddress(providerAccount))
665
+ // const dappAccount = await getUser(env, AccountKey.dapps)
666
+ //
667
+ // const randomCaptchasResult = await providerTasks.db.getRandomCaptcha(false, providerDetails.datasetId)
668
+ // if (randomCaptchasResult) {
669
+ // const unsolvedCaptcha = randomCaptchasResult[0]
670
+ // const solution = [
671
+ // unsolvedCaptcha.items[0].hash || '',
672
+ // unsolvedCaptcha.items[2].hash || '',
673
+ // unsolvedCaptcha.items[3].hash || '',
674
+ // ]
675
+ // const captchaSolution: CaptchaSolution = { ...unsolvedCaptcha, solution, salt: 'blah' }
676
+ // const commitments: string[] = []
677
+ // for (let count = 0; count < 10; count++) {
678
+ // const commitmentId = hexHash(`test${count}`)
679
+ // commitments.push(commitmentId)
680
+ // await providerTasks.db.storeDappUserSolution(
681
+ // [captchaSolution],
682
+ // commitmentId,
683
+ // randomAsHex(),
684
+ // accountContract(dappAccount),
685
+ // providerDetails.datasetId.toString()
686
+ // )
687
+ // const userSolutions = await providerTasks.db.getDappUserSolutionById(commitmentId)
688
+ // expect(userSolutions).to.be.not.empty
689
+ // }
690
+ //
691
+ // const result = await providerTasks.calculateCaptchaSolutions()
692
+ // expect(result).to.equal(1)
693
+ //
694
+ // for (const commitment of commitments) {
695
+ // const userSolution = await providerTasks.db.getDappUserSolutionById(commitment)
696
+ // expect(userSolution?.processed).to.be.true
697
+ // }
698
+ //
699
+ // const providerDetailsNew = await providerTasks.contractApi.getProvider(
700
+ // accountAddress(providerAccount)
701
+ // )
702
+ //
703
+ // const captchas = await providerTasks.db.getAllCaptchasByDatasetId(providerDetailsNew.datasetId.toString())
704
+ // expect(captchas?.every((captcha) => captcha.datasetId === providerDetailsNew.datasetId.toString())).to.be
705
+ // .true
706
+ //
707
+ // expect(providerDetails.datasetId).to.not.equal(providerDetailsNew.datasetId)
708
+ //
709
+ // expect(Promise.resolve(providerTasks.db.getCaptchaById([unsolvedCaptcha.captchaId]))).to.be.rejected.then(
710
+ // (error) => {
711
+ // expect(error.message).to.equal('Failed to get captcha')
712
+ // }
713
+ // )
714
+ // } else {
715
+ // throw new ProsopoEnvError('DATABASE.CAPTCHA_GET_FAILED')
716
+ // }
717
+ // })
718
+ });
719
+ //# sourceMappingURL=tasks.test.js.map