request-vm-on-golem 0.1.44__py3-none-any.whl → 0.1.46__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: request-vm-on-golem
3
- Version: 0.1.44
3
+ Version: 0.1.46
4
4
  Summary: VM on Golem Requestor CLI - Create and manage virtual machines on the Golem Network
5
5
  Keywords: golem,vm,cloud,decentralized,cli
6
6
  Author: Phillip Jensen
@@ -158,6 +158,19 @@ poetry run golem vm stream topup --stream-id 123 --hours 3
158
158
  poetry run golem vm stream topup --stream-id 123 --glm 25.0
159
159
  ```
160
160
 
161
+ - Check stream status via provider (by VM name recorded in your DB):
162
+
163
+ ```bash
164
+ poetry run golem vm stream status my-vm
165
+ # add --json for machine-readable output
166
+ ```
167
+
168
+ - Inspect a stream directly on-chain:
169
+
170
+ ```bash
171
+ poetry run golem vm stream inspect --stream-id 123
172
+ ```
173
+
161
174
  - Create a VM and attach an existing stream (no auto-streams are created by the requestor):
162
175
 
163
176
  ```bash
@@ -170,8 +183,9 @@ poetry run golem vm create my-vm \
170
183
  Environment (env prefix `GOLEM_REQUESTOR_`):
171
184
 
172
185
  - `polygon_rpc_url` — EVM RPC URL (default L2 RPC)
173
- - `stream_payment_address` — StreamPayment address (fallback if provider doesn’t advertise)
174
- - `glm_token_address` — Token address; set to zero address to use native ETH
186
+ - `stream_payment_address` — StreamPayment address (defaults from `contracts/deployments/l2.json`; overridden by provider info)
187
+ - `glm_token_address` — Token address (defaults from `contracts/deployments/l2.json`; zero address means native ETH)
188
+ - Optional override of deployments directory: set `GOLEM_DEPLOYMENTS_DIR` to a folder containing `l2.json`.
175
189
  - `provider_eth_address` — optional dev helper; in production always use `/provider/info`
176
190
  - `network` — Target network for discovery filtering: `testnet` (default) or `mainnet`
177
191
 
@@ -1,26 +1,27 @@
1
1
  requestor/__init__.py,sha256=OqSUAh1uZBMx7GW0MoSMg967PVdmT8XdPJx3QYjwkak,116
2
2
  requestor/api/main.py,sha256=7utCzFNbh5Ol-vsBWeSwT4lXeHD7zdA-GFZuS3rHMWc,2180
3
3
  requestor/cli/__init__.py,sha256=e3E4oEGxmGj-STPtFkQwg_qIWhR0JAiAQdw3G1hXciU,37
4
- requestor/cli/commands.py,sha256=YiJvCWDp__c8OZ1YdrVc7gAYRUab5ygNGPCGxDmn3jE,35288
5
- requestor/config.py,sha256=v6557eetioJmuOPWKV4iAl7DeqGjgQ0gegqc0zEzJZU,6396
4
+ requestor/cli/commands.py,sha256=ETMxE997la62G1XH0h7N0nNa6UT6_YDvrwjTfjBO-fc,39838
5
+ requestor/config.py,sha256=2ayNJzvIIoU0jMAVqbs-yfG4H63W_uALLScBG4EjUOw,8241
6
+ requestor/data/deployments/l2.json,sha256=XTNN2C5LkBfp4YbDKdUKfWMdp1fKnfv8D3TgcwVWxtQ,249
6
7
  requestor/db/__init__.py,sha256=Gm5DfWls6uvCZZ3HGGnyRHswbUQdeA5OGN8yPwH0hc8,88
7
8
  requestor/db/sqlite.py,sha256=l5pWbx2qlHuar1N_a0B9tVnmumLJY1w5rp3yZ7jmsC0,4146
8
9
  requestor/errors.py,sha256=wVpHBuYgQx5pTe_SamugfK-k768noikY1RxvPOjQGko,665
9
10
  requestor/payments/blockchain_service.py,sha256=EejW51A6Xqc3PKnKnzjRQ6pIVkH1FqacG4lwQAQ0HiM,6888
10
11
  requestor/provider/__init__.py,sha256=fmW23aYUVciF8-gmBZkG-PLhn22upmcDzdPfAOLHG6g,103
11
- requestor/provider/client.py,sha256=WXCm-1bytcgsuHEZzpg7RjjDOTuaXC9cj0Mrm7e6DSw,3676
12
+ requestor/provider/client.py,sha256=pfJymufYR13W4kfykHZSVvs6ikRUE5AdHp0W0DB17AE,4130
12
13
  requestor/run.py,sha256=GqOG6n34szt8Sp3SEqjRV6huWm737yCN6YnBqoiwI-U,1785
13
14
  requestor/security/faucet.py,sha256=35d8mD3fM0YqRIhUXuIKandOL8vbw2T2IFQWVsan9Lw,2056
14
15
  requestor/services/__init__.py,sha256=1qSn_6RMn0KB0A7LCnY2IW6_tC3HBQsdfkFeV-h94eM,172
15
16
  requestor/services/database_service.py,sha256=GlSrzzzd7PSYQJNup00sxkB-B2PMr1__04K8k5QSWvs,2996
16
- requestor/services/provider_service.py,sha256=SH76qxnbIm4EgCwQ0SHiwCidN19aTQMCxGUj1e0yGRQ,14712
17
+ requestor/services/provider_service.py,sha256=eb4t6tkcw9VzJev2sfawT1KvVc5TxQnb1pgYgoQZcM4,15000
17
18
  requestor/services/ssh_service.py,sha256=tcOCtk2SlB9Uuv-P2ghR22e7BJ9kigQh5b4zSGdPFns,4280
18
19
  requestor/services/vm_service.py,sha256=eQ2pPMpYlfPVbVFrkFElsRO5swPq-2XZEfuvxagyHDk,7941
19
20
  requestor/ssh/__init__.py,sha256=hNgSqJ5s1_AwwxVRyFjUqh_LTBpI4Hmzq0F-f_wXN9g,119
20
21
  requestor/ssh/manager.py,sha256=3jQtbbK7CVC2yD1zCO88jGXh2fBcuv3CzWEqDLuaQVk,9758
21
22
  requestor/utils/logging.py,sha256=oFNpO8pJboYM8Wp7g3HOU4HFyBTKypVdY15lUiz1a4I,3721
22
23
  requestor/utils/spinner.py,sha256=PUHJdTD9jpUHur__01_qxXy87WFfNmjQbD_sLG-KlGo,2459
23
- request_vm_on_golem-0.1.44.dist-info/METADATA,sha256=oNqjV3CuQMnhZA1SRGPBf9M8J_-J8WsVBaSOdJnrxKE,13912
24
- request_vm_on_golem-0.1.44.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
25
- request_vm_on_golem-0.1.44.dist-info/entry_points.txt,sha256=Z-skRNpJ8aZcIl_En9mEm1ygkp9FKy0bzQoL3zO52-0,44
26
- request_vm_on_golem-0.1.44.dist-info/RECORD,,
24
+ request_vm_on_golem-0.1.46.dist-info/METADATA,sha256=orZ8ilyk85is1BvRU0NdD_9dwEsUTPqOKIA1Kec9G34,14363
25
+ request_vm_on_golem-0.1.46.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
26
+ request_vm_on_golem-0.1.46.dist-info/entry_points.txt,sha256=Z-skRNpJ8aZcIl_En9mEm1ygkp9FKy0bzQoL3zO52-0,44
27
+ request_vm_on_golem-0.1.46.dist-info/RECORD,,
requestor/cli/commands.py CHANGED
@@ -384,6 +384,97 @@ async def stream_topup(stream_id: int, glm: float | None, hours: int | None):
384
384
  raise click.Abort()
385
385
 
386
386
 
387
+ @vm_stream.command('status')
388
+ @click.argument('name')
389
+ @click.option('--json', 'as_json', is_flag=True, help='Output in JSON format')
390
+ @async_command
391
+ async def stream_status(name: str, as_json: bool):
392
+ """Show the payment stream status for a VM by name."""
393
+ try:
394
+ # Resolve VM and provider
395
+ vm = await db_service.get_vm(name)
396
+ if not vm:
397
+ raise RequestorError(f"VM '{name}' not found in local DB")
398
+ provider_url = config.get_provider_url(vm['provider_ip'])
399
+ async with ProviderClient(provider_url) as client:
400
+ status = await client.get_vm_stream_status(vm['vm_id'])
401
+ if as_json:
402
+ click.echo(json.dumps(status, indent=2))
403
+ return
404
+ # Pretty print
405
+ c = status.get('chain', {})
406
+ comp = status.get('computed', {})
407
+ click.echo("\n" + "─" * 60)
408
+ click.echo(click.style(f" 💸 Stream Status for VM: {name}", fg="blue", bold=True))
409
+ click.echo("─" * 60)
410
+ click.echo(f" Stream ID : {click.style(str(status.get('stream_id')), fg='cyan')}")
411
+ click.echo(f" Verified : {click.style(str(status.get('verified')), fg='green' if status.get('verified') else 'yellow')}")
412
+ click.echo(f" Reason : {status.get('reason')}")
413
+ click.echo(" On-chain :")
414
+ click.echo(f" recipient : {c.get('recipient')} ")
415
+ click.echo(f" startTime : {c.get('startTime')} stopTime: {c.get('stopTime')}")
416
+ click.echo(f" rate/second : {c.get('ratePerSecond')} deposit: {c.get('deposit')} withdrawn: {c.get('withdrawn')} halted: {c.get('halted')}")
417
+ click.echo(" Computed :")
418
+ click.echo(f" now : {comp.get('now')} remaining: {comp.get('remaining_seconds')}s")
419
+ click.echo(f" vested : {comp.get('vested_wei')} withdrawable: {comp.get('withdrawable_wei')}")
420
+ click.echo("─" * 60)
421
+ except Exception as e:
422
+ logger.error(f"Failed to fetch stream status: {e}")
423
+ raise click.Abort()
424
+
425
+
426
+ @vm_stream.command('inspect')
427
+ @click.option('--stream-id', type=int, required=True)
428
+ @click.option('--json', 'as_json', is_flag=True, help='Output in JSON format')
429
+ @async_command
430
+ async def stream_inspect(stream_id: int, as_json: bool):
431
+ """Inspect a stream directly on-chain (no provider required)."""
432
+ try:
433
+ from web3 import Web3
434
+ from golem_streaming_abi import STREAM_PAYMENT_ABI
435
+ w3 = Web3(Web3.HTTPProvider(config.polygon_rpc_url))
436
+ contract = w3.eth.contract(address=Web3.to_checksum_address(config.stream_payment_address), abi=STREAM_PAYMENT_ABI)
437
+ token, sender, recipient, startTime, stopTime, ratePerSecond, deposit, withdrawn, halted = contract.functions.streams(int(stream_id)).call()
438
+ now = int(w3.eth.get_block('latest')['timestamp'])
439
+ vested = max(min(now, int(stopTime)) - int(startTime), 0) * int(ratePerSecond)
440
+ withdrawable = max(int(vested) - int(withdrawn), 0)
441
+ remaining = max(int(stopTime) - now, 0)
442
+ out = {
443
+ "stream_id": int(stream_id),
444
+ "chain": {
445
+ "token": token,
446
+ "sender": sender,
447
+ "recipient": recipient,
448
+ "startTime": int(startTime),
449
+ "stopTime": int(stopTime),
450
+ "ratePerSecond": int(ratePerSecond),
451
+ "deposit": int(deposit),
452
+ "withdrawn": int(withdrawn),
453
+ "halted": bool(halted),
454
+ },
455
+ "computed": {
456
+ "now": now,
457
+ "remaining_seconds": remaining,
458
+ "vested_wei": int(vested),
459
+ "withdrawable_wei": int(withdrawable),
460
+ }
461
+ }
462
+ if as_json:
463
+ click.echo(json.dumps(out, indent=2))
464
+ else:
465
+ click.echo("\n" + "─" * 60)
466
+ click.echo(click.style(f" 🔎 On-chain Stream Inspect: {stream_id}", fg="blue", bold=True))
467
+ click.echo("─" * 60)
468
+ click.echo(f" recipient : {recipient}")
469
+ click.echo(f" startTime : {int(startTime)} stopTime: {int(stopTime)} now: {now} remaining: {remaining}s")
470
+ click.echo(f" rate/second : {int(ratePerSecond)} deposit: {int(deposit)} withdrawn: {int(withdrawn)} halted: {bool(halted)}")
471
+ click.echo(f" vested : {int(vested)} withdrawable: {int(withdrawable)}")
472
+ click.echo("─" * 60)
473
+ except Exception as e:
474
+ logger.error(f"Failed to inspect stream: {e}")
475
+ raise click.Abort()
476
+
477
+
387
478
  @cli.group()
388
479
  def wallet():
389
480
  """Wallet utilities (funding, balance)."""
requestor/config.py CHANGED
@@ -106,12 +106,12 @@ class RequestorConfig(BaseSettings):
106
106
  description="EVM RPC URL for streaming payments (L2 by default)"
107
107
  )
108
108
  stream_payment_address: str = Field(
109
- default="0x0000000000000000000000000000000000000000",
110
- description="Deployed StreamPayment contract address"
109
+ default="",
110
+ description="Deployed StreamPayment contract address (defaults to contracts/deployments/l2.json)"
111
111
  )
112
112
  glm_token_address: str = Field(
113
- default="0x0000000000000000000000000000000000000000",
114
- description="Token address (0x0 means native ETH)"
113
+ default="",
114
+ description="Token address (0x0 means native ETH). Defaults from l2.json"
115
115
  )
116
116
  # Faucet settings (L2 payments)
117
117
  l2_faucet_url: str = Field(
@@ -145,6 +145,52 @@ class RequestorConfig(BaseSettings):
145
145
  return os.environ[key]
146
146
  return v
147
147
 
148
+ @staticmethod
149
+ def _load_l2_deployment() -> tuple[str | None, str | None]:
150
+ try:
151
+ base = os.environ.get("GOLEM_DEPLOYMENTS_DIR")
152
+ if base:
153
+ path = Path(base) / "l2.json"
154
+ else:
155
+ # repo root assumption: ../../ relative to this file
156
+ path = Path(__file__).resolve().parents[2] / "contracts" / "deployments" / "l2.json"
157
+ if not path.exists():
158
+ # Try package resource fallback
159
+ try:
160
+ import importlib.resources as ir
161
+ with ir.files("requestor.data.deployments").joinpath("l2.json").open("r") as fh: # type: ignore[attr-defined]
162
+ import json as _json
163
+ data = _json.load(fh)
164
+ except Exception:
165
+ return None, None
166
+ else:
167
+ import json as _json
168
+ data = _json.loads(path.read_text())
169
+ sp = data.get("StreamPayment", {})
170
+ addr = sp.get("address")
171
+ token = sp.get("glmToken")
172
+ if isinstance(addr, str) and addr:
173
+ return addr, token or "0x0000000000000000000000000000000000000000"
174
+ except Exception:
175
+ pass
176
+ return None, None
177
+
178
+ @field_validator("stream_payment_address", mode='before')
179
+ @classmethod
180
+ def default_stream_addr(cls, v: str) -> str:
181
+ if v:
182
+ return v
183
+ addr, _ = RequestorConfig._load_l2_deployment()
184
+ return addr or "0x0000000000000000000000000000000000000000"
185
+
186
+ @field_validator("glm_token_address", mode='before')
187
+ @classmethod
188
+ def default_token_addr(cls, v: str) -> str:
189
+ if v:
190
+ return v
191
+ _, token = RequestorConfig._load_l2_deployment()
192
+ return token or "0x0000000000000000000000000000000000000000"
193
+
148
194
  # Base Directory
149
195
  base_dir: Path = Field(
150
196
  default_factory=lambda: Path.home() / ".golem" / "requestor",
@@ -0,0 +1,9 @@
1
+ {
2
+ "network": "l2",
3
+ "timestamp": "",
4
+ "StreamPayment": {
5
+ "address": "0x0281B792b5491E3548c8Fc17C24A2e0Cb99FbeC2",
6
+ "oracle": "0xDd329f6EDf93637634E8862d34c3909d298A7055",
7
+ "glmToken": "0x0000000000000000000000000000000000000000"
8
+ }
9
+ }
@@ -103,3 +103,13 @@ class ProviderClient:
103
103
  error_text = await response.text()
104
104
  raise Exception(f"Failed to get VM access info: {error_text}")
105
105
  return await response.json()
106
+
107
+ async def get_vm_stream_status(self, vm_id: str) -> Dict:
108
+ """Get on-chain stream status for a VM from provider."""
109
+ async with self.session.get(
110
+ f"{self.provider_url}/api/v1/vms/{vm_id}/stream"
111
+ ) as response:
112
+ if not response.ok:
113
+ error_text = await response.text()
114
+ raise Exception(f"Failed to get VM stream status: {error_text}")
115
+ return await response.json()
@@ -92,6 +92,14 @@ class ProviderService:
92
92
  ) -> List[Dict]:
93
93
  """Find providers using Golem Base."""
94
94
  try:
95
+ def _to_float(val):
96
+ if val is None:
97
+ return None
98
+ try:
99
+ return float(val)
100
+ except Exception:
101
+ return None
102
+
95
103
  query = 'golem_type="provider"'
96
104
  # Filter by advertised network to avoid cross-network results
97
105
  if config.network:
@@ -128,12 +136,12 @@ class ProviderService:
128
136
  'storage': int(annotations.get('golem_storage', 0)),
129
137
  },
130
138
  'pricing': {
131
- 'usd_per_core_month': annotations.get('golem_price_usd_core_month'),
132
- 'usd_per_gb_ram_month': annotations.get('golem_price_usd_ram_gb_month'),
133
- 'usd_per_gb_storage_month': annotations.get('golem_price_usd_storage_gb_month'),
134
- 'glm_per_core_month': annotations.get('golem_price_glm_core_month'),
135
- 'glm_per_gb_ram_month': annotations.get('golem_price_glm_ram_gb_month'),
136
- 'glm_per_gb_storage_month': annotations.get('golem_price_glm_storage_gb_month'),
139
+ 'usd_per_core_month': _to_float(annotations.get('golem_price_usd_core_month')),
140
+ 'usd_per_gb_ram_month': _to_float(annotations.get('golem_price_usd_ram_gb_month')),
141
+ 'usd_per_gb_storage_month': _to_float(annotations.get('golem_price_usd_storage_gb_month')),
142
+ 'glm_per_core_month': _to_float(annotations.get('golem_price_glm_core_month')),
143
+ 'glm_per_gb_ram_month': _to_float(annotations.get('golem_price_glm_ram_gb_month')),
144
+ 'glm_per_gb_storage_month': _to_float(annotations.get('golem_price_glm_storage_gb_month')),
137
145
  },
138
146
  'created_at_block': metadata.expires_at_block - (config.advertisement_interval * 2)
139
147
  }