@antseed/node 0.1.0 → 0.1.2

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.
Files changed (140) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +7 -5
  3. package/dist/discovery/http-metadata-resolver.d.ts +6 -0
  4. package/dist/discovery/http-metadata-resolver.d.ts.map +1 -1
  5. package/dist/discovery/http-metadata-resolver.js +32 -4
  6. package/dist/discovery/http-metadata-resolver.js.map +1 -1
  7. package/dist/discovery/peer-lookup.d.ts +1 -0
  8. package/dist/discovery/peer-lookup.d.ts.map +1 -1
  9. package/dist/discovery/peer-lookup.js +10 -25
  10. package/dist/discovery/peer-lookup.js.map +1 -1
  11. package/dist/index.d.ts +2 -2
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/interfaces/seller-provider.d.ts +13 -1
  16. package/dist/interfaces/seller-provider.d.ts.map +1 -1
  17. package/dist/node.d.ts +13 -3
  18. package/dist/node.d.ts.map +1 -1
  19. package/dist/node.js +146 -21
  20. package/dist/node.js.map +1 -1
  21. package/dist/proxy/proxy-mux.d.ts +3 -1
  22. package/dist/proxy/proxy-mux.d.ts.map +1 -1
  23. package/dist/proxy/proxy-mux.js +9 -5
  24. package/dist/proxy/proxy-mux.js.map +1 -1
  25. package/dist/types/http.d.ts +1 -0
  26. package/dist/types/http.d.ts.map +1 -1
  27. package/dist/types/http.js +1 -1
  28. package/dist/types/http.js.map +1 -1
  29. package/package.json +14 -10
  30. package/contracts/AntseedEscrow.sol +0 -310
  31. package/contracts/MockUSDC.sol +0 -64
  32. package/contracts/README.md +0 -102
  33. package/src/config/encryption.test.ts +0 -49
  34. package/src/config/encryption.ts +0 -53
  35. package/src/config/plugin-config-manager.test.ts +0 -92
  36. package/src/config/plugin-config-manager.ts +0 -153
  37. package/src/config/plugin-loader.ts +0 -90
  38. package/src/discovery/announcer.ts +0 -169
  39. package/src/discovery/bootstrap.ts +0 -57
  40. package/src/discovery/default-metadata-resolver.ts +0 -18
  41. package/src/discovery/dht-health.ts +0 -136
  42. package/src/discovery/dht-node.ts +0 -191
  43. package/src/discovery/http-metadata-resolver.ts +0 -47
  44. package/src/discovery/index.ts +0 -15
  45. package/src/discovery/metadata-codec.ts +0 -453
  46. package/src/discovery/metadata-resolver.ts +0 -7
  47. package/src/discovery/metadata-server.ts +0 -73
  48. package/src/discovery/metadata-validator.ts +0 -172
  49. package/src/discovery/peer-lookup.ts +0 -122
  50. package/src/discovery/peer-metadata.ts +0 -34
  51. package/src/discovery/peer-selector.ts +0 -134
  52. package/src/discovery/profile-manager.ts +0 -131
  53. package/src/discovery/profile-search.ts +0 -100
  54. package/src/discovery/reputation-verifier.ts +0 -54
  55. package/src/index.ts +0 -61
  56. package/src/interfaces/buyer-router.ts +0 -21
  57. package/src/interfaces/plugin.ts +0 -36
  58. package/src/interfaces/seller-provider.ts +0 -81
  59. package/src/metering/index.ts +0 -6
  60. package/src/metering/receipt-generator.ts +0 -105
  61. package/src/metering/receipt-verifier.ts +0 -102
  62. package/src/metering/session-tracker.ts +0 -145
  63. package/src/metering/storage.ts +0 -600
  64. package/src/metering/token-counter.ts +0 -127
  65. package/src/metering/usage-aggregator.ts +0 -236
  66. package/src/node.ts +0 -1698
  67. package/src/p2p/connection-auth.ts +0 -152
  68. package/src/p2p/connection-manager.ts +0 -916
  69. package/src/p2p/handshake.ts +0 -162
  70. package/src/p2p/ice-config.ts +0 -59
  71. package/src/p2p/identity.ts +0 -110
  72. package/src/p2p/index.ts +0 -11
  73. package/src/p2p/keepalive.ts +0 -118
  74. package/src/p2p/message-protocol.ts +0 -171
  75. package/src/p2p/nat-traversal.ts +0 -169
  76. package/src/p2p/payment-codec.ts +0 -165
  77. package/src/p2p/payment-mux.ts +0 -153
  78. package/src/p2p/reconnect.ts +0 -117
  79. package/src/payments/balance-manager.ts +0 -77
  80. package/src/payments/buyer-payment-manager.ts +0 -414
  81. package/src/payments/disputes.ts +0 -72
  82. package/src/payments/evm/escrow-client.ts +0 -263
  83. package/src/payments/evm/keypair.ts +0 -31
  84. package/src/payments/evm/signatures.ts +0 -103
  85. package/src/payments/evm/wallet.ts +0 -42
  86. package/src/payments/index.ts +0 -50
  87. package/src/payments/settlement.ts +0 -40
  88. package/src/payments/types.ts +0 -79
  89. package/src/proxy/index.ts +0 -3
  90. package/src/proxy/provider-detection.ts +0 -78
  91. package/src/proxy/proxy-mux.ts +0 -173
  92. package/src/proxy/request-codec.ts +0 -294
  93. package/src/reputation/index.ts +0 -6
  94. package/src/reputation/rating-manager.ts +0 -118
  95. package/src/reputation/report-manager.ts +0 -91
  96. package/src/reputation/trust-engine.ts +0 -120
  97. package/src/reputation/trust-score.ts +0 -74
  98. package/src/reputation/uptime-tracker.ts +0 -155
  99. package/src/routing/default-router.ts +0 -75
  100. package/src/types/bittorrent-dht.d.ts +0 -19
  101. package/src/types/buyer.ts +0 -37
  102. package/src/types/capability.ts +0 -34
  103. package/src/types/connection.ts +0 -29
  104. package/src/types/http.ts +0 -20
  105. package/src/types/index.ts +0 -14
  106. package/src/types/metering.ts +0 -175
  107. package/src/types/nat-api.d.ts +0 -29
  108. package/src/types/peer-profile.ts +0 -25
  109. package/src/types/peer.ts +0 -62
  110. package/src/types/plugin-config.ts +0 -31
  111. package/src/types/protocol.ts +0 -162
  112. package/src/types/provider.ts +0 -40
  113. package/src/types/rating.ts +0 -23
  114. package/src/types/report.ts +0 -30
  115. package/src/types/seller.ts +0 -38
  116. package/src/types/staking.ts +0 -23
  117. package/src/utils/debug.ts +0 -30
  118. package/src/utils/hex.ts +0 -14
  119. package/tests/balance-manager.test.ts +0 -156
  120. package/tests/bootstrap.test.ts +0 -108
  121. package/tests/buyer-payment-manager.test.ts +0 -358
  122. package/tests/connection-auth.test.ts +0 -87
  123. package/tests/default-router.test.ts +0 -148
  124. package/tests/evm-keypair.test.ts +0 -173
  125. package/tests/identity.test.ts +0 -133
  126. package/tests/message-protocol.test.ts +0 -212
  127. package/tests/metadata-codec.test.ts +0 -165
  128. package/tests/metadata-validator.test.ts +0 -261
  129. package/tests/metering-storage.test.ts +0 -244
  130. package/tests/payment-codec.test.ts +0 -95
  131. package/tests/payment-mux.test.ts +0 -191
  132. package/tests/peer-selector.test.ts +0 -184
  133. package/tests/provider-detection.test.ts +0 -107
  134. package/tests/proxy-mux-security.test.ts +0 -38
  135. package/tests/receipt.test.ts +0 -215
  136. package/tests/reputation-integration.test.ts +0 -195
  137. package/tests/request-codec.test.ts +0 -144
  138. package/tests/token-counter.test.ts +0 -122
  139. package/tsconfig.json +0 -9
  140. package/vitest.config.ts +0 -7
@@ -1,153 +0,0 @@
1
- import { readFile, writeFile, mkdir } from 'node:fs/promises'
2
- import { dirname } from 'node:path'
3
- import type { PluginInstanceConfig, PluginConfigFile } from '../types/plugin-config.js'
4
- import type { ConfigField } from '../interfaces/plugin.js'
5
- import { encryptValue, decryptValue, deriveMachineKey, generateSalt } from './encryption.js'
6
-
7
- const ENCRYPTED_PREFIX = 'enc:'
8
-
9
- function isEncrypted(value: unknown): value is string {
10
- return typeof value === 'string' && value.startsWith(ENCRYPTED_PREFIX)
11
- }
12
-
13
- /**
14
- * Load plugin config file. Returns empty config if file doesn't exist.
15
- */
16
- export async function loadPluginConfig(configPath: string): Promise<PluginConfigFile> {
17
- try {
18
- const raw = await readFile(configPath, 'utf-8')
19
- const parsed = JSON.parse(raw) as Record<string, unknown>
20
- return {
21
- instances: Array.isArray(parsed['instances']) ? parsed['instances'] as PluginInstanceConfig[] : [],
22
- encryption: parsed['encryption'] as PluginConfigFile['encryption'],
23
- }
24
- } catch (err) {
25
- if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
26
- return { instances: [] }
27
- }
28
- throw err
29
- }
30
- }
31
-
32
- /**
33
- * Save plugin config file. Preserves non-plugin fields.
34
- */
35
- export async function savePluginConfig(configPath: string, pluginConfig: PluginConfigFile): Promise<void> {
36
- let existing: Record<string, unknown> = {}
37
- try {
38
- const raw = await readFile(configPath, 'utf-8')
39
- existing = JSON.parse(raw) as Record<string, unknown>
40
- } catch {
41
- // File doesn't exist yet
42
- }
43
-
44
- const merged = {
45
- ...existing,
46
- instances: pluginConfig.instances,
47
- ...(pluginConfig.encryption ? { encryption: pluginConfig.encryption } : {}),
48
- }
49
-
50
- await mkdir(dirname(configPath), { recursive: true })
51
- await writeFile(configPath, JSON.stringify(merged, null, 2), 'utf-8')
52
- }
53
-
54
- function getOrCreateEncryption(config: PluginConfigFile): { key: Buffer; encryption: NonNullable<PluginConfigFile['encryption']> } {
55
- if (config.encryption?.salt) {
56
- const salt = Buffer.from(config.encryption.salt, 'hex')
57
- return { key: deriveMachineKey(salt), encryption: config.encryption }
58
- }
59
- const salt = generateSalt()
60
- const encryption = { algorithm: 'aes-256-gcm', kdf: 'scrypt', salt: salt.toString('hex') }
61
- return { key: deriveMachineKey(salt), encryption }
62
- }
63
-
64
- function encryptSecrets(instanceConfig: Record<string, unknown>, key: Buffer, schema?: ConfigField[]): Record<string, unknown> {
65
- const result = { ...instanceConfig }
66
- const secretKeys = new Set(schema?.filter(f => f.type === 'secret').map(f => f.key) ?? [])
67
- for (const [k, v] of Object.entries(result)) {
68
- if (secretKeys.has(k) && typeof v === 'string' && !isEncrypted(v)) {
69
- result[k] = ENCRYPTED_PREFIX + encryptValue(v, key)
70
- }
71
- }
72
- return result
73
- }
74
-
75
- function decryptSecrets(instanceConfig: Record<string, unknown>, key: Buffer): Record<string, unknown> {
76
- const result = { ...instanceConfig }
77
- for (const [k, v] of Object.entries(result)) {
78
- if (isEncrypted(v)) {
79
- result[k] = decryptValue(v.slice(ENCRYPTED_PREFIX.length), key)
80
- }
81
- }
82
- return result
83
- }
84
-
85
- /**
86
- * Add a plugin instance with encrypted secrets.
87
- */
88
- export async function addInstance(
89
- configPath: string,
90
- instance: PluginInstanceConfig,
91
- schema?: ConfigField[],
92
- ): Promise<void> {
93
- const config = await loadPluginConfig(configPath)
94
- if (config.instances.some(i => i.id === instance.id)) {
95
- throw new Error(`Instance "${instance.id}" already exists`)
96
- }
97
- const { key, encryption } = getOrCreateEncryption(config)
98
- config.encryption = encryption
99
- const encrypted = { ...instance, config: encryptSecrets(instance.config, key, schema) }
100
- config.instances.push(encrypted)
101
- await savePluginConfig(configPath, config)
102
- }
103
-
104
- /**
105
- * Remove an instance by ID.
106
- */
107
- export async function removeInstance(configPath: string, instanceId: string): Promise<void> {
108
- const config = await loadPluginConfig(configPath)
109
- config.instances = config.instances.filter(i => i.id !== instanceId)
110
- await savePluginConfig(configPath, config)
111
- }
112
-
113
- /**
114
- * Get a single instance with decrypted secrets.
115
- */
116
- export async function getInstance(configPath: string, instanceId: string): Promise<PluginInstanceConfig | null> {
117
- const config = await loadPluginConfig(configPath)
118
- const instance = config.instances.find(i => i.id === instanceId)
119
- if (!instance) return null
120
- if (!config.encryption?.salt) return instance
121
- const salt = Buffer.from(config.encryption.salt, 'hex')
122
- const key = deriveMachineKey(salt)
123
- return { ...instance, config: decryptSecrets(instance.config, key) }
124
- }
125
-
126
- /**
127
- * Get all instances with decrypted secrets.
128
- */
129
- export async function getInstances(configPath: string): Promise<PluginInstanceConfig[]> {
130
- const config = await loadPluginConfig(configPath)
131
- if (!config.encryption?.salt) return config.instances
132
- const salt = Buffer.from(config.encryption.salt, 'hex')
133
- const key = deriveMachineKey(salt)
134
- return config.instances.map(i => ({ ...i, config: decryptSecrets(i.config, key) }))
135
- }
136
-
137
- /**
138
- * Update config for an existing instance.
139
- */
140
- export async function updateInstanceConfig(
141
- configPath: string,
142
- instanceId: string,
143
- newConfig: Record<string, unknown>,
144
- schema?: ConfigField[],
145
- ): Promise<void> {
146
- const config = await loadPluginConfig(configPath)
147
- const idx = config.instances.findIndex(i => i.id === instanceId)
148
- if (idx === -1) throw new Error(`Instance "${instanceId}" not found`)
149
- const { key, encryption } = getOrCreateEncryption(config)
150
- config.encryption = encryption
151
- config.instances[idx] = { ...config.instances[idx]!, config: encryptSecrets(newConfig, key, schema) }
152
- await savePluginConfig(configPath, config)
153
- }
@@ -1,90 +0,0 @@
1
- import type { Provider } from '../interfaces/seller-provider.js'
2
- import type { Router } from '../interfaces/buyer-router.js'
3
- import type { AntseedProviderPlugin, AntseedRouterPlugin, AntseedPlugin } from '../interfaces/plugin.js'
4
- import { getInstances } from './plugin-config-manager.js'
5
-
6
- export interface LoadedProvider {
7
- instanceId: string
8
- provider: Provider
9
- pluginName: string
10
- }
11
-
12
- export interface LoadedRouter {
13
- instanceId: string
14
- router: Router
15
- pluginName: string
16
- }
17
-
18
- /**
19
- * Load a plugin's default export from its npm package.
20
- */
21
- export async function loadPluginModule(packageName: string, pluginsDir: string): Promise<AntseedPlugin> {
22
- const { join, resolve } = await import('node:path')
23
- const pluginPath = join(pluginsDir, 'node_modules', packageName, 'dist', 'index.js')
24
- const resolved = resolve(pluginPath)
25
-
26
- if (!resolved.startsWith(resolve(pluginsDir))) {
27
- throw new Error(`Invalid plugin path: ${packageName}`)
28
- }
29
-
30
- let mod: { default?: unknown }
31
- try {
32
- mod = await import(resolved) as { default?: unknown }
33
- } catch (err) {
34
- const cause = err instanceof Error ? err.message : String(err)
35
- throw new Error(`Plugin "${packageName}" failed to load from ${resolved}: ${cause}`)
36
- }
37
-
38
- const plugin = mod.default
39
- if (!plugin || typeof plugin !== 'object') {
40
- throw new Error(`Plugin "${packageName}" does not export a valid plugin object`)
41
- }
42
-
43
- const typed = plugin as { type?: string }
44
- if (typed.type !== 'provider' && typed.type !== 'router') {
45
- throw new Error(`Plugin "${packageName}" has invalid type: ${typed.type}`)
46
- }
47
-
48
- return plugin as AntseedPlugin
49
- }
50
-
51
- function instanceConfigToRecord(config: Record<string, unknown>): Record<string, string> {
52
- const result: Record<string, string> = {}
53
- for (const [key, value] of Object.entries(config)) {
54
- if (value !== null && value !== undefined) {
55
- result[key] = String(value)
56
- }
57
- }
58
- return result
59
- }
60
-
61
- /**
62
- * Load all enabled plugin instances from config and instantiate them.
63
- */
64
- export async function loadAllPlugins(configPath: string, pluginsDir: string): Promise<{
65
- providers: LoadedProvider[]
66
- routers: LoadedRouter[]
67
- }> {
68
- const instances = await getInstances(configPath)
69
- const providers: LoadedProvider[] = []
70
- const routers: LoadedRouter[] = []
71
-
72
- for (const instance of instances) {
73
- if (instance.enabled === false) continue
74
-
75
- const plugin = await loadPluginModule(instance.package, pluginsDir)
76
- const configRecord = instanceConfigToRecord(instance.config)
77
-
78
- if (instance.type === 'provider' && plugin.type === 'provider') {
79
- const providerPlugin = plugin as AntseedProviderPlugin
80
- const provider = await providerPlugin.createProvider(configRecord)
81
- providers.push({ instanceId: instance.id, provider, pluginName: plugin.name })
82
- } else if (instance.type === 'router' && plugin.type === 'router') {
83
- const routerPlugin = plugin as AntseedRouterPlugin
84
- const router = await routerPlugin.createRouter(configRecord)
85
- routers.push({ instanceId: instance.id, router, pluginName: plugin.name })
86
- }
87
- }
88
-
89
- return { providers, routers }
90
- }
@@ -1,169 +0,0 @@
1
- import type { Identity } from "../p2p/identity.js";
2
- import { signData } from "../p2p/identity.js";
3
- import type { DHTNode } from "./dht-node.js";
4
- import { providerTopic, capabilityTopic, topicToInfoHash } from "./dht-node.js";
5
- import type { PeerOffering } from "../types/capability.js";
6
- import type { PeerMetadata, ProviderAnnouncement } from "./peer-metadata.js";
7
- import { METADATA_VERSION } from "./peer-metadata.js";
8
- import { encodeMetadataForSigning } from "./metadata-codec.js";
9
- import { debugWarn } from "../utils/debug.js";
10
- import { bytesToHex } from "../utils/hex.js";
11
- import type { BaseEscrowClient } from "../payments/evm/escrow-client.js";
12
- import { identityToEvmAddress } from "../payments/evm/keypair.js";
13
-
14
- export interface AnnouncerConfig {
15
- identity: Identity;
16
- dht: DHTNode;
17
- providers: Array<{
18
- provider: string;
19
- models: string[];
20
- maxConcurrency: number;
21
- }>;
22
- region: string;
23
- pricing: Map<
24
- string,
25
- {
26
- defaults: { inputUsdPerMillion: number; outputUsdPerMillion: number };
27
- models?: Record<string, { inputUsdPerMillion: number; outputUsdPerMillion: number }>;
28
- }
29
- >;
30
- offerings?: PeerOffering[];
31
- stakeAmountUSDC?: number;
32
- trustScore?: number;
33
- escrowClient?: BaseEscrowClient;
34
- reannounceIntervalMs: number;
35
- signalingPort: number;
36
- }
37
-
38
- export class PeerAnnouncer {
39
- private readonly config: AnnouncerConfig;
40
- private intervalHandle: ReturnType<typeof setInterval> | null = null;
41
- private readonly loadMap: Map<string, number> = new Map();
42
- private _latestMetadata: PeerMetadata | null = null;
43
-
44
- constructor(config: AnnouncerConfig) {
45
- this.config = config;
46
- }
47
-
48
- async announce(): Promise<void> {
49
- const providers: ProviderAnnouncement[] = this.config.providers.map((p) => {
50
- const pricing = this.config.pricing.get(p.provider) ?? {
51
- defaults: {
52
- inputUsdPerMillion: 0,
53
- outputUsdPerMillion: 0,
54
- },
55
- };
56
- return {
57
- provider: p.provider,
58
- models: p.models,
59
- defaultPricing: pricing.defaults,
60
- ...(pricing.models ? { modelPricing: pricing.models } : {}),
61
- maxConcurrency: p.maxConcurrency,
62
- currentLoad: this.loadMap.get(p.provider) ?? 0,
63
- };
64
- });
65
-
66
- const metadata: PeerMetadata = {
67
- peerId: this.config.identity.peerId,
68
- version: METADATA_VERSION,
69
- providers,
70
- ...(this.config.offerings && this.config.offerings.length > 0
71
- ? { offerings: this.config.offerings }
72
- : {}),
73
- ...(this.config.stakeAmountUSDC != null
74
- ? { stakeAmountUSDC: this.config.stakeAmountUSDC }
75
- : {}),
76
- ...(this.config.trustScore != null
77
- ? { trustScore: this.config.trustScore }
78
- : {}),
79
- region: this.config.region,
80
- timestamp: Date.now(),
81
- signature: "",
82
- };
83
-
84
- // Populate EVM address and on-chain reputation if escrow client is available
85
- if (this.config.escrowClient) {
86
- try {
87
- const evmAddress = identityToEvmAddress(this.config.identity);
88
- metadata.evmAddress = evmAddress;
89
- const reputation = await this.config.escrowClient.getReputation(evmAddress);
90
- metadata.onChainReputation = reputation.weightedAverage;
91
- metadata.onChainSessionCount = reputation.sessionCount;
92
- metadata.onChainDisputeCount = reputation.disputeCount;
93
- } catch {
94
- // Silently continue without reputation data
95
- }
96
- }
97
-
98
- // Sign metadata
99
- const dataToSign = encodeMetadataForSigning(metadata);
100
- const signature = await signData(
101
- this.config.identity.privateKey,
102
- dataToSign
103
- );
104
- metadata.signature = bytesToHex(signature);
105
- this._latestMetadata = metadata;
106
-
107
- // Announce under each provider topic (continue on failure)
108
- for (const p of providers) {
109
- try {
110
- const topic = providerTopic(p.provider);
111
- const infoHash = topicToInfoHash(topic);
112
- await this.config.dht.announce(infoHash, this.config.signalingPort);
113
- } catch {
114
- // DHT may not have peers yet — will retry on next cycle
115
- }
116
- }
117
-
118
- // Also announce under the wildcard topic for generic discovery
119
- try {
120
- const wildcardInfoHash = topicToInfoHash(providerTopic("*"));
121
- await this.config.dht.announce(wildcardInfoHash, this.config.signalingPort);
122
- } catch {
123
- // DHT may not have peers yet — will retry on next cycle
124
- }
125
-
126
- // Announce under each capability topic
127
- if (this.config.offerings) {
128
- for (const offering of this.config.offerings) {
129
- try {
130
- const topic = capabilityTopic(offering.capability, offering.name);
131
- const infoHash = topicToInfoHash(topic);
132
- await this.config.dht.announce(infoHash, this.config.signalingPort);
133
- } catch {
134
- // DHT may not have peers yet — will retry on next cycle
135
- }
136
- }
137
- }
138
- }
139
-
140
- startPeriodicAnnounce(): void {
141
- if (this.intervalHandle) {
142
- return;
143
- }
144
- // Announce immediately, then on interval
145
- void this.announce().catch((err) => {
146
- debugWarn(`[Announcer] Initial announce failed: ${err instanceof Error ? err.message : err}`);
147
- });
148
- this.intervalHandle = setInterval(() => {
149
- void this.announce().catch((err) => {
150
- debugWarn(`[Announcer] Periodic announce failed: ${err instanceof Error ? err.message : err}`);
151
- });
152
- }, this.config.reannounceIntervalMs);
153
- }
154
-
155
- stopPeriodicAnnounce(): void {
156
- if (this.intervalHandle) {
157
- clearInterval(this.intervalHandle);
158
- this.intervalHandle = null;
159
- }
160
- }
161
-
162
- updateLoad(providerName: string, currentLoad: number): void {
163
- this.loadMap.set(providerName, currentLoad);
164
- }
165
-
166
- getLatestMetadata(): PeerMetadata | null {
167
- return this._latestMetadata;
168
- }
169
- }
@@ -1,57 +0,0 @@
1
- export interface BootstrapNode {
2
- host: string;
3
- port: number;
4
- label?: string;
5
- }
6
-
7
- export const OFFICIAL_BOOTSTRAP_NODES: BootstrapNode[] = [
8
- { host: "router.bittorrent.com", port: 6881, label: "BitTorrent" },
9
- { host: "dht.transmissionbt.com", port: 6881, label: "Transmission" },
10
- { host: "router.utorrent.com", port: 6881, label: "uTorrent" },
11
- { host: "dht.libtorrent.org", port: 25401, label: "libtorrent" },
12
- { host: "router.silotis.us", port: 6881, label: "Silotis" },
13
- { host: "dht.aelitis.com", port: 6881, label: "Vuze" },
14
- ];
15
-
16
- export function parseBootstrapList(entries: string[]): BootstrapNode[] {
17
- return entries.map((entry) => {
18
- const lastColon = entry.lastIndexOf(":");
19
- if (lastColon === -1) {
20
- throw new Error(`Invalid bootstrap entry, missing port: "${entry}"`);
21
- }
22
-
23
- const host = entry.slice(0, lastColon);
24
- const portStr = entry.slice(lastColon + 1);
25
- const port = parseInt(portStr, 10);
26
-
27
- if (!Number.isFinite(port) || port < 1 || port > 65535) {
28
- throw new Error(`Invalid port in bootstrap entry: "${entry}"`);
29
- }
30
-
31
- return { host, port };
32
- });
33
- }
34
-
35
- export function mergeBootstrapNodes(
36
- official: BootstrapNode[],
37
- userConfigured: BootstrapNode[]
38
- ): BootstrapNode[] {
39
- const seen = new Set<string>();
40
- const result: BootstrapNode[] = [];
41
-
42
- for (const node of [...official, ...userConfigured]) {
43
- const key = `${node.host}:${node.port}`;
44
- if (!seen.has(key)) {
45
- seen.add(key);
46
- result.push(node);
47
- }
48
- }
49
-
50
- return result;
51
- }
52
-
53
- export function toBootstrapConfig(
54
- nodes: BootstrapNode[]
55
- ): Array<{ host: string; port: number }> {
56
- return nodes.map((n) => ({ host: n.host, port: n.port }));
57
- }
@@ -1,18 +0,0 @@
1
- import type { PeerEndpoint, MetadataResolver } from "./metadata-resolver.js";
2
- import type { PeerMetadata } from "./peer-metadata.js";
3
-
4
- /**
5
- * Default fail-closed metadata resolver.
6
- *
7
- * Always returns `null`, meaning no peer metadata can be resolved.
8
- * This is intentional: without a real transport-specific resolver wired in,
9
- * no peers will pass the metadata resolution step and `findSellers()` will
10
- * return an empty list. Callers must supply a concrete `MetadataResolver`
11
- * implementation (e.g. one that fetches metadata over HTTP or a P2P channel)
12
- * to discover real peers.
13
- */
14
- export class DefaultMetadataResolver implements MetadataResolver {
15
- async resolve(_peer: PeerEndpoint): Promise<PeerMetadata | null> {
16
- return null;
17
- }
18
- }
@@ -1,136 +0,0 @@
1
- export interface DHTHealthSnapshot {
2
- nodeCount: number;
3
- totalLookups: number;
4
- successfulLookups: number;
5
- failedLookups: number;
6
- totalAnnounces: number;
7
- successfulAnnounces: number;
8
- failedAnnounces: number;
9
- averageLookupLatencyMs: number;
10
- isHealthy: boolean;
11
- }
12
-
13
- export interface HealthThresholds {
14
- minNodeCount: number;
15
- minLookupSuccessRate: number;
16
- maxAvgLookupLatencyMs: number;
17
- }
18
-
19
- export const DEFAULT_HEALTH_THRESHOLDS: HealthThresholds = {
20
- minNodeCount: 5,
21
- minLookupSuccessRate: 0.3,
22
- maxAvgLookupLatencyMs: 15000,
23
- };
24
-
25
- export class DHTHealthMonitor {
26
- private readonly thresholds: HealthThresholds;
27
- private readonly getNodeCount: () => number;
28
- private _totalLookups = 0;
29
- private _successfulLookups = 0;
30
- private _failedLookups = 0;
31
- private _totalAnnounces = 0;
32
- private _successfulAnnounces = 0;
33
- private _failedAnnounces = 0;
34
- private readonly latencySamples: number[] = [];
35
- private static readonly MAX_LATENCY_SAMPLES = 100;
36
-
37
- constructor(
38
- getNodeCount: () => number,
39
- thresholds: HealthThresholds = DEFAULT_HEALTH_THRESHOLDS
40
- ) {
41
- this.getNodeCount = getNodeCount;
42
- this.thresholds = thresholds;
43
- }
44
-
45
- recordLookup(success: boolean, latencyMs: number): void {
46
- this._totalLookups++;
47
- if (success) {
48
- this._successfulLookups++;
49
- } else {
50
- this._failedLookups++;
51
- }
52
-
53
- this.latencySamples.push(latencyMs);
54
- if (this.latencySamples.length > DHTHealthMonitor.MAX_LATENCY_SAMPLES) {
55
- this.latencySamples.shift();
56
- }
57
- }
58
-
59
- recordAnnounce(success: boolean): void {
60
- this._totalAnnounces++;
61
- if (success) {
62
- this._successfulAnnounces++;
63
- } else {
64
- this._failedAnnounces++;
65
- }
66
- }
67
-
68
- getSnapshot(): DHTHealthSnapshot {
69
- const nodeCount = this.getNodeCount();
70
- const averageLookupLatencyMs = this.computeAverageLatency();
71
- const healthy = this.evaluateHealth(nodeCount, averageLookupLatencyMs);
72
-
73
- return {
74
- nodeCount,
75
- totalLookups: this._totalLookups,
76
- successfulLookups: this._successfulLookups,
77
- failedLookups: this._failedLookups,
78
- totalAnnounces: this._totalAnnounces,
79
- successfulAnnounces: this._successfulAnnounces,
80
- failedAnnounces: this._failedAnnounces,
81
- averageLookupLatencyMs,
82
- isHealthy: healthy,
83
- };
84
- }
85
-
86
- isHealthy(): boolean {
87
- const nodeCount = this.getNodeCount();
88
- const averageLookupLatencyMs = this.computeAverageLatency();
89
- return this.evaluateHealth(nodeCount, averageLookupLatencyMs);
90
- }
91
-
92
- reset(): void {
93
- this._totalLookups = 0;
94
- this._successfulLookups = 0;
95
- this._failedLookups = 0;
96
- this._totalAnnounces = 0;
97
- this._successfulAnnounces = 0;
98
- this._failedAnnounces = 0;
99
- this.latencySamples.length = 0;
100
- }
101
-
102
- private computeAverageLatency(): number {
103
- if (this.latencySamples.length === 0) {
104
- return 0;
105
- }
106
- const sum = this.latencySamples.reduce((a, b) => a + b, 0);
107
- return sum / this.latencySamples.length;
108
- }
109
-
110
- private evaluateHealth(
111
- nodeCount: number,
112
- averageLookupLatencyMs: number
113
- ): boolean {
114
- // Must have minimum node count
115
- if (nodeCount < this.thresholds.minNodeCount) {
116
- return false;
117
- }
118
-
119
- // Check lookup success rate (only if we have 5+ lookups)
120
- if (this._totalLookups >= 5) {
121
- const successRate = this._successfulLookups / this._totalLookups;
122
- if (successRate < this.thresholds.minLookupSuccessRate) {
123
- return false;
124
- }
125
- }
126
-
127
- // Check average latency (only if we have 5+ samples)
128
- if (this.latencySamples.length >= 5) {
129
- if (averageLookupLatencyMs > this.thresholds.maxAvgLookupLatencyMs) {
130
- return false;
131
- }
132
- }
133
-
134
- return true;
135
- }
136
- }