@prosopo/provider 0.1.14
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.
- package/.dockerignore +5 -0
- package/Dockerfile +12 -0
- package/README.md +259 -0
- package/dist/api.d.ts +10 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +136 -0
- package/dist/api.js.map +1 -0
- package/dist/batch/commitments.d.ts +24 -0
- package/dist/batch/commitments.d.ts.map +1 -0
- package/dist/batch/commitments.js +150 -0
- package/dist/batch/commitments.js.map +1 -0
- package/dist/batch/index.d.ts +2 -0
- package/dist/batch/index.d.ts.map +1 -0
- package/dist/batch/index.js +5 -0
- package/dist/batch/index.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/scheduler.d.ts +4 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +34 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/tasks/index.d.ts +2 -0
- package/dist/tasks/index.d.ts.map +1 -0
- package/dist/tasks/index.js +18 -0
- package/dist/tasks/index.js.map +1 -0
- package/dist/tasks/tasks.d.ts +108 -0
- package/dist/tasks/tasks.d.ts.map +1 -0
- package/dist/tasks/tasks.js +405 -0
- package/dist/tasks/tasks.js.map +1 -0
- package/dist/util.d.ts +20 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +92 -0
- package/dist/util.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAaA,cAAc,SAAS,CAAA;AACvB,cAAc,QAAQ,CAAA;AACtB,cAAc,SAAS,CAAA;AACvB,cAAc,OAAO,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
// Copyright 2021-2022 Prosopo (UK) Ltd.
|
|
5
|
+
//
|
|
6
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
// you may not use this file except in compliance with the License.
|
|
8
|
+
// You may obtain a copy of the License at
|
|
9
|
+
//
|
|
10
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
//
|
|
12
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
// See the License for the specific language governing permissions and
|
|
16
|
+
// limitations under the License.
|
|
17
|
+
tslib_1.__exportStar(require("./tasks"), exports);
|
|
18
|
+
tslib_1.__exportStar(require("./util"), exports);
|
|
19
|
+
tslib_1.__exportStar(require("./batch"), exports);
|
|
20
|
+
tslib_1.__exportStar(require("./api"), exports);
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,wCAAwC;AACxC,EAAE;AACF,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,iDAAiD;AACjD,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;AACjC,kDAAuB;AACvB,iDAAsB;AACtB,kDAAuB;AACvB,gDAAqB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAI9C,yBAA+B,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,iBActE"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright 2021-2022 Prosopo (UK) Ltd.
|
|
3
|
+
//
|
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
// you may not use this file except in compliance with the License.
|
|
6
|
+
// You may obtain a copy of the License at
|
|
7
|
+
//
|
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
// See the License for the specific language governing permissions and
|
|
14
|
+
// limitations under the License.
|
|
15
|
+
// [object Object]
|
|
16
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
const cron_1 = require("cron");
|
|
19
|
+
const env_1 = require("@prosopo/env");
|
|
20
|
+
const tasks_1 = require("./tasks/tasks");
|
|
21
|
+
async function default_1(pair, config) {
|
|
22
|
+
const env = new env_1.ProviderEnvironment(pair, config);
|
|
23
|
+
await env.isReady();
|
|
24
|
+
const tasks = new tasks_1.Tasks(env);
|
|
25
|
+
const job = new cron_1.CronJob(process.argv[2], () => {
|
|
26
|
+
env.logger.debug('It works....');
|
|
27
|
+
tasks.calculateCaptchaSolutions().catch((err) => {
|
|
28
|
+
env.logger.error(err);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
job.start();
|
|
32
|
+
}
|
|
33
|
+
exports.default = default_1;
|
|
34
|
+
//# sourceMappingURL=scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":";AAAA,wCAAwC;AACxC,EAAE;AACF,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,iDAAiD;AACjD,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;AACjC,kBAAkB;AAClB,sCAAsC;;AAEtC,+BAA8B;AAI9B,sCAAkD;AAClD,yCAAqC;AAEtB,KAAK,oBAAW,IAAiB,EAAE,MAAqB;IACnE,MAAM,GAAG,GAAG,IAAI,yBAAmB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAEjD,MAAM,GAAG,CAAC,OAAO,EAAE,CAAA;IAEnB,MAAM,KAAK,GAAG,IAAI,aAAK,CAAC,GAAG,CAAC,CAAA;IAC5B,MAAM,GAAG,GAAG,IAAI,cAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE;QAC1C,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAA;QAChC,KAAK,CAAC,yBAAyB,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YAC5C,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QACzB,CAAC,CAAC,CAAA;IACN,CAAC,CAAC,CAAA;IAEF,GAAG,CAAC,KAAK,EAAE,CAAA;AACf,CAAC;AAdD,4BAcC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tasks/index.ts"],"names":[],"mappings":"AAaA,cAAc,SAAS,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
// Copyright 2021-2022 Prosopo (UK) Ltd.
|
|
5
|
+
//
|
|
6
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
// you may not use this file except in compliance with the License.
|
|
8
|
+
// You may obtain a copy of the License at
|
|
9
|
+
//
|
|
10
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
//
|
|
12
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
// See the License for the specific language governing permissions and
|
|
16
|
+
// limitations under the License.
|
|
17
|
+
tslib_1.__exportStar(require("./tasks"), exports);
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tasks/index.ts"],"names":[],"mappings":";;;AAAA,wCAAwC;AACxC,EAAE;AACF,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,iDAAiD;AACjD,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;AACjC,kDAAuB"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { ArgumentTypes, Captcha, CaptchaConfig, CaptchaSolution, CaptchaSolutionConfig, CaptchaWithProof, DappUserSolutionResult, DatasetRaw, Hash, PendingCaptchaRequest } from '@prosopo/types';
|
|
2
|
+
import { CaptchaMerkleTree } from '@prosopo/datasets';
|
|
3
|
+
import { Database, UserCommitmentRecord } from '@prosopo/types-database';
|
|
4
|
+
import { Header } from '@polkadot/types/interfaces/runtime/index';
|
|
5
|
+
import { Logger } from '@prosopo/common';
|
|
6
|
+
import { ProsopoCaptchaContract } from '@prosopo/contract';
|
|
7
|
+
import { ProviderEnvironment } from '@prosopo/types-env';
|
|
8
|
+
import { SubmittableResult } from '@polkadot/api';
|
|
9
|
+
/**
|
|
10
|
+
* @description Tasks that are shared by the API and CLI
|
|
11
|
+
*/
|
|
12
|
+
export declare class Tasks {
|
|
13
|
+
contract: ProsopoCaptchaContract;
|
|
14
|
+
db: Database;
|
|
15
|
+
captchaConfig: CaptchaConfig;
|
|
16
|
+
captchaSolutionConfig: CaptchaSolutionConfig;
|
|
17
|
+
logger: Logger;
|
|
18
|
+
constructor(env: ProviderEnvironment);
|
|
19
|
+
providerSetDatasetFromFile(file: JSON): Promise<SubmittableResult | undefined>;
|
|
20
|
+
providerSetDataset(datasetRaw: DatasetRaw): Promise<SubmittableResult | undefined>;
|
|
21
|
+
/**
|
|
22
|
+
* @description Get random captchas that are solved or not solved, along with the merkle proof for each
|
|
23
|
+
* @param {string} datasetId the id of the data set
|
|
24
|
+
* @param {boolean} solved `true` when captcha is solved
|
|
25
|
+
* @param {number} size the number of records to be returned
|
|
26
|
+
*/
|
|
27
|
+
getCaptchaWithProof(datasetId: ArgumentTypes.Hash, solved: boolean, size: number): Promise<CaptchaWithProof[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Validate and store the text captcha solution(s) from the Dapp User in a web2 environment
|
|
30
|
+
* @param {string} userAccount
|
|
31
|
+
* @param {string} dappAccount
|
|
32
|
+
* @param {string} requestHash
|
|
33
|
+
* @param {JSON} captchas
|
|
34
|
+
* @param {string} signature
|
|
35
|
+
* @return {Promise<DappUserSolutionResult>} result containing the contract event
|
|
36
|
+
*/
|
|
37
|
+
dappUserSolution(userAccount: string, dappAccount: string, requestHash: string, captchas: CaptchaSolution[], signature: string): Promise<DappUserSolutionResult>;
|
|
38
|
+
/**
|
|
39
|
+
* Validate that the dapp is active in the contract
|
|
40
|
+
*/
|
|
41
|
+
dappIsActive(dappAccount: string): Promise<boolean>;
|
|
42
|
+
/**
|
|
43
|
+
* Validate that the provider is active in the contract
|
|
44
|
+
*/
|
|
45
|
+
providerIsActive(providerAccount: string): Promise<boolean>;
|
|
46
|
+
/**
|
|
47
|
+
* Validate length of received captchas array matches length of captchas found in database
|
|
48
|
+
* Validate that the datasetId is the same for all captchas and is equal to the datasetId on the stored captchas
|
|
49
|
+
*/
|
|
50
|
+
validateReceivedCaptchasAgainstStoredCaptchas(captchas: CaptchaSolution[]): Promise<{
|
|
51
|
+
storedCaptchas: Captcha[];
|
|
52
|
+
receivedCaptchas: CaptchaSolution[];
|
|
53
|
+
captchaIds: string[];
|
|
54
|
+
}>;
|
|
55
|
+
/**
|
|
56
|
+
* Build merkle tree and get commitment from contract, returning the tree, commitment, and commitmentId
|
|
57
|
+
* @param {CaptchaSolution[]} captchaSolutions
|
|
58
|
+
* @returns {Promise<{ tree: CaptchaMerkleTree, commitment: CaptchaSolutionCommitment, commitmentId: string }>}
|
|
59
|
+
*/
|
|
60
|
+
buildTreeAndGetCommitmentId(captchaSolutions: CaptchaSolution[]): Promise<{
|
|
61
|
+
tree: CaptchaMerkleTree;
|
|
62
|
+
commitmentId: string;
|
|
63
|
+
}>;
|
|
64
|
+
/**
|
|
65
|
+
* Validate that a Dapp User is responding to their own pending captcha request
|
|
66
|
+
* @param {string} requestHash
|
|
67
|
+
* @param {PendingCaptchaRequest} pendingRecord
|
|
68
|
+
* @param {string} userAccount
|
|
69
|
+
* @param {string[]} captchaIds
|
|
70
|
+
*/
|
|
71
|
+
validateDappUserSolutionRequestIsPending(requestHash: string, pendingRecord: PendingCaptchaRequest, userAccount: string, captchaIds: string[]): Promise<boolean>;
|
|
72
|
+
/**
|
|
73
|
+
* Get two random captchas from specified dataset, create the response and store a hash of it, marked as pending
|
|
74
|
+
* @param {string} datasetId
|
|
75
|
+
* @param {string} userAccount
|
|
76
|
+
*/
|
|
77
|
+
getRandomCaptchasAndRequestHash(datasetId: string, userAccount: string): Promise<{
|
|
78
|
+
captchas: CaptchaWithProof[];
|
|
79
|
+
requestHash: string;
|
|
80
|
+
}>;
|
|
81
|
+
/**
|
|
82
|
+
* Apply new captcha solutions to captcha dataset and recalculate merkle tree
|
|
83
|
+
*/
|
|
84
|
+
calculateCaptchaSolutions(): Promise<number>;
|
|
85
|
+
/**
|
|
86
|
+
* Block by block search for blockNo
|
|
87
|
+
*/
|
|
88
|
+
isRecentBlock(contract: any, header: Header, blockNo: number, depth?: number): any;
|
|
89
|
+
/**
|
|
90
|
+
* Validate that provided `datasetId` was a result of calling `get_random_provider` method
|
|
91
|
+
* @param {string} userAccount - Same user that called `get_random_provider`
|
|
92
|
+
* @param {string} dappContractAccount - account of dapp that is requesting captcha
|
|
93
|
+
* @param {string} datasetId - `captcha_dataset_id` from the result of `get_random_provider`
|
|
94
|
+
* @param {string} blockNumber - Block on which `get_random_provider` was called
|
|
95
|
+
*/
|
|
96
|
+
validateProviderWasRandomlyChosen(userAccount: string, dappContractAccount: string, datasetId: string | Hash, blockNumber: number): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* Get payment info for a transaction
|
|
99
|
+
* @param {string} userAccount
|
|
100
|
+
* @param {string} blockHash
|
|
101
|
+
* @param {string} txHash
|
|
102
|
+
* @returns {Promise<RuntimeDispatchInfo|null>}
|
|
103
|
+
*/
|
|
104
|
+
private getPaymentInfo;
|
|
105
|
+
getDappUserCommitmentById(commitmentId: string): Promise<UserCommitmentRecord>;
|
|
106
|
+
getDappUserCommitmentByAccount(userAccount: string): Promise<UserCommitmentRecord | undefined>;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=tasks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../../src/tasks/tasks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Tasks = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
// Copyright 2021-2022 Prosopo (UK) Ltd.
|
|
6
|
+
//
|
|
7
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
// you may not use this file except in compliance with the License.
|
|
9
|
+
// You may obtain a copy of the License at
|
|
10
|
+
//
|
|
11
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
//
|
|
13
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
// See the License for the specific language governing permissions and
|
|
17
|
+
// limitations under the License.
|
|
18
|
+
const types_1 = require("@prosopo/types");
|
|
19
|
+
const datasets_1 = require("@prosopo/datasets");
|
|
20
|
+
const common_1 = require("@prosopo/common");
|
|
21
|
+
const contract_1 = require("@prosopo/contract");
|
|
22
|
+
const util_1 = require("../util");
|
|
23
|
+
const util_2 = require("@polkadot/util");
|
|
24
|
+
const util_crypto_1 = require("@polkadot/util-crypto");
|
|
25
|
+
const contract_2 = require("@prosopo/contract");
|
|
26
|
+
const consola_1 = tslib_1.__importDefault(require("consola"));
|
|
27
|
+
/**
|
|
28
|
+
* @description Tasks that are shared by the API and CLI
|
|
29
|
+
*/
|
|
30
|
+
class Tasks {
|
|
31
|
+
contract;
|
|
32
|
+
db;
|
|
33
|
+
captchaConfig;
|
|
34
|
+
captchaSolutionConfig;
|
|
35
|
+
logger;
|
|
36
|
+
constructor(env) {
|
|
37
|
+
if (!env.contractInterface) {
|
|
38
|
+
throw new common_1.ProsopoEnvError('CONTRACT.CONTRACT_UNDEFINED', this.constructor.name, {}, { contractAddress: env.contractAddress });
|
|
39
|
+
}
|
|
40
|
+
this.contract = env.contractInterface;
|
|
41
|
+
this.db = env.db;
|
|
42
|
+
this.captchaConfig = env.config.captchas;
|
|
43
|
+
this.captchaSolutionConfig = env.config.captchaSolutions;
|
|
44
|
+
this.logger = (0, common_1.logger)(env.config.logLevel, 'Tasks');
|
|
45
|
+
}
|
|
46
|
+
async providerSetDatasetFromFile(file) {
|
|
47
|
+
const datasetRaw = (0, datasets_1.parseCaptchaDataset)(file);
|
|
48
|
+
return await this.providerSetDataset(datasetRaw);
|
|
49
|
+
}
|
|
50
|
+
async providerSetDataset(datasetRaw) {
|
|
51
|
+
const dataset = await (0, datasets_1.buildDataset)(datasetRaw);
|
|
52
|
+
if (!dataset.datasetId || !dataset.datasetContentId) {
|
|
53
|
+
throw new common_1.ProsopoEnvError('DATASET.DATASET_ID_UNDEFINED', this.providerSetDataset.name);
|
|
54
|
+
}
|
|
55
|
+
await this.db?.storeDataset(dataset);
|
|
56
|
+
const txResult = await this.contract.methods.providerSetDataset(dataset.datasetId, dataset.datasetContentId, {
|
|
57
|
+
value: 0,
|
|
58
|
+
});
|
|
59
|
+
return txResult.result;
|
|
60
|
+
}
|
|
61
|
+
// Other tasks
|
|
62
|
+
/**
|
|
63
|
+
* @description Get random captchas that are solved or not solved, along with the merkle proof for each
|
|
64
|
+
* @param {string} datasetId the id of the data set
|
|
65
|
+
* @param {boolean} solved `true` when captcha is solved
|
|
66
|
+
* @param {number} size the number of records to be returned
|
|
67
|
+
*/
|
|
68
|
+
async getCaptchaWithProof(datasetId, solved, size) {
|
|
69
|
+
const captchaDocs = await this.db.getRandomCaptcha(solved, datasetId, size);
|
|
70
|
+
if (captchaDocs) {
|
|
71
|
+
const captchas = [];
|
|
72
|
+
for (const captcha of captchaDocs) {
|
|
73
|
+
const datasetDetails = await this.db.getDatasetDetails(datasetId);
|
|
74
|
+
const tree = new datasets_1.CaptchaMerkleTree();
|
|
75
|
+
if (datasetDetails.contentTree) {
|
|
76
|
+
tree.layers = datasetDetails.contentTree;
|
|
77
|
+
const proof = tree.proof(captcha.captchaContentId);
|
|
78
|
+
// cannot pass solution to dapp user as they are required to solve the captcha!
|
|
79
|
+
delete captcha.solution;
|
|
80
|
+
captcha.items = (0, util_1.shuffleArray)(captcha.items);
|
|
81
|
+
captchas.push({ captcha, proof });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return captchas;
|
|
85
|
+
}
|
|
86
|
+
throw new common_1.ProsopoEnvError('DATABASE.CAPTCHA_GET_FAILED', this.getCaptchaWithProof.name, {}, { datasetId, solved, size });
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Validate and store the text captcha solution(s) from the Dapp User in a web2 environment
|
|
90
|
+
* @param {string} userAccount
|
|
91
|
+
* @param {string} dappAccount
|
|
92
|
+
* @param {string} requestHash
|
|
93
|
+
* @param {JSON} captchas
|
|
94
|
+
* @param {string} signature
|
|
95
|
+
* @return {Promise<DappUserSolutionResult>} result containing the contract event
|
|
96
|
+
*/
|
|
97
|
+
async dappUserSolution(userAccount, dappAccount, requestHash, captchas, signature // the signature to indicate ownership of account (web2 only)
|
|
98
|
+
) {
|
|
99
|
+
if (!(await this.dappIsActive(dappAccount))) {
|
|
100
|
+
throw new common_1.ProsopoEnvError('CONTRACT.DAPP_NOT_ACTIVE', this.getPaymentInfo.name, {}, { dappAccount });
|
|
101
|
+
}
|
|
102
|
+
// check that the signature is valid (i.e. the web2 user has signed the message with their private key, proving they own their account)
|
|
103
|
+
const verification = (0, util_crypto_1.signatureVerify)((0, util_2.stringToHex)(requestHash), signature, userAccount);
|
|
104
|
+
if (!verification.isValid) {
|
|
105
|
+
// the signature is not valid, so the user is not the owner of the account. May have given a false account address with good reputation in an attempt to impersonate
|
|
106
|
+
throw new common_1.ProsopoEnvError('GENERAL.INVALID_SIGNATURE', this.dappUserSolution.name, {}, { userAccount });
|
|
107
|
+
}
|
|
108
|
+
let response = {
|
|
109
|
+
captchas: [],
|
|
110
|
+
solutionApproved: false,
|
|
111
|
+
};
|
|
112
|
+
const { storedCaptchas, receivedCaptchas, captchaIds } = await this.validateReceivedCaptchasAgainstStoredCaptchas(captchas);
|
|
113
|
+
const { tree, commitmentId } = await this.buildTreeAndGetCommitmentId(receivedCaptchas);
|
|
114
|
+
const provider = (await this.contract.methods.getProvider(this.contract.pair.address, {})).value
|
|
115
|
+
.unwrap()
|
|
116
|
+
.unwrap();
|
|
117
|
+
const pendingRecord = await this.db.getDappUserPending(requestHash);
|
|
118
|
+
const pendingRequest = await this.validateDappUserSolutionRequestIsPending(requestHash, pendingRecord, userAccount, captchaIds);
|
|
119
|
+
// Only do stuff if the request is in the local DB
|
|
120
|
+
const userSignature = (0, util_2.hexToU8a)(signature);
|
|
121
|
+
const blockNumber = (await (0, contract_1.getBlockNumber)(this.contract.api)).toNumber();
|
|
122
|
+
if (pendingRequest) {
|
|
123
|
+
const commit = {
|
|
124
|
+
id: commitmentId,
|
|
125
|
+
userAccount: userAccount,
|
|
126
|
+
dappContract: dappAccount,
|
|
127
|
+
providerAccount: this.contract.pair.address,
|
|
128
|
+
datasetId: provider.datasetId.toString(),
|
|
129
|
+
status: types_1.CaptchaStatus.pending,
|
|
130
|
+
userSignature: Array.from(userSignature),
|
|
131
|
+
requestedAt: pendingRecord.requestedAtBlock,
|
|
132
|
+
completedAt: blockNumber,
|
|
133
|
+
processed: false,
|
|
134
|
+
};
|
|
135
|
+
await this.db.storeDappUserSolution(receivedCaptchas, commit);
|
|
136
|
+
if ((0, datasets_1.compareCaptchaSolutions)(receivedCaptchas, storedCaptchas)) {
|
|
137
|
+
response = {
|
|
138
|
+
captchas: captchaIds.map((id) => ({
|
|
139
|
+
captchaId: id,
|
|
140
|
+
proof: tree.proof(id),
|
|
141
|
+
})),
|
|
142
|
+
solutionApproved: true,
|
|
143
|
+
};
|
|
144
|
+
await this.db.approveDappUserCommitment(commitmentId);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
response = {
|
|
148
|
+
captchas: captchaIds.map((id) => ({
|
|
149
|
+
captchaId: id,
|
|
150
|
+
proof: [[]],
|
|
151
|
+
})),
|
|
152
|
+
solutionApproved: false,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return response;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Validate that the dapp is active in the contract
|
|
160
|
+
*/
|
|
161
|
+
async dappIsActive(dappAccount) {
|
|
162
|
+
const dapp = await (0, contract_2.wrapQuery)(this.contract.query.getDapp, this.contract.query)(dappAccount);
|
|
163
|
+
//dapp.status.isActive doesn't work: https://substrate.stackexchange.com/questions/6333/how-do-we-work-with-polkadot-js-enums-in-typescript
|
|
164
|
+
return dapp.status.toString() === 'Active';
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Validate that the provider is active in the contract
|
|
168
|
+
*/
|
|
169
|
+
async providerIsActive(providerAccount) {
|
|
170
|
+
const provider = await (0, contract_2.wrapQuery)(this.contract.query.getProvider, this.contract.query)(providerAccount);
|
|
171
|
+
return provider.status.toString() === 'Active';
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Validate length of received captchas array matches length of captchas found in database
|
|
175
|
+
* Validate that the datasetId is the same for all captchas and is equal to the datasetId on the stored captchas
|
|
176
|
+
*/
|
|
177
|
+
async validateReceivedCaptchasAgainstStoredCaptchas(captchas) {
|
|
178
|
+
const receivedCaptchas = (0, datasets_1.parseAndSortCaptchaSolutions)(captchas);
|
|
179
|
+
const captchaIds = receivedCaptchas.map((captcha) => captcha.captchaId);
|
|
180
|
+
const storedCaptchas = await this.db.getCaptchaById(captchaIds);
|
|
181
|
+
if (!storedCaptchas || receivedCaptchas.length !== storedCaptchas.length) {
|
|
182
|
+
throw new common_1.ProsopoEnvError('CAPTCHA.INVALID_CAPTCHA_ID', this.validateReceivedCaptchasAgainstStoredCaptchas.name, {}, captchas);
|
|
183
|
+
}
|
|
184
|
+
if (!storedCaptchas.every((captcha) => captcha.datasetId === storedCaptchas[0].datasetId)) {
|
|
185
|
+
throw new common_1.ProsopoEnvError('CAPTCHA.DIFFERENT_DATASET_IDS', this.validateReceivedCaptchasAgainstStoredCaptchas.name, {}, captchas);
|
|
186
|
+
}
|
|
187
|
+
return { storedCaptchas, receivedCaptchas, captchaIds };
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Build merkle tree and get commitment from contract, returning the tree, commitment, and commitmentId
|
|
191
|
+
* @param {CaptchaSolution[]} captchaSolutions
|
|
192
|
+
* @returns {Promise<{ tree: CaptchaMerkleTree, commitment: CaptchaSolutionCommitment, commitmentId: string }>}
|
|
193
|
+
*/
|
|
194
|
+
async buildTreeAndGetCommitmentId(captchaSolutions) {
|
|
195
|
+
const tree = new datasets_1.CaptchaMerkleTree();
|
|
196
|
+
const solutionsHashed = captchaSolutions.map((captcha) => (0, datasets_1.computeCaptchaSolutionHash)(captcha));
|
|
197
|
+
tree.build(solutionsHashed);
|
|
198
|
+
const commitmentId = tree.root?.hash;
|
|
199
|
+
if (!commitmentId) {
|
|
200
|
+
throw new common_1.ProsopoEnvError('CONTRACT.CAPTCHA_SOLUTION_COMMITMENT_DOES_NOT_EXIST', this.buildTreeAndGetCommitmentId.name, {}, { commitmentId: commitmentId });
|
|
201
|
+
}
|
|
202
|
+
return { tree, commitmentId };
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Validate that a Dapp User is responding to their own pending captcha request
|
|
206
|
+
* @param {string} requestHash
|
|
207
|
+
* @param {PendingCaptchaRequest} pendingRecord
|
|
208
|
+
* @param {string} userAccount
|
|
209
|
+
* @param {string[]} captchaIds
|
|
210
|
+
*/
|
|
211
|
+
async validateDappUserSolutionRequestIsPending(requestHash, pendingRecord, userAccount, captchaIds) {
|
|
212
|
+
const currentTime = Date.now();
|
|
213
|
+
if (pendingRecord.deadlineTimestamp < currentTime) {
|
|
214
|
+
// deadline for responding to the captcha has expired
|
|
215
|
+
this.logger.info('Deadline for responding to captcha has expired');
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
if (pendingRecord) {
|
|
219
|
+
const pendingHashComputed = (0, datasets_1.computePendingRequestHash)(captchaIds, userAccount, pendingRecord.salt);
|
|
220
|
+
return requestHash === pendingHashComputed;
|
|
221
|
+
}
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Get two random captchas from specified dataset, create the response and store a hash of it, marked as pending
|
|
226
|
+
* @param {string} datasetId
|
|
227
|
+
* @param {string} userAccount
|
|
228
|
+
*/
|
|
229
|
+
async getRandomCaptchasAndRequestHash(datasetId, userAccount) {
|
|
230
|
+
const dataset = await this.db.getDatasetDetails(datasetId);
|
|
231
|
+
if (!dataset) {
|
|
232
|
+
throw new common_1.ProsopoEnvError('DATABASE.DATASET_GET_FAILED');
|
|
233
|
+
}
|
|
234
|
+
const unsolvedCount = Math.abs(Math.trunc(this.captchaConfig.unsolved.count));
|
|
235
|
+
const solvedCount = Math.abs(Math.trunc(this.captchaConfig.solved.count));
|
|
236
|
+
if (!solvedCount) {
|
|
237
|
+
throw new common_1.ProsopoEnvError('CONFIG.INVALID_CAPTCHA_NUMBER');
|
|
238
|
+
}
|
|
239
|
+
const solved = await this.getCaptchaWithProof(datasetId, true, solvedCount);
|
|
240
|
+
let unsolved = [];
|
|
241
|
+
if (unsolvedCount) {
|
|
242
|
+
unsolved = await this.getCaptchaWithProof(datasetId, false, unsolvedCount);
|
|
243
|
+
}
|
|
244
|
+
const captchas = (0, util_1.shuffleArray)([...solved, ...unsolved]);
|
|
245
|
+
const salt = (0, util_crypto_1.randomAsHex)();
|
|
246
|
+
const requestHash = (0, datasets_1.computePendingRequestHash)(captchas.map((c) => c.captcha.captchaId), userAccount, salt);
|
|
247
|
+
const currentTime = Date.now();
|
|
248
|
+
const timeLimit = captchas.map((captcha) => captcha.captcha.timeLimitMs || 30000).reduce((a, b) => a + b, 0);
|
|
249
|
+
const deadlineTs = timeLimit + currentTime;
|
|
250
|
+
const currentBlockNumber = await (0, contract_1.getBlockNumber)(this.contract.api);
|
|
251
|
+
await this.db.storeDappUserPending(userAccount, requestHash, salt, deadlineTs, currentBlockNumber.toNumber());
|
|
252
|
+
return { captchas, requestHash };
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Apply new captcha solutions to captcha dataset and recalculate merkle tree
|
|
256
|
+
*/
|
|
257
|
+
async calculateCaptchaSolutions() {
|
|
258
|
+
try {
|
|
259
|
+
// Get the current datasetId from the contract
|
|
260
|
+
const provider = (await this.contract.methods.getProvider(this.contract.pair.address, {})).value
|
|
261
|
+
.unwrap()
|
|
262
|
+
.unwrap();
|
|
263
|
+
// Get any unsolved CAPTCHA challenges from the database for this datasetId
|
|
264
|
+
const unsolvedCaptchas = await this.db.getAllCaptchasByDatasetId(provider.datasetId.toString(), types_1.CaptchaStates.Unsolved);
|
|
265
|
+
// edge case when a captcha dataset contains no unsolved CAPTCHA challenges
|
|
266
|
+
if (!unsolvedCaptchas) {
|
|
267
|
+
return 0;
|
|
268
|
+
}
|
|
269
|
+
// Sort the unsolved CAPTCHA challenges by their captchaId
|
|
270
|
+
const unsolvedSorted = unsolvedCaptchas.sort(datasets_1.captchaSort);
|
|
271
|
+
consola_1.default.info(`There are ${unsolvedSorted.length} unsolved CAPTCHA challenges`);
|
|
272
|
+
// Get the solution configuration from the config file
|
|
273
|
+
const requiredNumberOfSolutions = this.captchaSolutionConfig.requiredNumberOfSolutions;
|
|
274
|
+
const winningPercentage = this.captchaSolutionConfig.solutionWinningPercentage;
|
|
275
|
+
const winningNumberOfSolutions = Math.round(requiredNumberOfSolutions * (winningPercentage / 100));
|
|
276
|
+
if (unsolvedSorted && unsolvedSorted.length > 0) {
|
|
277
|
+
const captchaIds = unsolvedSorted.map((captcha) => captcha.captchaId);
|
|
278
|
+
const solutions = (await this.db.getAllDappUserSolutions(captchaIds)) || [];
|
|
279
|
+
const solutionsToUpdate = (0, util_1.calculateNewSolutions)(solutions, winningNumberOfSolutions);
|
|
280
|
+
if (solutionsToUpdate.rows().length > 0) {
|
|
281
|
+
consola_1.default.info(`There are ${solutionsToUpdate.rows().length} CAPTCHA challenges to update with solutions`);
|
|
282
|
+
try {
|
|
283
|
+
const captchaIdsToUpdate = [...solutionsToUpdate['captchaId'].values()];
|
|
284
|
+
const commitmentIds = solutions
|
|
285
|
+
.filter((s) => captchaIdsToUpdate.indexOf(s.captchaId) > -1)
|
|
286
|
+
.map((s) => s.commitmentId);
|
|
287
|
+
const dataset = await this.db.getDataset(provider.datasetId.toString());
|
|
288
|
+
dataset.captchas = (0, util_1.updateSolutions)(solutionsToUpdate, dataset.captchas, this.logger);
|
|
289
|
+
// store new solutions in database
|
|
290
|
+
await this.providerSetDataset(dataset);
|
|
291
|
+
// mark user solutions as used to calculate new solutions
|
|
292
|
+
await this.db.flagUsedDappUserSolutions(captchaIdsToUpdate);
|
|
293
|
+
// mark user commitments as used to calculate new solutions
|
|
294
|
+
await this.db.flagUsedDappUserCommitments(commitmentIds);
|
|
295
|
+
// remove old captcha challenges from database
|
|
296
|
+
await this.db.removeCaptchas(captchaIdsToUpdate);
|
|
297
|
+
return solutionsToUpdate.rows().length;
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
consola_1.default.error(error);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return 0;
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
consola_1.default.info(`There are no CAPTCHA challenges that require their solutions to be updated`);
|
|
307
|
+
return 0;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
throw new common_1.ProsopoEnvError(error, 'GENERAL.CALCULATE_CAPTCHA_SOLUTION');
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Block by block search for blockNo
|
|
316
|
+
*/
|
|
317
|
+
async isRecentBlock(contract, header, blockNo, depth = this.captchaSolutionConfig.captchaBlockRecency) {
|
|
318
|
+
if (depth == 0) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
const headerBlockNo = header.number.toNumber();
|
|
322
|
+
if (headerBlockNo === blockNo) {
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
const parent = await contract.api.rpc.chain.getBlock(header.parentHash);
|
|
326
|
+
return this.isRecentBlock(contract, parent.toHuman().block.header, blockNo, depth - 1);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Validate that provided `datasetId` was a result of calling `get_random_provider` method
|
|
330
|
+
* @param {string} userAccount - Same user that called `get_random_provider`
|
|
331
|
+
* @param {string} dappContractAccount - account of dapp that is requesting captcha
|
|
332
|
+
* @param {string} datasetId - `captcha_dataset_id` from the result of `get_random_provider`
|
|
333
|
+
* @param {string} blockNumber - Block on which `get_random_provider` was called
|
|
334
|
+
*/
|
|
335
|
+
async validateProviderWasRandomlyChosen(userAccount, dappContractAccount, datasetId, blockNumber) {
|
|
336
|
+
const contract = await this.contract.contract;
|
|
337
|
+
if (!contract) {
|
|
338
|
+
throw new common_1.ProsopoEnvError('CONTRACT.CONTRACT_UNDEFINED', this.validateProviderWasRandomlyChosen.name);
|
|
339
|
+
}
|
|
340
|
+
const header = await contract.api.rpc.chain.getHeader();
|
|
341
|
+
const isBlockNoValid = await this.isRecentBlock(contract, header, blockNumber);
|
|
342
|
+
if (!isBlockNoValid) {
|
|
343
|
+
throw new common_1.ProsopoEnvError('CAPTCHA.INVALID_BLOCK_NO', this.validateProviderWasRandomlyChosen.name, {}, {
|
|
344
|
+
userAccount,
|
|
345
|
+
dappContractAccount,
|
|
346
|
+
datasetId,
|
|
347
|
+
header,
|
|
348
|
+
blockNumber,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
const block = (await contract.api.rpc.chain.getBlockHash(blockNumber));
|
|
352
|
+
const randomProviderAndBlockNo = await this.contract.queryAtBlock(block, 'getRandomActiveProvider', [userAccount, dappContractAccount]);
|
|
353
|
+
if (datasetId.toString().localeCompare(randomProviderAndBlockNo.provider.datasetId.toString())) {
|
|
354
|
+
throw new common_1.ProsopoEnvError('DATASET.INVALID_DATASET_ID', this.validateProviderWasRandomlyChosen.name, {}, randomProviderAndBlockNo);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Get payment info for a transaction
|
|
359
|
+
* @param {string} userAccount
|
|
360
|
+
* @param {string} blockHash
|
|
361
|
+
* @param {string} txHash
|
|
362
|
+
* @returns {Promise<RuntimeDispatchInfo|null>}
|
|
363
|
+
*/
|
|
364
|
+
async getPaymentInfo(userAccount, blockHash, txHash) {
|
|
365
|
+
// Validate block and transaction, checking that the signer matches the userAccount
|
|
366
|
+
const signedBlock = (await this.contract.api.rpc.chain.getBlock(blockHash));
|
|
367
|
+
if (!signedBlock) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
const extrinsic = signedBlock.block.extrinsics.find((extrinsic) => extrinsic.hash.toString() === txHash);
|
|
371
|
+
if (!extrinsic || extrinsic.signer.toString() !== userAccount) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
// Retrieve tx fee for extrinsic
|
|
375
|
+
const paymentInfo = (await this.contract.api.rpc.payment.queryInfo(extrinsic.toHex(), blockHash));
|
|
376
|
+
if (!paymentInfo) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
return paymentInfo;
|
|
380
|
+
}
|
|
381
|
+
/*
|
|
382
|
+
* Get dapp user solution from database
|
|
383
|
+
*/
|
|
384
|
+
async getDappUserCommitmentById(commitmentId) {
|
|
385
|
+
const dappUserSolution = await this.db.getDappUserCommitmentById(commitmentId);
|
|
386
|
+
if (!dappUserSolution) {
|
|
387
|
+
throw new common_1.ProsopoEnvError('CAPTCHA.DAPP_USER_SOLUTION_NOT_FOUND', this.getDappUserCommitmentById.name, {}, { commitmentId: commitmentId });
|
|
388
|
+
}
|
|
389
|
+
return dappUserSolution;
|
|
390
|
+
}
|
|
391
|
+
/* Check if dapp user has verified solution in cache */
|
|
392
|
+
async getDappUserCommitmentByAccount(userAccount) {
|
|
393
|
+
const dappUserSolutions = await this.db.getDappUserCommitmentByAccount(userAccount);
|
|
394
|
+
if (dappUserSolutions.length > 0) {
|
|
395
|
+
for (const dappUserSolution of dappUserSolutions) {
|
|
396
|
+
if (dappUserSolution.status === types_1.ArgumentTypes.CaptchaStatus.approved) {
|
|
397
|
+
return dappUserSolution;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return undefined;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
exports.Tasks = Tasks;
|
|
405
|
+
//# sourceMappingURL=tasks.js.map
|