@moneypot/hub 1.12.0 → 1.13.0
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/README.md +4 -0
- package/dist/src/db/index.js +15 -11
- package/dist/src/hash-chain/db-hash-chain.js +17 -2
- package/dist/src/hash-chain/plugins/hub-create-hash-chain.js +5 -2
- package/dist/src/hash-chain/plugins/hub-reveal-hash-chain.d.ts +1 -0
- package/dist/src/hash-chain/plugins/hub-reveal-hash-chain.js +74 -0
- package/dist/src/hash-chain/reveal-hash-chain.d.ts +5 -0
- package/dist/src/hash-chain/reveal-hash-chain.js +35 -0
- package/dist/src/pg-versions/005-hash-chain.sql +1 -0
- package/dist/src/plugins/hub-make-outcome-bet.js +23 -43
- package/dist/src/server/graphile.config.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,6 +76,10 @@ insert into hub.api_key default values returning key;
|
|
|
76
76
|
|
|
77
77
|
## Changelog
|
|
78
78
|
|
|
79
|
+
### 1.13.x
|
|
80
|
+
|
|
81
|
+
- Added `hubRevealHashChain` mutation to generate a preimage hash for the chain and mark it as inactive.
|
|
82
|
+
|
|
79
83
|
### 1.12.x
|
|
80
84
|
|
|
81
85
|
- Added `hub.audit_log` table to track balance and bankroll changes.
|
package/dist/src/db/index.js
CHANGED
|
@@ -136,17 +136,21 @@ export async function setTransferCursor(pgClient, { cursor, casinoId, }) {
|
|
|
136
136
|
export async function upsertUser(pgClient, { uname, casinoId, mpUserId, }) {
|
|
137
137
|
const user = await pgClient
|
|
138
138
|
.query(`
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
139
|
+
WITH upsert AS (
|
|
140
|
+
INSERT INTO hub.user (casino_id, mp_user_id, uname)
|
|
141
|
+
VALUES ($1, $2, $3)
|
|
142
|
+
ON CONFLICT (casino_id, mp_user_id) DO UPDATE
|
|
143
|
+
SET uname = EXCLUDED.uname
|
|
144
|
+
WHERE hub.user.uname IS DISTINCT FROM EXCLUDED.uname
|
|
145
|
+
RETURNING *
|
|
146
|
+
)
|
|
147
|
+
SELECT * FROM upsert
|
|
148
|
+
UNION ALL
|
|
149
|
+
SELECT * FROM hub.user
|
|
150
|
+
WHERE casino_id = $1 AND mp_user_id = $2
|
|
151
|
+
AND NOT EXISTS (SELECT 1 FROM upsert)
|
|
152
|
+
LIMIT 1;
|
|
153
|
+
|
|
150
154
|
`, [casinoId, mpUserId, uname])
|
|
151
155
|
.then(exactlyOneRow);
|
|
152
156
|
return user;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { DbHashKind, } from "../db/types.js";
|
|
2
2
|
import { exactlyOneRow, maybeOneRow, } from "../db/index.js";
|
|
3
3
|
import { assert } from "tsafe";
|
|
4
|
+
import { logger } from "../logger.js";
|
|
4
5
|
export async function dbLockHubHashChain(pgClient, { userId, experienceId, casinoId, hashChainId, }) {
|
|
5
6
|
assert(pgClient._inTransaction, "dbLockHubHashChain must be called in a transaction");
|
|
6
|
-
|
|
7
|
+
const hashChain = await pgClient
|
|
7
8
|
.query(`
|
|
8
9
|
SELECT *
|
|
9
10
|
FROM hub.hash_chain
|
|
@@ -17,10 +18,24 @@ export async function dbLockHubHashChain(pgClient, { userId, experienceId, casin
|
|
|
17
18
|
`, [hashChainId, userId, experienceId, casinoId])
|
|
18
19
|
.then(maybeOneRow)
|
|
19
20
|
.then((row) => row ?? null);
|
|
21
|
+
if (hashChain) {
|
|
22
|
+
assert(hashChain.current_iteration >= 1, "Hash chain iteration must be >= 1");
|
|
23
|
+
}
|
|
24
|
+
return hashChain;
|
|
20
25
|
}
|
|
21
26
|
export async function dbInsertHubHash(pgClient, { hashChainId, kind, digest, iteration, clientSeed, metadata = {}, }) {
|
|
27
|
+
logger.debug({
|
|
28
|
+
hashChainId,
|
|
29
|
+
kind,
|
|
30
|
+
iteration,
|
|
31
|
+
clientSeed,
|
|
32
|
+
metadata,
|
|
33
|
+
digest: Buffer.from(digest).toString("base64"),
|
|
34
|
+
}, "[dbInsertHubHash] Inserting hash");
|
|
22
35
|
assert(pgClient._inTransaction, "dbInsertHash must be called in a transaction");
|
|
23
|
-
|
|
36
|
+
if (kind === DbHashKind.INTERMEDIATE) {
|
|
37
|
+
assert(typeof clientSeed === "string", "clientSeed must be provided for INTERMEDIATE hashes");
|
|
38
|
+
}
|
|
24
39
|
return pgClient
|
|
25
40
|
.query(`
|
|
26
41
|
INSERT INTO hub.hash (hash_chain_id, kind, digest, iteration, client_seed, metadata)
|
|
@@ -65,9 +65,12 @@ export const HubCreateHashChainPlugin = makeExtendSchemaPlugin((build) => {
|
|
|
65
65
|
config.HASHCHAINSERVER_MAX_ITERATIONS,
|
|
66
66
|
])
|
|
67
67
|
.then(exactlyOneRow);
|
|
68
|
-
const
|
|
68
|
+
const terminalHashResult = await HashCommon.getTerminalHash({
|
|
69
69
|
hashChainId: dbHashChain.id,
|
|
70
70
|
});
|
|
71
|
+
if (terminalHashResult.type !== "success") {
|
|
72
|
+
throw new Error("Failed to get terminal hash");
|
|
73
|
+
}
|
|
71
74
|
await pgClient.query(`
|
|
72
75
|
INSERT INTO hub.hash (
|
|
73
76
|
hash_chain_id,
|
|
@@ -79,7 +82,7 @@ export const HubCreateHashChainPlugin = makeExtendSchemaPlugin((build) => {
|
|
|
79
82
|
`, [
|
|
80
83
|
dbHashChain.id,
|
|
81
84
|
DbHashKind.TERMINAL,
|
|
82
|
-
|
|
85
|
+
terminalHashResult.hash,
|
|
83
86
|
config.HASHCHAINSERVER_MAX_ITERATIONS,
|
|
84
87
|
]);
|
|
85
88
|
return dbHashChain.id;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const HubRevealHashChainPlugin: GraphileConfig.Plugin;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { context, object, sideEffect } from "postgraphile/grafast";
|
|
2
|
+
import { gql, makeExtendSchemaPlugin } from "postgraphile/utils";
|
|
3
|
+
import { dbLockHubHashChain, withPgPoolTransaction } from "../../db/index.js";
|
|
4
|
+
import z, { prettifyError } from "zod/v4";
|
|
5
|
+
import { GraphQLError } from "graphql";
|
|
6
|
+
import { assert } from "tsafe";
|
|
7
|
+
import { dbRevealHashChain } from "../reveal-hash-chain.js";
|
|
8
|
+
const InputSchema = z.object({
|
|
9
|
+
hashChainId: z.uuidv7("Invalid hash chain ID"),
|
|
10
|
+
});
|
|
11
|
+
export const HubRevealHashChainPlugin = makeExtendSchemaPlugin((build) => {
|
|
12
|
+
const hashTable = build.input.pgRegistry.pgResources.hub_hash;
|
|
13
|
+
return {
|
|
14
|
+
typeDefs: gql `
|
|
15
|
+
input HubRevealHashChainInput {
|
|
16
|
+
hashChainId: UUID!
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type HubRevealHashChainPayload {
|
|
20
|
+
preimageHash: HubHash!
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
extend type Mutation {
|
|
24
|
+
hubRevealHashChain(
|
|
25
|
+
input: HubRevealHashChainInput!
|
|
26
|
+
): HubRevealHashChainPayload!
|
|
27
|
+
}
|
|
28
|
+
`,
|
|
29
|
+
objects: {
|
|
30
|
+
Mutation: {
|
|
31
|
+
plans: {
|
|
32
|
+
hubRevealHashChain: (_, { $input }) => {
|
|
33
|
+
const $identity = context().get("identity");
|
|
34
|
+
const $superuserPool = context().get("superuserPool");
|
|
35
|
+
const $preimageHashId = sideEffect([$input, $identity, $superuserPool], ([rawInput, identity, superuserPool]) => {
|
|
36
|
+
if (identity?.kind !== "user") {
|
|
37
|
+
throw new GraphQLError("Unauthorized");
|
|
38
|
+
}
|
|
39
|
+
let input;
|
|
40
|
+
try {
|
|
41
|
+
input = InputSchema.parse(rawInput);
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
if (e instanceof z.ZodError) {
|
|
45
|
+
throw new GraphQLError(prettifyError(e));
|
|
46
|
+
}
|
|
47
|
+
throw e;
|
|
48
|
+
}
|
|
49
|
+
return withPgPoolTransaction(superuserPool, async (pgClient) => {
|
|
50
|
+
const hashChain = await dbLockHubHashChain(pgClient, {
|
|
51
|
+
userId: identity.session.user_id,
|
|
52
|
+
experienceId: identity.session.experience_id,
|
|
53
|
+
casinoId: identity.session.casino_id,
|
|
54
|
+
hashChainId: input.hashChainId,
|
|
55
|
+
});
|
|
56
|
+
if (!hashChain || !hashChain.active) {
|
|
57
|
+
throw new GraphQLError("Active hash chain not found");
|
|
58
|
+
}
|
|
59
|
+
assert(hashChain.current_iteration >= 1, "Invalid hash chain iteration");
|
|
60
|
+
const dbPreimageHash = await dbRevealHashChain(pgClient, {
|
|
61
|
+
hashChainId: input.hashChainId,
|
|
62
|
+
});
|
|
63
|
+
return dbPreimageHash.id;
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
return object({
|
|
67
|
+
preimageHash: hashTable.get({ id: $preimageHashId }),
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { logger } from "../logger.js";
|
|
2
|
+
import { getPreimageHash } from "./get-hash.js";
|
|
3
|
+
import { DbHashKind } from "../db/types.js";
|
|
4
|
+
import { dbInsertHubHash } from "../db/index.js";
|
|
5
|
+
import { assert } from "tsafe";
|
|
6
|
+
export async function dbRevealHashChain(pgClient, { hashChainId, }) {
|
|
7
|
+
assert(pgClient._inTransaction, "dbRevealHashChain must be called in a transaction");
|
|
8
|
+
logger.debug({ hashChainId }, "Revealing hash chain");
|
|
9
|
+
const preimageHashResult = await getPreimageHash({
|
|
10
|
+
hashChainId,
|
|
11
|
+
});
|
|
12
|
+
logger.debug({ preimageHashResult }, "Preimage hash result");
|
|
13
|
+
if (preimageHashResult.type === "success") {
|
|
14
|
+
const dbPreimageHash = await dbInsertHubHash(pgClient, {
|
|
15
|
+
hashChainId,
|
|
16
|
+
kind: DbHashKind.PREIMAGE,
|
|
17
|
+
digest: preimageHashResult.hash,
|
|
18
|
+
iteration: 0,
|
|
19
|
+
});
|
|
20
|
+
const result = await pgClient.query(`
|
|
21
|
+
UPDATE hub.hash_chain
|
|
22
|
+
SET current_iteration = 0,
|
|
23
|
+
active = false
|
|
24
|
+
WHERE id = $1
|
|
25
|
+
`, [hashChainId]);
|
|
26
|
+
if (result.rowCount !== 1) {
|
|
27
|
+
throw new Error("Failed to update hash chain iteration");
|
|
28
|
+
}
|
|
29
|
+
return dbPreimageHash;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
logger.warn({ hashChainId, error: preimageHashResult }, "Failed to reveal hash chain");
|
|
33
|
+
throw new Error("Failed to reveal hash chain");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -69,6 +69,7 @@ CREATE POLICY select_hash_chain ON hub.hash_chain FOR SELECT USING (
|
|
|
69
69
|
)
|
|
70
70
|
);
|
|
71
71
|
|
|
72
|
+
-- TODO: Since we have RLS we could just make hashes public for simplicity/perf
|
|
72
73
|
CREATE POLICY select_hash ON hub.hash FOR SELECT USING (
|
|
73
74
|
-- Operator can see all rows
|
|
74
75
|
hub_hidden.is_operator() OR
|
|
@@ -5,11 +5,12 @@ import { GraphQLError } from "graphql";
|
|
|
5
5
|
import { DbHashKind, dbLockPlayerBalanceAndHouseBankroll, exactlyOneRow, maybeOneRow, withPgPoolTransaction, } from "../db/index.js";
|
|
6
6
|
import { assert } from "tsafe";
|
|
7
7
|
import { dbInsertHubHash, dbLockHubHashChain, } from "../hash-chain/db-hash-chain.js";
|
|
8
|
-
import { getIntermediateHash
|
|
8
|
+
import { getIntermediateHash } from "../hash-chain/get-hash.js";
|
|
9
9
|
import { makeFinalHash, pickRandomOutcome } from "../hash-chain/util.js";
|
|
10
10
|
import { logger } from "../logger.js";
|
|
11
11
|
import { validateRisk } from "../risk-policy.js";
|
|
12
12
|
import { insertAuditLog } from "../audit-log.js";
|
|
13
|
+
import { dbRevealHashChain } from "../hash-chain/reveal-hash-chain.js";
|
|
13
14
|
const FLOAT_EPSILON = 1e-10;
|
|
14
15
|
function sum(ns) {
|
|
15
16
|
return ns.reduce((a, b) => a + b, 0);
|
|
@@ -215,15 +216,24 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
215
216
|
message: "Active hash chain not found",
|
|
216
217
|
};
|
|
217
218
|
}
|
|
218
|
-
if (dbHashChain.current_iteration
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
219
|
+
if (dbHashChain.current_iteration < 1) {
|
|
220
|
+
logger.error({
|
|
221
|
+
hashChainId: input.hashChainId,
|
|
222
|
+
iteration: dbHashChain.current_iteration,
|
|
223
|
+
}, "Hash chain has invalid iteration < 1");
|
|
224
|
+
return {
|
|
225
|
+
__typename: "HubBadHashChainError",
|
|
226
|
+
message: "Hash chain is in an invalid state",
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (dbHashChain.current_iteration === 1) {
|
|
230
|
+
await pgClient.query(`UPDATE hub.hash_chain SET active = false WHERE id = $1`, [input.hashChainId]);
|
|
231
|
+
revealHashChainInBackground({
|
|
232
|
+
hashChainId: input.hashChainId,
|
|
233
|
+
pool: superuserPool,
|
|
234
|
+
}).catch((e) => {
|
|
235
|
+
logger.error({ hashChainId: input.hashChainId, error: e }, "Error finishing hash chain in background");
|
|
236
|
+
});
|
|
227
237
|
return {
|
|
228
238
|
__typename: "HubBadHashChainError",
|
|
229
239
|
message: "Hash chain drained. Create a new one.",
|
|
@@ -413,38 +423,8 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
413
423
|
};
|
|
414
424
|
}, "HubMakeOutcomeBetPlugin");
|
|
415
425
|
}
|
|
416
|
-
async function
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
hashChainId,
|
|
426
|
+
async function revealHashChainInBackground({ hashChainId, pool, }) {
|
|
427
|
+
return await withPgPoolTransaction(pool, async (pgClient) => {
|
|
428
|
+
await dbRevealHashChain(pgClient, { hashChainId });
|
|
420
429
|
});
|
|
421
|
-
logger.debug({ preimageHashResult }, "Preimage hash result");
|
|
422
|
-
if (preimageHashResult.type === "success") {
|
|
423
|
-
logger.debug({
|
|
424
|
-
hashChainId,
|
|
425
|
-
kind: DbHashKind.PREIMAGE,
|
|
426
|
-
digest: preimageHashResult.hash,
|
|
427
|
-
iteration: 0,
|
|
428
|
-
}, "Inserting preimage hash");
|
|
429
|
-
await withPgPoolTransaction(pool, async (pgClient) => {
|
|
430
|
-
await dbInsertHubHash(pgClient, {
|
|
431
|
-
hashChainId,
|
|
432
|
-
kind: DbHashKind.PREIMAGE,
|
|
433
|
-
digest: preimageHashResult.hash,
|
|
434
|
-
iteration: 0,
|
|
435
|
-
});
|
|
436
|
-
const result = await pgClient.query(`
|
|
437
|
-
UPDATE hub.hash_chain
|
|
438
|
-
SET current_iteration = 0,
|
|
439
|
-
active = false
|
|
440
|
-
WHERE id = $1
|
|
441
|
-
`, [hashChainId]);
|
|
442
|
-
if (result.rowCount !== 1) {
|
|
443
|
-
throw new Error("Failed to update hash chain iteration");
|
|
444
|
-
}
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
else {
|
|
448
|
-
logger.warn({ hashChainId, error: preimageHashResult }, "Failed to insert preimage hash in background");
|
|
449
|
-
}
|
|
450
430
|
}
|
|
@@ -23,6 +23,7 @@ import { HubBadHashChainErrorPlugin } from "../hash-chain/plugins/hub-bad-hash-c
|
|
|
23
23
|
import { HubUserActiveHashChainPlugin } from "../hash-chain/plugins/hub-user-active-hash-chain.js";
|
|
24
24
|
import { HubOutcomeInputNonNullFieldsPlugin } from "../plugins/hub-outcome-input-non-null-fields.js";
|
|
25
25
|
import { HubPutAlertPlugin } from "../plugins/hub-put-alert.js";
|
|
26
|
+
import { HubRevealHashChainPlugin } from "../hash-chain/plugins/hub-reveal-hash-chain.js";
|
|
26
27
|
export const requiredPlugins = [
|
|
27
28
|
SmartTagsPlugin,
|
|
28
29
|
IdToNodeIdPlugin,
|
|
@@ -42,6 +43,7 @@ export const defaultPlugins = [
|
|
|
42
43
|
HubBadHashChainErrorPlugin,
|
|
43
44
|
HubCreateHashChainPlugin,
|
|
44
45
|
HubUserActiveHashChainPlugin,
|
|
46
|
+
HubRevealHashChainPlugin,
|
|
45
47
|
customPgOmitArchivedPlugin("deleted"),
|
|
46
48
|
];
|
|
47
49
|
export function createPreset({ plugins, exportSchemaSDLPath, extraPgSchemas, abortSignal, context, }) {
|