@antseed/node 0.2.152 → 0.2.153
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/buyer-identity-context.d.ts +2 -0
- package/dist/buyer-identity-context.d.ts.map +1 -1
- package/dist/buyer-identity-context.js +1 -0
- package/dist/buyer-identity-context.js.map +1 -1
- package/dist/discovery/announcer.d.ts +2 -0
- package/dist/discovery/announcer.d.ts.map +1 -1
- package/dist/discovery/announcer.js +11 -9
- package/dist/discovery/announcer.js.map +1 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/metering/index.d.ts +1 -1
- package/dist/metering/index.d.ts.map +1 -1
- package/dist/metering/index.js.map +1 -1
- package/dist/metering/storage.d.ts +66 -6
- package/dist/metering/storage.d.ts.map +1 -1
- package/dist/metering/storage.js +112 -12
- package/dist/metering/storage.js.map +1 -1
- package/dist/node.d.ts +30 -0
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +93 -2
- package/dist/node.js.map +1 -1
- package/dist/payments/evm/seller-pools-client.d.ts +9 -0
- package/dist/payments/evm/seller-pools-client.d.ts.map +1 -1
- package/dist/payments/evm/seller-pools-client.js +23 -0
- package/dist/payments/evm/seller-pools-client.js.map +1 -1
- package/dist/payments/free-tier-delegation.d.ts +65 -0
- package/dist/payments/free-tier-delegation.d.ts.map +1 -0
- package/dist/payments/free-tier-delegation.js +145 -0
- package/dist/payments/free-tier-delegation.js.map +1 -0
- package/dist/payments/free-tier-program.d.ts +97 -0
- package/dist/payments/free-tier-program.d.ts.map +1 -0
- package/dist/payments/free-tier-program.js +267 -0
- package/dist/payments/free-tier-program.js.map +1 -0
- package/dist/payments/free-tier-stake-resolver.d.ts +79 -0
- package/dist/payments/free-tier-stake-resolver.d.ts.map +1 -0
- package/dist/payments/free-tier-stake-resolver.js +152 -0
- package/dist/payments/free-tier-stake-resolver.js.map +1 -0
- package/dist/payments/free-tier-stake.d.ts +101 -0
- package/dist/payments/free-tier-stake.d.ts.map +1 -0
- package/dist/payments/free-tier-stake.js +116 -0
- package/dist/payments/free-tier-stake.js.map +1 -0
- package/dist/payments/index.d.ts +7 -1
- package/dist/payments/index.d.ts.map +1 -1
- package/dist/payments/index.js +3 -0
- package/dist/payments/index.js.map +1 -1
- package/dist/payments/seller-free-tier-limiter.d.ts +124 -6
- package/dist/payments/seller-free-tier-limiter.d.ts.map +1 -1
- package/dist/payments/seller-free-tier-limiter.js +342 -32
- package/dist/payments/seller-free-tier-limiter.js.map +1 -1
- package/dist/payments/seller-free-usage-manager.d.ts.map +1 -1
- package/dist/payments/seller-free-usage-manager.js +5 -1
- package/dist/payments/seller-free-usage-manager.js.map +1 -1
- package/dist/seller-request-handler.d.ts.map +1 -1
- package/dist/seller-request-handler.js +105 -19
- package/dist/seller-request-handler.js.map +1 -1
- package/dist/storage/migrations/metering/004_add_free_tier_staked_usage.d.ts +3 -0
- package/dist/storage/migrations/metering/004_add_free_tier_staked_usage.d.ts.map +1 -0
- package/dist/storage/migrations/metering/004_add_free_tier_staked_usage.js +21 -0
- package/dist/storage/migrations/metering/004_add_free_tier_staked_usage.js.map +1 -0
- package/dist/storage/migrations/metering/005_add_free_tier_scope.d.ts +8 -0
- package/dist/storage/migrations/metering/005_add_free_tier_scope.d.ts.map +1 -0
- package/dist/storage/migrations/metering/005_add_free_tier_scope.js +23 -0
- package/dist/storage/migrations/metering/005_add_free_tier_scope.js.map +1 -0
- package/dist/storage/migrations/metering/index.d.ts.map +1 -1
- package/dist/storage/migrations/metering/index.js +3 -1
- package/dist/storage/migrations/metering/index.js.map +1 -1
- package/dist/utils/file-lock.d.ts +12 -0
- package/dist/utils/file-lock.d.ts.map +1 -0
- package/dist/utils/file-lock.js +58 -0
- package/dist/utils/file-lock.js.map +1 -0
- package/package.json +11 -3
|
@@ -1,22 +1,57 @@
|
|
|
1
1
|
import { type FreeTierConsumption, type MeteringStorage } from '../metering/storage.js';
|
|
2
|
-
|
|
2
|
+
import { type FreeTierProgram, type FreeTierStakeTier } from './free-tier-program.js';
|
|
3
|
+
import type { FreeTierStakeResolver, FreeTierStakeStanding } from './free-tier-stake-resolver.js';
|
|
4
|
+
/** One free-tier program: base limits and the staked tiers above them. */
|
|
5
|
+
export interface SellerFreeTierProgramConfig {
|
|
3
6
|
/** Maximum zero-priced requests accepted from one buyer address per window. */
|
|
4
7
|
maxRequestsPerAddress?: number;
|
|
5
8
|
/** Maximum zero-priced requests accepted from one remote IP per window. */
|
|
6
9
|
maxRequestsPerIp?: number;
|
|
7
10
|
/** Sliding-window duration in milliseconds. Default: 24 hours. */
|
|
8
11
|
windowMs?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Larger allowances for buyers staked on this seller, by staking power.
|
|
14
|
+
* Default: `DEFAULT_FREE_TIER_STAKE_TIERS`. `[]` turns staked tiers off.
|
|
15
|
+
*/
|
|
16
|
+
stakeTiers?: FreeTierStakeTier[];
|
|
17
|
+
/**
|
|
18
|
+
* Serve only buyers whose stake reaches a tier. The base limits are still
|
|
19
|
+
* required: they are the allowance the tier multipliers apply to.
|
|
20
|
+
*/
|
|
21
|
+
stakersOnly?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface SellerFreeTierConfig extends SellerFreeTierProgramConfig {
|
|
24
|
+
/**
|
|
25
|
+
* Programs for individual services, keyed by announced service id. A listed
|
|
26
|
+
* service uses only its own program and its own counters; the other
|
|
27
|
+
* zero-priced services use the seller-wide program above, or are not limited
|
|
28
|
+
* when there is none. On a priced service the program is a free allowance:
|
|
29
|
+
* requests it covers are served free, and the rest are paid as usual.
|
|
30
|
+
*/
|
|
31
|
+
services?: Record<string, SellerFreeTierProgramConfig>;
|
|
9
32
|
}
|
|
10
33
|
export interface FreeTierDecision extends FreeTierConsumption {
|
|
11
34
|
buyerAddress: string;
|
|
12
35
|
/** Normalized remote IP the decision was keyed on, or null when unknown. */
|
|
13
36
|
remoteIp: string | null;
|
|
37
|
+
/** Staked standing the decision used, or null for the base limits. */
|
|
38
|
+
standing: FreeTierStakeStanding | null;
|
|
39
|
+
/** Tier the standing earned under this program, or null for the base limits. */
|
|
40
|
+
tier: FreeTierStakeTier | null;
|
|
41
|
+
/** Limit that applied to the binding counter; null when unlimited. */
|
|
42
|
+
limit: number | null;
|
|
43
|
+
/** The program the request was decided under, as advertised. */
|
|
44
|
+
program: FreeTierProgram;
|
|
14
45
|
}
|
|
15
46
|
export interface FreeTierConsumeInput {
|
|
16
47
|
buyerPeerId: string;
|
|
17
48
|
service: string;
|
|
18
49
|
/** Raw socket remote address of the buyer connection, if known. */
|
|
19
50
|
remoteIp?: string | null;
|
|
51
|
+
/** Staked standing from `resolveStanding`; omitted or null applies the base limits. */
|
|
52
|
+
standing?: FreeTierStakeStanding | null;
|
|
53
|
+
/** The service is priced: only its own program applies, never the seller-wide one. */
|
|
54
|
+
paid?: boolean;
|
|
20
55
|
nowMs?: number;
|
|
21
56
|
}
|
|
22
57
|
export declare const DEFAULT_FREE_TIER_WINDOW_MS: number;
|
|
@@ -27,17 +62,100 @@ export declare const DEFAULT_FREE_TIER_WINDOW_MS: number;
|
|
|
27
62
|
*/
|
|
28
63
|
export declare function normalizeRemoteIp(remoteIp: string | null | undefined): string | null;
|
|
29
64
|
export declare class SellerFreeTierLimiter {
|
|
30
|
-
readonly
|
|
31
|
-
readonly
|
|
32
|
-
|
|
65
|
+
private readonly _default;
|
|
66
|
+
private readonly _services;
|
|
67
|
+
/** Longest window of any program: usage younger than this is never pruned. */
|
|
68
|
+
private readonly _retentionMs;
|
|
33
69
|
private readonly _storage;
|
|
34
70
|
private readonly _inMemoryUsage;
|
|
71
|
+
private _stakeResolver;
|
|
72
|
+
private _chainId;
|
|
73
|
+
/** Last verified delegations per buyer address, in insertion order for eviction. */
|
|
74
|
+
private readonly _delegations;
|
|
75
|
+
/** Priced services, whose allowances never admit a free-usage channel. */
|
|
76
|
+
private _paidServices;
|
|
77
|
+
/** Zero-priced services this seller offers, or null when not told: they decide channel admission. */
|
|
78
|
+
private _freeServices;
|
|
35
79
|
constructor(config: SellerFreeTierConfig, storage?: MeteringStorage | null);
|
|
80
|
+
/**
|
|
81
|
+
* @deprecated The seller-wide program's per-address limit, or null when it
|
|
82
|
+
* has none (including a limiter with only per-service programs). Use
|
|
83
|
+
* `program(service)` for the limits a service is served under.
|
|
84
|
+
*/
|
|
85
|
+
get maxRequestsPerAddress(): number | null;
|
|
86
|
+
/** @deprecated The seller-wide program's per-IP limit, or null. Use `program(service)`. */
|
|
87
|
+
get maxRequestsPerIp(): number | null;
|
|
88
|
+
/** @deprecated The seller-wide program's window, else the default window. Use `program(service)`. */
|
|
89
|
+
get windowMs(): number;
|
|
90
|
+
/**
|
|
91
|
+
* Enable staked tiers. Without a resolver every buyer gets the base limits.
|
|
92
|
+
* `chainId` is the EIP-712 domain delegations are verified against.
|
|
93
|
+
*/
|
|
94
|
+
setStakeResolver(resolver: FreeTierStakeResolver | null, chainId?: number): void;
|
|
95
|
+
/** Whether any program has staked tiers, so stake lookups are worth enabling. */
|
|
96
|
+
get hasStakeTiers(): boolean;
|
|
97
|
+
/** Whether staked tiers are live: configured and backed by on-chain reads. */
|
|
98
|
+
get stakeTiersActive(): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Whether a program applies to `service`: its own, or for a zero-priced
|
|
101
|
+
* service the seller-wide one. A priced service has one only when configured.
|
|
102
|
+
*/
|
|
103
|
+
appliesTo(service: string, options?: {
|
|
104
|
+
paid?: boolean;
|
|
105
|
+
}): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Services that are priced. Their programs are free allowances on paid
|
|
108
|
+
* models, served without a free-usage channel, so channel admission ignores them.
|
|
109
|
+
*/
|
|
110
|
+
setPaidServices(services: Iterable<string>): void;
|
|
111
|
+
/** The zero-priced services this seller offers, so channel admission checks exactly their programs. */
|
|
112
|
+
setFreeServices(services: Iterable<string>): void;
|
|
113
|
+
/**
|
|
114
|
+
* The program `service` is served under, or the seller-wide program when
|
|
115
|
+
* `service` is omitted; tiers only when they are enforced. Null when none applies.
|
|
116
|
+
*/
|
|
117
|
+
program(service?: string, options?: {
|
|
118
|
+
paid?: boolean;
|
|
119
|
+
}): FreeTierProgram | null;
|
|
120
|
+
/**
|
|
121
|
+
* Peer-metadata capability strings advertising every program: the
|
|
122
|
+
* seller-wide one first, then services in id order. A program that would
|
|
123
|
+
* exceed `budget` strings is skipped whole, never advertised in part.
|
|
124
|
+
*/
|
|
125
|
+
capabilities(budget?: number): string[];
|
|
126
|
+
/**
|
|
127
|
+
* Staked standing for this buyer, or null for the base limits. Never throws.
|
|
128
|
+
*
|
|
129
|
+
* `delegationsHeader` is the buyer's `x-antseed-free-tier-delegations`
|
|
130
|
+
* value (from a request or a free usage open), or '' when a request carried
|
|
131
|
+
* none. Verified stakers are remembered for the buyer, so an open from an
|
|
132
|
+
* older buyer, which passes undefined, sees the standing of its requests.
|
|
133
|
+
*/
|
|
134
|
+
resolveStanding(buyerPeerId: string, delegationsHeader?: string): Promise<FreeTierStakeStanding | null>;
|
|
135
|
+
private _rememberDelegations;
|
|
136
|
+
/**
|
|
137
|
+
* One line per program: the seller-wide one for free models, then each
|
|
138
|
+
* service's own. A priced service's line is its free allowance before its
|
|
139
|
+
* price applies, as set by `setPaidServices`.
|
|
140
|
+
*/
|
|
141
|
+
describePrograms(): string[];
|
|
36
142
|
describe(): string;
|
|
143
|
+
/** Consume one request for `input.service`. Callers check `appliesTo` first. */
|
|
37
144
|
consume(input: FreeTierConsumeInput): FreeTierDecision;
|
|
38
|
-
/**
|
|
39
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Check channel-open admission without consuming an inference request.
|
|
147
|
+
* The channel serves every free service, so it is admitted when the program
|
|
148
|
+
* of some offered free service would still serve the buyer, or when one of
|
|
149
|
+
* them is not limited at all. Otherwise the first refusal (the seller-wide
|
|
150
|
+
* one when it applies) is reported. Without the offered services (see
|
|
151
|
+
* `setFreeServices`), every program of a zero-priced service is considered,
|
|
152
|
+
* and no seller-wide program means some free service is unlimited.
|
|
153
|
+
*/
|
|
154
|
+
check(input: Omit<FreeTierConsumeInput, 'service' | 'paid'>): FreeTierDecision | null;
|
|
155
|
+
private _policies;
|
|
156
|
+
private _policyFor;
|
|
40
157
|
private _evaluate;
|
|
158
|
+
private _bindingLimit;
|
|
41
159
|
private _evaluateInMemory;
|
|
42
160
|
private _pruneExpiredBuckets;
|
|
43
161
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seller-free-tier-limiter.d.ts","sourceRoot":"","sources":["../../src/payments/seller-free-tier-limiter.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,mBAAmB,EAExB,KAAK,eAAe,EACrB,MAAM,wBAAwB,CAAC;AAGhC,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"seller-free-tier-limiter.d.ts","sourceRoot":"","sources":["../../src/payments/seller-free-tier-limiter.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { isIP } from 'node:net';
|
|
2
2
|
import { evaluateFreeTierLimits, } from '../metering/storage.js';
|
|
3
3
|
import { peerIdToAddress } from '../types/peer.js';
|
|
4
|
+
import { MAX_PEER_CAPABILITY_LENGTH } from '../discovery/metadata-validator.js';
|
|
5
|
+
import { DEFAULT_FREE_TIER_STAKE_TIERS, encodeFreeTierCapabilities, freeTierStakeTierForPower, validateFreeTierStakeTiers, } from './free-tier-program.js';
|
|
6
|
+
import { decodeFreeTierDelegationsHeader, verifyFreeTierDelegations, } from './free-tier-delegation.js';
|
|
4
7
|
export const DEFAULT_FREE_TIER_WINDOW_MS = 24 * 60 * 60_000;
|
|
5
8
|
const MAX_IN_MEMORY_KEYS = 20_000;
|
|
6
9
|
/**
|
|
@@ -41,83 +44,390 @@ function expandIpv6(ip) {
|
|
|
41
44
|
const zeros = isCompressed ? Array(missing).fill('0') : [];
|
|
42
45
|
return [...headParts, ...zeros, ...tailParts].map((part) => part.padStart(4, '0'));
|
|
43
46
|
}
|
|
44
|
-
|
|
47
|
+
/** One validated program and the counter scope it is accounted under. */
|
|
48
|
+
class FreeTierPolicy {
|
|
49
|
+
scope;
|
|
45
50
|
maxRequestsPerAddress;
|
|
46
51
|
maxRequestsPerIp;
|
|
47
52
|
windowMs;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
constructor(config,
|
|
53
|
+
stakeTiers;
|
|
54
|
+
stakersOnly;
|
|
55
|
+
constructor(scope, config, label) {
|
|
56
|
+
this.scope = scope;
|
|
51
57
|
const perAddress = config.maxRequestsPerAddress;
|
|
52
58
|
const perIp = config.maxRequestsPerIp;
|
|
53
59
|
if (perAddress !== undefined && (!Number.isSafeInteger(perAddress) || perAddress < 1)) {
|
|
54
|
-
throw new Error(
|
|
60
|
+
throw new Error(`${label}.maxRequestsPerAddress must be a positive safe integer`);
|
|
55
61
|
}
|
|
56
62
|
if (perIp !== undefined && (!Number.isSafeInteger(perIp) || perIp < 1)) {
|
|
57
|
-
throw new Error(
|
|
63
|
+
throw new Error(`${label}.maxRequestsPerIp must be a positive safe integer`);
|
|
58
64
|
}
|
|
59
65
|
if (perAddress === undefined && perIp === undefined) {
|
|
60
|
-
throw new Error(
|
|
66
|
+
throw new Error(`${label} requires maxRequestsPerAddress and/or maxRequestsPerIp`);
|
|
61
67
|
}
|
|
62
68
|
const windowMs = config.windowMs ?? DEFAULT_FREE_TIER_WINDOW_MS;
|
|
63
69
|
if (!Number.isSafeInteger(windowMs) || windowMs < 1_000) {
|
|
64
|
-
throw new Error(
|
|
70
|
+
throw new Error(`${label}.windowMs must be a safe integer of at least 1000`);
|
|
71
|
+
}
|
|
72
|
+
const stakeTiers = config.stakeTiers ?? DEFAULT_FREE_TIER_STAKE_TIERS;
|
|
73
|
+
validateFreeTierStakeTiers(stakeTiers, `${label}.stakeTiers`);
|
|
74
|
+
if (config.stakersOnly && stakeTiers.length === 0) {
|
|
75
|
+
throw new Error(`${label}.stakersOnly requires at least one stake tier`);
|
|
65
76
|
}
|
|
66
77
|
this.maxRequestsPerAddress = perAddress ?? null;
|
|
67
78
|
this.maxRequestsPerIp = perIp ?? null;
|
|
68
79
|
this.windowMs = windowMs;
|
|
69
|
-
this.
|
|
80
|
+
this.stakeTiers = Object.freeze([...stakeTiers]);
|
|
81
|
+
this.stakersOnly = config.stakersOnly === true;
|
|
82
|
+
// Peer metadata caps each capability string; a program that cannot be
|
|
83
|
+
// advertised would fail every metadata publish, so refuse it here.
|
|
84
|
+
const serviceId = scope === '' ? undefined : scope;
|
|
85
|
+
const encodings = [
|
|
86
|
+
encodeFreeTierCapabilities(this.program(true), serviceId),
|
|
87
|
+
...(serviceId !== undefined ? [encodeFreeTierCapabilities(this.program(true), serviceId, { sellerWideTiers: this.stakeTiers })] : []),
|
|
88
|
+
];
|
|
89
|
+
const tooLong = encodings.flat().find((capability) => capability.length > MAX_PEER_CAPABILITY_LENGTH);
|
|
90
|
+
if (tooLong) {
|
|
91
|
+
throw new Error(`${label} is too large to advertise ("${tooLong}" exceeds ${MAX_PEER_CAPABILITY_LENGTH} characters): lower its limits or window`);
|
|
92
|
+
}
|
|
70
93
|
}
|
|
71
|
-
|
|
94
|
+
program(tiersActive) {
|
|
95
|
+
return {
|
|
96
|
+
base: {
|
|
97
|
+
maxRequestsPerAddress: this.maxRequestsPerAddress,
|
|
98
|
+
maxRequestsPerIp: this.maxRequestsPerIp,
|
|
99
|
+
windowMs: this.windowMs,
|
|
100
|
+
...(this.stakersOnly ? { stakersOnly: true } : {}),
|
|
101
|
+
},
|
|
102
|
+
stakeTiers: tiersActive ? [...this.stakeTiers] : [],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** Requests per window a staked tier allows, or null when unlimited. */
|
|
106
|
+
limitForTier(tier) {
|
|
107
|
+
if (tier.multiplier === 'unlimited')
|
|
108
|
+
return null;
|
|
109
|
+
const base = this.maxRequestsPerAddress ?? this.maxRequestsPerIp;
|
|
110
|
+
return Math.min(Number.MAX_SAFE_INTEGER, base * tier.multiplier);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* "free requests a day: 100 per buyer address, then paid; staked (a day):
|
|
114
|
+
* 1,000,000 power → 1,000, 10,000,000 power → unlimited". `paid`: the service is priced.
|
|
115
|
+
*/
|
|
116
|
+
describe(paid = false) {
|
|
117
|
+
const window = describeWindow(this.windowMs);
|
|
72
118
|
const parts = [];
|
|
73
119
|
if (this.maxRequestsPerAddress !== null)
|
|
74
|
-
parts.push(`${this.maxRequestsPerAddress} per buyer address`);
|
|
120
|
+
parts.push(`${count(this.maxRequestsPerAddress)} per buyer address`);
|
|
75
121
|
if (this.maxRequestsPerIp !== null)
|
|
76
|
-
parts.push(`${this.maxRequestsPerIp} per IP`);
|
|
77
|
-
|
|
122
|
+
parts.push(`${count(this.maxRequestsPerIp)} per IP`);
|
|
123
|
+
const head = this.stakersOnly
|
|
124
|
+
? `free only for stakers${paid ? ', others pay' : ', others are refused'}`
|
|
125
|
+
: `free requests ${window}: ${parts.join(', ')}${paid ? ', then paid' : ''}`;
|
|
126
|
+
const tiers = this.stakeTiers.map((tier) => {
|
|
127
|
+
const limit = this.limitForTier(tier);
|
|
128
|
+
return `${count(tier.minPower)} power → ${limit === null ? 'unlimited' : count(limit)}`;
|
|
129
|
+
});
|
|
130
|
+
return tiers.length > 0 ? `${head}; staked (${window}): ${tiers.join(', ')}` : head;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function count(value) {
|
|
134
|
+
return value.toLocaleString('en-US');
|
|
135
|
+
}
|
|
136
|
+
function describeWindow(windowMs) {
|
|
137
|
+
const hours = windowMs / 3_600_000;
|
|
138
|
+
if (hours === 24)
|
|
139
|
+
return 'a day';
|
|
140
|
+
if (hours >= 1 && Number.isInteger(hours))
|
|
141
|
+
return `per ${hours} h`;
|
|
142
|
+
return windowMs % 60_000 === 0 ? `per ${windowMs / 60_000} min` : `per ${windowMs / 1000} s`;
|
|
143
|
+
}
|
|
144
|
+
function hasSellerWideProgram(config) {
|
|
145
|
+
return config.maxRequestsPerAddress !== undefined
|
|
146
|
+
|| config.maxRequestsPerIp !== undefined
|
|
147
|
+
|| config.windowMs !== undefined
|
|
148
|
+
|| config.stakeTiers !== undefined
|
|
149
|
+
|| config.stakersOnly !== undefined;
|
|
150
|
+
}
|
|
151
|
+
export class SellerFreeTierLimiter {
|
|
152
|
+
_default;
|
|
153
|
+
_services;
|
|
154
|
+
/** Longest window of any program: usage younger than this is never pruned. */
|
|
155
|
+
_retentionMs;
|
|
156
|
+
_storage;
|
|
157
|
+
_inMemoryUsage = new Map();
|
|
158
|
+
_stakeResolver = null;
|
|
159
|
+
_chainId = 0;
|
|
160
|
+
/** Last verified delegations per buyer address, in insertion order for eviction. */
|
|
161
|
+
_delegations = new Map();
|
|
162
|
+
/** Priced services, whose allowances never admit a free-usage channel. */
|
|
163
|
+
_paidServices = new Set();
|
|
164
|
+
/** Zero-priced services this seller offers, or null when not told: they decide channel admission. */
|
|
165
|
+
_freeServices = null;
|
|
166
|
+
constructor(config, storage = null) {
|
|
167
|
+
const services = Object.entries(config.services ?? {});
|
|
168
|
+
if (!hasSellerWideProgram(config) && services.length === 0) {
|
|
169
|
+
throw new Error('freeTier requires maxRequestsPerAddress and/or maxRequestsPerIp');
|
|
170
|
+
}
|
|
171
|
+
this._default = hasSellerWideProgram(config) ? new FreeTierPolicy('', config, 'freeTier') : null;
|
|
172
|
+
this._services = new Map(services.map(([service, program]) => {
|
|
173
|
+
if (service.length === 0)
|
|
174
|
+
throw new Error('freeTier.services keys must be non-empty service ids');
|
|
175
|
+
return [service, new FreeTierPolicy(service, program, `freeTier.services.${service}`)];
|
|
176
|
+
}));
|
|
177
|
+
this._retentionMs = Math.max(...this._policies().map((policy) => policy.windowMs));
|
|
178
|
+
this._storage = storage;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* @deprecated The seller-wide program's per-address limit, or null when it
|
|
182
|
+
* has none (including a limiter with only per-service programs). Use
|
|
183
|
+
* `program(service)` for the limits a service is served under.
|
|
184
|
+
*/
|
|
185
|
+
get maxRequestsPerAddress() {
|
|
186
|
+
return this._default?.maxRequestsPerAddress ?? null;
|
|
187
|
+
}
|
|
188
|
+
/** @deprecated The seller-wide program's per-IP limit, or null. Use `program(service)`. */
|
|
189
|
+
get maxRequestsPerIp() {
|
|
190
|
+
return this._default?.maxRequestsPerIp ?? null;
|
|
191
|
+
}
|
|
192
|
+
/** @deprecated The seller-wide program's window, else the default window. Use `program(service)`. */
|
|
193
|
+
get windowMs() {
|
|
194
|
+
return this._default?.windowMs ?? DEFAULT_FREE_TIER_WINDOW_MS;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Enable staked tiers. Without a resolver every buyer gets the base limits.
|
|
198
|
+
* `chainId` is the EIP-712 domain delegations are verified against.
|
|
199
|
+
*/
|
|
200
|
+
setStakeResolver(resolver, chainId = 0) {
|
|
201
|
+
this._stakeResolver = resolver;
|
|
202
|
+
this._chainId = chainId;
|
|
203
|
+
}
|
|
204
|
+
/** Whether any program has staked tiers, so stake lookups are worth enabling. */
|
|
205
|
+
get hasStakeTiers() {
|
|
206
|
+
return this._policies().some((policy) => policy.stakeTiers.length > 0);
|
|
78
207
|
}
|
|
208
|
+
/** Whether staked tiers are live: configured and backed by on-chain reads. */
|
|
209
|
+
get stakeTiersActive() {
|
|
210
|
+
return this._stakeResolver !== null && this.hasStakeTiers;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Whether a program applies to `service`: its own, or for a zero-priced
|
|
214
|
+
* service the seller-wide one. A priced service has one only when configured.
|
|
215
|
+
*/
|
|
216
|
+
appliesTo(service, options = {}) {
|
|
217
|
+
return this._policyFor(service, options.paid === true) !== null;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Services that are priced. Their programs are free allowances on paid
|
|
221
|
+
* models, served without a free-usage channel, so channel admission ignores them.
|
|
222
|
+
*/
|
|
223
|
+
setPaidServices(services) {
|
|
224
|
+
this._paidServices = new Set(services);
|
|
225
|
+
}
|
|
226
|
+
/** The zero-priced services this seller offers, so channel admission checks exactly their programs. */
|
|
227
|
+
setFreeServices(services) {
|
|
228
|
+
this._freeServices = [...new Set(services)];
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* The program `service` is served under, or the seller-wide program when
|
|
232
|
+
* `service` is omitted; tiers only when they are enforced. Null when none applies.
|
|
233
|
+
*/
|
|
234
|
+
program(service, options = {}) {
|
|
235
|
+
const policy = service === undefined ? this._default : this._policyFor(service, options.paid === true);
|
|
236
|
+
return policy?.program(this.stakeTiersActive) ?? null;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Peer-metadata capability strings advertising every program: the
|
|
240
|
+
* seller-wide one first, then services in id order. A program that would
|
|
241
|
+
* exceed `budget` strings is skipped whole, never advertised in part.
|
|
242
|
+
*/
|
|
243
|
+
capabilities(budget = Number.POSITIVE_INFINITY) {
|
|
244
|
+
const active = this.stakeTiersActive;
|
|
245
|
+
const out = [];
|
|
246
|
+
const programs = [
|
|
247
|
+
...(this._default ? [[undefined, this._default]] : []),
|
|
248
|
+
...[...this._services].sort(([a], [b]) => a.localeCompare(b)),
|
|
249
|
+
];
|
|
250
|
+
// Service programs may point at the seller-wide tiers only once those are advertised.
|
|
251
|
+
let sellerWideTiers;
|
|
252
|
+
for (const [service, policy] of programs) {
|
|
253
|
+
const strings = encodeFreeTierCapabilities(policy.program(active), service, sellerWideTiers ? { sellerWideTiers } : {});
|
|
254
|
+
if (out.length + strings.length > budget)
|
|
255
|
+
continue;
|
|
256
|
+
out.push(...strings);
|
|
257
|
+
if (service === undefined)
|
|
258
|
+
sellerWideTiers = policy.program(active).stakeTiers;
|
|
259
|
+
}
|
|
260
|
+
return out;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Staked standing for this buyer, or null for the base limits. Never throws.
|
|
264
|
+
*
|
|
265
|
+
* `delegationsHeader` is the buyer's `x-antseed-free-tier-delegations`
|
|
266
|
+
* value (from a request or a free usage open), or '' when a request carried
|
|
267
|
+
* none. Verified stakers are remembered for the buyer, so an open from an
|
|
268
|
+
* older buyer, which passes undefined, sees the standing of its requests.
|
|
269
|
+
*/
|
|
270
|
+
async resolveStanding(buyerPeerId, delegationsHeader) {
|
|
271
|
+
if (!this._stakeResolver || !this.hasStakeTiers)
|
|
272
|
+
return null;
|
|
273
|
+
const buyer = peerIdToAddress(buyerPeerId).toLowerCase();
|
|
274
|
+
if (delegationsHeader !== undefined) {
|
|
275
|
+
const delegations = decodeFreeTierDelegationsHeader(delegationsHeader, buyer);
|
|
276
|
+
this._rememberDelegations(buyer, verifyFreeTierDelegations(delegations, buyer, this._chainId));
|
|
277
|
+
}
|
|
278
|
+
const nowSeconds = Date.now() / 1000;
|
|
279
|
+
const stakers = (this._delegations.get(buyer) ?? [])
|
|
280
|
+
.filter((delegation) => delegation.expiresAt > nowSeconds)
|
|
281
|
+
.map((delegation) => delegation.staker);
|
|
282
|
+
return this._stakeResolver.resolve(buyer, stakers);
|
|
283
|
+
}
|
|
284
|
+
_rememberDelegations(buyer, delegations) {
|
|
285
|
+
this._delegations.delete(buyer);
|
|
286
|
+
if (delegations.length === 0)
|
|
287
|
+
return;
|
|
288
|
+
if (this._delegations.size >= MAX_IN_MEMORY_KEYS) {
|
|
289
|
+
const oldest = this._delegations.keys().next().value;
|
|
290
|
+
if (oldest !== undefined)
|
|
291
|
+
this._delegations.delete(oldest);
|
|
292
|
+
}
|
|
293
|
+
this._delegations.set(buyer, delegations);
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* One line per program: the seller-wide one for free models, then each
|
|
297
|
+
* service's own. A priced service's line is its free allowance before its
|
|
298
|
+
* price applies, as set by `setPaidServices`.
|
|
299
|
+
*/
|
|
300
|
+
describePrograms() {
|
|
301
|
+
const lines = this._default ? [`free models: ${this._default.describe()}`] : [];
|
|
302
|
+
for (const [service, policy] of this._services) {
|
|
303
|
+
lines.push(`${service}: ${policy.describe(this._paidServices.has(service))}`);
|
|
304
|
+
}
|
|
305
|
+
return lines;
|
|
306
|
+
}
|
|
307
|
+
describe() {
|
|
308
|
+
return this.describePrograms().join(' | ');
|
|
309
|
+
}
|
|
310
|
+
/** Consume one request for `input.service`. Callers check `appliesTo` first. */
|
|
79
311
|
consume(input) {
|
|
80
|
-
|
|
312
|
+
const policy = this._policyFor(input.service, input.paid === true);
|
|
313
|
+
if (!policy)
|
|
314
|
+
throw new Error(`no free-tier program applies to ${input.service}`);
|
|
315
|
+
return this._evaluate(policy, input, input.service);
|
|
81
316
|
}
|
|
82
|
-
/**
|
|
317
|
+
/**
|
|
318
|
+
* Check channel-open admission without consuming an inference request.
|
|
319
|
+
* The channel serves every free service, so it is admitted when the program
|
|
320
|
+
* of some offered free service would still serve the buyer, or when one of
|
|
321
|
+
* them is not limited at all. Otherwise the first refusal (the seller-wide
|
|
322
|
+
* one when it applies) is reported. Without the offered services (see
|
|
323
|
+
* `setFreeServices`), every program of a zero-priced service is considered,
|
|
324
|
+
* and no seller-wide program means some free service is unlimited.
|
|
325
|
+
*/
|
|
83
326
|
check(input) {
|
|
84
|
-
|
|
327
|
+
let policies;
|
|
328
|
+
if (this._freeServices) {
|
|
329
|
+
const applicable = this._freeServices.map((service) => this._policyFor(service, false));
|
|
330
|
+
if (applicable.length === 0 || applicable.some((policy) => policy === null))
|
|
331
|
+
return null;
|
|
332
|
+
policies = [...new Set(applicable)]
|
|
333
|
+
.sort((a, b) => (a === this._default ? -1 : b === this._default ? 1 : 0));
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
if (!this._default)
|
|
337
|
+
return null;
|
|
338
|
+
policies = this._policies().filter((policy) => !this._paidServices.has(policy.scope));
|
|
339
|
+
}
|
|
340
|
+
const decisions = policies.map((policy) => this._evaluate(policy, input, null));
|
|
341
|
+
return decisions.find((decision) => decision.allowed) ?? decisions[0];
|
|
342
|
+
}
|
|
343
|
+
_policies() {
|
|
344
|
+
return [...(this._default ? [this._default] : []), ...this._services.values()];
|
|
85
345
|
}
|
|
86
|
-
|
|
346
|
+
_policyFor(service, paid) {
|
|
347
|
+
return this._services.get(service) ?? (paid ? null : this._default);
|
|
348
|
+
}
|
|
349
|
+
_evaluate(policy, input, service) {
|
|
87
350
|
const nowMs = input.nowMs ?? Date.now();
|
|
88
351
|
const buyerAddress = peerIdToAddress(input.buyerPeerId).toLowerCase();
|
|
89
352
|
const remoteIp = normalizeRemoteIp(input.remoteIp);
|
|
353
|
+
const program = policy.program(this.stakeTiersActive);
|
|
354
|
+
// A standing below this program's lowest tier gets the base limits.
|
|
355
|
+
const tier = input.standing ? freeTierStakeTierForPower(policy.stakeTiers, input.standing.power) : null;
|
|
356
|
+
const standing = tier ? input.standing : null;
|
|
357
|
+
if (policy.stakersOnly && !tier) {
|
|
358
|
+
return {
|
|
359
|
+
allowed: false,
|
|
360
|
+
remaining: 0,
|
|
361
|
+
retryAfterMs: 0,
|
|
362
|
+
limitedBy: 'stake_required',
|
|
363
|
+
buyerAddress,
|
|
364
|
+
remoteIp,
|
|
365
|
+
standing: input.standing ?? null,
|
|
366
|
+
tier: null,
|
|
367
|
+
limit: 0,
|
|
368
|
+
program,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
const ownerLimit = tier ? policy.limitForTier(tier) : null;
|
|
90
372
|
const usage = {
|
|
91
373
|
buyerAddress,
|
|
92
374
|
remoteIp,
|
|
93
|
-
maxRequestsPerAddress:
|
|
94
|
-
maxRequestsPerIp:
|
|
95
|
-
windowMs:
|
|
375
|
+
maxRequestsPerAddress: policy.maxRequestsPerAddress,
|
|
376
|
+
maxRequestsPerIp: policy.maxRequestsPerIp,
|
|
377
|
+
windowMs: policy.windowMs,
|
|
378
|
+
retentionMs: this._retentionMs,
|
|
379
|
+
scope: policy.scope,
|
|
96
380
|
nowMs,
|
|
381
|
+
stake: standing ? { positionIds: standing.positionIds, limit: ownerLimit } : null,
|
|
97
382
|
};
|
|
98
383
|
const decision = this._storage
|
|
99
384
|
? (service === null
|
|
100
385
|
? this._storage.checkFreeTierRequest(usage)
|
|
101
386
|
: this._storage.consumeFreeTierRequest({ ...usage, service }))
|
|
102
|
-
: this._evaluateInMemory(buyerAddress, remoteIp, nowMs, service !== null);
|
|
103
|
-
return {
|
|
387
|
+
: this._evaluateInMemory(policy, buyerAddress, remoteIp, standing, ownerLimit, nowMs, service !== null);
|
|
388
|
+
return {
|
|
389
|
+
...decision,
|
|
390
|
+
buyerAddress,
|
|
391
|
+
remoteIp,
|
|
392
|
+
standing,
|
|
393
|
+
tier,
|
|
394
|
+
limit: this._bindingLimit(policy, decision, standing, ownerLimit),
|
|
395
|
+
program,
|
|
396
|
+
};
|
|
104
397
|
}
|
|
105
|
-
|
|
106
|
-
|
|
398
|
+
_bindingLimit(policy, decision, standing, ownerLimit) {
|
|
399
|
+
if (standing)
|
|
400
|
+
return ownerLimit;
|
|
401
|
+
if (decision.limitedBy === 'ip')
|
|
402
|
+
return policy.maxRequestsPerIp;
|
|
403
|
+
return policy.maxRequestsPerAddress ?? policy.maxRequestsPerIp;
|
|
404
|
+
}
|
|
405
|
+
_evaluateInMemory(policy, buyerAddress, remoteIp, standing, ownerLimit, nowMs, consume) {
|
|
406
|
+
const windowStart = nowMs - policy.windowMs;
|
|
107
407
|
const buckets = new Map();
|
|
108
408
|
const checks = [];
|
|
109
409
|
const track = (kind, key, limit) => {
|
|
110
|
-
const
|
|
111
|
-
|
|
410
|
+
const scopedKey = `${policy.scope}|${key}`;
|
|
411
|
+
const timestamps = (this._inMemoryUsage.get(scopedKey) ?? []).filter((timestamp) => timestamp >= windowStart);
|
|
412
|
+
buckets.set(scopedKey, timestamps);
|
|
112
413
|
checks.push({ kind, limit, count: timestamps.length, oldestTimestamp: timestamps[0] ?? null });
|
|
113
414
|
};
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
415
|
+
if (standing) {
|
|
416
|
+
// Unlimited stake is not tracked: nothing would ever read the bucket.
|
|
417
|
+
if (ownerLimit !== null) {
|
|
418
|
+
for (const positionId of standing.positionIds)
|
|
419
|
+
track('stake', `position:${positionId}`, ownerLimit);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
if (policy.maxRequestsPerAddress !== null)
|
|
424
|
+
track('address', `address:${buyerAddress}`, policy.maxRequestsPerAddress);
|
|
425
|
+
if (policy.maxRequestsPerIp !== null && remoteIp !== null)
|
|
426
|
+
track('ip', `ip:${remoteIp}`, policy.maxRequestsPerIp);
|
|
427
|
+
}
|
|
428
|
+
const decision = evaluateFreeTierLimits(checks, policy.windowMs, nowMs);
|
|
119
429
|
if (decision.allowed && consume) {
|
|
120
|
-
this._pruneExpiredBuckets(
|
|
430
|
+
this._pruneExpiredBuckets(nowMs - this._retentionMs);
|
|
121
431
|
const newKeys = [...buckets.keys()].filter((key) => !this._inMemoryUsage.has(key)).length;
|
|
122
432
|
if (this._inMemoryUsage.size + newKeys > MAX_IN_MEMORY_KEYS) {
|
|
123
433
|
throw new Error('free-tier limiter capacity reached while persistent metering is unavailable');
|