@prosopo/provider 0.2.32 → 0.2.33
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/dist/cjs/api/admin.cjs +116 -0
- package/dist/cjs/api/authMiddleware.cjs +57 -0
- package/dist/cjs/api/captcha.cjs +33 -27
- package/dist/cjs/batch/commitments.cjs +4 -1
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/tasks/calculateSolutions.cjs +1 -1
- package/dist/cjs/tasks/tasks.cjs +101 -70
- package/dist/cjs/util.cjs +4 -4
- package/package.json +10 -10
- package/dist/cjs/contracts/captcha/dist/types-returns/captcha.cjs +0 -62
- package/dist/tests/data/captchas.d.ts +0 -37
- package/dist/tests/data/captchas.d.ts.map +0 -1
- package/dist/tests/data/captchas.js +0 -902
- package/dist/tests/data/captchas.js.map +0 -1
- package/dist/tests/data/captchas1.json +0 -53
- package/dist/tests/data/captchas2.json +0 -69
- package/dist/tests/data/captchas3.json +0 -54
- package/dist/tests/data/captchas4.json +0 -53
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const z = require("zod");
|
|
4
|
+
const types = require("@prosopo/types");
|
|
5
|
+
require("../index.cjs");
|
|
6
|
+
const typesReturns = require("@prosopo/captcha-contract/types-returns");
|
|
7
|
+
const common = require("@prosopo/common");
|
|
8
|
+
const express = require("express");
|
|
9
|
+
const authMiddleware = require("./authMiddleware.cjs");
|
|
10
|
+
const contract = require("@prosopo/contract");
|
|
11
|
+
const tasks = require("../tasks/tasks.cjs");
|
|
12
|
+
const commitments = require("../batch/commitments.cjs");
|
|
13
|
+
function _interopNamespaceDefault(e) {
|
|
14
|
+
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
15
|
+
if (e) {
|
|
16
|
+
for (const k in e) {
|
|
17
|
+
if (k !== "default") {
|
|
18
|
+
const d = Object.getOwnPropertyDescriptor(e, k);
|
|
19
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
get: () => e[k]
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
n.default = e;
|
|
27
|
+
return Object.freeze(n);
|
|
28
|
+
}
|
|
29
|
+
const z__namespace = /* @__PURE__ */ _interopNamespaceDefault(z);
|
|
30
|
+
const apiBatchCommitConfig = {
|
|
31
|
+
interval: 0,
|
|
32
|
+
maxBatchExtrinsicPercentage: 59
|
|
33
|
+
};
|
|
34
|
+
function prosopoAdminRouter(env) {
|
|
35
|
+
const router = express.Router();
|
|
36
|
+
const tasks$1 = new tasks.Tasks(env);
|
|
37
|
+
router.use(authMiddleware.authMiddleware(tasks$1, env));
|
|
38
|
+
router.post(types.AdminApiPaths.BatchCommit, async (req, res, next) => {
|
|
39
|
+
if (env.db) {
|
|
40
|
+
try {
|
|
41
|
+
const batchCommitter = new commitments.BatchCommitmentsTask(
|
|
42
|
+
apiBatchCommitConfig,
|
|
43
|
+
env.getContractInterface(),
|
|
44
|
+
env.db,
|
|
45
|
+
0n,
|
|
46
|
+
env.logger
|
|
47
|
+
);
|
|
48
|
+
const result = await batchCommitter.run();
|
|
49
|
+
console.info(`Batch commit complete: ${result}`);
|
|
50
|
+
res.status(200).send(result);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error(err);
|
|
53
|
+
res.status(500).send(err);
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
console.error("No database configured");
|
|
57
|
+
res.status(500).send("No database configured");
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
router.post(types.AdminApiPaths.UpdateDataset, async (req, res, next) => {
|
|
61
|
+
try {
|
|
62
|
+
const result = await tasks$1.providerSetDataset(req.body);
|
|
63
|
+
console.info(`Dataset update complete: ${result}`);
|
|
64
|
+
res.status(200).send(result);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.error(err);
|
|
67
|
+
res.status(500).send(err);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
router.post(types.AdminApiPaths.ProviderDeregister, async (req, res, next) => {
|
|
71
|
+
var _a;
|
|
72
|
+
try {
|
|
73
|
+
const address = (_a = env.pair) == null ? void 0 : _a.address;
|
|
74
|
+
if (!address) {
|
|
75
|
+
throw new common.ProsopoEnvError("DEVELOPER.MISSING_ENV_VARIABLE", { context: { error: "No address" } });
|
|
76
|
+
}
|
|
77
|
+
await tasks$1.contract.tx.providerDeregister();
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.error(err);
|
|
80
|
+
res.status(500).send(err);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
router.post(types.AdminApiPaths.ProviderUpdate, async (req, res, next) => {
|
|
84
|
+
try {
|
|
85
|
+
const { url, fee, payee, value, address } = z__namespace.object({
|
|
86
|
+
url: z__namespace.string(),
|
|
87
|
+
fee: z__namespace.number().optional(),
|
|
88
|
+
payee: z__namespace.nativeEnum(typesReturns.Payee).optional(),
|
|
89
|
+
value: z__namespace.number().optional(),
|
|
90
|
+
address: z__namespace.string()
|
|
91
|
+
}).parse(req.body);
|
|
92
|
+
const provider = (await tasks$1.contract.query.getProvider(address, {})).value.unwrap().unwrap();
|
|
93
|
+
if (provider && (url || fee || payee || value)) {
|
|
94
|
+
const urlConverted = url ? Array.from(new common.UrlConverter().encode(url.toString())) : provider.url;
|
|
95
|
+
await contract.wrapQuery(tasks$1.contract.query.providerUpdate, tasks$1.contract.query)(
|
|
96
|
+
urlConverted,
|
|
97
|
+
fee || provider.fee,
|
|
98
|
+
payee || provider.payee,
|
|
99
|
+
{ value: value || 0 }
|
|
100
|
+
);
|
|
101
|
+
const result = await tasks$1.contract.tx.providerUpdate(
|
|
102
|
+
urlConverted,
|
|
103
|
+
fee || provider.fee,
|
|
104
|
+
payee || provider.payee,
|
|
105
|
+
{ value: value || 0 }
|
|
106
|
+
);
|
|
107
|
+
console.info(JSON.stringify(result, null, 2));
|
|
108
|
+
}
|
|
109
|
+
} catch (err) {
|
|
110
|
+
console.error(err);
|
|
111
|
+
res.status(500).send(err);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
return router;
|
|
115
|
+
}
|
|
116
|
+
exports.prosopoAdminRouter = prosopoAdminRouter;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const common = require("@prosopo/common");
|
|
4
|
+
const util = require("@polkadot/util");
|
|
5
|
+
const authMiddleware = (tasks, env) => {
|
|
6
|
+
return async (req, res, next) => {
|
|
7
|
+
try {
|
|
8
|
+
const { signature, blocknumber } = extractHeaders(req);
|
|
9
|
+
if (!env.pair) {
|
|
10
|
+
throw new common.ProsopoEnvError("CONTRACT.CANNOT_FIND_KEYPAIR");
|
|
11
|
+
}
|
|
12
|
+
verifyEnvironmentKeyPair(env);
|
|
13
|
+
await verifyBlockNumber(blocknumber, tasks);
|
|
14
|
+
verifySignature(signature, blocknumber, env.pair);
|
|
15
|
+
next();
|
|
16
|
+
} catch (err) {
|
|
17
|
+
console.error("Auth Middleware Error:", err);
|
|
18
|
+
res.status(401).json({ error: "Unauthorized", message: err });
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
const extractHeaders = (req) => {
|
|
23
|
+
const signature = req.headers.signature;
|
|
24
|
+
const blocknumber = req.headers.blocknumber;
|
|
25
|
+
if (!signature || !blocknumber) {
|
|
26
|
+
throw new common.ProsopoApiError("CONTRACT.INVALID_DATA_FORMAT", {
|
|
27
|
+
context: { error: "Missing signature or block number" }
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (Array.isArray(signature) || Array.isArray(blocknumber) || !util.isHex(signature)) {
|
|
31
|
+
throw new common.ProsopoApiError("CONTRACT.INVALID_DATA_FORMAT", { context: { error: "Invalid header format" } });
|
|
32
|
+
}
|
|
33
|
+
return { signature, blocknumber };
|
|
34
|
+
};
|
|
35
|
+
const verifyEnvironmentKeyPair = (env) => {
|
|
36
|
+
if (!env.pair) {
|
|
37
|
+
throw new common.ProsopoEnvError("CONTRACT.CANNOT_FIND_KEYPAIR");
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const verifyBlockNumber = async (blockNumber, tasks) => {
|
|
41
|
+
const parsedBlockNumber = parseInt(blockNumber);
|
|
42
|
+
const currentBlockNumber = await tasks.getCurrentBlockNumber();
|
|
43
|
+
if (isNaN(parsedBlockNumber) || parsedBlockNumber < currentBlockNumber - 500 || parsedBlockNumber > currentBlockNumber) {
|
|
44
|
+
throw new common.ProsopoApiError("API.BAD_REQUEST", {
|
|
45
|
+
context: {
|
|
46
|
+
error: `Invalid block number ${parsedBlockNumber}, current block number is ${currentBlockNumber}`
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const verifySignature = (signature, blockNumber, pair) => {
|
|
52
|
+
const u8Sig = util.hexToU8a(signature);
|
|
53
|
+
if (!pair.verify(blockNumber, u8Sig, pair.publicKey)) {
|
|
54
|
+
throw new common.ProsopoApiError("GENERAL.INVALID_SIGNATURE", { context: { error: "Signature verification failed" } });
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
exports.authMiddleware = authMiddleware;
|
package/dist/cjs/api/captcha.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const types = require("@prosopo/types");
|
|
4
|
-
const
|
|
4
|
+
const typesReturns = require("@prosopo/captcha-contract/types-returns");
|
|
5
5
|
const common = require("@prosopo/common");
|
|
6
6
|
const tasks = require("../tasks/tasks.cjs");
|
|
7
7
|
const util = require("../util.cjs");
|
|
@@ -18,7 +18,9 @@ function prosopoRouter(env) {
|
|
|
18
18
|
const { blockNumber, datasetId, user, dapp } = types.CaptchaRequestBody.parse(req.params);
|
|
19
19
|
const api = env.api;
|
|
20
20
|
if (api === void 0) {
|
|
21
|
-
throw new
|
|
21
|
+
throw new common.ProsopoApiError("DEVELOPER.METHOD_NOT_IMPLEMENTED", {
|
|
22
|
+
context: { error: "api not setup", env }
|
|
23
|
+
});
|
|
22
24
|
}
|
|
23
25
|
address.validateAddress(user, false, api.registry.chainSS58);
|
|
24
26
|
const blockNumberParsed = util.parseBlockNumber(blockNumber);
|
|
@@ -36,7 +38,7 @@ function prosopoRouter(env) {
|
|
|
36
38
|
};
|
|
37
39
|
return res.json(captchaResponse);
|
|
38
40
|
} catch (err) {
|
|
39
|
-
return next(new common.ProsopoApiError(
|
|
41
|
+
return next(new common.ProsopoApiError("API.BAD_REQUEST", { context: { error: err, errorCode: 400 } }));
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
);
|
|
@@ -45,7 +47,7 @@ function prosopoRouter(env) {
|
|
|
45
47
|
try {
|
|
46
48
|
parsed = types.CaptchaSolutionBody.parse(req.body);
|
|
47
49
|
} catch (err) {
|
|
48
|
-
return next(new common.ProsopoApiError(
|
|
50
|
+
return next(new common.ProsopoApiError("CAPTCHA.PARSE_ERROR", { context: { errorCode: 400, error: err } }));
|
|
49
51
|
}
|
|
50
52
|
try {
|
|
51
53
|
const result = await tasks$1.dappUserSolution(
|
|
@@ -60,7 +62,7 @@ function prosopoRouter(env) {
|
|
|
60
62
|
...result
|
|
61
63
|
});
|
|
62
64
|
} catch (err) {
|
|
63
|
-
return next(new common.ProsopoApiError(
|
|
65
|
+
return next(new common.ProsopoApiError("API.UNKNOWN", { context: { errorCode: 400, error: err } }));
|
|
64
66
|
}
|
|
65
67
|
});
|
|
66
68
|
router.post(types.ApiPaths.VerifyCaptchaSolution, async (req, res, next) => {
|
|
@@ -68,34 +70,38 @@ function prosopoRouter(env) {
|
|
|
68
70
|
try {
|
|
69
71
|
parsed = types.VerifySolutionBody.parse(req.body);
|
|
70
72
|
} catch (err) {
|
|
71
|
-
return next(new common.ProsopoApiError(
|
|
73
|
+
return next(new common.ProsopoApiError("CAPTCHA.PARSE_ERROR", { context: { errorCode: 400, error: err } }));
|
|
72
74
|
}
|
|
73
75
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
solution = await tasks$1.getDappUserCommitmentByAccount(parsed.user);
|
|
78
|
-
} else {
|
|
79
|
-
solution = await tasks$1.getDappUserCommitmentById(parsed.commitmentId);
|
|
76
|
+
const solution = await (parsed.commitmentId ? tasks$1.getDappUserCommitmentById(parsed.commitmentId) : tasks$1.getDappUserCommitmentByAccount(parsed.user));
|
|
77
|
+
if (!solution) {
|
|
78
|
+
return res.json({ status: req.t("API.USER_NOT_VERIFIED"), solutionApproved: false });
|
|
80
79
|
}
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
if (parsed.maxVerifiedTime) {
|
|
81
|
+
const currentBlockNumber = await tasks$1.getCurrentBlockNumber();
|
|
82
|
+
const blockTimeMs = await tasks$1.getBlockTimeMs();
|
|
83
|
+
const timeSinceCompletion = (currentBlockNumber - solution.completedAt) * blockTimeMs;
|
|
84
|
+
if (timeSinceCompletion > parsed.maxVerifiedTime) {
|
|
85
|
+
return res.json({ status: req.t("API.USER_NOT_VERIFIED"), solutionApproved: false });
|
|
86
86
|
}
|
|
87
|
-
return res.json({
|
|
88
|
-
status: req.t(statusMessage),
|
|
89
|
-
solutionApproved: approved,
|
|
90
|
-
commitmentId: solution.id
|
|
91
|
-
});
|
|
92
87
|
}
|
|
88
|
+
const isApproved = solution.status === typesReturns.CaptchaStatus.approved;
|
|
93
89
|
return res.json({
|
|
94
|
-
status: req.t(
|
|
95
|
-
solutionApproved:
|
|
90
|
+
status: req.t(isApproved ? "API.USER_VERIFIED" : "API.USER_NOT_VERIFIED"),
|
|
91
|
+
solutionApproved: isApproved,
|
|
92
|
+
commitmentId: solution.id
|
|
96
93
|
});
|
|
97
94
|
} catch (err) {
|
|
98
|
-
return next(new common.ProsopoApiError(
|
|
95
|
+
return next(new common.ProsopoApiError("API.BAD_REQUEST", { context: { errorCode: 400, error: err } }));
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
router.post(types.ApiPaths.SubmitUserEvents, async (req, res, next) => {
|
|
99
|
+
try {
|
|
100
|
+
const { events, accountId } = req.body;
|
|
101
|
+
await tasks$1.saveCaptchaEvent(events, accountId);
|
|
102
|
+
return res.json({ status: "success" });
|
|
103
|
+
} catch (err) {
|
|
104
|
+
return next(new common.ProsopoApiError("API.BAD_REQUEST", { context: { errorCode: 400, error: err } }));
|
|
99
105
|
}
|
|
100
106
|
});
|
|
101
107
|
router.get(types.ApiPaths.GetProviderStatus, async (req, res, next) => {
|
|
@@ -103,7 +109,7 @@ function prosopoRouter(env) {
|
|
|
103
109
|
const status = await tasks$1.providerStatus();
|
|
104
110
|
return res.json({ status });
|
|
105
111
|
} catch (err) {
|
|
106
|
-
return next(new common.ProsopoApiError(
|
|
112
|
+
return next(new common.ProsopoApiError("API.BAD_REQUEST", { context: { errorCode: 400, error: err } }));
|
|
107
113
|
}
|
|
108
114
|
});
|
|
109
115
|
router.get(types.ApiPaths.GetProviderDetails, async (req, res, next) => {
|
|
@@ -111,7 +117,7 @@ function prosopoRouter(env) {
|
|
|
111
117
|
const details = await tasks$1.getProviderDetails();
|
|
112
118
|
return res.json(details);
|
|
113
119
|
} catch (err) {
|
|
114
|
-
return next(new common.ProsopoApiError(
|
|
120
|
+
return next(new common.ProsopoApiError("API.BAD_REQUEST", { context: { errorCode: 400, error: err } }));
|
|
115
121
|
}
|
|
116
122
|
});
|
|
117
123
|
return router;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const bn = require("@polkadot/util/bn");
|
|
4
4
|
const types = require("@prosopo/types");
|
|
5
|
+
const common = require("@prosopo/common");
|
|
5
6
|
const contract = require("@prosopo/contract");
|
|
6
7
|
const util = require("../util.cjs");
|
|
7
8
|
const random = require("@polkadot/util-crypto/random");
|
|
@@ -44,6 +45,7 @@ class BatchCommitmentsTask {
|
|
|
44
45
|
}
|
|
45
46
|
);
|
|
46
47
|
}
|
|
48
|
+
return commitments;
|
|
47
49
|
} catch (e) {
|
|
48
50
|
const err = e;
|
|
49
51
|
this.logger.error(e);
|
|
@@ -55,6 +57,7 @@ class BatchCommitmentsTask {
|
|
|
55
57
|
error: JSON.stringify(e && err.message ? err.message : e)
|
|
56
58
|
}
|
|
57
59
|
);
|
|
60
|
+
return err.message;
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
63
|
}
|
|
@@ -119,7 +122,7 @@ class BatchCommitmentsTask {
|
|
|
119
122
|
}
|
|
120
123
|
}
|
|
121
124
|
if (!extrinsic) {
|
|
122
|
-
throw new
|
|
125
|
+
throw new common.ProsopoContractError("CONTRACT.TX_ERROR", { context: { error: "No extrinsics created" } });
|
|
123
126
|
}
|
|
124
127
|
txs.push(extrinsic);
|
|
125
128
|
this.logger.info(`${txs.length} transactions will be batched`);
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -4,6 +4,7 @@ require("./tasks/index.cjs");
|
|
|
4
4
|
const util = require("./util.cjs");
|
|
5
5
|
require("./batch/index.cjs");
|
|
6
6
|
const captcha = require("./api/captcha.cjs");
|
|
7
|
+
const admin = require("./api/admin.cjs");
|
|
7
8
|
const tasks = require("./tasks/tasks.cjs");
|
|
8
9
|
const calculateSolutions = require("./tasks/calculateSolutions.cjs");
|
|
9
10
|
const commitments = require("./batch/commitments.cjs");
|
|
@@ -15,6 +16,7 @@ exports.promiseQueue = util.promiseQueue;
|
|
|
15
16
|
exports.shuffleArray = util.shuffleArray;
|
|
16
17
|
exports.updateSolutions = util.updateSolutions;
|
|
17
18
|
exports.prosopoRouter = captcha.prosopoRouter;
|
|
19
|
+
exports.prosopoAdminRouter = admin.prosopoAdminRouter;
|
|
18
20
|
exports.Tasks = tasks.Tasks;
|
|
19
21
|
exports.CalculateSolutionsTask = calculateSolutions.CalculateSolutionsTask;
|
|
20
22
|
exports.BatchCommitmentsTask = commitments.BatchCommitmentsTask;
|
|
@@ -60,7 +60,7 @@ class CalculateSolutionsTask extends tasks.Tasks {
|
|
|
60
60
|
}
|
|
61
61
|
return 0;
|
|
62
62
|
} catch (error) {
|
|
63
|
-
throw new common.ProsopoEnvError(
|
|
63
|
+
throw new common.ProsopoEnvError("GENERAL.CALCULATE_CAPTCHA_SOLUTION", { context: { error } });
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
}
|
package/dist/cjs/tasks/tasks.cjs
CHANGED
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const datasets = require("@prosopo/datasets");
|
|
4
|
-
const
|
|
4
|
+
const typesReturns = require("@prosopo/captcha-contract/types-returns");
|
|
5
5
|
const common = require("@prosopo/common");
|
|
6
6
|
const contract = require("@prosopo/contract");
|
|
7
7
|
const util$1 = require("@prosopo/util");
|
|
8
8
|
const hex = require("@polkadot/util/hex");
|
|
9
9
|
const random = require("@polkadot/util-crypto/random");
|
|
10
|
+
const database = require("@prosopo/database");
|
|
10
11
|
const util = require("../util.cjs");
|
|
11
12
|
const signature = require("@polkadot/util-crypto/signature");
|
|
12
13
|
const string = require("@polkadot/util/string");
|
|
13
14
|
class Tasks {
|
|
14
15
|
constructor(env) {
|
|
15
16
|
if (!env.contractInterface) {
|
|
16
|
-
throw new common.ProsopoEnvError(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
{},
|
|
20
|
-
{ contractAddress: env.contractAddress }
|
|
21
|
-
);
|
|
17
|
+
throw new common.ProsopoEnvError("CONTRACT.CONTRACT_UNDEFINED", {
|
|
18
|
+
context: { failedFuncName: this.constructor.name, contractAddress: env.contractAddress }
|
|
19
|
+
});
|
|
22
20
|
}
|
|
23
21
|
this.config = env.config;
|
|
24
22
|
this.contract = env.contractInterface;
|
|
@@ -35,18 +33,26 @@ class Tasks {
|
|
|
35
33
|
async providerSetDataset(datasetRaw) {
|
|
36
34
|
var _a;
|
|
37
35
|
if (datasetRaw.captchas.length < this.config.captchas.solved.count + this.config.captchas.unsolved.count) {
|
|
38
|
-
throw new common.ProsopoEnvError("DATASET.CAPTCHAS_COUNT_LESS_THAN_CONFIGURED",
|
|
36
|
+
throw new common.ProsopoEnvError("DATASET.CAPTCHAS_COUNT_LESS_THAN_CONFIGURED", {
|
|
37
|
+
context: { failedFuncName: this.providerSetDataset.name }
|
|
38
|
+
});
|
|
39
39
|
}
|
|
40
|
-
const solutions = datasetRaw.captchas.map((
|
|
40
|
+
const solutions = datasetRaw.captchas.map((captcha) => captcha.solution ? 1 : 0).reduce((partialSum, b) => partialSum + b, 0);
|
|
41
41
|
if (solutions < this.config.captchas.solved.count) {
|
|
42
|
-
throw new common.ProsopoEnvError("DATASET.SOLUTIONS_COUNT_LESS_THAN_CONFIGURED",
|
|
42
|
+
throw new common.ProsopoEnvError("DATASET.SOLUTIONS_COUNT_LESS_THAN_CONFIGURED", {
|
|
43
|
+
context: { failedFuncName: this.providerSetDataset.name }
|
|
44
|
+
});
|
|
43
45
|
}
|
|
44
46
|
if (solutions < this.config.captchas.unsolved.count) {
|
|
45
|
-
throw new common.ProsopoEnvError("DATASET.SOLUTIONS_COUNT_LESS_THAN_CONFIGURED",
|
|
47
|
+
throw new common.ProsopoEnvError("DATASET.SOLUTIONS_COUNT_LESS_THAN_CONFIGURED", {
|
|
48
|
+
context: { failedFuncName: this.providerSetDataset.name }
|
|
49
|
+
});
|
|
46
50
|
}
|
|
47
51
|
const dataset = await datasets.buildDataset(datasetRaw);
|
|
48
52
|
if (!dataset.datasetId || !dataset.datasetContentId) {
|
|
49
|
-
throw new common.ProsopoEnvError("DATASET.DATASET_ID_UNDEFINED",
|
|
53
|
+
throw new common.ProsopoEnvError("DATASET.DATASET_ID_UNDEFINED", {
|
|
54
|
+
context: { failedFuncName: this.providerSetDataset.name }
|
|
55
|
+
});
|
|
50
56
|
}
|
|
51
57
|
await ((_a = this.db) == null ? void 0 : _a.storeDataset(dataset));
|
|
52
58
|
await contract.wrapQuery(this.contract.query.providerSetDataset, this.contract.query)(
|
|
@@ -69,25 +75,22 @@ class Tasks {
|
|
|
69
75
|
const captchaDocs = await this.db.getRandomCaptcha(solved, datasetId, size);
|
|
70
76
|
if (captchaDocs) {
|
|
71
77
|
const captchas = [];
|
|
72
|
-
for (const
|
|
78
|
+
for (const captcha of captchaDocs) {
|
|
73
79
|
const datasetDetails = await this.db.getDatasetDetails(datasetId);
|
|
74
80
|
const tree = new datasets.CaptchaMerkleTree();
|
|
75
81
|
if (datasetDetails.contentTree) {
|
|
76
82
|
tree.layers = datasetDetails.contentTree;
|
|
77
|
-
const proof = tree.proof(
|
|
78
|
-
delete
|
|
79
|
-
|
|
80
|
-
captchas.push({ captcha
|
|
83
|
+
const proof = tree.proof(captcha.captchaContentId);
|
|
84
|
+
delete captcha.solution;
|
|
85
|
+
captcha.items = util.shuffleArray(captcha.items);
|
|
86
|
+
captchas.push({ captcha, proof });
|
|
81
87
|
}
|
|
82
88
|
}
|
|
83
89
|
return captchas;
|
|
84
90
|
}
|
|
85
|
-
throw new common.ProsopoEnvError(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
{},
|
|
89
|
-
{ datasetId, solved, size }
|
|
90
|
-
);
|
|
91
|
+
throw new common.ProsopoEnvError("DATABASE.CAPTCHA_GET_FAILED", {
|
|
92
|
+
context: { failedFuncName: this.getCaptchaWithProof.name, datasetId, solved, size }
|
|
93
|
+
});
|
|
91
94
|
}
|
|
92
95
|
/**
|
|
93
96
|
* Validate and store the text captcha solution(s) from the Dapp User in a web2 environment
|
|
@@ -100,11 +103,15 @@ class Tasks {
|
|
|
100
103
|
*/
|
|
101
104
|
async dappUserSolution(userAccount, dappAccount, requestHash, captchas, signature$1) {
|
|
102
105
|
if (!await this.dappIsActive(dappAccount)) {
|
|
103
|
-
throw new common.ProsopoEnvError("CONTRACT.DAPP_NOT_ACTIVE",
|
|
106
|
+
throw new common.ProsopoEnvError("CONTRACT.DAPP_NOT_ACTIVE", {
|
|
107
|
+
context: { failedFuncName: this.getPaymentInfo.name, dappAccount }
|
|
108
|
+
});
|
|
104
109
|
}
|
|
105
110
|
const verification = signature.signatureVerify(string.stringToHex(requestHash), signature$1, userAccount);
|
|
106
111
|
if (!verification.isValid) {
|
|
107
|
-
throw new common.ProsopoEnvError("GENERAL.INVALID_SIGNATURE",
|
|
112
|
+
throw new common.ProsopoEnvError("GENERAL.INVALID_SIGNATURE", {
|
|
113
|
+
context: { failedFuncName: this.dappUserSolution.name, userAccount }
|
|
114
|
+
});
|
|
108
115
|
}
|
|
109
116
|
let response = {
|
|
110
117
|
captchas: [],
|
|
@@ -129,7 +136,7 @@ class Tasks {
|
|
|
129
136
|
dappContract: dappAccount,
|
|
130
137
|
providerAccount: this.contract.pair.address,
|
|
131
138
|
datasetId: provider.datasetId.toString(),
|
|
132
|
-
status:
|
|
139
|
+
status: typesReturns.CaptchaStatus.pending,
|
|
133
140
|
userSignature: Array.from(userSignature),
|
|
134
141
|
requestedAt: pendingRecord.requestedAtBlock,
|
|
135
142
|
// TODO is this correct or should it be block number?
|
|
@@ -186,23 +193,23 @@ class Tasks {
|
|
|
186
193
|
*/
|
|
187
194
|
async validateReceivedCaptchasAgainstStoredCaptchas(captchas) {
|
|
188
195
|
const receivedCaptchas = datasets.parseAndSortCaptchaSolutions(captchas);
|
|
189
|
-
const captchaIds = receivedCaptchas.map((
|
|
196
|
+
const captchaIds = receivedCaptchas.map((captcha) => captcha.captchaId);
|
|
190
197
|
const storedCaptchas = await this.db.getCaptchaById(captchaIds);
|
|
191
198
|
if (!storedCaptchas || receivedCaptchas.length !== storedCaptchas.length) {
|
|
192
|
-
throw new common.ProsopoEnvError(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
);
|
|
199
|
+
throw new common.ProsopoEnvError("CAPTCHA.INVALID_CAPTCHA_ID", {
|
|
200
|
+
context: {
|
|
201
|
+
failedFuncName: this.validateReceivedCaptchasAgainstStoredCaptchas.name,
|
|
202
|
+
captchas
|
|
203
|
+
}
|
|
204
|
+
});
|
|
198
205
|
}
|
|
199
|
-
if (!storedCaptchas.every((
|
|
200
|
-
throw new common.ProsopoEnvError(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
);
|
|
206
|
+
if (!storedCaptchas.every((captcha) => captcha.datasetId === util$1.at(storedCaptchas, 0).datasetId)) {
|
|
207
|
+
throw new common.ProsopoEnvError("CAPTCHA.DIFFERENT_DATASET_IDS", {
|
|
208
|
+
context: {
|
|
209
|
+
failedFuncName: this.validateReceivedCaptchasAgainstStoredCaptchas.name,
|
|
210
|
+
captchas
|
|
211
|
+
}
|
|
212
|
+
});
|
|
206
213
|
}
|
|
207
214
|
return { storedCaptchas, receivedCaptchas, captchaIds };
|
|
208
215
|
}
|
|
@@ -214,16 +221,16 @@ class Tasks {
|
|
|
214
221
|
async buildTreeAndGetCommitmentId(captchaSolutions) {
|
|
215
222
|
var _a;
|
|
216
223
|
const tree = new datasets.CaptchaMerkleTree();
|
|
217
|
-
const solutionsHashed = captchaSolutions.map((
|
|
224
|
+
const solutionsHashed = captchaSolutions.map((captcha) => datasets.computeCaptchaSolutionHash(captcha));
|
|
218
225
|
tree.build(solutionsHashed);
|
|
219
226
|
const commitmentId = (_a = tree.root) == null ? void 0 : _a.hash;
|
|
220
227
|
if (!commitmentId) {
|
|
221
|
-
throw new common.ProsopoEnvError(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
);
|
|
228
|
+
throw new common.ProsopoEnvError("CONTRACT.CAPTCHA_SOLUTION_COMMITMENT_DOES_NOT_EXIST", {
|
|
229
|
+
context: {
|
|
230
|
+
failedFuncName: this.buildTreeAndGetCommitmentId.name,
|
|
231
|
+
commitmentId
|
|
232
|
+
}
|
|
233
|
+
});
|
|
227
234
|
}
|
|
228
235
|
return { tree, commitmentId };
|
|
229
236
|
}
|
|
@@ -274,7 +281,7 @@ class Tasks {
|
|
|
274
281
|
salt
|
|
275
282
|
);
|
|
276
283
|
const currentTime = Date.now();
|
|
277
|
-
const timeLimit = captchas.map((
|
|
284
|
+
const timeLimit = captchas.map((captcha) => captcha.captcha.timeLimitMs || 3e4).reduce((a, b) => a + b, 0);
|
|
278
285
|
const deadlineTs = timeLimit + currentTime;
|
|
279
286
|
const currentBlockNumber = await contract.getBlockNumber(this.contract.api);
|
|
280
287
|
await this.db.storeDappUserPending(userAccount, requestHash, salt, deadlineTs, currentBlockNumber.toNumber());
|
|
@@ -304,23 +311,23 @@ class Tasks {
|
|
|
304
311
|
async validateProviderWasRandomlyChosen(userAccount, dappContractAccount, datasetId, blockNumber) {
|
|
305
312
|
const contract2 = await this.contract.contract;
|
|
306
313
|
if (!contract2) {
|
|
307
|
-
throw new common.ProsopoEnvError("CONTRACT.CONTRACT_UNDEFINED",
|
|
314
|
+
throw new common.ProsopoEnvError("CONTRACT.CONTRACT_UNDEFINED", {
|
|
315
|
+
context: { failedFuncName: this.validateProviderWasRandomlyChosen.name }
|
|
316
|
+
});
|
|
308
317
|
}
|
|
309
318
|
const header = await contract2.api.rpc.chain.getHeader();
|
|
310
319
|
const isBlockNoValid = await this.isRecentBlock(contract2, header, blockNumber);
|
|
311
320
|
if (!isBlockNoValid) {
|
|
312
|
-
throw new common.ProsopoEnvError(
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
{},
|
|
316
|
-
{
|
|
321
|
+
throw new common.ProsopoEnvError("CAPTCHA.INVALID_BLOCK_NO", {
|
|
322
|
+
context: {
|
|
323
|
+
failedFuncName: this.validateProviderWasRandomlyChosen.name,
|
|
317
324
|
userAccount,
|
|
318
325
|
dappContractAccount,
|
|
319
326
|
datasetId,
|
|
320
327
|
header,
|
|
321
328
|
blockNumber
|
|
322
329
|
}
|
|
323
|
-
);
|
|
330
|
+
});
|
|
324
331
|
}
|
|
325
332
|
const block = await contract2.api.rpc.chain.getBlockHash(blockNumber);
|
|
326
333
|
const randomProviderAndBlockNo = await this.contract.queryAtBlock(
|
|
@@ -329,12 +336,12 @@ class Tasks {
|
|
|
329
336
|
[userAccount, dappContractAccount]
|
|
330
337
|
);
|
|
331
338
|
if (datasetId.toString().localeCompare(randomProviderAndBlockNo.provider.datasetId.toString())) {
|
|
332
|
-
throw new common.ProsopoEnvError(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
);
|
|
339
|
+
throw new common.ProsopoEnvError("DATASET.INVALID_DATASET_ID", {
|
|
340
|
+
context: {
|
|
341
|
+
failedFuncName: this.validateProviderWasRandomlyChosen.name,
|
|
342
|
+
randomProviderAndBlockNo
|
|
343
|
+
}
|
|
344
|
+
});
|
|
338
345
|
}
|
|
339
346
|
}
|
|
340
347
|
/**
|
|
@@ -368,12 +375,12 @@ class Tasks {
|
|
|
368
375
|
async getDappUserCommitmentById(commitmentId) {
|
|
369
376
|
const dappUserSolution = await this.db.getDappUserCommitmentById(commitmentId);
|
|
370
377
|
if (!dappUserSolution) {
|
|
371
|
-
throw new common.ProsopoEnvError(
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
);
|
|
378
|
+
throw new common.ProsopoEnvError("CAPTCHA.DAPP_USER_SOLUTION_NOT_FOUND", {
|
|
379
|
+
context: {
|
|
380
|
+
failedFuncName: this.getDappUserCommitmentById.name,
|
|
381
|
+
commitmentId
|
|
382
|
+
}
|
|
383
|
+
});
|
|
377
384
|
}
|
|
378
385
|
return dappUserSolution;
|
|
379
386
|
}
|
|
@@ -382,7 +389,7 @@ class Tasks {
|
|
|
382
389
|
const dappUserSolutions = await this.db.getDappUserCommitmentByAccount(userAccount);
|
|
383
390
|
if (dappUserSolutions.length > 0) {
|
|
384
391
|
for (const dappUserSolution of dappUserSolutions) {
|
|
385
|
-
if (dappUserSolution.status ===
|
|
392
|
+
if (dappUserSolution.status === typesReturns.CaptchaStatus.approved) {
|
|
386
393
|
return dappUserSolution;
|
|
387
394
|
}
|
|
388
395
|
}
|
|
@@ -391,11 +398,35 @@ class Tasks {
|
|
|
391
398
|
}
|
|
392
399
|
/* Returns public details of provider */
|
|
393
400
|
async getProviderDetails() {
|
|
394
|
-
|
|
401
|
+
const provider = await contract.wrapQuery(
|
|
402
|
+
this.contract.query.getProvider,
|
|
403
|
+
this.contract.query
|
|
404
|
+
)(this.contract.pair.address);
|
|
405
|
+
const dbConnectionOk = await this.getCaptchaWithProof(provider.datasetId, true, 1).then(() => true).catch(() => false);
|
|
406
|
+
return { provider, dbConnectionOk };
|
|
395
407
|
}
|
|
396
|
-
/** Get the dataset from the
|
|
408
|
+
/** Get the dataset from the database */
|
|
397
409
|
async getProviderDataset(datasetId) {
|
|
398
410
|
return await this.db.getDataset(datasetId);
|
|
399
411
|
}
|
|
412
|
+
/**
|
|
413
|
+
* Get the current block number
|
|
414
|
+
*/
|
|
415
|
+
async getCurrentBlockNumber() {
|
|
416
|
+
return (await contract.getBlockNumber(this.contract.api)).toNumber();
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Get the current block time in milliseconds
|
|
420
|
+
*/
|
|
421
|
+
async getBlockTimeMs() {
|
|
422
|
+
const blockTime = this.contract.api.consts.babe.expectedBlockTime;
|
|
423
|
+
return blockTime.toNumber();
|
|
424
|
+
}
|
|
425
|
+
async saveCaptchaEvent(events, accountId) {
|
|
426
|
+
if (!this.config.mongoAtlasUri) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
await database.saveCaptchaEvent(events, accountId, this.config.mongoAtlasUri);
|
|
430
|
+
}
|
|
400
431
|
}
|
|
401
432
|
exports.Tasks = Tasks;
|