stellarium-ts 0.19.0 → 0.20.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/dist/client/operatorClient.d.ts +34 -3
- package/dist/client/operatorClient.d.ts.map +1 -1
- package/dist/client/operatorClient.js +15 -0
- package/dist/contract/repository.d.ts +49 -0
- package/dist/contract/repository.d.ts.map +1 -0
- package/dist/contract/repository.js +80 -0
- package/dist/contract/service.d.ts +103 -0
- package/dist/contract/service.d.ts.map +1 -0
- package/dist/contract/service.js +297 -0
- package/dist/contract/state.d.ts +43 -0
- package/dist/contract/state.d.ts.map +1 -0
- package/dist/contract/state.js +79 -0
- package/dist/db/database.d.ts +25 -0
- package/dist/db/database.d.ts.map +1 -0
- package/dist/db/database.js +1 -0
- package/dist/db/db.d.ts +12 -4
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +97 -24
- package/dist/db/postgres.d.ts +39 -0
- package/dist/db/postgres.d.ts.map +1 -0
- package/dist/db/postgres.js +132 -0
- package/dist/db/sqlite.d.ts +16 -0
- package/dist/db/sqlite.d.ts.map +1 -0
- package/dist/db/sqlite.js +53 -0
- package/dist/grant/repository.d.ts +2 -2
- package/dist/grant/repository.d.ts.map +1 -1
- package/dist/grant/repository.js +2 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/peer/repository.d.ts +2 -2
- package/dist/peer/repository.d.ts.map +1 -1
- package/dist/peer/repository.js +1 -1
- package/dist/resource/invoker.d.ts +85 -0
- package/dist/resource/invoker.d.ts.map +1 -1
- package/dist/resource/invoker.js +69 -6
- package/dist/resource/repository.d.ts +51 -10
- package/dist/resource/repository.d.ts.map +1 -1
- package/dist/resource/repository.js +116 -61
- package/dist/resource/settlement.d.ts +5 -0
- package/dist/resource/settlement.d.ts.map +1 -1
- package/dist/resource/settlement.js +19 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +161 -25
- package/dist/server.d.ts +30 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +80 -5
- package/dist/service.d.ts +27 -2
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +16 -1
- package/dist/startup.d.ts +2 -2
- package/dist/startup.d.ts.map +1 -1
- package/dist/startup.js +43 -34
- package/dist/tests/contract/repository.test.d.ts +2 -0
- package/dist/tests/contract/repository.test.d.ts.map +1 -0
- package/dist/tests/contract/repository.test.js +68 -0
- package/dist/tests/contract/routes.test.d.ts +2 -0
- package/dist/tests/contract/routes.test.d.ts.map +1 -0
- package/dist/tests/contract/routes.test.js +252 -0
- package/dist/tests/contract/state-routes.test.d.ts +2 -0
- package/dist/tests/contract/state-routes.test.d.ts.map +1 -0
- package/dist/tests/contract/state-routes.test.js +158 -0
- package/dist/tests/contract/state.test.d.ts +2 -0
- package/dist/tests/contract/state.test.d.ts.map +1 -0
- package/dist/tests/contract/state.test.js +57 -0
- package/dist/tests/db/db.test.js +1 -1
- package/dist/tests/db/postgres.test.d.ts +2 -0
- package/dist/tests/db/postgres.test.d.ts.map +1 -0
- package/dist/tests/db/postgres.test.js +43 -0
- package/dist/tests/grant/service.test.js +3 -4
- package/dist/tests/helpers.d.ts +8 -0
- package/dist/tests/helpers.d.ts.map +1 -1
- package/dist/tests/helpers.js +37 -0
- package/dist/tests/integration/network.test.js +74 -4
- package/dist/tests/peer/repository.test.js +3 -4
- package/dist/tests/resource/repository.test.js +3 -4
- package/dist/tests/routes.test.js +10 -11
- package/dist/tests/server.test.js +30 -0
- package/dist/tests/startup.test.js +4 -5
- package/package.json +5 -2
package/dist/server.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ import { RateLimitConfig } from './routes.js';
|
|
|
8
8
|
import { type NodeConfig } from './service.js';
|
|
9
9
|
export declare const DEFAULT_COST_LIMIT_MICRO_USD = 10000;
|
|
10
10
|
export declare const DEFAULT_GRANT_REFRESH_INTERVAL_MS: number;
|
|
11
|
+
/** How often a node with an operator asks it again for its organization and price, which may change on the dashboard. */
|
|
12
|
+
export declare const DEFAULT_SESSION_REFRESH_INTERVAL_MS: number;
|
|
11
13
|
export interface StellariumNodeOptions {
|
|
12
14
|
port?: number;
|
|
13
15
|
host?: string;
|
|
@@ -36,8 +38,33 @@ export interface StellariumNodeOptions {
|
|
|
36
38
|
* Defaults to `INFERENCE_COST_LIMIT_MICRO_USD`, then to the resolved `costLimitMicroUsd`.
|
|
37
39
|
*/
|
|
38
40
|
inferenceCostLimitMicroUsd?: number;
|
|
39
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Most one call to a contract on another node may cost: what its voucher lets the provider hold for the
|
|
43
|
+
* contract's tokens and its paid calls. Defaults to `CONTRACT_COST_LIMIT_MICRO_USD`, then to the resolved
|
|
44
|
+
* `costLimitMicroUsd`.
|
|
45
|
+
*/
|
|
46
|
+
contractCostLimitMicroUsd?: number;
|
|
47
|
+
/** What another organization pays to deploy a contract to this node. Defaults to `CONTRACT_DEPLOY_FEE_MICRO_USD`, then 0. */
|
|
48
|
+
contractDeployFeeMicroUsd?: number;
|
|
49
|
+
/** Largest contract source this node accepts, in bytes. Defaults to `MAX_CONTRACT_SOURCE_BYTES`, then 64 KiB. */
|
|
50
|
+
maxContractSourceBytes?: number;
|
|
51
|
+
/**
|
|
52
|
+
* Most one contract's state may hold in one scope, in bytes of its variables' encoded values. A call that
|
|
53
|
+
* would grow it past this fails and saves nothing. Defaults to `MAX_CONTRACT_STATE_BYTES`, then 64 KiB.
|
|
54
|
+
*/
|
|
55
|
+
maxContractStateBytes?: number;
|
|
56
|
+
/** SQLite file. Defaults to `DATABASE_PATH`, then `./requestscript.db`; use ':memory:' for tests. */
|
|
40
57
|
databasePath?: string;
|
|
58
|
+
/**
|
|
59
|
+
* A `postgres://` or `postgresql://` URL. Defaults to `DATABASE_URL`. When set, the node keeps its data in
|
|
60
|
+
* Postgres instead of a SQLite file, so `databasePath` must not be set too.
|
|
61
|
+
*/
|
|
62
|
+
databaseUrl?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Postgres schema for the node's tables, created if missing, so several nodes can share a database.
|
|
65
|
+
* Defaults to `DATABASE_SCHEMA`, then to the connection's search path.
|
|
66
|
+
*/
|
|
67
|
+
databaseSchema?: string;
|
|
41
68
|
/**
|
|
42
69
|
* Grant chains addressed to this node, stored at start. Each is root first. A stored renewal
|
|
43
70
|
* of the same grant is kept, and an expired chain is skipped with a warning.
|
|
@@ -45,6 +72,8 @@ export interface StellariumNodeOptions {
|
|
|
45
72
|
grants?: Grant[][];
|
|
46
73
|
/** How often held grants past half their lifetime are renewed. Defaults to 5 minutes. */
|
|
47
74
|
grantRefreshIntervalMs?: number;
|
|
75
|
+
/** How often to ask the operator again for this node's organization and price. Defaults to five minutes. */
|
|
76
|
+
sessionRefreshIntervalMs?: number;
|
|
48
77
|
/**
|
|
49
78
|
* Decides callers' requests for grants on this node's resources (`POST /v1/grants/request`).
|
|
50
79
|
* Without it, callers can only get grants from `issueGrant`.
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import 'dotenv/config';
|
|
2
|
-
import { DEFAULT_DATABASE_PATH, openDatabase } from './db/db.js';
|
|
2
|
+
import { DEFAULT_DATABASE_PATH, isPostgresUrl, openDatabase } from './db/db.js';
|
|
3
|
+
import { assertSchemaName } from './db/postgres.js';
|
|
4
|
+
import { DEFAULT_MAX_CONTRACT_SOURCE_BYTES } from './contract/service.js';
|
|
5
|
+
import { DEFAULT_MAX_CONTRACT_STATE_BYTES } from './contract/state.js';
|
|
3
6
|
import { validateInferenceFunction } from './resource/inference.js';
|
|
4
7
|
import routes from './routes.js';
|
|
5
8
|
import { createService } from './service.js';
|
|
6
|
-
import { StartupService } from './startup.js';
|
|
9
|
+
import { announceResources, StartupService } from './startup.js';
|
|
7
10
|
import { OperatorClient } from './client/operatorClient.js';
|
|
8
11
|
export const DEFAULT_COST_LIMIT_MICRO_USD = 10_000;
|
|
9
12
|
export const DEFAULT_GRANT_REFRESH_INTERVAL_MS = 1000 * 60 * 5;
|
|
13
|
+
/** How often a node with an operator asks it again for its organization and price, which may change on the dashboard. */
|
|
14
|
+
export const DEFAULT_SESSION_REFRESH_INTERVAL_MS = 1000 * 60 * 5;
|
|
10
15
|
function required(name, value) {
|
|
11
16
|
if (!value) {
|
|
12
17
|
throw new Error(`${name} is required: pass it to StellariumNode.start or set it in the environment`);
|
|
@@ -28,6 +33,10 @@ export function resolveConfig(options, resources = []) {
|
|
|
28
33
|
for (const resource of resources) {
|
|
29
34
|
resource.functions.forEach(validateInferenceFunction);
|
|
30
35
|
}
|
|
36
|
+
const contractCostLimit = nonnegativeInteger('contractCostLimitMicroUsd', options.contractCostLimitMicroUsd, process.env.CONTRACT_COST_LIMIT_MICRO_USD) ?? costLimit;
|
|
37
|
+
const contractDeployFee = nonnegativeInteger('contractDeployFeeMicroUsd', options.contractDeployFeeMicroUsd, process.env.CONTRACT_DEPLOY_FEE_MICRO_USD) ?? 0;
|
|
38
|
+
const maxContractSourceBytes = nonnegativeInteger('maxContractSourceBytes', options.maxContractSourceBytes, process.env.MAX_CONTRACT_SOURCE_BYTES) ?? DEFAULT_MAX_CONTRACT_SOURCE_BYTES;
|
|
39
|
+
const maxContractStateBytes = nonnegativeInteger('maxContractStateBytes', options.maxContractStateBytes, process.env.MAX_CONTRACT_STATE_BYTES) ?? DEFAULT_MAX_CONTRACT_STATE_BYTES;
|
|
31
40
|
return {
|
|
32
41
|
baseUrl: required('BASE_URL', options.baseUrl ?? process.env.BASE_URL),
|
|
33
42
|
publicKey: required('SIGNING_PUBLIC_KEY', options.publicKey ?? process.env.SIGNING_PUBLIC_KEY),
|
|
@@ -39,8 +48,43 @@ export function resolveConfig(options, resources = []) {
|
|
|
39
48
|
: options.operatorUrl ?? process.env.OPERATOR_URL,
|
|
40
49
|
costLimitMicroUsd: costLimit,
|
|
41
50
|
inferenceCostLimitMicroUsd: inferenceCostLimit,
|
|
51
|
+
contractCostLimitMicroUsd: contractCostLimit,
|
|
52
|
+
contractDeployFeeMicroUsd: contractDeployFee,
|
|
53
|
+
maxContractSourceBytes,
|
|
54
|
+
maxContractStateBytes,
|
|
55
|
+
...resolveDatabase(options),
|
|
42
56
|
};
|
|
43
57
|
}
|
|
58
|
+
/** An option, else its environment variable, which must be a nonnegative integer. Undefined when neither is set. */
|
|
59
|
+
function nonnegativeInteger(name, option, env) {
|
|
60
|
+
const value = option ?? (env ? Number(env) : undefined);
|
|
61
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {
|
|
62
|
+
throw new Error(`${name} must be a nonnegative integer`);
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
function resolveDatabase(options) {
|
|
67
|
+
// Empty variables, as a compose file or .env may leave them, count as unset.
|
|
68
|
+
const databaseUrl = options.databaseUrl ?? (process.env.DATABASE_URL || undefined);
|
|
69
|
+
const databasePath = options.databasePath ?? (process.env.DATABASE_PATH || undefined);
|
|
70
|
+
const databaseSchema = options.databaseSchema ?? (process.env.DATABASE_SCHEMA || undefined);
|
|
71
|
+
if (databaseSchema !== undefined) {
|
|
72
|
+
if (databaseUrl === undefined) {
|
|
73
|
+
throw new Error('databaseSchema (DATABASE_SCHEMA) needs a Postgres databaseUrl (DATABASE_URL)');
|
|
74
|
+
}
|
|
75
|
+
assertSchemaName(databaseSchema);
|
|
76
|
+
}
|
|
77
|
+
if (databaseUrl === undefined) {
|
|
78
|
+
return { databasePath: databasePath ?? DEFAULT_DATABASE_PATH };
|
|
79
|
+
}
|
|
80
|
+
if (!isPostgresUrl(databaseUrl)) {
|
|
81
|
+
throw new Error('databaseUrl (DATABASE_URL) must be a postgres:// or postgresql:// URL');
|
|
82
|
+
}
|
|
83
|
+
if (databasePath !== undefined) {
|
|
84
|
+
throw new Error('Set either databaseUrl (DATABASE_URL) for Postgres or databasePath (DATABASE_PATH) for SQLite, not both');
|
|
85
|
+
}
|
|
86
|
+
return { databaseUrl, databaseSchema };
|
|
87
|
+
}
|
|
44
88
|
export class StellariumNode {
|
|
45
89
|
resources = [];
|
|
46
90
|
service;
|
|
@@ -61,7 +105,7 @@ export class StellariumNode {
|
|
|
61
105
|
const session = () => new OperatorClient(config.operatorUrl, config.publicKey, config.privateKey).session(enrollmentToken, config.baseUrl);
|
|
62
106
|
if (hostsPaidFunctions(this.resources) || enrollmentToken) {
|
|
63
107
|
try {
|
|
64
|
-
({ organizationId: config.organizationId } = await session());
|
|
108
|
+
({ organizationId: config.organizationId, priceMicroUsdPerMillionTokens: config.priceMicroUsdPerMillionTokens } = await session());
|
|
65
109
|
}
|
|
66
110
|
catch (error) {
|
|
67
111
|
const hint = enrollmentToken ? '' : '. Create an enrollment token for it at https://platform.stellarium.sh and set ENROLLMENT_TOKEN';
|
|
@@ -71,13 +115,14 @@ export class StellariumNode {
|
|
|
71
115
|
else if (config.operatorUrl) {
|
|
72
116
|
// Serving doesn't depend on it, so it doesn't hold up the start. Until it answers, run tokens from
|
|
73
117
|
// this node's own organization are refused as if billed.
|
|
74
|
-
session().then(({ organizationId }) => {
|
|
118
|
+
session().then(({ organizationId, priceMicroUsdPerMillionTokens }) => {
|
|
75
119
|
config.organizationId = organizationId;
|
|
120
|
+
config.priceMicroUsdPerMillionTokens = priceMicroUsdPerMillionTokens;
|
|
76
121
|
}, (error) => {
|
|
77
122
|
fastify.log.warn(`No session with the operator, so this node can't be listed: ${error instanceof Error ? error.message : String(error)}`);
|
|
78
123
|
});
|
|
79
124
|
}
|
|
80
|
-
const db = await openDatabase(
|
|
125
|
+
const db = await openDatabase(config.databaseUrl ?? config.databasePath, { schema: config.databaseSchema });
|
|
81
126
|
fastify.addHook('onClose', async () => db.close());
|
|
82
127
|
const service = createService(db, this.resources, config, { approveGrant: options.approveGrant });
|
|
83
128
|
// A configured chain may be older than its stored renewal, or long expired; neither should stop the node.
|
|
@@ -112,11 +157,41 @@ export class StellariumNode {
|
|
|
112
157
|
refresher.unref();
|
|
113
158
|
};
|
|
114
159
|
scheduleRefresh();
|
|
160
|
+
// Contracts are advertised at the node's price, which its organization may change at any time.
|
|
161
|
+
let sessionRefresher;
|
|
162
|
+
const scheduleSessionRefresh = () => {
|
|
163
|
+
sessionRefresher = setTimeout(async () => {
|
|
164
|
+
try {
|
|
165
|
+
const { organizationId, priceMicroUsdPerMillionTokens } = await session();
|
|
166
|
+
const changed = organizationId !== config.organizationId || priceMicroUsdPerMillionTokens !== config.priceMicroUsdPerMillionTokens;
|
|
167
|
+
config.organizationId = organizationId;
|
|
168
|
+
config.priceMicroUsdPerMillionTokens = priceMicroUsdPerMillionTokens;
|
|
169
|
+
if (changed && (await service.contractRepository.allLatest()).length > 0) {
|
|
170
|
+
service.contractService.announce?.();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
fastify.log.warn(`Failed to refresh this node's session with the operator: ${error instanceof Error ? error.message : String(error)}`);
|
|
175
|
+
}
|
|
176
|
+
if (!closed) {
|
|
177
|
+
scheduleSessionRefresh();
|
|
178
|
+
}
|
|
179
|
+
}, options.sessionRefreshIntervalMs ?? DEFAULT_SESSION_REFRESH_INTERVAL_MS);
|
|
180
|
+
sessionRefresher.unref();
|
|
181
|
+
};
|
|
182
|
+
if (config.operatorUrl) {
|
|
183
|
+
scheduleSessionRefresh();
|
|
184
|
+
}
|
|
115
185
|
fastify.addHook('onClose', async () => {
|
|
116
186
|
closed = true;
|
|
117
187
|
clearTimeout(refresher);
|
|
188
|
+
clearTimeout(sessionRefresher);
|
|
118
189
|
});
|
|
119
190
|
await fastify.register(routes, { prefix: '/v1', service, rateLimit: options.rateLimit });
|
|
191
|
+
// Peers learn of a contract as soon as it is deployed, and forget it when it is deleted.
|
|
192
|
+
service.contractService.announce = () => {
|
|
193
|
+
announceResources(fastify.log, service).catch(error => fastify.log.error(error, 'Failed to announce resources'));
|
|
194
|
+
};
|
|
120
195
|
const address = await fastify.listen({ port: options.port ?? 3000, host: options.host ?? '0.0.0.0' });
|
|
121
196
|
fastify.log.info(`server listening on ${address}`);
|
|
122
197
|
if (options.startingPeer) {
|
package/dist/service.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import type { JWTVerifyGetKey } from "jose";
|
|
2
2
|
import { ResourceResolver } from "requestscript";
|
|
3
|
-
import type {
|
|
3
|
+
import type { NodeDatabase } from "./db/database.js";
|
|
4
4
|
import { PeerRepository } from "./peer/repository.js";
|
|
5
5
|
import { ResourceRepository, type StellariumResource } from "./resource/repository.js";
|
|
6
6
|
import { CombinationResourceInvoker } from "./resource/invoker.js";
|
|
7
7
|
import { OperatorClient } from "./client/operatorClient.js";
|
|
8
8
|
import { RunTokenVerifier } from "./client/runTokens.js";
|
|
9
9
|
import { type ApproveGrant, GrantService } from "./grant/service.js";
|
|
10
|
+
import { ContractRepository } from "./contract/repository.js";
|
|
11
|
+
import { ContractService } from "./contract/service.js";
|
|
12
|
+
import { ContractStateRepository } from "./contract/state.js";
|
|
10
13
|
/** Settings a node runs with. `StellariumNode.start` fills these from its options, then the environment. */
|
|
11
14
|
export interface NodeConfig {
|
|
12
15
|
/** URL peers use to reach this node. Also its peer name. */
|
|
@@ -28,6 +31,25 @@ export interface NodeConfig {
|
|
|
28
31
|
costLimitMicroUsd: number;
|
|
29
32
|
/** Most this node will sign for in one remote inference call. */
|
|
30
33
|
inferenceCostLimitMicroUsd: number;
|
|
34
|
+
/** Most one call to a contract on another node may hold: its voucher's `maxAmount`. Defaults to `costLimitMicroUsd`. */
|
|
35
|
+
contractCostLimitMicroUsd?: number;
|
|
36
|
+
/** What another organization pays to deploy a contract to this node. 0 or unset is free. */
|
|
37
|
+
contractDeployFeeMicroUsd?: number;
|
|
38
|
+
/** Largest contract source this node accepts. Defaults to 64 KiB. */
|
|
39
|
+
maxContractSourceBytes?: number;
|
|
40
|
+
/** Most one contract's state may hold in one scope, its variables' encoded values together. Defaults to 64 KiB. */
|
|
41
|
+
maxContractStateBytes?: number;
|
|
42
|
+
/**
|
|
43
|
+
* This node's listed price per million script tokens, which its contracts are charged at. `StellariumNode.start`
|
|
44
|
+
* learns it from the operator.
|
|
45
|
+
*/
|
|
46
|
+
priceMicroUsdPerMillionTokens?: number;
|
|
47
|
+
/** SQLite file the node opens, when it has no `databaseUrl`. */
|
|
48
|
+
databasePath?: string;
|
|
49
|
+
/** Postgres URL. When set, the node uses Postgres instead of SQLite. */
|
|
50
|
+
databaseUrl?: string;
|
|
51
|
+
/** Postgres schema the node's tables live in. */
|
|
52
|
+
databaseSchema?: string;
|
|
31
53
|
}
|
|
32
54
|
export interface Service {
|
|
33
55
|
config: NodeConfig;
|
|
@@ -37,6 +59,9 @@ export interface Service {
|
|
|
37
59
|
resourceInvoker: CombinationResourceInvoker;
|
|
38
60
|
operatorClient: OperatorClient;
|
|
39
61
|
grantService: GrantService;
|
|
62
|
+
contractRepository: ContractRepository;
|
|
63
|
+
contractService: ContractService;
|
|
64
|
+
contractStateRepository: ContractStateRepository;
|
|
40
65
|
/** Verifies the run tokens `POST /v1/run` accepts. Set when the node has an operator URL. */
|
|
41
66
|
runTokenVerifier?: RunTokenVerifier;
|
|
42
67
|
}
|
|
@@ -46,5 +71,5 @@ export interface ServiceOptions {
|
|
|
46
71
|
/** Replaces the operator's published keys for verifying run tokens, in tests. */
|
|
47
72
|
runTokenKeys?: JWTVerifyGetKey;
|
|
48
73
|
}
|
|
49
|
-
export declare function createService(db:
|
|
74
|
+
export declare function createService(db: NodeDatabase, resources: StellariumResource[], config: NodeConfig, options?: ServiceOptions): Service;
|
|
50
75
|
//# sourceMappingURL=service.d.ts.map
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACvF,OAAO,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,OAAO,EAAE,KAAK,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,4GAA4G;AAC5G,MAAM,WAAW,UAAU;IACvB,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,iJAAiJ;IACjJ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iHAAiH;IACjH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iEAAiE;IACjE,0BAA0B,EAAE,MAAM,CAAC;IACnC,wHAAwH;IACxH,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,4FAA4F;IAC5F,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,qEAAqE;IACrE,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,mHAAmH;IACnH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,OAAO;IACpB,MAAM,EAAE,UAAU,CAAC;IACnB,cAAc,EAAE,cAAc,CAAC;IAC/B,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,eAAe,EAAE,0BAA0B,CAAC;IAC5C,cAAc,EAAE,cAAc,CAAC;IAC/B,YAAY,EAAE,YAAY,CAAC;IAC3B,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,eAAe,EAAE,eAAe,CAAC;IACjC,uBAAuB,EAAE,uBAAuB,CAAC;IACjD,6FAA6F;IAC7F,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACvC;AAED,MAAM,WAAW,cAAc;IAC3B,+EAA+E;IAC/E,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,iFAAiF;IACjF,YAAY,CAAC,EAAE,eAAe,CAAC;CAClC;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,YAAY,EAAE,SAAS,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAqC1I"}
|
package/dist/service.js
CHANGED
|
@@ -6,8 +6,17 @@ import { OperatorClient } from "./client/operatorClient.js";
|
|
|
6
6
|
import { RunTokenVerifier } from "./client/runTokens.js";
|
|
7
7
|
import { GrantRepository } from "./grant/repository.js";
|
|
8
8
|
import { GrantService } from "./grant/service.js";
|
|
9
|
+
import { ContractRepository } from "./contract/repository.js";
|
|
10
|
+
import { ContractService } from "./contract/service.js";
|
|
11
|
+
import { ContractStateRepository } from "./contract/state.js";
|
|
9
12
|
export function createService(db, resources, config, options = {}) {
|
|
10
|
-
const
|
|
13
|
+
const contractRepository = new ContractRepository(db);
|
|
14
|
+
// Read from the config on each use, since the node may learn its organization and price after it starts.
|
|
15
|
+
const resourceRepository = new ResourceRepository(db, resources, config.baseUrl, config.organizationId, {
|
|
16
|
+
repository: contractRepository,
|
|
17
|
+
price: () => config.priceMicroUsdPerMillionTokens ?? 0,
|
|
18
|
+
organizationId: () => config.organizationId,
|
|
19
|
+
});
|
|
11
20
|
const resourceResolver = new CombinationResourceResolver(resourceRepository);
|
|
12
21
|
const grantService = new GrantService(new GrantRepository(db), config, options.approveGrant);
|
|
13
22
|
const operatorClient = new OperatorClient(config.operatorUrl, config.publicKey, config.privateKey);
|
|
@@ -15,6 +24,9 @@ export function createService(db, resources, config, options = {}) {
|
|
|
15
24
|
operatorClient,
|
|
16
25
|
organizationId: config.organizationId,
|
|
17
26
|
});
|
|
27
|
+
const contractStateRepository = new ContractStateRepository(db);
|
|
28
|
+
const contractService = new ContractService(contractRepository, contractStateRepository, resourceRepository, resourceResolver, resourceInvoker, operatorClient, config);
|
|
29
|
+
resourceInvoker.contracts = { costLimitMicroUsd: config.contractCostLimitMicroUsd ?? config.costLimitMicroUsd, host: contractService };
|
|
18
30
|
return {
|
|
19
31
|
config,
|
|
20
32
|
peerRepository: new PeerRepository(db),
|
|
@@ -23,6 +35,9 @@ export function createService(db, resources, config, options = {}) {
|
|
|
23
35
|
resourceInvoker,
|
|
24
36
|
operatorClient,
|
|
25
37
|
grantService,
|
|
38
|
+
contractRepository,
|
|
39
|
+
contractService,
|
|
40
|
+
contractStateRepository,
|
|
26
41
|
runTokenVerifier: config.operatorUrl ? new RunTokenVerifier(config.operatorUrl, config.publicKey, options.runTokenKeys) : undefined,
|
|
27
42
|
};
|
|
28
43
|
}
|
package/dist/startup.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { Service } from "./service.js";
|
|
|
4
4
|
export declare const STARTUP_REQUEST_TIMEOUT_MS = 10000;
|
|
5
5
|
/** Peers and resources requested per page from the starting peer. It may return fewer. */
|
|
6
6
|
export declare const STARTUP_PAGE_SIZE = 100;
|
|
7
|
+
/** Sends this node's resources, signed, to every peer it knows. Run at startup, and when a contract is deployed or deleted. */
|
|
8
|
+
export declare function announceResources(log: FastifyBaseLogger, service: Service): Promise<void>;
|
|
7
9
|
export declare class StartupService {
|
|
8
10
|
private readonly log;
|
|
9
11
|
private readonly service;
|
|
@@ -23,8 +25,6 @@ export declare class StartupService {
|
|
|
23
25
|
private isSelf;
|
|
24
26
|
populatePeers(): Promise<void>;
|
|
25
27
|
populateResources(): Promise<void>;
|
|
26
|
-
/** PUT to every peer, logging failures instead of throwing so one bad peer doesn't stop the rest. */
|
|
27
|
-
private putToPeers;
|
|
28
28
|
broadcastSelf(): Promise<void>;
|
|
29
29
|
}
|
|
30
30
|
//# sourceMappingURL=startup.d.ts.map
|
package/dist/startup.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"startup.d.ts","sourceRoot":"","sources":["../src/startup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAI5C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,4CAA4C;AAC5C,eAAO,MAAM,0BAA0B,QAAS,CAAC;AAEjD,0FAA0F;AAC1F,eAAO,MAAM,iBAAiB,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"startup.d.ts","sourceRoot":"","sources":["../src/startup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAI5C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,4CAA4C;AAC5C,eAAO,MAAM,0BAA0B,QAAS,CAAC;AAEjD,0FAA0F;AAC1F,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAiCrC,+HAA+H;AAC/H,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB/F;AAED,qBAAa,cAAc;IAEnB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAN/B,YACqB,GAAG,EAAE,iBAAiB,EACtB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EACnC;IAGE,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAUlC;IAED,iFAAiF;YACnE,mBAAmB;IAQjC;;;OAGG;YACW,2BAA2B;IAsBzC,OAAO,CAAC,MAAM;IAIR,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAkBnC;IAEK,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAWvC;IAEK,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAenC;CACJ"}
|
package/dist/startup.js
CHANGED
|
@@ -7,6 +7,47 @@ export const STARTUP_REQUEST_TIMEOUT_MS = 10_000;
|
|
|
7
7
|
export const STARTUP_PAGE_SIZE = 100;
|
|
8
8
|
// 10 minutes
|
|
9
9
|
const SIGNATURE_LIFETIME_MS = 1000 * 60 * 10;
|
|
10
|
+
/** PUT to every peer, logging failures instead of throwing so one bad peer doesn't stop the rest. */
|
|
11
|
+
async function putToPeers(log, peers, path, body, description) {
|
|
12
|
+
for (const peer of peers) {
|
|
13
|
+
try {
|
|
14
|
+
const response = await fetch(`${peer.baseUrl}/v1${path}`, {
|
|
15
|
+
method: 'PUT',
|
|
16
|
+
headers: {
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
},
|
|
19
|
+
body: JSON.stringify(body),
|
|
20
|
+
signal: AbortSignal.timeout(STARTUP_REQUEST_TIMEOUT_MS),
|
|
21
|
+
});
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
log.warn(`Failed to broadcast ${description} to node ${peer.baseUrl}: ${response.status} ${await response.text()}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
log.warn(error, `Failed to broadcast ${description} to node ${peer.baseUrl}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** The peers this node knows, other than itself. */
|
|
32
|
+
async function otherPeers(service) {
|
|
33
|
+
const { baseUrl, publicKey } = service.config;
|
|
34
|
+
return (await service.peerRepository.getPeerList()).filter(peer => peer.publicKey !== publicKey && peer.baseUrl !== baseUrl);
|
|
35
|
+
}
|
|
36
|
+
/** Sends this node's resources, signed, to every peer it knows. Run at startup, and when a contract is deployed or deleted. */
|
|
37
|
+
export async function announceResources(log, service) {
|
|
38
|
+
log.info(`Broadcasting resources to peers`);
|
|
39
|
+
const peers = await otherPeers(service);
|
|
40
|
+
const resources = await service.resourceRepository.getInternalResources();
|
|
41
|
+
const expiry = new Date(Date.now() + SIGNATURE_LIFETIME_MS).toISOString();
|
|
42
|
+
const signature = signData(resourceAnnouncementPayload(resources, expiry), service.config.privateKey);
|
|
43
|
+
await putToPeers(log, peers, '/all-resources', {
|
|
44
|
+
publicKey: service.config.publicKey,
|
|
45
|
+
resources,
|
|
46
|
+
expiry,
|
|
47
|
+
signature,
|
|
48
|
+
}, 'resources');
|
|
49
|
+
log.info(`Broadcasted ${resources.length} resources to ${peers.length} peers`);
|
|
50
|
+
}
|
|
10
51
|
export class StartupService {
|
|
11
52
|
log;
|
|
12
53
|
service;
|
|
@@ -91,27 +132,6 @@ export class StartupService {
|
|
|
91
132
|
});
|
|
92
133
|
this.log.info(`Learned up to ${count} resources`);
|
|
93
134
|
}
|
|
94
|
-
/** PUT to every peer, logging failures instead of throwing so one bad peer doesn't stop the rest. */
|
|
95
|
-
async putToPeers(peers, path, body, description) {
|
|
96
|
-
for (const peer of peers) {
|
|
97
|
-
try {
|
|
98
|
-
const response = await fetch(`${peer.baseUrl}/v1${path}`, {
|
|
99
|
-
method: 'PUT',
|
|
100
|
-
headers: {
|
|
101
|
-
'Content-Type': 'application/json',
|
|
102
|
-
},
|
|
103
|
-
body: JSON.stringify(body),
|
|
104
|
-
signal: AbortSignal.timeout(STARTUP_REQUEST_TIMEOUT_MS),
|
|
105
|
-
});
|
|
106
|
-
if (!response.ok) {
|
|
107
|
-
this.log.warn(`Failed to broadcast ${description} to node ${peer.baseUrl}: ${response.status} ${await response.text()}`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
catch (error) {
|
|
111
|
-
this.log.warn(error, `Failed to broadcast ${description} to node ${peer.baseUrl}`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
135
|
async broadcastSelf() {
|
|
116
136
|
this.log.info(`Broadcasting self to peers`);
|
|
117
137
|
// Let the peers know about this node.
|
|
@@ -119,19 +139,8 @@ export class StartupService {
|
|
|
119
139
|
const self = { baseUrl: this.baseUrl, name: this.baseUrl, publicKey: this.publicKey };
|
|
120
140
|
const peerSignatureExpiry = new Date(Date.now() + SIGNATURE_LIFETIME_MS).toISOString();
|
|
121
141
|
const peerSignature = signData(peerAnnouncementPayload(self, peerSignatureExpiry), this.privateKey);
|
|
122
|
-
await this.
|
|
142
|
+
await putToPeers(this.log, peers, '/peers', { ...self, signature: peerSignature, expiry: peerSignatureExpiry }, 'self');
|
|
123
143
|
this.log.info(`Broadcasted self to ${peers.length} peers`);
|
|
124
|
-
this.log.
|
|
125
|
-
// Let the peers know about the resources available on this node.
|
|
126
|
-
const resources = await this.service.resourceRepository.getInternalResources();
|
|
127
|
-
const resourceSignatureExpiry = new Date(Date.now() + SIGNATURE_LIFETIME_MS).toISOString();
|
|
128
|
-
const resourceSignature = signData(resourceAnnouncementPayload(resources, resourceSignatureExpiry), this.privateKey);
|
|
129
|
-
await this.putToPeers(peers, '/all-resources', {
|
|
130
|
-
publicKey: this.publicKey,
|
|
131
|
-
resources,
|
|
132
|
-
expiry: resourceSignatureExpiry,
|
|
133
|
-
signature: resourceSignature,
|
|
134
|
-
}, 'resources');
|
|
135
|
-
this.log.info(`Broadcasted ${resources.length} resources to ${peers.length} peers`);
|
|
144
|
+
await announceResources(this.log, this.service);
|
|
136
145
|
}
|
|
137
146
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repository.test.d.ts","sourceRoot":"","sources":["../../../src/tests/contract/repository.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
2
|
+
import { ContractRepository } from "../../contract/repository.js";
|
|
3
|
+
import { DIALECTS, openTestDatabase } from "../helpers.js";
|
|
4
|
+
function contract(version, overrides = {}) {
|
|
5
|
+
return {
|
|
6
|
+
path: "com.example",
|
|
7
|
+
name: "Sum",
|
|
8
|
+
version,
|
|
9
|
+
source: `contract com.example.Sum@${version}(a: int32): int32 { return a }`,
|
|
10
|
+
parameters: [{ name: "a", type: "int32" }],
|
|
11
|
+
returnType: "int32",
|
|
12
|
+
ownerOrganizationId: "org_a",
|
|
13
|
+
description: null,
|
|
14
|
+
...overrides,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
describe.each(DIALECTS)("ContractRepository (%s)", (dialect) => {
|
|
18
|
+
let db;
|
|
19
|
+
let repository;
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
db = await openTestDatabase(dialect);
|
|
22
|
+
repository = new ContractRepository(db);
|
|
23
|
+
});
|
|
24
|
+
afterEach(async () => {
|
|
25
|
+
await db.close();
|
|
26
|
+
});
|
|
27
|
+
test("finds the latest version by comparing versions numerically", async () => {
|
|
28
|
+
for (const version of ["1", "1.10", "1.9"]) {
|
|
29
|
+
expect(await repository.save(contract(version))).toEqual({ ok: true });
|
|
30
|
+
}
|
|
31
|
+
expect(await repository.latest("com.example", "Sum")).toMatchObject({ version: "1.10", parameters: [{ name: "a", type: "int32" }], returnType: "int32" });
|
|
32
|
+
expect(await repository.latest("com.example", "Other")).toBeUndefined();
|
|
33
|
+
expect((await repository.allLatest()).map(c => c.version)).toEqual(["1.10"]);
|
|
34
|
+
});
|
|
35
|
+
test("keeps versions immutable", async () => {
|
|
36
|
+
await repository.save(contract("1"));
|
|
37
|
+
expect(await repository.save(contract("1", { source: "changed" }))).toEqual({ ok: false, refused: "version_exists" });
|
|
38
|
+
expect((await repository.latest("com.example", "Sum"))?.source).toContain("@1(");
|
|
39
|
+
});
|
|
40
|
+
test("gives every version to the first deployer's organization, and lets the admin add versions", async () => {
|
|
41
|
+
await repository.save(contract("1"));
|
|
42
|
+
expect(await repository.save(contract("2", { ownerOrganizationId: "org_b" }))).toEqual({ ok: false, refused: "owned_by_another" });
|
|
43
|
+
expect(await repository.save(contract("2", { ownerOrganizationId: null }))).toEqual({ ok: true });
|
|
44
|
+
expect(await repository.owner("com.example", "Sum")).toBe("org_a");
|
|
45
|
+
expect((await repository.latest("com.example", "Sum"))?.ownerOrganizationId).toBe("org_a");
|
|
46
|
+
});
|
|
47
|
+
test("an admin's contract belongs to no organization", async () => {
|
|
48
|
+
await repository.save(contract("1", { ownerOrganizationId: null }));
|
|
49
|
+
expect(await repository.owner("com.example", "Sum")).toBeNull();
|
|
50
|
+
expect(await repository.save(contract("2", { ownerOrganizationId: "org_b" }))).toEqual({ ok: false, refused: "owned_by_another" });
|
|
51
|
+
});
|
|
52
|
+
test("deletes one version or all of them", async () => {
|
|
53
|
+
await repository.save(contract("1"));
|
|
54
|
+
await repository.save(contract("2"));
|
|
55
|
+
expect(await repository.delete("com.example", "Sum", "2")).toBe(true);
|
|
56
|
+
expect((await repository.latest("com.example", "Sum"))?.version).toBe("1");
|
|
57
|
+
expect(await repository.delete("com.example", "Sum")).toBe(true);
|
|
58
|
+
expect(await repository.owner("com.example", "Sum")).toBeUndefined();
|
|
59
|
+
expect(await repository.delete("com.example", "Sum")).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
test("lists every version a page at a time", async () => {
|
|
62
|
+
await repository.save(contract("1"));
|
|
63
|
+
await repository.save(contract("2"));
|
|
64
|
+
await repository.save(contract("1", { name: "Product" }));
|
|
65
|
+
expect((await repository.list(0, 2)).map(c => `${c.name}@${c.version}`)).toEqual(["Product@1", "Sum@1"]);
|
|
66
|
+
expect((await repository.list(2, 2)).map(c => `${c.name}@${c.version}`)).toEqual(["Sum@2"]);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.test.d.ts","sourceRoot":"","sources":["../../../src/tests/contract/routes.test.ts"],"names":[],"mappings":""}
|