golem-vm-provider 0.1.53__py3-none-any.whl → 0.1.55__py3-none-any.whl
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.
- {golem_vm_provider-0.1.53.dist-info → golem_vm_provider-0.1.55.dist-info}/METADATA +66 -29
- {golem_vm_provider-0.1.53.dist-info → golem_vm_provider-0.1.55.dist-info}/RECORD +10 -10
- provider/config.py +111 -14
- provider/discovery/golem_base_advertiser.py +1 -0
- provider/main.py +3 -0
- provider/payments/monitor.py +27 -7
- provider/security/l2_faucet.py +4 -1
- provider/service.py +62 -0
- {golem_vm_provider-0.1.53.dist-info → golem_vm_provider-0.1.55.dist-info}/WHEEL +0 -0
- {golem_vm_provider-0.1.53.dist-info → golem_vm_provider-0.1.55.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: golem-vm-provider
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.55
|
4
4
|
Summary: VM on Golem Provider Node - Run your own provider node to offer VMs on the Golem Network
|
5
5
|
Keywords: golem,vm,provider,cloud,decentralized
|
6
6
|
Author: Phillip Jensen
|
@@ -45,7 +45,35 @@ Description-Content-Type: text/markdown
|
|
45
45
|
|
46
46
|
# VM on Golem Provider Node
|
47
47
|
|
48
|
-
|
48
|
+
Earn by renting out your machine’s compute — like Airbnb for servers. The Provider service runs VMs for requestors, verifies payments via streaming, and lets you withdraw earnings.
|
49
|
+
|
50
|
+
## Quick Start (Host and Earn)
|
51
|
+
|
52
|
+
1) Install (Python 3.11+ recommended):
|
53
|
+
|
54
|
+
```bash
|
55
|
+
pip install golem-vm-provider
|
56
|
+
```
|
57
|
+
|
58
|
+
2) Start the provider (testnet by default is fine):
|
59
|
+
|
60
|
+
```bash
|
61
|
+
golem-provider start --network testnet
|
62
|
+
```
|
63
|
+
|
64
|
+
3) Set pricing in USD (GLM rates auto‑compute):
|
65
|
+
|
66
|
+
```bash
|
67
|
+
golem-provider pricing set --usd-per-core 12 --usd-per-mem 4 --usd-per-disk 0.1
|
68
|
+
```
|
69
|
+
|
70
|
+
4) On testnets, optionally fund gas for withdrawals:
|
71
|
+
|
72
|
+
```bash
|
73
|
+
golem-provider wallet faucet-l2
|
74
|
+
```
|
75
|
+
|
76
|
+
You are now discoverable to requestors and will earn as your VMs run.
|
49
77
|
|
50
78
|
## System Architecture
|
51
79
|
|
@@ -178,26 +206,26 @@ sequenceDiagram
|
|
178
206
|
- Clean connection handling
|
179
207
|
- Automatic proxy cleanup
|
180
208
|
|
181
|
-
## Installation
|
209
|
+
## Installation (from source / development)
|
182
210
|
|
183
211
|
1. Prerequisites:
|
212
|
+
- Python 3.11+
|
213
|
+
- Multipass
|
214
|
+
- Poetry (for development)
|
184
215
|
|
185
|
-
|
186
|
-
- Multipass
|
187
|
-
- Poetry
|
216
|
+
2. Install from source:
|
188
217
|
|
189
|
-
|
218
|
+
```bash
|
219
|
+
cd provider-server
|
220
|
+
poetry install
|
221
|
+
```
|
190
222
|
|
191
|
-
|
192
|
-
cd provider-server
|
193
|
-
poetry install
|
194
|
-
```
|
223
|
+
3. Local environment (optional):
|
195
224
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
```
|
225
|
+
```bash
|
226
|
+
cp .env.example .env
|
227
|
+
# Edit .env to tweak defaults if needed
|
228
|
+
```
|
201
229
|
|
202
230
|
## Configuration
|
203
231
|
|
@@ -227,9 +255,9 @@ GOLEM_PROVIDER_PORT_RANGE_START={start_port} # Default: 50800
|
|
227
255
|
GOLEM_PROVIDER_PORT_RANGE_END={end_port} # Default: 50900
|
228
256
|
GOLEM_PROVIDER_PUBLIC_IP="auto"
|
229
257
|
|
230
|
-
#
|
231
|
-
GOLEM_PROVIDER_DISCOVERY_URL="http://discovery.golem.network:9001"
|
232
|
-
GOLEM_PROVIDER_ADVERTISEMENT_INTERVAL=240
|
258
|
+
# Legacy discovery (optional; not required in normal operation)
|
259
|
+
# GOLEM_PROVIDER_DISCOVERY_URL="http://discovery.golem.network:9001"
|
260
|
+
# GOLEM_PROVIDER_ADVERTISEMENT_INTERVAL=240
|
233
261
|
|
234
262
|
# Network Selection
|
235
263
|
# Adds an annotation to on-chain advertisements and can be used by requestors to filter
|
@@ -266,7 +294,7 @@ When enabled, the provider verifies each VM creation request’s `stream_id` and
|
|
266
294
|
- deposit is zero, stream not started, or stream halted
|
267
295
|
- (Optional) remaining runway < `STREAM_MIN_REMAINING_SECONDS`
|
268
296
|
|
269
|
-
## API Reference
|
297
|
+
## API Reference (for integrators)
|
270
298
|
|
271
299
|
### Create VM
|
272
300
|
|
@@ -372,11 +400,11 @@ Notes:
|
|
372
400
|
### Starting the Provider
|
373
401
|
|
374
402
|
```bash
|
375
|
-
#
|
376
|
-
|
403
|
+
# Production mode
|
404
|
+
golem-provider start
|
377
405
|
|
378
|
-
#
|
379
|
-
GOLEM_PROVIDER_ENVIRONMENT=development
|
406
|
+
# Development mode with extra logs and reload
|
407
|
+
GOLEM_PROVIDER_ENVIRONMENT=development golem-provider start --network testnet
|
380
408
|
```
|
381
409
|
|
382
410
|
### Mode vs. Network
|
@@ -390,6 +418,9 @@ GOLEM_PROVIDER_ENVIRONMENT=development poetry run golem-provider start --network
|
|
390
418
|
- Pair with appropriate RPC envs (`GOLEM_PROVIDER_GOLEM_BASE_RPC_URL`, `GOLEM_PROVIDER_GOLEM_BASE_WS_URL`).
|
391
419
|
- Does not change dev ergonomics (logging, reload, or port verification behavior).
|
392
420
|
|
421
|
+
- Payments Network (`GOLEM_PROVIDER_PAYMENTS_NETWORK`)
|
422
|
+
- Selects the payments chain profile (e.g., `l2.holesky`, `mainnet`). Determines default payments RPC, faucet enablement, and symbols.
|
423
|
+
|
393
424
|
Common setups:
|
394
425
|
- Local dev on testnet: `GOLEM_PROVIDER_ENVIRONMENT=development` plus `--network testnet`.
|
395
426
|
- Staging on testnet: keep `ENVIRONMENT=production`, set `--network testnet` and testnet RPCs.
|
@@ -406,17 +437,20 @@ The provider will:
|
|
406
437
|
4. Begin resource advertisement
|
407
438
|
5. Listen for VM requests
|
408
439
|
|
440
|
+
Notes:
|
441
|
+
- Advertisements include both `golem_network` (testnet/mainnet) and `golem_payments_network` (e.g., `l2.holesky`). Requestors default to matching both; they can list all payments networks with a CLI flag.
|
442
|
+
|
409
443
|
### Faucet
|
410
444
|
|
411
445
|
- L3 (Golem Base adverts): provider auto-requests funds on startup from `FAUCET_URL` (defaults to EthWarsaw Holesky) protected by CAPTCHA at `CAPTCHA_URL/05381a2cef5e`.
|
412
|
-
- L2 (payments): Use the CLI to request native ETH:
|
446
|
+
- L2 (payments): Use the CLI to request native ETH (enabled only on testnet profiles):
|
413
447
|
|
414
448
|
```bash
|
415
|
-
|
449
|
+
golem-provider wallet faucet-l2
|
416
450
|
```
|
417
451
|
|
418
452
|
Defaults:
|
419
|
-
-
|
453
|
+
- Faucet URL and enablement come from the active payments profile. On `mainnet` (or other profiles without faucet) the command is disabled.
|
420
454
|
- CAPTCHA: `https://cap.gobas.me/05381a2cef5e`
|
421
455
|
- Override with env: `GOLEM_PROVIDER_L2_FAUCET_URL`, `GOLEM_PROVIDER_L2_CAPTCHA_URL`, `GOLEM_PROVIDER_L2_CAPTCHA_API_KEY`.
|
422
456
|
|
@@ -460,10 +494,10 @@ Configure monitor and withdraw via CLI:
|
|
460
494
|
|
461
495
|
```bash
|
462
496
|
# Set monitor to require 1h remaining, check every 30s
|
463
|
-
|
497
|
+
golem-provider config monitor --enable true --interval 30 --min-remaining 3600
|
464
498
|
|
465
499
|
# Enable auto-withdraw every 15 minutes when >= 1e15 wei
|
466
|
-
|
500
|
+
golem-provider config withdraw --enable true --interval 900 --min-wei 1000000000000000
|
467
501
|
```
|
468
502
|
|
469
503
|
### Resource Advertisement Flow
|
@@ -476,6 +510,7 @@ sequenceDiagram
|
|
476
510
|
participant DS as Discovery Service
|
477
511
|
|
478
512
|
P->>RT: Initialize
|
513
|
+
P->>RT: Sync with existing VMs
|
479
514
|
RT->>AD: Register Callback
|
480
515
|
loop Every 4 minutes
|
481
516
|
AD->>RT: Get Resources
|
@@ -485,6 +520,8 @@ sequenceDiagram
|
|
485
520
|
end
|
486
521
|
```
|
487
522
|
|
523
|
+
On startup, the provider syncs the resource tracker with all VMs currently running on the host (via Multipass). This ensures advertisements reflect already-allocated CPU, RAM, and storage after restarts, preventing false “outdated advertisement” updates when existing VMs are consuming resources. The sync is based on actual VMs present, independent of any still-open payment streams.
|
524
|
+
|
488
525
|
### Monitoring
|
489
526
|
|
490
527
|
The provider includes comprehensive logging:
|
@@ -2,26 +2,26 @@ provider/__init__.py,sha256=HO1fkPpZqPO3z8O8-eVIyx8xXSMIVuTR_b1YF0RtXOg,45
|
|
2
2
|
provider/api/__init__.py,sha256=ssX1ugDqEPt8Fn04IymgmG-Ev8PiXLsCSaiZVvHQnec,344
|
3
3
|
provider/api/models.py,sha256=CmfgXqSH3m0HLqY6JvUFI-2IrdGf3EhNKtZ5kbIAX-U,4304
|
4
4
|
provider/api/routes.py,sha256=RaOhdUZLJVmCHFWHyhYF9kdBmsFSe5rThIYsW6meMrQ,13194
|
5
|
-
provider/config.py,sha256=
|
5
|
+
provider/config.py,sha256=IDeAYQ4z8oaT5HcG9jFQhSZrLlLU6wMTGDTbSxK6FSc,28901
|
6
6
|
provider/container.py,sha256=81x5LiA-qjYN1Uh_JdOxqvuIXiNDr9X3OXNN0VqYFCI,3681
|
7
7
|
provider/data/deployments/l2.json,sha256=XTNN2C5LkBfp4YbDKdUKfWMdp1fKnfv8D3TgcwVWxtQ,249
|
8
8
|
provider/discovery/__init__.py,sha256=Y6o8RxGevBpuQS3k32y-zSVbP6HBXG3veBl9ElVPKaU,349
|
9
9
|
provider/discovery/advertiser.py,sha256=o-LiDl1j0lXMUU0-zPe3qerjpoD2360EA60Y_V_VeBc,6571
|
10
|
-
provider/discovery/golem_base_advertiser.py,sha256=
|
10
|
+
provider/discovery/golem_base_advertiser.py,sha256=A8bg40b2Ua7PIjx3Y8-SC0s-dUUPWxaiQCzr6AcpYaQ,7334
|
11
11
|
provider/discovery/golem_base_utils.py,sha256=xk7vznhMgzrn0AuGyk6-9N9ukp9oPdBbbk1RI-sVjp0,607
|
12
12
|
provider/discovery/multi_advertiser.py,sha256=_J79wA1-XQ4GsLzt9KrKpWigGSGBqtut7DaocIk2fyE,991
|
13
13
|
provider/discovery/resource_monitor.py,sha256=AmiEc7yBGEGXCunQ-QKmVgosDX3gOhK1Y58LJZXrwAs,949
|
14
14
|
provider/discovery/resource_tracker.py,sha256=MP7IXd3aIMsjB4xz5Oj9zFDTEnvrnw-Cyxpl33xcJcc,6006
|
15
15
|
provider/discovery/service.py,sha256=vX_mVSxvn3arnb2cKDM_SeJp1ZgPdImP2aUubeXgdRg,915
|
16
|
-
provider/main.py,sha256=
|
16
|
+
provider/main.py,sha256=_j92g56B-d8CE09Ugv0fqWVMi5jw_iuTrysxSw7845A,32309
|
17
17
|
provider/network/port_verifier.py,sha256=3l6WNwBHydggJRFYkAsuBp1eCxaU619kjWuM-zSVj2o,13267
|
18
18
|
provider/payments/blockchain_service.py,sha256=4GrzDKwCSUVoENqjD4RLyJ0qwBOJKMyVk5Li-XNsyTc,3567
|
19
|
-
provider/payments/monitor.py,sha256=
|
19
|
+
provider/payments/monitor.py,sha256=seo8vE622IdbcRE3x69IpvHn2mel_tlMNGt_DxOIoww,5386
|
20
20
|
provider/payments/stream_map.py,sha256=qk6Y8hS72DplAifZ0ZMWPHBAyc_3IWIQyWUBuCU3_To,1191
|
21
21
|
provider/security/ethereum.py,sha256=EwPZj4JR8OEpto6LhKjuuT3Z9pBX6P7-UQaqJtqFkYQ,1242
|
22
22
|
provider/security/faucet.py,sha256=8T4lW1fVQgUk8EQILgbrr9UUosw9e7eA40tlZ2_KCPQ,4368
|
23
|
-
provider/security/l2_faucet.py,sha256=
|
24
|
-
provider/service.py,sha256=
|
23
|
+
provider/security/l2_faucet.py,sha256=yRV4xdPBgU8-LDTLqtuAijfgIoe2kYxvXqJLxFd-BVI,2662
|
24
|
+
provider/service.py,sha256=hlQn0woppsYFHZDMEgq-40cOjmiPWruiWLy_dQvaCRU,6859
|
25
25
|
provider/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
26
26
|
provider/utils/ascii_art.py,sha256=ykBFsztk57GIiz1NJ-EII5UvN74iECqQL4h9VmiW6Z8,3161
|
27
27
|
provider/utils/logging.py,sha256=VV3oTYSRT8hUejtXLuua1M6kCHmIJgPspIkzsUVhYW0,1920
|
@@ -39,7 +39,7 @@ provider/vm/port_manager.py,sha256=iYSwjTjD_ziOhG8aI7juKHw1OwwRUTJQyQoRUNQvz9w,1
|
|
39
39
|
provider/vm/provider.py,sha256=A7QN89EJjcSS40_SmKeinG1Jp_NGffJaLse-XdKciAs,1164
|
40
40
|
provider/vm/proxy_manager.py,sha256=n4NTsyz2rtrvjtf_ceKBk-g2q_mzqPwruB1q7UlQVBc,14928
|
41
41
|
provider/vm/service.py,sha256=Ki4SGNIZUq3XmaPMwAOoNzdZzKQsmFXid374wgjFPes,4636
|
42
|
-
golem_vm_provider-0.1.
|
43
|
-
golem_vm_provider-0.1.
|
44
|
-
golem_vm_provider-0.1.
|
45
|
-
golem_vm_provider-0.1.
|
42
|
+
golem_vm_provider-0.1.55.dist-info/METADATA,sha256=_GZ2hyX-aeTtK--VOiSE4tZfAnQwANw21tO63EvOskY,18877
|
43
|
+
golem_vm_provider-0.1.55.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
44
|
+
golem_vm_provider-0.1.55.dist-info/entry_points.txt,sha256=5Jiie1dIXygmxmDW66bKKxQpmBLJ7leSKRrb8bkQALw,52
|
45
|
+
golem_vm_provider-0.1.55.dist-info/RECORD,,
|
provider/config.py
CHANGED
@@ -57,6 +57,17 @@ class Settings(BaseSettings):
|
|
57
57
|
# Logical network selector for annotation and client defaults
|
58
58
|
NETWORK: str = "mainnet" # one of: "testnet", "mainnet"
|
59
59
|
|
60
|
+
# Payments chain selection (modular network profiles). Keep default on l2.holesky
|
61
|
+
PAYMENTS_NETWORK: str = Field(
|
62
|
+
default="l2.holesky",
|
63
|
+
description="Payments network profile (e.g., 'l2.holesky', 'kaolin.holesky', 'mainnet')"
|
64
|
+
)
|
65
|
+
|
66
|
+
@field_validator("PAYMENTS_NETWORK", mode='before')
|
67
|
+
@classmethod
|
68
|
+
def prefer_payments_network_env(cls, v: str) -> str:
|
69
|
+
return os.environ.get("GOLEM_PROVIDER_PAYMENTS_NETWORK", v)
|
70
|
+
|
60
71
|
@property
|
61
72
|
def DEV_MODE(self) -> bool:
|
62
73
|
return self.ENVIRONMENT == "development"
|
@@ -138,8 +149,8 @@ class Settings(BaseSettings):
|
|
138
149
|
|
139
150
|
# Polygon / Payments
|
140
151
|
POLYGON_RPC_URL: str = Field(
|
141
|
-
default="
|
142
|
-
description="EVM RPC URL for streaming payments
|
152
|
+
default="",
|
153
|
+
description="EVM RPC URL for streaming payments; defaults from PAYMENTS_NETWORK profile"
|
143
154
|
)
|
144
155
|
STREAM_PAYMENT_ADDRESS: str = Field(
|
145
156
|
default="",
|
@@ -181,8 +192,8 @@ class Settings(BaseSettings):
|
|
181
192
|
|
182
193
|
# L2 payments faucet (native ETH)
|
183
194
|
L2_FAUCET_URL: str = Field(
|
184
|
-
default="
|
185
|
-
description="
|
195
|
+
default="",
|
196
|
+
description="Faucet base URL (no trailing /api). Only used on testnets; defaults from PAYMENTS_NETWORK profile"
|
186
197
|
)
|
187
198
|
L2_CAPTCHA_URL: str = Field(
|
188
199
|
default="https://cap.gobas.me",
|
@@ -193,17 +204,42 @@ class Settings(BaseSettings):
|
|
193
204
|
description="CAPTCHA API key path segment"
|
194
205
|
)
|
195
206
|
|
207
|
+
@field_validator("L2_CAPTCHA_URL", mode='before')
|
208
|
+
@classmethod
|
209
|
+
def prefer_l2_captcha_url(cls, v: str) -> str:
|
210
|
+
return os.environ.get("GOLEM_PROVIDER_L2_CAPTCHA_URL", v)
|
211
|
+
|
212
|
+
@field_validator("L2_CAPTCHA_API_KEY", mode='before')
|
213
|
+
@classmethod
|
214
|
+
def prefer_l2_captcha_key(cls, v: str) -> str:
|
215
|
+
return os.environ.get("GOLEM_PROVIDER_L2_CAPTCHA_API_KEY", v)
|
216
|
+
|
196
217
|
@field_validator("POLYGON_RPC_URL", mode='before')
|
197
218
|
@classmethod
|
198
|
-
def prefer_custom_env(cls, v: str) -> str:
|
219
|
+
def prefer_custom_env(cls, v: str, values: dict) -> str:
|
199
220
|
# Accept alternative aliases for payments RPC
|
200
221
|
for key in ("GOLEM_PROVIDER_L2_RPC_URL", "GOLEM_PROVIDER_KAOLIN_RPC_URL"):
|
201
222
|
if os.environ.get(key):
|
202
223
|
return os.environ[key]
|
203
|
-
|
224
|
+
if v:
|
225
|
+
return v
|
226
|
+
# Default from profile
|
227
|
+
pn = values.data.get("PAYMENTS_NETWORK") or "l2.holesky"
|
228
|
+
return Settings._profile_defaults(pn)["rpc_url"]
|
229
|
+
|
230
|
+
@field_validator("L2_FAUCET_URL", mode='before')
|
231
|
+
@classmethod
|
232
|
+
def prefer_faucet_env(cls, v: str, values: dict) -> str:
|
233
|
+
for key in ("GOLEM_PROVIDER_L2_FAUCET_URL",):
|
234
|
+
if os.environ.get(key):
|
235
|
+
return os.environ[key]
|
236
|
+
if v:
|
237
|
+
return v
|
238
|
+
pn = values.data.get("PAYMENTS_NETWORK") or "l2.holesky"
|
239
|
+
return Settings._profile_defaults(pn).get("faucet_url", "")
|
204
240
|
|
205
241
|
@staticmethod
|
206
|
-
def
|
242
|
+
def _load_deployment(network: str) -> tuple[str | None, str | None]:
|
207
243
|
"""Try to load default StreamPayment + token from contracts/deployments/l2.json.
|
208
244
|
|
209
245
|
Returns (stream_payment_address, glm_token_address) or (None, None) if not found.
|
@@ -212,15 +248,15 @@ class Settings(BaseSettings):
|
|
212
248
|
# Allow override via env
|
213
249
|
base = os.environ.get("GOLEM_DEPLOYMENTS_DIR")
|
214
250
|
if base:
|
215
|
-
path = Path(base) / "
|
251
|
+
path = Path(base) / f"{Settings._deployment_basename(network)}.json"
|
216
252
|
else:
|
217
253
|
# repo root = ../../ from this file
|
218
|
-
path = Path(__file__).resolve().parents[2] / "contracts" / "deployments" / "
|
254
|
+
path = Path(__file__).resolve().parents[2] / "contracts" / "deployments" / f"{Settings._deployment_basename(network)}.json"
|
219
255
|
if not path.exists():
|
220
256
|
# Try package resource fallback
|
221
257
|
try:
|
222
258
|
import importlib.resources as ir
|
223
|
-
with ir.files("provider.data.deployments").joinpath("
|
259
|
+
with ir.files("provider.data.deployments").joinpath(f"{Settings._deployment_basename(network)}.json").open("r") as fh: # type: ignore[attr-defined]
|
224
260
|
data = json.load(fh)
|
225
261
|
except Exception:
|
226
262
|
return None, None
|
@@ -235,29 +271,90 @@ class Settings(BaseSettings):
|
|
235
271
|
pass
|
236
272
|
return None, None
|
237
273
|
|
274
|
+
# Backwards-compat helper used by tests expecting this method name
|
275
|
+
@staticmethod
|
276
|
+
def _load_l2_deployment() -> tuple[str | None, str | None]:
|
277
|
+
return Settings._load_deployment("l2.holesky")
|
278
|
+
|
279
|
+
@staticmethod
|
280
|
+
def _deployment_basename(network: str) -> str:
|
281
|
+
n = (network or "").lower()
|
282
|
+
if n in ("l2", "l2.holesky"):
|
283
|
+
return "l2"
|
284
|
+
if "." in n:
|
285
|
+
return n.split(".")[0]
|
286
|
+
return n or "l2"
|
287
|
+
|
288
|
+
@staticmethod
|
289
|
+
def _profile_defaults(network: str) -> dict[str, str | bool]:
|
290
|
+
n = (network or "l2.holesky").lower()
|
291
|
+
profiles = {
|
292
|
+
"l2.holesky": {
|
293
|
+
"rpc_url": "https://l2.holesky.golemdb.io/rpc",
|
294
|
+
"faucet_url": "https://l2.holesky.golemdb.io/faucet",
|
295
|
+
"faucet_enabled": True,
|
296
|
+
"token_symbol": "GLM",
|
297
|
+
"gas_symbol": "ETH",
|
298
|
+
},
|
299
|
+
"mainnet": {
|
300
|
+
"rpc_url": "",
|
301
|
+
"faucet_url": "",
|
302
|
+
"faucet_enabled": False,
|
303
|
+
"token_symbol": "GLM",
|
304
|
+
"gas_symbol": "ETH",
|
305
|
+
},
|
306
|
+
}
|
307
|
+
return profiles.get(n, profiles["l2.holesky"]) # default to current standard
|
308
|
+
|
238
309
|
@field_validator("STREAM_PAYMENT_ADDRESS", mode='before')
|
239
310
|
@classmethod
|
240
|
-
def default_stream_addr(cls, v: str) -> str:
|
311
|
+
def default_stream_addr(cls, v: str, values: dict) -> str:
|
241
312
|
# Disable payments during pytest to keep unit tests independent
|
242
313
|
if os.environ.get("PYTEST_CURRENT_TEST"):
|
243
314
|
return "0x0000000000000000000000000000000000000000"
|
244
315
|
if v:
|
245
316
|
return v
|
246
|
-
|
317
|
+
pn = values.data.get("PAYMENTS_NETWORK") or "l2.holesky"
|
318
|
+
addr, _ = Settings._load_deployment(pn)
|
247
319
|
return addr or "0x0000000000000000000000000000000000000000"
|
248
320
|
|
249
321
|
@field_validator("GLM_TOKEN_ADDRESS", mode='before')
|
250
322
|
@classmethod
|
251
|
-
def default_token_addr(cls, v: str) -> str:
|
323
|
+
def default_token_addr(cls, v: str, values: dict) -> str:
|
252
324
|
if os.environ.get("PYTEST_CURRENT_TEST"):
|
253
325
|
return "0x0000000000000000000000000000000000000000"
|
254
326
|
if v:
|
255
327
|
return v
|
256
|
-
|
328
|
+
pn = values.data.get("PAYMENTS_NETWORK") or "l2.holesky"
|
329
|
+
_, token = Settings._load_deployment(pn)
|
257
330
|
return token or "0x0000000000000000000000000000000000000000"
|
258
331
|
|
259
332
|
# VM Settings
|
260
333
|
MAX_VMS: int = 10
|
334
|
+
|
335
|
+
# Optional human-friendly symbols from profile
|
336
|
+
TOKEN_SYMBOL: str = Field(default="", description="Payment token symbol, e.g., GLM")
|
337
|
+
GAS_TOKEN_SYMBOL: str = Field(default="", description="Gas token symbol, e.g., ETH")
|
338
|
+
|
339
|
+
@field_validator("TOKEN_SYMBOL", mode='before')
|
340
|
+
@classmethod
|
341
|
+
def default_token_symbol(cls, v: str, values: dict) -> str:
|
342
|
+
if v:
|
343
|
+
return v
|
344
|
+
pn = values.data.get("PAYMENTS_NETWORK") or "l2.holesky"
|
345
|
+
return str(Settings._profile_defaults(pn).get("token_symbol", ""))
|
346
|
+
|
347
|
+
@field_validator("GAS_TOKEN_SYMBOL", mode='before')
|
348
|
+
@classmethod
|
349
|
+
def default_gas_symbol(cls, v: str, values: dict) -> str:
|
350
|
+
if v:
|
351
|
+
return v
|
352
|
+
pn = values.data.get("PAYMENTS_NETWORK") or "l2.holesky"
|
353
|
+
return str(Settings._profile_defaults(pn).get("gas_symbol", ""))
|
354
|
+
|
355
|
+
@property
|
356
|
+
def FAUCET_ENABLED(self) -> bool:
|
357
|
+
return bool(self._profile_defaults(self.PAYMENTS_NETWORK).get("faucet_enabled", False))
|
261
358
|
DEFAULT_VM_IMAGE: str = "ubuntu:24.04"
|
262
359
|
VM_DATA_DIR: str = ""
|
263
360
|
SSH_KEY_DIR: str = ""
|
@@ -68,6 +68,7 @@ class GolemBaseAdvertiser(Advertiser):
|
|
68
68
|
string_annotations = [
|
69
69
|
Annotation(key="golem_type", value="provider"),
|
70
70
|
Annotation(key="golem_network", value=settings.NETWORK),
|
71
|
+
Annotation(key="golem_payments_network", value=settings.PAYMENTS_NETWORK),
|
71
72
|
Annotation(key="golem_provider_id", value=settings.PROVIDER_ID),
|
72
73
|
Annotation(key="golem_ip_address", value=ip_address),
|
73
74
|
Annotation(key="golem_country", value=settings.PROVIDER_COUNTRY),
|
provider/main.py
CHANGED
@@ -149,6 +149,9 @@ def wallet_faucet_l2():
|
|
149
149
|
from .config import settings
|
150
150
|
from .security.l2_faucet import L2FaucetService
|
151
151
|
try:
|
152
|
+
if not bool(getattr(settings, "FAUCET_ENABLED", False)):
|
153
|
+
print("Faucet is disabled for current payments network.")
|
154
|
+
raise typer.Exit(code=0)
|
152
155
|
addr = settings.PROVIDER_ID
|
153
156
|
async def _run():
|
154
157
|
svc = L2FaucetService(settings)
|
provider/payments/monitor.py
CHANGED
@@ -56,21 +56,41 @@ class StreamMonitor:
|
|
56
56
|
logger.warning(f"stream {stream_id} lookup failed: {e}")
|
57
57
|
continue
|
58
58
|
# Stop VM if remaining runway < threshold
|
59
|
-
remaining = max(s["stopTime"] - now, 0)
|
59
|
+
remaining = max(int(s["stopTime"]) - int(now), 0)
|
60
60
|
logger.debug(
|
61
61
|
f"stream {stream_id} for VM {vm_id}: start={s['startTime']} stop={s['stopTime']} "
|
62
62
|
f"rate={s['ratePerSecond']} withdrawn={s['withdrawn']} halted={s['halted']} remaining={remaining}s"
|
63
63
|
)
|
64
|
-
|
65
|
-
|
64
|
+
# If stream is force-halted, delete immediately to free all resources
|
65
|
+
if bool(s.get("halted")):
|
66
|
+
logger.info(
|
67
|
+
f"Deleting VM {vm_id} due to halted stream (id={stream_id}, now={now})"
|
68
|
+
)
|
69
|
+
try:
|
70
|
+
await self.vm_service.delete_vm(vm_id)
|
71
|
+
except Exception as e:
|
72
|
+
logger.warning(f"delete_vm failed for {vm_id}: {e}")
|
73
|
+
try:
|
74
|
+
await self.stream_map.remove(vm_id)
|
75
|
+
except Exception as e:
|
76
|
+
logger.debug(f"failed to remove vm {vm_id} from stream map: {e}")
|
77
|
+
continue
|
78
|
+
|
79
|
+
# Only stop a VM when runway is completely empty
|
80
|
+
if remaining == 0:
|
81
|
+
logger.info(
|
82
|
+
f"Stopping VM {vm_id} as stream runway is exhausted (id={stream_id}, now={now}, stop={s.get('stopTime')})"
|
83
|
+
)
|
66
84
|
try:
|
67
85
|
await self.vm_service.stop_vm(vm_id)
|
68
86
|
except Exception as e:
|
69
87
|
logger.warning(f"stop_vm failed for {vm_id}: {e}")
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
88
|
+
continue
|
89
|
+
|
90
|
+
# Otherwise, do not stop; just log health and consider withdrawals
|
91
|
+
logger.debug(
|
92
|
+
f"VM {vm_id} stream {stream_id} healthy (remaining={remaining}s)"
|
93
|
+
)
|
74
94
|
# Withdraw if enough vested and configured
|
75
95
|
if self._get("STREAM_WITHDRAW_ENABLED", False) and self.client:
|
76
96
|
vested = max(min(now, s["stopTime"]) - s["startTime"], 0) * s["ratePerSecond"]
|
provider/security/l2_faucet.py
CHANGED
@@ -38,6 +38,10 @@ class L2FaucetService:
|
|
38
38
|
|
39
39
|
Returns tx hash string on payout, or None if skipped/failed.
|
40
40
|
"""
|
41
|
+
# Respect profile gating only if explicitly present and false
|
42
|
+
if hasattr(self.cfg, "FAUCET_ENABLED") and not bool(getattr(self.cfg, "FAUCET_ENABLED")):
|
43
|
+
logger.info("Faucet disabled for current payments network; skipping.")
|
44
|
+
return None
|
41
45
|
bal = self._balance_eth(address)
|
42
46
|
if bal > 0.01:
|
43
47
|
logger.info(f"Sufficient L2 funds ({bal} ETH), skipping faucet.")
|
@@ -60,4 +64,3 @@ class L2FaucetService:
|
|
60
64
|
if tx:
|
61
65
|
logger.success(f"L2 faucet sent tx: {tx}")
|
62
66
|
return tx
|
63
|
-
|
provider/service.py
CHANGED
@@ -37,6 +37,68 @@ class ProviderService:
|
|
37
37
|
# Initialize services
|
38
38
|
await self.port_manager.initialize()
|
39
39
|
await self.vm_service.provider.initialize()
|
40
|
+
|
41
|
+
# Before starting advertisement, sync allocated resources with existing VMs
|
42
|
+
try:
|
43
|
+
vm_resources = await self.vm_service.get_all_vms_resources()
|
44
|
+
await self.vm_service.resource_tracker.sync_with_multipass(vm_resources)
|
45
|
+
except Exception as e:
|
46
|
+
logger.warning(f"Failed to sync resources with existing VMs: {e}")
|
47
|
+
|
48
|
+
# Cross-check running VMs against payment streams. If a VM has no
|
49
|
+
# active stream, it is no longer rented: terminate it and free resources.
|
50
|
+
try:
|
51
|
+
# Only perform checks if payments are configured
|
52
|
+
if settings.STREAM_PAYMENT_ADDRESS and not settings.STREAM_PAYMENT_ADDRESS.lower().endswith("0000000000000000000000000000000000000000") and settings.POLYGON_RPC_URL:
|
53
|
+
stream_map = app.container.stream_map()
|
54
|
+
reader = app.container.stream_reader()
|
55
|
+
|
56
|
+
# Use the most recent view of VMs from the previous sync
|
57
|
+
vm_ids = list(vm_resources.keys()) if 'vm_resources' in locals() else []
|
58
|
+
for vm_id in vm_ids:
|
59
|
+
try:
|
60
|
+
stream_id = await stream_map.get(vm_id)
|
61
|
+
except Exception:
|
62
|
+
stream_id = None
|
63
|
+
|
64
|
+
if stream_id is None:
|
65
|
+
reason = "no stream mapped"
|
66
|
+
should_terminate = True
|
67
|
+
else:
|
68
|
+
try:
|
69
|
+
ok, msg = reader.verify_stream(int(stream_id), settings.PROVIDER_ID)
|
70
|
+
should_terminate = not ok
|
71
|
+
reason = msg if not ok else "ok"
|
72
|
+
except Exception as e:
|
73
|
+
# If verification cannot be performed, be conservative and keep the VM
|
74
|
+
logger.warning(f"Stream verification error for VM {vm_id} (stream {stream_id}): {e}")
|
75
|
+
should_terminate = False
|
76
|
+
reason = f"verification error: {e}"
|
77
|
+
|
78
|
+
if should_terminate:
|
79
|
+
logger.info(
|
80
|
+
f"Deleting VM {vm_id}: inactive stream (stream_id={stream_id}, reason={reason})"
|
81
|
+
)
|
82
|
+
try:
|
83
|
+
await self.vm_service.delete_vm(vm_id)
|
84
|
+
except Exception as e:
|
85
|
+
logger.warning(f"Failed to delete VM {vm_id}: {e}")
|
86
|
+
try:
|
87
|
+
await stream_map.remove(vm_id)
|
88
|
+
except Exception:
|
89
|
+
pass
|
90
|
+
|
91
|
+
# Re-sync after any terminations to ensure ads reflect capacity
|
92
|
+
try:
|
93
|
+
vm_resources = await self.vm_service.get_all_vms_resources()
|
94
|
+
await self.vm_service.resource_tracker.sync_with_multipass(vm_resources)
|
95
|
+
except Exception as e:
|
96
|
+
logger.warning(f"Post-termination resource sync failed: {e}")
|
97
|
+
else:
|
98
|
+
logger.info("Payments not configured; skipping startup stream checks")
|
99
|
+
except Exception as e:
|
100
|
+
logger.warning(f"Failed to reconcile VMs with payment streams: {e}")
|
101
|
+
|
40
102
|
await self.advertisement_service.start()
|
41
103
|
# Start pricing auto-updater; trigger re-advertise after updates
|
42
104
|
async def _on_price_updated(platform: str, glm_usd):
|
File without changes
|
File without changes
|