@nosana/node 1.1.27 → 1.1.29-rc
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/package.json +1 -1
- package/dist/src/NodeManager/node/market/marketHandler.d.ts +1 -1
- package/dist/src/NodeManager/node/market/marketHandler.js +21 -2
- package/dist/src/NodeManager/node/resource/volume/volumeManager.d.ts +10 -0
- package/dist/src/NodeManager/node/resource/volume/volumeManager.js +50 -4
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/package.json
CHANGED
|
@@ -15,7 +15,7 @@ export declare class MarketHandler {
|
|
|
15
15
|
clear(): void;
|
|
16
16
|
isInMarket(): boolean;
|
|
17
17
|
setInMarket(): void;
|
|
18
|
-
request(requestedMarket?: string, session?: string): Promise<Market>;
|
|
18
|
+
request(requestedMarket?: string, session?: string, benchmarkRetries?: number): Promise<Market>;
|
|
19
19
|
private logFeedbackReport;
|
|
20
20
|
private logLatestMeasurements;
|
|
21
21
|
check(market: string): Promise<Market>;
|
|
@@ -6,6 +6,8 @@ import { NodeBannedError } from "../../errors/NodeBannedError.js";
|
|
|
6
6
|
import { NodeNotQualifiedError } from "../../errors/NodeNotQualifiedError.js";
|
|
7
7
|
import { MarketAccessHandler } from "./marketAccess.js";
|
|
8
8
|
import { sleep } from "../../utils/utils.js";
|
|
9
|
+
const RETEST_BUFFER_S = 60;
|
|
10
|
+
const MAX_BENCHMARK_RETRIES = 3;
|
|
9
11
|
export class MarketHandler {
|
|
10
12
|
constructor(sdk, provider, repository) {
|
|
11
13
|
this.sdk = sdk;
|
|
@@ -24,7 +26,7 @@ export class MarketHandler {
|
|
|
24
26
|
setInMarket() {
|
|
25
27
|
this.inMarket = true;
|
|
26
28
|
}
|
|
27
|
-
async request(requestedMarket, session) {
|
|
29
|
+
async request(requestedMarket, session, benchmarkRetries = 0) {
|
|
28
30
|
const result = await HostManager.requestMarket(requestedMarket, session);
|
|
29
31
|
// Not registered - caller will handle registration and retry
|
|
30
32
|
if (result.notRegistered) {
|
|
@@ -40,7 +42,7 @@ export class MarketHandler {
|
|
|
40
42
|
await benchmark.run();
|
|
41
43
|
// Re-poll within the same request-market cycle, echoing the session the
|
|
42
44
|
// host issued so lifecycle metrics aren't re-requested on the retry.
|
|
43
|
-
return await this.request(requestedMarket, result.session ?? session);
|
|
45
|
+
return await this.request(requestedMarket, result.session ?? session, benchmarkRetries);
|
|
44
46
|
}
|
|
45
47
|
// Sign and send SFT mint/burn transaction if provided
|
|
46
48
|
if (result.market?.sftTx) {
|
|
@@ -53,6 +55,14 @@ export class MarketHandler {
|
|
|
53
55
|
this.logFeedbackReport(result.feedbackReport);
|
|
54
56
|
}
|
|
55
57
|
if (!result.market?.address) {
|
|
58
|
+
const nextTestAt = parseFutureDate(result.nextTestAt);
|
|
59
|
+
if (nextTestAt && benchmarkRetries < MAX_BENCHMARK_RETRIES) {
|
|
60
|
+
const waitSeconds = Math.ceil((nextTestAt.getTime() - Date.now()) / 1000) +
|
|
61
|
+
RETEST_BUFFER_S;
|
|
62
|
+
console.log(chalk.yellow(`Node does not qualify for a market yet. Next eligible retest at ${nextTestAt.toISOString()}, retrying in ~${Math.ceil(waitSeconds / 60)} minutes.`));
|
|
63
|
+
await sleep(waitSeconds);
|
|
64
|
+
return await this.request(requestedMarket, result.session ?? session, benchmarkRetries + 1);
|
|
65
|
+
}
|
|
56
66
|
throw new NodeNotQualifiedError(result.nextTestAt);
|
|
57
67
|
}
|
|
58
68
|
const onchainMarket = await this.sdk.jobs.getMarket(result.market.address);
|
|
@@ -263,6 +273,15 @@ export class MarketHandler {
|
|
|
263
273
|
this.clear();
|
|
264
274
|
}
|
|
265
275
|
}
|
|
276
|
+
function parseFutureDate(value) {
|
|
277
|
+
if (!value)
|
|
278
|
+
return undefined;
|
|
279
|
+
const date = new Date(value);
|
|
280
|
+
if (Number.isNaN(date.getTime()) || date.getTime() <= Date.now()) {
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
return date;
|
|
284
|
+
}
|
|
266
285
|
function flattenMetrics(obj, prefix = "") {
|
|
267
286
|
const result = [];
|
|
268
287
|
for (const [key, value] of Object.entries(obj)) {
|
|
@@ -2,6 +2,16 @@ import { Resource } from '@nosana/sdk/dist/types/resources.js';
|
|
|
2
2
|
import { RequiredResource } from '@nosana/sdk';
|
|
3
3
|
import { ContainerOrchestrationInterface } from '../../../provider/containerOrchestration/interface.js';
|
|
4
4
|
import { NodeRepository } from '../../../repository/NodeRepository.js';
|
|
5
|
+
/**
|
|
6
|
+
* The reason the resource manager gave for failing, if it gave one.
|
|
7
|
+
*
|
|
8
|
+
* It reports failures as a JSON line on stderr — `{"event":"error","message":
|
|
9
|
+
* "..."}` — from every service it implements, then exits non-zero. Read the
|
|
10
|
+
* last such line, since a run may log several before giving up.
|
|
11
|
+
*/
|
|
12
|
+
export declare function findReportedError(logs: {
|
|
13
|
+
log?: string;
|
|
14
|
+
}[]): string | undefined;
|
|
5
15
|
export declare class VolumeManager {
|
|
6
16
|
private containerOrchestration;
|
|
7
17
|
private repository;
|
|
@@ -11,6 +11,43 @@ import { createHFArgs } from '../helpers/createHFArgs.js';
|
|
|
11
11
|
import { createS3Args } from '../helpers/createS3Args.js';
|
|
12
12
|
import { createOllamaArgs } from '../helpers/createOllamaArgs.js';
|
|
13
13
|
import { liveAbortSignal } from '../../../utils/liveAbortSignal.js';
|
|
14
|
+
/**
|
|
15
|
+
* Renders the runtime state that says why the download stopped, for inclusion in
|
|
16
|
+
* the error message. A process that is killed rather than failing logs nothing,
|
|
17
|
+
* so this is the only evidence of an OOM.
|
|
18
|
+
*
|
|
19
|
+
* Optional because the caller inspects on a best-effort basis; the fields
|
|
20
|
+
* themselves are always present when it succeeds.
|
|
21
|
+
*/
|
|
22
|
+
function describeContainerState(state) {
|
|
23
|
+
if (!state)
|
|
24
|
+
return '';
|
|
25
|
+
const parts = [
|
|
26
|
+
`exitCode: ${state.ExitCode}`,
|
|
27
|
+
`OOMKilled: ${state.OOMKilled}`
|
|
28
|
+
];
|
|
29
|
+
return ` [${parts.join(', ')}]`;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The reason the resource manager gave for failing, if it gave one.
|
|
33
|
+
*
|
|
34
|
+
* It reports failures as a JSON line on stderr — `{"event":"error","message":
|
|
35
|
+
* "..."}` — from every service it implements, then exits non-zero. Read the
|
|
36
|
+
* last such line, since a run may log several before giving up.
|
|
37
|
+
*/
|
|
38
|
+
export function findReportedError(logs) {
|
|
39
|
+
for (const log of logs.reverse()) {
|
|
40
|
+
if (log.log?.includes('{"event":"error"')) {
|
|
41
|
+
try {
|
|
42
|
+
const parsed = JSON.parse(log.log.slice(log.log.indexOf('{')));
|
|
43
|
+
if (typeof parsed.message === 'string')
|
|
44
|
+
return parsed.message;
|
|
45
|
+
}
|
|
46
|
+
catch { }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
14
51
|
export class VolumeManager {
|
|
15
52
|
constructor(containerOrchestration, repository) {
|
|
16
53
|
this.containerOrchestration = containerOrchestration;
|
|
@@ -149,12 +186,21 @@ export class VolumeManager {
|
|
|
149
186
|
timestamps: true,
|
|
150
187
|
});
|
|
151
188
|
const { logs } = extractLogsAndResultsFromLogBuffer(errrorBuffer, undefined);
|
|
189
|
+
// Read the state before removing it: a killed process logs nothing, so
|
|
190
|
+
// the runtime's own record is the only account of why.
|
|
191
|
+
const state = await container
|
|
192
|
+
.inspect()
|
|
193
|
+
.then(({ State }) => State)
|
|
194
|
+
.catch(() => undefined);
|
|
152
195
|
await container.remove({ force: true });
|
|
153
196
|
await this.containerOrchestration.deleteVolume(volume);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
197
|
+
// A non-zero exit is a failed download whether or not the container
|
|
198
|
+
// managed to report a reason, so this must throw either way: returning
|
|
199
|
+
// normally here reports success for a volume already deleted, and the run
|
|
200
|
+
// then fails later with the cause lost.
|
|
201
|
+
const reason = findReportedError(logs) ??
|
|
202
|
+
`resource download failed (exit code ${StatusCode})`;
|
|
203
|
+
throw new Error(`${reason}${describeContainerState(state)}`);
|
|
158
204
|
}
|
|
159
205
|
this.progressBarReporter.stop(`${syncing ? 'Synced' : 'Downloaded'} resource ${name} completed`);
|
|
160
206
|
await this.containerOrchestration.stopAndDeleteContainer(container.id);
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nosana/node",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.29-rc",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@nosana/node",
|
|
9
|
-
"version": "1.1.
|
|
9
|
+
"version": "1.1.29-rc",
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@coral-xyz/anchor": "^0.28.1-beta.1",
|