@moneypot/hub 1.2.7 → 1.3.0-dev.10
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/config.d.ts +8 -13
- package/dist/src/config.js +78 -52
- package/dist/src/db/index.d.ts +2 -2
- package/dist/src/db/index.js +1 -1
- package/dist/src/db/types.d.ts +28 -0
- package/dist/src/db/types.js +5 -1
- package/dist/src/hash-chain/db-hash-chain.d.ts +15 -0
- package/dist/src/hash-chain/db-hash-chain.js +35 -0
- package/dist/src/hash-chain/get-hash.d.ts +17 -0
- package/dist/src/hash-chain/get-hash.js +57 -0
- package/dist/src/hash-chain/plugins/hub-bad-hash-chain-error.d.ts +1 -0
- package/dist/src/hash-chain/plugins/hub-bad-hash-chain-error.js +20 -0
- package/dist/src/hash-chain/plugins/hub-create-hash-chain.d.ts +1 -0
- package/dist/src/hash-chain/plugins/hub-create-hash-chain.js +111 -0
- package/dist/src/hash-chain/plugins/hub-user-active-hash-chain.d.ts +1 -0
- package/dist/src/hash-chain/plugins/hub-user-active-hash-chain.js +46 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +5 -1
- package/dist/src/pg-advisory-lock.d.ts +9 -4
- package/dist/src/pg-advisory-lock.js +8 -1
- package/dist/src/pg-versions/005-hash-chain.sql +84 -0
- package/dist/src/pg-versions/006-outcome-bet.sql +63 -0
- package/dist/src/plugins/hub-authenticate.js +24 -3
- package/dist/src/plugins/hub-make-outcome-bet.d.ts +84 -0
- package/dist/src/plugins/hub-make-outcome-bet.js +477 -0
- package/dist/src/process-transfers.js +1 -1
- package/dist/src/server/graphile.config.js +7 -1
- package/dist/src/server/handle-errors.js +1 -1
- package/dist/src/server/index.js +1 -1
- package/dist/src/take-request/process-take-request.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
type ServerOptions,
|
|
29
29
|
defaultPlugins,
|
|
30
30
|
startAndListen,
|
|
31
|
+
MakeOutcomeBetPlugin,
|
|
31
32
|
} from "@moneypot/hub";
|
|
32
33
|
import path from "path";
|
|
33
34
|
|
|
@@ -40,6 +41,9 @@ const options: ServerOptions = {
|
|
|
40
41
|
// These are required for the hub server to function
|
|
41
42
|
...defaultPlugins,
|
|
42
43
|
// Add your custom plugins here to extend server functionality
|
|
44
|
+
MakeOutcomeBetPlugin({
|
|
45
|
+
houseEdge: 0.01,
|
|
46
|
+
}),
|
|
43
47
|
],
|
|
44
48
|
|
|
45
49
|
// File path where the generated GraphQL schema definition will be saved
|
package/dist/src/config.d.ts
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import "dotenv/config";
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
applicationSecret: string;
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
declare const config: Config;
|
|
14
|
-
export default config;
|
|
2
|
+
export declare const NODE_ENV: string;
|
|
3
|
+
export declare const PORT: number;
|
|
4
|
+
export declare const MP_GRAPHQL_URL: string;
|
|
5
|
+
export declare const DATABASE_URL: string;
|
|
6
|
+
export declare const SUPERUSER_DATABASE_URL: string;
|
|
7
|
+
export declare const HASHCHAINSERVER_URL: string;
|
|
8
|
+
export declare const HASHCHAINSERVER_MAX_ITERATIONS: number;
|
|
9
|
+
export declare const HASHCHAINSERVER_APPLICATION_SECRET: string;
|
package/dist/src/config.js
CHANGED
|
@@ -1,57 +1,83 @@
|
|
|
1
1
|
import "dotenv/config";
|
|
2
2
|
import pgConnectionString from "pg-connection-string";
|
|
3
3
|
import { logger } from "./logger.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
logger.warn("Missing NODE_ENV env var. Defaulting to 'development'");
|
|
4
|
+
import { assert } from "tsafe";
|
|
5
|
+
function getEnvVariable(key, transform = (value) => value) {
|
|
6
|
+
return transform(process.env[key] || "");
|
|
8
7
|
}
|
|
9
|
-
|
|
10
|
-
if (!
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
let MP_GRAPHQL_URL = process.env.MP_GRAPHQL_URL || "";
|
|
15
|
-
if (!MP_GRAPHQL_URL) {
|
|
16
|
-
MP_GRAPHQL_URL = "http://localhost:3000/graphql";
|
|
17
|
-
logger.warn(`Missing MP_GRAPHQL_URL env var. Defaulting to ${MP_GRAPHQL_URL}`);
|
|
18
|
-
}
|
|
19
|
-
if (!MP_GRAPHQL_URL.includes("/graphql")) {
|
|
20
|
-
logger.warn(`MP_GRAPHQL_URL didn't include '/graphql'. Are you sure it points to a graphql endpoint?`);
|
|
21
|
-
}
|
|
22
|
-
const DATABASE_URL = process.env.DATABASE_URL || "";
|
|
23
|
-
if (!DATABASE_URL) {
|
|
24
|
-
throw new Error(`Missing DATABASE_URL env var.`);
|
|
25
|
-
}
|
|
26
|
-
const databaseUrlUsername = pgConnectionString.parse(DATABASE_URL).user;
|
|
27
|
-
if (databaseUrlUsername !== "app_postgraphile") {
|
|
28
|
-
logger.warn(`DATABASE_URL username is ${databaseUrlUsername}, expected app_postgraphile`);
|
|
29
|
-
}
|
|
30
|
-
const SUPERUSER_DATABASE_URL = process.env.SUPERUSER_DATABASE_URL || "";
|
|
31
|
-
if (!SUPERUSER_DATABASE_URL) {
|
|
32
|
-
throw new Error("SUPERUSER_DATABASE_URL env var is required");
|
|
33
|
-
}
|
|
34
|
-
const HASHCHAINSERVER_URL = process.env.HASHCHAINSERVER_URL || "mock-server";
|
|
35
|
-
if (HASHCHAINSERVER_URL == "mock-server") {
|
|
36
|
-
logger.warn(`Missing HASHCHAINSERVER_URL env var, defaulting to mock-server.`);
|
|
37
|
-
if (NODE_ENV !== "development") {
|
|
38
|
-
logger.warn("Missing HASHCHAINSERVER_URL and NODE_ENV != 'development': You're using the hashchain mock-server outside of development. (If you aren't using the hashchain server, you can ignore this.)");
|
|
8
|
+
export const NODE_ENV = getEnvVariable("NODE_ENV", (value) => {
|
|
9
|
+
if (!value) {
|
|
10
|
+
logger.warn("Missing NODE_ENV env var. Defaulting to 'development'");
|
|
11
|
+
return "development";
|
|
39
12
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
13
|
+
return value;
|
|
14
|
+
});
|
|
15
|
+
export const PORT = getEnvVariable("PORT", (value) => {
|
|
16
|
+
const parsed = Number.parseInt(value, 10);
|
|
17
|
+
if (!parsed || parsed <= 0 || !Number.isSafeInteger(parsed)) {
|
|
18
|
+
logger.warn("Warning: PORT missing or invalid, defaulting to ", parsed);
|
|
19
|
+
return 4000;
|
|
20
|
+
}
|
|
21
|
+
return parsed;
|
|
22
|
+
});
|
|
23
|
+
export const MP_GRAPHQL_URL = getEnvVariable("MP_GRAPHQL_URL", (value) => {
|
|
24
|
+
if (!value) {
|
|
25
|
+
logger.warn("Missing MP_GRAPHQL_URL env var. Defaulting to http://localhost:3000/graphql");
|
|
26
|
+
return "http://localhost:3000/graphql";
|
|
27
|
+
}
|
|
28
|
+
if (!URL.parse(value)) {
|
|
29
|
+
logger.warn("MP_GRAPHQL_URL is not a valid URL. Defaulting to http://localhost:3000/graphql");
|
|
30
|
+
}
|
|
31
|
+
const url = new URL(value);
|
|
32
|
+
if (url.pathname !== "/graphql") {
|
|
33
|
+
logger.warn("MP_GRAPHQL_URL pathname is not '/graphql'. Are you sure it points to a graphql endpoint?");
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
});
|
|
37
|
+
export const DATABASE_URL = getEnvVariable("DATABASE_URL", (value) => {
|
|
38
|
+
if (!value) {
|
|
39
|
+
throw new Error(`Missing DATABASE_URL env var.`);
|
|
40
|
+
}
|
|
41
|
+
if (!URL.parse(value)) {
|
|
42
|
+
logger.warn("DATABASE_URL is not a valid URL.");
|
|
43
|
+
}
|
|
44
|
+
const databaseUrlUsername = pgConnectionString.parse(value).user;
|
|
45
|
+
if (databaseUrlUsername !== "app_postgraphile") {
|
|
46
|
+
logger.warn(`DATABASE_URL username is ${databaseUrlUsername}, expected app_postgraphile`);
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
});
|
|
50
|
+
export const SUPERUSER_DATABASE_URL = getEnvVariable("SUPERUSER_DATABASE_URL", (value) => {
|
|
51
|
+
if (!value) {
|
|
52
|
+
throw new Error("SUPERUSER_DATABASE_URL env var is required");
|
|
53
|
+
}
|
|
54
|
+
if (!URL.parse(value)) {
|
|
55
|
+
logger.warn("SUPERUSER_DATABASE_URL is not a valid URL.");
|
|
56
|
+
}
|
|
57
|
+
return value;
|
|
58
|
+
});
|
|
59
|
+
export const HASHCHAINSERVER_URL = getEnvVariable("HASHCHAINSERVER_URL", (value) => {
|
|
60
|
+
value = value || "mock-server";
|
|
61
|
+
if (value === "mock-server") {
|
|
62
|
+
logger.warn("Using mock-server for HASHCHAINSERVER_URL. This is only allowed in development.");
|
|
63
|
+
}
|
|
64
|
+
else if (!URL.parse(value)) {
|
|
65
|
+
logger.warn("HASHCHAINSERVER_URL is not a valid URL. It can either be empty, 'mock-server', or URL but it was: " +
|
|
66
|
+
value);
|
|
67
|
+
}
|
|
68
|
+
if (NODE_ENV !== "development" && value === "mock-server") {
|
|
69
|
+
logger.warn("Using mock-server for HASHCHAINSERVER_URL. This is only allowed in development.");
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
});
|
|
73
|
+
export const HASHCHAINSERVER_MAX_ITERATIONS = getEnvVariable("HASHCHAINSERVER_MAX_ITERATIONS", (value) => {
|
|
74
|
+
const iterations = Number.parseInt(value, 10) || 1_000;
|
|
75
|
+
assert(iterations >= 10, "HASHCHAINSERVER_MAX_ITERATIONS must be >= 10");
|
|
76
|
+
return iterations;
|
|
77
|
+
});
|
|
78
|
+
export const HASHCHAINSERVER_APPLICATION_SECRET = getEnvVariable("HASHCHAINSERVER_APPLICATION_SECRET", (value) => {
|
|
79
|
+
if (!value && NODE_ENV !== "development") {
|
|
80
|
+
logger.warn("Missing HASHCHAINSERVER_APPLICATION_SECRET and NODE_ENV != 'development': To use the hashchain server you must pick a random (but stable) HASHCHAINSERVER_APPLICATION_SECRET for secure communciation with it. (If you aren't using the hashchain server, you can ignore this.)");
|
|
81
|
+
}
|
|
82
|
+
return value || "";
|
|
83
|
+
});
|
package/dist/src/db/index.d.ts
CHANGED
|
@@ -6,8 +6,8 @@ export * from "./types.js";
|
|
|
6
6
|
export * from "./public.js";
|
|
7
7
|
export * from "./util.js";
|
|
8
8
|
export declare function getPgClient(connectionString: string): InstanceType<typeof pg.Client>;
|
|
9
|
-
export declare const postgraphilePool:
|
|
10
|
-
export declare const superuserPool:
|
|
9
|
+
export declare const postgraphilePool: pg.Pool;
|
|
10
|
+
export declare const superuserPool: pg.Pool;
|
|
11
11
|
export interface QueryExecutor {
|
|
12
12
|
query<T extends pg.QueryResultRow = any>(queryText: string, values?: any[]): Promise<pg.QueryResult<T>>;
|
|
13
13
|
}
|
package/dist/src/db/index.js
CHANGED
package/dist/src/db/types.d.ts
CHANGED
|
@@ -114,3 +114,31 @@ export type DbTakeRequest = {
|
|
|
114
114
|
transfer_completion_attempted_at: Date | null;
|
|
115
115
|
updated_at: Date;
|
|
116
116
|
};
|
|
117
|
+
export type DbHashChain = {
|
|
118
|
+
id: string;
|
|
119
|
+
user_id: string;
|
|
120
|
+
experience_id: string;
|
|
121
|
+
casino_id: string;
|
|
122
|
+
client_seed: string;
|
|
123
|
+
max_iterations: number;
|
|
124
|
+
current_iteration: number;
|
|
125
|
+
active: boolean;
|
|
126
|
+
};
|
|
127
|
+
export declare const DbHashKind: {
|
|
128
|
+
readonly TERMINAL: "TERMINAL";
|
|
129
|
+
readonly INTERMEDIATE: "INTERMEDIATE";
|
|
130
|
+
readonly PREIMAGE: "PREIMAGE";
|
|
131
|
+
};
|
|
132
|
+
export type DbHashKind = (typeof DbHashKind)[keyof typeof DbHashKind];
|
|
133
|
+
export type DbHash = {
|
|
134
|
+
id: string;
|
|
135
|
+
kind: DbHashKind;
|
|
136
|
+
hash_chain_id: string;
|
|
137
|
+
iteration: number;
|
|
138
|
+
digest: Uint8Array;
|
|
139
|
+
metadata: Record<string, unknown>;
|
|
140
|
+
};
|
|
141
|
+
export type DbHubOutcome = {
|
|
142
|
+
weight: number;
|
|
143
|
+
profit: number;
|
|
144
|
+
};
|
package/dist/src/db/types.js
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DbCasino, DbExperience, DbHash, DbHashChain, DbUser } from "../db/types.js";
|
|
2
|
+
import { PgClientInTransaction } from "../db/index.js";
|
|
3
|
+
export declare function dbLockHashChain(pgClient: PgClientInTransaction, { userId, experienceId, casinoId, hashChainId, }: {
|
|
4
|
+
userId: DbUser["id"];
|
|
5
|
+
experienceId: DbExperience["id"];
|
|
6
|
+
casinoId: DbCasino["id"];
|
|
7
|
+
hashChainId: DbHashChain["id"];
|
|
8
|
+
}): Promise<DbHashChain | null>;
|
|
9
|
+
export declare function dbInsertHash(pgClient: PgClientInTransaction, { hashChainId, kind, digest, iteration, metadata, }: {
|
|
10
|
+
hashChainId: DbHashChain["id"];
|
|
11
|
+
kind: DbHash["kind"];
|
|
12
|
+
digest: DbHash["digest"];
|
|
13
|
+
iteration: number;
|
|
14
|
+
metadata?: DbHash["metadata"];
|
|
15
|
+
}): Promise<DbHash>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { exactlyOneRow, maybeOneRow, } from "../db/index.js";
|
|
2
|
+
import { assert } from "tsafe";
|
|
3
|
+
export async function dbLockHashChain(pgClient, { userId, experienceId, casinoId, hashChainId, }) {
|
|
4
|
+
assert(pgClient._inTransaction, "dbLockHashChain must be called in a transaction");
|
|
5
|
+
return pgClient
|
|
6
|
+
.query(`
|
|
7
|
+
SELECT *
|
|
8
|
+
FROM hub.hash_chain
|
|
9
|
+
WHERE id = $1
|
|
10
|
+
AND user_id = $2
|
|
11
|
+
AND experience_id = $3
|
|
12
|
+
AND casino_id = $4
|
|
13
|
+
AND active = TRUE
|
|
14
|
+
|
|
15
|
+
FOR UPDATE
|
|
16
|
+
`, [hashChainId, userId, experienceId, casinoId])
|
|
17
|
+
.then(maybeOneRow)
|
|
18
|
+
.then((row) => row ?? null);
|
|
19
|
+
}
|
|
20
|
+
export async function dbInsertHash(pgClient, { hashChainId, kind, digest, iteration, metadata = {}, }) {
|
|
21
|
+
assert(pgClient._inTransaction, "dbInsertHash must be called in a transaction");
|
|
22
|
+
return pgClient
|
|
23
|
+
.query(`
|
|
24
|
+
INSERT INTO hub.hash (hash_chain_id, kind, digest, iteration, metadata)
|
|
25
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
26
|
+
RETURNING *
|
|
27
|
+
`, [
|
|
28
|
+
hashChainId,
|
|
29
|
+
kind,
|
|
30
|
+
digest,
|
|
31
|
+
iteration,
|
|
32
|
+
metadata,
|
|
33
|
+
])
|
|
34
|
+
.then(exactlyOneRow);
|
|
35
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type HashResult = {
|
|
2
|
+
type: "bad_hash_chain";
|
|
3
|
+
reason: "hashchain_too_old" | "empty_response";
|
|
4
|
+
} | {
|
|
5
|
+
type: "success";
|
|
6
|
+
hash: Uint8Array;
|
|
7
|
+
};
|
|
8
|
+
export declare function getIntermediateHash({ hashChainId, iteration, }: {
|
|
9
|
+
hashChainId: string;
|
|
10
|
+
iteration: number;
|
|
11
|
+
}): Promise<HashResult>;
|
|
12
|
+
export declare function getPreimageHash({ hashChainId, }: {
|
|
13
|
+
hashChainId: string;
|
|
14
|
+
}): Promise<HashResult>;
|
|
15
|
+
export declare function getTerminalHash({ hashChainId, }: {
|
|
16
|
+
hashChainId: string;
|
|
17
|
+
}): Promise<HashResult>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { getHash } from "@moneypot/hash-herald";
|
|
2
|
+
import * as config from "../config.js";
|
|
3
|
+
const HASH_HERALD_OPTIONS = {
|
|
4
|
+
baseUrl: config.HASHCHAINSERVER_URL,
|
|
5
|
+
applicationSecret: config.HASHCHAINSERVER_APPLICATION_SECRET,
|
|
6
|
+
};
|
|
7
|
+
function resultFromGetHashResponse(response) {
|
|
8
|
+
if (!response.resp) {
|
|
9
|
+
return { type: "bad_hash_chain", reason: "empty_response" };
|
|
10
|
+
}
|
|
11
|
+
const $case = response.resp.$case;
|
|
12
|
+
switch ($case) {
|
|
13
|
+
case "hash":
|
|
14
|
+
return { type: "success", hash: response.resp.value };
|
|
15
|
+
case "hashchainTooOldError":
|
|
16
|
+
return { type: "bad_hash_chain", reason: "hashchain_too_old" };
|
|
17
|
+
default: {
|
|
18
|
+
const _exhaustiveCheck = $case;
|
|
19
|
+
throw new Error(`Unknown hash response: ${_exhaustiveCheck}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function getIntermediateHash({ hashChainId, iteration, }) {
|
|
24
|
+
const req = {
|
|
25
|
+
hashchainId: hashChainId,
|
|
26
|
+
iterations: iteration,
|
|
27
|
+
context: undefined,
|
|
28
|
+
};
|
|
29
|
+
return getHash(HASH_HERALD_OPTIONS, req).then(resultFromGetHashResponse);
|
|
30
|
+
}
|
|
31
|
+
export async function getPreimageHash({ hashChainId, }) {
|
|
32
|
+
const req = {
|
|
33
|
+
hashchainId: hashChainId,
|
|
34
|
+
iterations: 0,
|
|
35
|
+
context: {
|
|
36
|
+
event: {
|
|
37
|
+
$case: "fetchingPreimage",
|
|
38
|
+
value: {},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
return getHash(HASH_HERALD_OPTIONS, req).then(resultFromGetHashResponse);
|
|
43
|
+
}
|
|
44
|
+
export async function getTerminalHash({ hashChainId, }) {
|
|
45
|
+
const iterations = 1_000;
|
|
46
|
+
const req = {
|
|
47
|
+
hashchainId: hashChainId,
|
|
48
|
+
iterations,
|
|
49
|
+
context: {
|
|
50
|
+
event: {
|
|
51
|
+
$case: "fetchingTerminalHash",
|
|
52
|
+
value: {},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
return getHash(HASH_HERALD_OPTIONS, req).then(resultFromGetHashResponse);
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const HubBadHashChainErrorPlugin: GraphileConfig.Plugin;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ObjectStep, access } from "postgraphile/grafast";
|
|
2
|
+
import { gql, makeExtendSchemaPlugin } from "postgraphile/utils";
|
|
3
|
+
export const HubBadHashChainErrorPlugin = makeExtendSchemaPlugin(() => {
|
|
4
|
+
return {
|
|
5
|
+
typeDefs: gql `
|
|
6
|
+
type HubBadHashChainError {
|
|
7
|
+
message: String
|
|
8
|
+
}
|
|
9
|
+
`,
|
|
10
|
+
plans: {
|
|
11
|
+
HubBadHashChainError: {
|
|
12
|
+
__assertStep: ObjectStep,
|
|
13
|
+
message($data) {
|
|
14
|
+
const $message = access($data, "message");
|
|
15
|
+
return $message;
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}, "HubBadHashChainErrorPlugin");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const HubCreateHashChainPlugin: GraphileConfig.Plugin;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { context, object, sideEffect } from "@moneypot/hub/grafast";
|
|
2
|
+
import { gql, makeExtendSchemaPlugin } from "@moneypot/hub/graphile";
|
|
3
|
+
import { GraphQLError } from "graphql";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { PgAdvisoryLock } from "../../pg-advisory-lock.js";
|
|
6
|
+
import { exactlyOneRow, superuserPool, withPgPoolTransaction, } from "@moneypot/hub/db";
|
|
7
|
+
import * as HashCommon from "../get-hash.js";
|
|
8
|
+
import { DbHashKind } from "../../db/types.js";
|
|
9
|
+
import * as config from "../../config.js";
|
|
10
|
+
const InputSchema = z.object({
|
|
11
|
+
clientSeed: z.string(),
|
|
12
|
+
});
|
|
13
|
+
export const HubCreateHashChainPlugin = makeExtendSchemaPlugin((build) => {
|
|
14
|
+
const hashChainTable = build.input.pgRegistry.pgResources.hub_hash_chain;
|
|
15
|
+
return {
|
|
16
|
+
typeDefs: gql `
|
|
17
|
+
input HubCreateHashChainInput {
|
|
18
|
+
clientSeed: String!
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type HubCreateHashChainPayload {
|
|
22
|
+
hashChain: HubHashChain!
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
extend type Mutation {
|
|
26
|
+
hubCreateHashChain(
|
|
27
|
+
input: HubCreateHashChainInput!
|
|
28
|
+
): HubCreateHashChainPayload!
|
|
29
|
+
}
|
|
30
|
+
`,
|
|
31
|
+
plans: {
|
|
32
|
+
Mutation: {
|
|
33
|
+
hubCreateHashChain: (_, { $input }) => {
|
|
34
|
+
const $identity = context().get("identity");
|
|
35
|
+
const $hashChainId = sideEffect([$input, $identity], ([rawInput, identity]) => {
|
|
36
|
+
if (identity?.kind !== "user") {
|
|
37
|
+
throw new GraphQLError("Unauthorized");
|
|
38
|
+
}
|
|
39
|
+
const result = InputSchema.safeParse(rawInput);
|
|
40
|
+
if (!result.success) {
|
|
41
|
+
const message = result.error.errors[0].message;
|
|
42
|
+
throw new GraphQLError(message);
|
|
43
|
+
}
|
|
44
|
+
const { clientSeed } = result.data;
|
|
45
|
+
return withPgPoolTransaction(superuserPool, async (pgClient) => {
|
|
46
|
+
await PgAdvisoryLock.forNewHashChain(pgClient, {
|
|
47
|
+
userId: identity.session.user_id,
|
|
48
|
+
experienceId: identity.session.experience_id,
|
|
49
|
+
casinoId: identity.session.casino_id,
|
|
50
|
+
});
|
|
51
|
+
await pgClient.query(`
|
|
52
|
+
UPDATE hub.hash_chain
|
|
53
|
+
SET active = false
|
|
54
|
+
WHERE user_id = $1
|
|
55
|
+
AND experience_id = $2
|
|
56
|
+
AND casino_id = $3
|
|
57
|
+
AND active = true
|
|
58
|
+
`, [
|
|
59
|
+
identity.session.user_id,
|
|
60
|
+
identity.session.experience_id,
|
|
61
|
+
identity.session.casino_id,
|
|
62
|
+
]);
|
|
63
|
+
const dbHashChain = await pgClient
|
|
64
|
+
.query(`
|
|
65
|
+
INSERT INTO hub.hash_chain (
|
|
66
|
+
user_id,
|
|
67
|
+
experience_id,
|
|
68
|
+
casino_id,
|
|
69
|
+
client_seed,
|
|
70
|
+
active,
|
|
71
|
+
max_iteration,
|
|
72
|
+
current_iteration
|
|
73
|
+
)
|
|
74
|
+
VALUES ($1, $2, $3, $4, true, $5, $5)
|
|
75
|
+
RETURNING *
|
|
76
|
+
`, [
|
|
77
|
+
identity.session.user_id,
|
|
78
|
+
identity.session.experience_id,
|
|
79
|
+
identity.session.casino_id,
|
|
80
|
+
clientSeed,
|
|
81
|
+
config.HASHCHAINSERVER_MAX_ITERATIONS,
|
|
82
|
+
])
|
|
83
|
+
.then(exactlyOneRow);
|
|
84
|
+
const terminalHash = await HashCommon.getTerminalHash({
|
|
85
|
+
hashChainId: dbHashChain.id,
|
|
86
|
+
});
|
|
87
|
+
await pgClient.query(`
|
|
88
|
+
INSERT INTO hub.hash (
|
|
89
|
+
hash_chain_id,
|
|
90
|
+
kind,
|
|
91
|
+
digest,
|
|
92
|
+
iteration
|
|
93
|
+
)
|
|
94
|
+
VALUES ($1, $2, $3, $4)
|
|
95
|
+
`, [
|
|
96
|
+
dbHashChain.id,
|
|
97
|
+
DbHashKind.TERMINAL,
|
|
98
|
+
terminalHash,
|
|
99
|
+
config.HASHCHAINSERVER_MAX_ITERATIONS,
|
|
100
|
+
]);
|
|
101
|
+
return dbHashChain.id;
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
return object({
|
|
105
|
+
hashChain: hashChainTable.get({ id: $hashChainId }),
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}, "CreateHashChainPlugin");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const HubUserActiveHashChainPlugin: GraphileConfig.Plugin;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { maybeOneRow } from "@moneypot/hub/db";
|
|
2
|
+
import { inhibitOnNull } from "@moneypot/hub/grafast";
|
|
3
|
+
import { context } from "@moneypot/hub/grafast";
|
|
4
|
+
import { gql, makeExtendSchemaPlugin } from "@moneypot/hub/graphile";
|
|
5
|
+
import { withPgClient } from "postgraphile/@dataplan/pg";
|
|
6
|
+
import { object } from "postgraphile/grafast";
|
|
7
|
+
export const HubUserActiveHashChainPlugin = makeExtendSchemaPlugin((build) => {
|
|
8
|
+
const hashChainTable = build.input.pgRegistry.pgResources.hub_hash_chain;
|
|
9
|
+
return {
|
|
10
|
+
typeDefs: gql `
|
|
11
|
+
extend type HubUser {
|
|
12
|
+
activeHashChain: HubHashChain
|
|
13
|
+
}
|
|
14
|
+
`,
|
|
15
|
+
plans: {
|
|
16
|
+
HubUser: {
|
|
17
|
+
activeHashChain: ($record) => {
|
|
18
|
+
const $identity = context().get("identity");
|
|
19
|
+
const $hashChainId = withPgClient(hashChainTable.executor, object({ userId: $record.get("id"), identity: $identity }), async (pgClient, { userId, identity }) => {
|
|
20
|
+
if (identity?.kind !== "user") {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const { session } = identity;
|
|
24
|
+
const activeHashChain = await pgClient
|
|
25
|
+
.query({
|
|
26
|
+
text: `
|
|
27
|
+
select id
|
|
28
|
+
from hub.hash_chain
|
|
29
|
+
where user_id = $1
|
|
30
|
+
and experience_id = $2
|
|
31
|
+
and casino_id = $3
|
|
32
|
+
and active = TRUE
|
|
33
|
+
order by id desc
|
|
34
|
+
limit 1
|
|
35
|
+
`,
|
|
36
|
+
values: [userId, session.experience_id, session.casino_id],
|
|
37
|
+
})
|
|
38
|
+
.then(maybeOneRow);
|
|
39
|
+
return activeHashChain?.id ?? null;
|
|
40
|
+
});
|
|
41
|
+
return hashChainTable.get({ id: inhibitOnNull($hashChainId) });
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}, "HubActiveHashChainPlugin");
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Express } from "express";
|
|
2
2
|
import { Logger } from "./logger.js";
|
|
3
|
+
export { MakeOutcomeBetPlugin, type OutcomeBetConfigMap, type OutcomeBetConfig, } from "./plugins/hub-make-outcome-bet.js";
|
|
3
4
|
export { defaultPlugins, type PluginContext, type PluginIdentity, type UserSessionContext, } from "./server/graphile.config.js";
|
|
4
5
|
export type ServerOptions = {
|
|
5
6
|
configureApp?: (app: Express) => void;
|
package/dist/src/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import PgUpgradeSchema, { DatabaseAheadError, } from "@moneypot/pg-upgrade-schema";
|
|
2
2
|
import * as db from "./db/index.js";
|
|
3
|
-
import config from "./config.js";
|
|
3
|
+
import * as config from "./config.js";
|
|
4
4
|
import { createHubServer } from "./server/index.js";
|
|
5
5
|
import { initializeTransferProcessors } from "./process-transfers.js";
|
|
6
6
|
import { join } from "path";
|
|
7
7
|
import { logger, setLogger } from "./logger.js";
|
|
8
|
+
export { MakeOutcomeBetPlugin, } from "./plugins/hub-make-outcome-bet.js";
|
|
8
9
|
export { defaultPlugins, } from "./server/graphile.config.js";
|
|
9
10
|
async function initialize(options) {
|
|
10
11
|
if (options.logger) {
|
|
@@ -54,6 +55,9 @@ async function initialize(options) {
|
|
|
54
55
|
});
|
|
55
56
|
}
|
|
56
57
|
export async function startAndListen(options) {
|
|
58
|
+
if (options.plugins && options.plugins.some((p) => typeof p === "function")) {
|
|
59
|
+
throw new Error("`plugins` should be an array of GraphileConfig.Plugin but one of the items is a function. Did you forget to call it?");
|
|
60
|
+
}
|
|
57
61
|
if (options.userDatabaseMigrationsPath &&
|
|
58
62
|
!options.userDatabaseMigrationsPath.startsWith("/")) {
|
|
59
63
|
throw new Error(`userDatabaseMigrationsPath must be an absolute path, got ${options.userDatabaseMigrationsPath}`);
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
export declare const
|
|
4
|
-
forMpTakeRequestProcessing: (pgClient:
|
|
1
|
+
import { DbCasino, DbExperience, DbProcessedTakeRequest, DbUser } from "./db/types.js";
|
|
2
|
+
import { PgClientInTransaction } from "./db/index.js";
|
|
3
|
+
export declare const PgAdvisoryLock: {
|
|
4
|
+
forMpTakeRequestProcessing: (pgClient: PgClientInTransaction, params: {
|
|
5
5
|
mpTakeRequestId: DbProcessedTakeRequest["mp_take_request_id"];
|
|
6
6
|
casinoId: DbCasino["id"];
|
|
7
7
|
}) => Promise<void>;
|
|
8
|
+
forNewHashChain: (pgClient: PgClientInTransaction, params: {
|
|
9
|
+
userId: DbUser["id"];
|
|
10
|
+
experienceId: DbExperience["id"];
|
|
11
|
+
casinoId: DbCasino["id"];
|
|
12
|
+
}) => Promise<void>;
|
|
8
13
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { assert } from "tsafe";
|
|
1
2
|
var LockNamespace;
|
|
2
3
|
(function (LockNamespace) {
|
|
3
4
|
LockNamespace[LockNamespace["MP_TAKE_REQUEST"] = 1] = "MP_TAKE_REQUEST";
|
|
5
|
+
LockNamespace[LockNamespace["NEW_HASH_CHAIN"] = 2] = "NEW_HASH_CHAIN";
|
|
4
6
|
})(LockNamespace || (LockNamespace = {}));
|
|
5
7
|
function simpleHash32(text) {
|
|
6
8
|
let hash = 0;
|
|
@@ -20,8 +22,13 @@ async function acquireAdvisoryLock(pgClient, namespace, hash) {
|
|
|
20
22
|
values: [namespace, hash],
|
|
21
23
|
});
|
|
22
24
|
}
|
|
23
|
-
export const
|
|
25
|
+
export const PgAdvisoryLock = {
|
|
24
26
|
forMpTakeRequestProcessing: async (pgClient, params) => {
|
|
27
|
+
assert(pgClient._inTransaction, "pgClient must be in a transaction");
|
|
25
28
|
await acquireAdvisoryLock(pgClient, LockNamespace.MP_TAKE_REQUEST, createHashKey(params.mpTakeRequestId, params.casinoId));
|
|
26
29
|
},
|
|
30
|
+
forNewHashChain: async (pgClient, params) => {
|
|
31
|
+
assert(pgClient._inTransaction, "pgClient must be in a transaction");
|
|
32
|
+
await acquireAdvisoryLock(pgClient, LockNamespace.NEW_HASH_CHAIN, createHashKey(params.userId, params.experienceId, params.casinoId));
|
|
33
|
+
},
|
|
27
34
|
};
|