fleetmesh-node 0.2.0__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.
- fleetmesh_node-0.2.0.dist-info/METADATA +57 -0
- fleetmesh_node-0.2.0.dist-info/RECORD +338 -0
- fleetmesh_node-0.2.0.dist-info/WHEEL +5 -0
- fleetmesh_node-0.2.0.dist-info/entry_points.txt +2 -0
- fleetmesh_node-0.2.0.dist-info/licenses/LICENSE +121 -0
- fleetmesh_node-0.2.0.dist-info/top_level.txt +2 -0
- node/RELEASE +1 -0
- node/__init__.py +35 -0
- node/app/__init__.py +49 -0
- node/app/commands.py +265 -0
- node/app/config.py +131 -0
- node/app/control.py +670 -0
- node/app/core.py +867 -0
- node/app/plugins.py +76 -0
- node/app/router.py +83 -0
- node/app/socket.py +431 -0
- node/app/systemd.py +36 -0
- node/app/views.py +204 -0
- node/channels/__init__.py +378 -0
- node/channels/bus.py +331 -0
- node/channels/journal.py +46 -0
- node/commands/__init__.py +140 -0
- node/commands/adapter.py +210 -0
- node/commands/base.py +278 -0
- node/commands/cluster_cmds.py +183 -0
- node/commands/compute_cmds.py +401 -0
- node/commands/core.py +474 -0
- node/commands/gossip_cmds.py +106 -0
- node/commands/nsite_cmds.py +141 -0
- node/commands/peering.py +402 -0
- node/commands/plugin_cmds.py +135 -0
- node/commands/sidecar_cmds.py +120 -0
- node/commands/sync.py +388 -0
- node/commands/transport_cmds.py +451 -0
- node/commands/video_cmds.py +99 -0
- node/compartment/__init__.py +124 -0
- node/compartment/base.py +868 -0
- node/compartment/command.py +458 -0
- node/compartment/context.py +163 -0
- node/compartment/every.py +108 -0
- node/compartment/handler.py +131 -0
- node/compartment/lifecycle.py +460 -0
- node/compartment/lint.py +388 -0
- node/compartment/need.py +158 -0
- node/compartment/problem.py +521 -0
- node/compartment/refusals.py +120 -0
- node/compartment/registry.py +790 -0
- node/compartment/schema.py +290 -0
- node/compartment/sidecar.py +499 -0
- node/compartment/stores.py +662 -0
- node/compartment/wire.py +225 -0
- node/compartment/worker.py +61 -0
- node/config/__init__.py +43 -0
- node/config/base.py +116 -0
- node/config/change.py +441 -0
- node/config/changestore.py +201 -0
- node/config/compartments.py +1144 -0
- node/config/node.py +318 -0
- node/config/parameter.py +563 -0
- node/config/protocols.py +274 -0
- node/config/records.py +171 -0
- node/examples/__init__.py +1 -0
- node/examples/demo.py +226 -0
- node/examples/follower.py +46 -0
- node/interfaces.py +235 -0
- node/keystore/__init__.py +27 -0
- node/keystore/core.py +103 -0
- node/keystore/enclave.py +688 -0
- node/meshnode.py +111 -0
- node/metrics/__init__.py +396 -0
- node/metrics/adapters.py +102 -0
- node/metrics/face.py +110 -0
- node/planes/__init__.py +21 -0
- node/planes/identity/__init__.py +20 -0
- node/planes/identity/announce/__init__.py +9 -0
- node/planes/identity/announce/compartment.py +447 -0
- node/planes/identity/announce/types.py +26 -0
- node/planes/identity/discovery/__init__.py +9 -0
- node/planes/identity/discovery/compartment.py +522 -0
- node/planes/identity/discovery/types.py +13 -0
- node/planes/identity/linked/__init__.py +1 -0
- node/planes/identity/linked/compartment.py +270 -0
- node/planes/identity/linked/types.py +25 -0
- node/planes/identity/presence/__init__.py +7 -0
- node/planes/identity/presence/compartment.py +409 -0
- node/planes/identity/presence/types.py +14 -0
- node/planes/operations/__init__.py +13 -0
- node/planes/operations/scheduler/__init__.py +9 -0
- node/planes/operations/scheduler/compartment.py +353 -0
- node/planes/operations/scheduler/types.py +21 -0
- node/planes/operations/updates/__init__.py +17 -0
- node/planes/operations/updates/core.py +310 -0
- node/planes/operations/updates/store.py +79 -0
- node/planes/operations/updates/types.py +20 -0
- node/planes/peering/__init__.py +28 -0
- node/planes/peering/budget/__init__.py +43 -0
- node/planes/peering/budget/chain.py +43 -0
- node/planes/peering/budget/core.py +537 -0
- node/planes/peering/budget/evaluation.py +80 -0
- node/planes/peering/budget/meter.py +100 -0
- node/planes/peering/budget/receipts.py +439 -0
- node/planes/peering/budget/shape.py +101 -0
- node/planes/peering/budget/tick.py +110 -0
- node/planes/peering/budget/types.py +67 -0
- node/planes/peering/budget/windows.py +346 -0
- node/planes/peering/capacity/__init__.py +7 -0
- node/planes/peering/capacity/compartment.py +342 -0
- node/planes/peering/capacity/types.py +24 -0
- node/planes/peering/peering/__init__.py +25 -0
- node/planes/peering/peering/core.py +464 -0
- node/planes/peering/peering/envelope.py +142 -0
- node/planes/peering/peering/handshake.py +198 -0
- node/planes/peering/peering/shape.py +80 -0
- node/planes/peering/peering/types.py +39 -0
- node/planes/peering/reporter/__init__.py +42 -0
- node/planes/peering/reporter/constants.py +29 -0
- node/planes/peering/reporter/core.py +299 -0
- node/planes/peering/reporter/finding.py +74 -0
- node/planes/peering/reporter/findings.py +171 -0
- node/planes/peering/reporter/publish.py +285 -0
- node/planes/peering/reporter/reading.py +193 -0
- node/planes/peering/reporter/shape.py +86 -0
- node/planes/peering/reporter/types.py +50 -0
- node/planes/peering/strain/__init__.py +9 -0
- node/planes/peering/strain/compartment.py +204 -0
- node/planes/peering/strain/types.py +21 -0
- node/planes/peering/trust/__init__.py +9 -0
- node/planes/peering/trust/compartment.py +239 -0
- node/planes/peering/trust/types.py +30 -0
- node/planes/services/__init__.py +33 -0
- node/planes/services/compute/__init__.py +160 -0
- node/planes/services/compute/common.py +134 -0
- node/planes/services/compute/compartment.py +681 -0
- node/planes/services/compute/executors/__init__.py +121 -0
- node/planes/services/compute/executors/agent.py +840 -0
- node/planes/services/compute/executors/bacalhau.py +605 -0
- node/planes/services/compute/executors/base.py +127 -0
- node/planes/services/compute/executors/harness.py +439 -0
- node/planes/services/compute/executors/mcp.py +454 -0
- node/planes/services/compute/executors/media.py +898 -0
- node/planes/services/compute/executors/nondet.py +283 -0
- node/planes/services/compute/executors/providers.py +499 -0
- node/planes/services/compute/executors/registry.py +140 -0
- node/planes/services/compute/executors/wasm.py +244 -0
- node/planes/services/compute/executors/zk.py +604 -0
- node/planes/services/compute/jobs.py +227 -0
- node/planes/services/compute/load.py +180 -0
- node/planes/services/compute/market.py +433 -0
- node/planes/services/compute/sandbox.py +282 -0
- node/planes/services/compute/types.py +84 -0
- node/planes/services/compute_wire/__init__.py +58 -0
- node/planes/services/compute_wire/compartment.py +256 -0
- node/planes/services/compute_wire/messages.py +320 -0
- node/planes/services/compute_wire/sealing.py +147 -0
- node/planes/services/compute_wire/session.py +572 -0
- node/planes/services/compute_wire/types.py +49 -0
- node/planes/services/compute_wire/verify.py +331 -0
- node/planes/services/dvm/__init__.py +24 -0
- node/planes/services/dvm/gateway.py +526 -0
- node/planes/services/dvm/protocol.py +35 -0
- node/planes/services/dvm/translate.py +15 -0
- node/planes/services/dvm/types.py +25 -0
- node/planes/services/gateway/__init__.py +40 -0
- node/planes/services/gateway/settle.py +279 -0
- node/planes/services/gateway/translate.py +644 -0
- node/planes/services/gossip/__init__.py +22 -0
- node/planes/services/gossip/core.py +929 -0
- node/planes/services/gossip/envelope.py +154 -0
- node/planes/services/gossip/mesh.py +127 -0
- node/planes/services/gossip/types.py +50 -0
- node/planes/services/nsite/__init__.py +7 -0
- node/planes/services/nsite/client.py +62 -0
- node/planes/services/nsite/compartment.py +752 -0
- node/planes/services/nsite/types.py +44 -0
- node/planes/services/paidfetch/__init__.py +7 -0
- node/planes/services/paidfetch/compartment.py +862 -0
- node/planes/services/paidfetch/types.py +99 -0
- node/planes/services/settle/__init__.py +27 -0
- node/planes/services/settle/compartment.py +384 -0
- node/planes/services/settle/engine.py +456 -0
- node/planes/services/settle/gateways.py +541 -0
- node/planes/services/settle/payment.py +512 -0
- node/planes/services/settle/store.py +74 -0
- node/planes/services/settle/types.py +58 -0
- node/planes/services/sidecars/__init__.py +8 -0
- node/planes/services/sidecars/compartment.py +461 -0
- node/planes/services/sidecars/manifest.py +304 -0
- node/planes/services/sidecars/manifests/10-grasp.json +37 -0
- node/planes/services/sidecars/manifests/20-ncontainer.json +31 -0
- node/planes/services/sidecars/manifests/30-magpie.json +28 -0
- node/planes/services/sidecars/types.py +43 -0
- node/planes/services/video/__init__.py +106 -0
- node/planes/services/video/barter.py +407 -0
- node/planes/services/video/cli.py +158 -0
- node/planes/services/video/compartment.py +932 -0
- node/planes/services/video/ingest.py +524 -0
- node/planes/services/video/ledger.py +705 -0
- node/planes/services/video/packager.py +847 -0
- node/planes/services/video/player.py +456 -0
- node/planes/services/video/timed_segments.py +124 -0
- node/planes/services/video/transit.py +478 -0
- node/planes/store/__init__.py +24 -0
- node/planes/store/blob_replication/__init__.py +7 -0
- node/planes/store/blob_replication/compartment.py +496 -0
- node/planes/store/blob_replication/types.py +35 -0
- node/planes/store/blob_wire/__init__.py +48 -0
- node/planes/store/blob_wire/client.py +57 -0
- node/planes/store/blob_wire/core.py +359 -0
- node/planes/store/blob_wire/intake.py +74 -0
- node/planes/store/blob_wire/messages.py +85 -0
- node/planes/store/projected/__init__.py +7 -0
- node/planes/store/projected/compartment.py +258 -0
- node/planes/store/projected/types.py +29 -0
- node/planes/store/retention/__init__.py +7 -0
- node/planes/store/retention/compartment.py +214 -0
- node/planes/store/retention/types.py +12 -0
- node/planes/store/sync/__init__.py +35 -0
- node/planes/store/sync/core.py +311 -0
- node/planes/store/sync/envelope.py +106 -0
- node/planes/store/sync/handover.py +206 -0
- node/planes/store/sync/initiator.py +128 -0
- node/planes/store/sync/responder.py +114 -0
- node/planes/store/sync/rules.py +179 -0
- node/planes/store/sync/shape.py +80 -0
- node/planes/store/sync/types.py +51 -0
- node/planes/transport/__init__.py +23 -0
- node/planes/transport/bridge/__init__.py +13 -0
- node/planes/transport/bridge/compartment.py +785 -0
- node/planes/transport/bridge/ratelimit.py +44 -0
- node/planes/transport/bridge/types.py +43 -0
- node/planes/transport/lan/__init__.py +9 -0
- node/planes/transport/lan/compartment.py +446 -0
- node/planes/transport/lan/types.py +25 -0
- node/planes/transport/link/__init__.py +7 -0
- node/planes/transport/link/compartment.py +257 -0
- node/planes/transport/link/types.py +27 -0
- node/planes/transport/mediation/__init__.py +45 -0
- node/planes/transport/mediation/compartment.py +238 -0
- node/planes/transport/mediation/leaf.py +107 -0
- node/planes/transport/mediation/messages.py +302 -0
- node/planes/transport/mediation/service.py +233 -0
- node/planes/transport/mediation/store.py +37 -0
- node/planes/transport/mediation/types.py +20 -0
- node/planes/transport/p2p/__init__.py +7 -0
- node/planes/transport/p2p/compartment.py +349 -0
- node/planes/transport/p2p/types.py +22 -0
- node/protocols/__init__.py +277 -0
- node/protocols/activitypub/__init__.py +56 -0
- node/protocols/activitypub/actor.py +230 -0
- node/protocols/activitypub/federation.py +490 -0
- node/protocols/activitypub/media.py +145 -0
- node/protocols/activitypub/models.py +286 -0
- node/protocols/activitypub/package.py +271 -0
- node/protocols/activitypub/store.py +564 -0
- node/protocols/arcade/__init__.py +26 -0
- node/protocols/atproto/__init__.py +13 -0
- node/protocols/atproto/firehose.py +126 -0
- node/protocols/atproto/metering.py +165 -0
- node/protocols/atproto/package.py +155 -0
- node/protocols/atproto/repo.py +250 -0
- node/protocols/atproto/xrpc.py +285 -0
- node/protocols/base.py +109 -0
- node/protocols/circles/__init__.py +29 -0
- node/protocols/dm/__init__.py +50 -0
- node/protocols/grasp/__init__.py +22 -0
- node/protocols/isotime.py +15 -0
- node/protocols/nostr/__init__.py +51 -0
- node/protocols/nostr/blobstore.py +383 -0
- node/protocols/nostr/blossom.py +627 -0
- node/protocols/nostr/client.py +377 -0
- node/protocols/nostr/descriptor.py +211 -0
- node/protocols/nostr/eventstore.py +304 -0
- node/protocols/nostr/package.py +182 -0
- node/protocols/nostr/peers.py +109 -0
- node/protocols/nostr/relayserver.py +656 -0
- node/protocols/p2panda/__init__.py +22 -0
- node/protocols/p2panda/identity.py +19 -0
- node/protocols/p2panda/package.py +236 -0
- node/protocols/p2panda/rung.py +152 -0
- node/protocols/p2panda/store.py +273 -0
- node/protocols/rhio/__init__.py +33 -0
- node/protocols/rhio/package.py +275 -0
- node/protocols/rhio/rung.py +136 -0
- node/protocols/rhio/store.py +1423 -0
- node/protocols/ssb/muxrpc.py +256 -0
- node/protocols/ssb/package.py +267 -0
- node/protocols/textile/__init__.py +111 -0
- node/protocols/textile/transducers.py +259 -0
- node/release.py +119 -0
- node/storage/__init__.py +88 -0
- node/storage/accounts.py +305 -0
- node/storage/adapter.py +161 -0
- node/storage/base.py +385 -0
- node/storage/dialect.py +209 -0
- node/storage/grants.py +317 -0
- node/storage/locator.py +150 -0
- node/storage/postgres.py +294 -0
- node/storage/projected.py +953 -0
- node/storage/sharding.py +448 -0
- node/storage/standing.py +568 -0
- node/testnet/__init__.py +9 -0
- node/testnet/cluster.py +731 -0
- node/testnet/fips_daemon.py +230 -0
- node/transport/__init__.py +94 -0
- node/transport/const.py +110 -0
- node/transport/didcomm_inbound.py +251 -0
- node/transport/dnstxt.py +169 -0
- node/transport/fips_rung/__init__.py +67 -0
- node/transport/fips_rung/core.py +666 -0
- node/transport/fips_rung/listener.py +96 -0
- node/transport/fips_rung/types.py +12 -0
- node/transport/host.py +334 -0
- node/transport/iroh_rung/__init__.py +29 -0
- node/transport/iroh_rung/core.py +477 -0
- node/transport/iroh_rung/quic.py +942 -0
- node/transport/iroh_rung/types.py +22 -0
- node/transport/ladder.py +145 -0
- node/transport/lineproto.py +49 -0
- node/transport/mdns.py +303 -0
- node/transport/netfetch.py +283 -0
- node/transport/relay_rung/__init__.py +23 -0
- node/transport/relay_rung/cooldown.py +87 -0
- node/transport/relay_rung/core.py +138 -0
- node/transport/relay_rung/inbound.py +161 -0
- node/transport/relay_rung/ledger.py +104 -0
- node/transport/relay_rung/listen.py +121 -0
- node/transport/relay_rung/outbound.py +181 -0
- node/transport/relay_rung/shape.py +71 -0
- node/transport/relay_rung/types.py +58 -0
- node/transport/relay_rung/wire.py +63 -0
- node/transport/router.py +761 -0
- node/transport/rung.py +276 -0
- node/transport/ssb_rung.py +166 -0
- node/transport/ws.py +502 -0
- sidecar/fleetmesh_announce.py +338 -0
- sidecar/fleetmesh_p2p.py +373 -0
- sidecar/fleetmesh_rhio.py +352 -0
- sidecar/fleetmesh_ssb.py +603 -0
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
"""ComputeSession: the per-job state machine driving quote/accept/sealed/settled/refute.
|
|
2
|
+
|
|
3
|
+
Separate because it is the largest piece of compute/wire and the only one with per-job mutable state.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import hashlib
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import time
|
|
11
|
+
from typing import Any, Callable, Collection, Mapping, Optional, Sequence, Union, cast
|
|
12
|
+
|
|
13
|
+
from fleetmesh_core.accounting import market
|
|
14
|
+
from fleetmesh_core.crypto import crypto
|
|
15
|
+
from fleetmesh_core.wire.interfaces import NostrEvent
|
|
16
|
+
from fleetmesh_core.wire.wire_types import (
|
|
17
|
+
ComputeAcceptBody, ComputeCancelBody, ComputeProgressBody, ComputeQuoteBody,
|
|
18
|
+
ComputeRefuteBody, ComputeSealedBody, ComputeSettledBody, ComputeStreamBody,
|
|
19
|
+
Message, ProgressState, ProofFailure, SealedVerify,
|
|
20
|
+
)
|
|
21
|
+
from node.planes.services.compute import executors as compute_executors, jobs as compute_jobs
|
|
22
|
+
from fleetmesh_core.identity.did import did_nostr as did_of
|
|
23
|
+
from .messages import (
|
|
24
|
+
ComputeWireError,
|
|
25
|
+
make_quote,
|
|
26
|
+
make_accept,
|
|
27
|
+
make_sealed,
|
|
28
|
+
make_settled,
|
|
29
|
+
make_refute,
|
|
30
|
+
make_stream_chunk,
|
|
31
|
+
make_progress,
|
|
32
|
+
make_cancel,
|
|
33
|
+
)
|
|
34
|
+
from .sealing import seal_result, open_sealed, encrypt_stream_frame, decrypt_stream_frame
|
|
35
|
+
from .verify import evaluate_job_evidence
|
|
36
|
+
|
|
37
|
+
# -------------------------------------------------------------------------
|
|
38
|
+
# Bilateral Compute State Machine (Sessions)
|
|
39
|
+
# -------------------------------------------------------------------------
|
|
40
|
+
class ComputeSession:
|
|
41
|
+
"""Manages the bilateral state machine for a compute job."""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
role: str, # 'buyer' or 'seller'
|
|
46
|
+
my_sk: str,
|
|
47
|
+
peer_pk: str,
|
|
48
|
+
ask_id: str,
|
|
49
|
+
job_payload: Optional[Mapping[str, Any]] = None,
|
|
50
|
+
encrypted: bool = False,
|
|
51
|
+
) -> None:
|
|
52
|
+
self.role = role
|
|
53
|
+
self.my_sk = my_sk
|
|
54
|
+
self.my_pk = crypto.pubkey(my_sk)
|
|
55
|
+
self.peer_pk = peer_pk
|
|
56
|
+
self.ask_id = ask_id
|
|
57
|
+
self.thid = ask_id
|
|
58
|
+
self.job_payload = dict(job_payload) if job_payload else None
|
|
59
|
+
self.state = 'INITIAL'
|
|
60
|
+
self.encrypted = encrypted
|
|
61
|
+
self._session_key: Optional[bytes] = None
|
|
62
|
+
|
|
63
|
+
# Negotiation parameters
|
|
64
|
+
# ours as built, or the peer's as received: either way read, not edited
|
|
65
|
+
self.quote_msg: Optional[Mapping[str, Any]] = None
|
|
66
|
+
self.accept_msg: Optional[Mapping[str, Any]] = None
|
|
67
|
+
self.sealed_msg: Optional[Mapping[str, Any]] = None
|
|
68
|
+
self.settled_msg: Optional[Mapping[str, Any]] = None
|
|
69
|
+
self.refute_msg: Optional[Message[ComputeRefuteBody]] = None
|
|
70
|
+
self.receipt_event: Optional[NostrEvent] = None
|
|
71
|
+
|
|
72
|
+
# Cryptographic keys
|
|
73
|
+
self.payment_preimage: Optional[str] = None
|
|
74
|
+
self.payment_hash: Optional[str] = None
|
|
75
|
+
self.raw_output: Optional[bytes] = None
|
|
76
|
+
self.result_hash: Optional[str] = None
|
|
77
|
+
self.fuel_used: int = 0
|
|
78
|
+
self._engine: str = 'wasmtime'
|
|
79
|
+
self._wasi_profile_digest: str = '0' * 64
|
|
80
|
+
|
|
81
|
+
# Streaming and progress tracking (compute/1.1)
|
|
82
|
+
self.stream_chunks: list[Mapping[str, Any]] = []
|
|
83
|
+
self.stream_buffer: list[str] = []
|
|
84
|
+
self.progress_history: list[Mapping[str, Any]] = []
|
|
85
|
+
self.cancel_msg: Optional[Message[ComputeCancelBody]] = None
|
|
86
|
+
self._cancellation_hooks: list[Callable[[str], None]] = []
|
|
87
|
+
self.last_seq: int = -1
|
|
88
|
+
|
|
89
|
+
def session_key(self) -> bytes:
|
|
90
|
+
"""Derive the 32-byte ChaCha20Poly1305 symmetric session key via HKDF over ECDH."""
|
|
91
|
+
if self._session_key is None:
|
|
92
|
+
shared_x = crypto.ecdh_x(self.my_sk, self.peer_pk)
|
|
93
|
+
self._session_key = crypto.hkdf(
|
|
94
|
+
bytes.fromhex(shared_x),
|
|
95
|
+
info=b'fleetmesh/compute/session-stream/1',
|
|
96
|
+
salt=b'fleetmesh/compute/1',
|
|
97
|
+
length=32,
|
|
98
|
+
)
|
|
99
|
+
return self._session_key
|
|
100
|
+
|
|
101
|
+
# Seller methods
|
|
102
|
+
def seller_quote(
|
|
103
|
+
self,
|
|
104
|
+
price_msat: int,
|
|
105
|
+
invoice: str,
|
|
106
|
+
payment_hash: Optional[str] = None,
|
|
107
|
+
preimage_hex: Optional[str] = None,
|
|
108
|
+
estimated_duration_s: int = 10,
|
|
109
|
+
) -> Message[ComputeQuoteBody]:
|
|
110
|
+
if self.role != 'seller':
|
|
111
|
+
raise ComputeWireError('not a seller')
|
|
112
|
+
if preimage_hex is not None:
|
|
113
|
+
self.payment_preimage = preimage_hex
|
|
114
|
+
self.payment_hash = hashlib.sha256(bytes.fromhex(preimage_hex)).hexdigest()
|
|
115
|
+
elif payment_hash is not None:
|
|
116
|
+
self.payment_hash = payment_hash
|
|
117
|
+
else:
|
|
118
|
+
self.payment_preimage = os.urandom(32).hex()
|
|
119
|
+
self.payment_hash = hashlib.sha256(bytes.fromhex(self.payment_preimage)).hexdigest()
|
|
120
|
+
|
|
121
|
+
q = make_quote(
|
|
122
|
+
from_did=did_of(self.my_pk),
|
|
123
|
+
to_did=did_of(self.peer_pk),
|
|
124
|
+
ask_id=self.ask_id,
|
|
125
|
+
price_msat=price_msat,
|
|
126
|
+
invoice=invoice,
|
|
127
|
+
payment_hash=self.payment_hash,
|
|
128
|
+
thid=self.thid,
|
|
129
|
+
estimated_duration_s=estimated_duration_s,
|
|
130
|
+
)
|
|
131
|
+
self.quote_msg = q
|
|
132
|
+
self.state = 'QUOTED'
|
|
133
|
+
return q
|
|
134
|
+
|
|
135
|
+
def seller_receive_accept(
|
|
136
|
+
self,
|
|
137
|
+
accept_msg: Mapping[str, Any],
|
|
138
|
+
ask_event: Optional[NostrEvent] = None,
|
|
139
|
+
registry: Optional[compute_executors.ExecutorRegistry] = None,
|
|
140
|
+
) -> Message[ComputeSealedBody]:
|
|
141
|
+
if self.role != 'seller':
|
|
142
|
+
raise ComputeWireError('not a seller')
|
|
143
|
+
self.accept_msg = dict(accept_msg)
|
|
144
|
+
body = accept_msg['body']
|
|
145
|
+
job = body['job']
|
|
146
|
+
self.job_payload = job
|
|
147
|
+
|
|
148
|
+
if ask_event and not market.commitment_holds(ask_event, job):
|
|
149
|
+
raise ComputeWireError('accept job payload does not match ask commitment')
|
|
150
|
+
|
|
151
|
+
# Execute the job
|
|
152
|
+
reg = registry or compute_executors.DEFAULT_REGISTRY
|
|
153
|
+
exec_res = compute_jobs.run_job(job, registry=reg)
|
|
154
|
+
self.raw_output = exec_res['raw_output']
|
|
155
|
+
self.result_hash = exec_res['result_hash']
|
|
156
|
+
self.fuel_used = exec_res['fuel_used']
|
|
157
|
+
self._engine = str(exec_res.get('engine') or job.get('executor') or 'wasmtime')
|
|
158
|
+
self._wasi_profile_digest = str(exec_res.get('wasi_profile_digest') or ('0' * 64))
|
|
159
|
+
|
|
160
|
+
# Seal the result under preimage
|
|
161
|
+
seal_data = seal_result(self.raw_output, preimage_hex=self.payment_preimage)
|
|
162
|
+
self.payment_preimage = seal_data['payment_preimage']
|
|
163
|
+
self.payment_hash = seal_data['payment_hash']
|
|
164
|
+
|
|
165
|
+
verify_meta: Optional[SealedVerify] = None
|
|
166
|
+
verify_decl = job.get('verify', {})
|
|
167
|
+
if verify_decl.get('mode') == 'proof':
|
|
168
|
+
verify_meta = {
|
|
169
|
+
'mode': 'proof',
|
|
170
|
+
'proof_system': 'zk-verify-groth16',
|
|
171
|
+
'proof': job.get('input', {}).get('proof', ''),
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
sealed = make_sealed(
|
|
175
|
+
from_did=did_of(self.my_pk),
|
|
176
|
+
to_did=did_of(self.peer_pk),
|
|
177
|
+
thid=self.thid,
|
|
178
|
+
ciphertext=seal_data['ciphertext'],
|
|
179
|
+
payment_hash=self.payment_hash,
|
|
180
|
+
result=self.result_hash,
|
|
181
|
+
fuel_used=self.fuel_used,
|
|
182
|
+
verify=verify_meta,
|
|
183
|
+
)
|
|
184
|
+
self.sealed_msg = sealed
|
|
185
|
+
self.state = 'SEALED'
|
|
186
|
+
return sealed
|
|
187
|
+
|
|
188
|
+
def seller_receive_settled(
|
|
189
|
+
self,
|
|
190
|
+
settled_msg: Mapping[str, Any],
|
|
191
|
+
blinding: Optional[str] = None,
|
|
192
|
+
) -> NostrEvent:
|
|
193
|
+
if self.role != 'seller':
|
|
194
|
+
raise ComputeWireError('not a seller')
|
|
195
|
+
self.settled_msg = dict(settled_msg)
|
|
196
|
+
preimage = settled_msg['body']['payment_preimage']
|
|
197
|
+
|
|
198
|
+
# Verify preimage settles invoice
|
|
199
|
+
expected_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
|
200
|
+
if expected_hash != self.payment_hash:
|
|
201
|
+
raise ComputeWireError('disclosed preimage does not hash to payment_hash')
|
|
202
|
+
|
|
203
|
+
# Issue Kind 30811 Job Receipt
|
|
204
|
+
if self.job_payload is None:
|
|
205
|
+
raise ComputeWireError('no job payload accepted; nothing to receipt')
|
|
206
|
+
job_payload = self.job_payload
|
|
207
|
+
blind = blinding or crypto.sha256(os.urandom(32))
|
|
208
|
+
receipt_payload = {
|
|
209
|
+
'ask': self.ask_id,
|
|
210
|
+
'module': job_payload.get('module', {}).get('sha256', '0' * 64),
|
|
211
|
+
'input': job_payload.get('input', {}).get('sha256', '0' * 64),
|
|
212
|
+
'result': self.result_hash,
|
|
213
|
+
'fuel_used': self.fuel_used,
|
|
214
|
+
'paid_msat': self.quote_msg.get('body', {}).get('price_msat', 0) if self.quote_msg else 0,
|
|
215
|
+
'payment_hash': self.payment_hash,
|
|
216
|
+
'executor': {
|
|
217
|
+
'engine': self._engine,
|
|
218
|
+
'wasi_profile_digest': self._wasi_profile_digest,
|
|
219
|
+
},
|
|
220
|
+
'verify': job_payload.get('verify', {'mode': 'none'}),
|
|
221
|
+
'blinding': blind,
|
|
222
|
+
}
|
|
223
|
+
rec_ev = compute_jobs.build_receipt(
|
|
224
|
+
seller_sk=self.my_sk,
|
|
225
|
+
buyer_pk=self.peer_pk,
|
|
226
|
+
receipt_payload=receipt_payload,
|
|
227
|
+
blinding=blind,
|
|
228
|
+
)
|
|
229
|
+
self.receipt_event = rec_ev
|
|
230
|
+
self.state = 'RECEIPT_ISSUED'
|
|
231
|
+
return rec_ev
|
|
232
|
+
|
|
233
|
+
# Buyer methods
|
|
234
|
+
def buyer_accept_quote(
|
|
235
|
+
self,
|
|
236
|
+
quote_msg: Mapping[str, Any],
|
|
237
|
+
job_payload: Mapping[str, Any],
|
|
238
|
+
input_key: Optional[str] = None,
|
|
239
|
+
) -> Message[ComputeAcceptBody]:
|
|
240
|
+
if self.role != 'buyer':
|
|
241
|
+
raise ComputeWireError('not a buyer')
|
|
242
|
+
self.quote_msg = dict(quote_msg)
|
|
243
|
+
self.payment_hash = quote_msg['body']['payment_hash']
|
|
244
|
+
self.job_payload = dict(job_payload)
|
|
245
|
+
|
|
246
|
+
acc = make_accept(
|
|
247
|
+
from_did=did_of(self.my_pk),
|
|
248
|
+
to_did=did_of(self.peer_pk),
|
|
249
|
+
thid=self.thid,
|
|
250
|
+
payment_hash=self.payment_hash,
|
|
251
|
+
job_payload=self.job_payload,
|
|
252
|
+
input_key=input_key,
|
|
253
|
+
)
|
|
254
|
+
self.accept_msg = acc
|
|
255
|
+
self.state = 'ACCEPTED'
|
|
256
|
+
return acc
|
|
257
|
+
|
|
258
|
+
def buyer_receive_sealed(
|
|
259
|
+
self,
|
|
260
|
+
sealed_msg: Mapping[str, Any],
|
|
261
|
+
preimage_hex: str,
|
|
262
|
+
) -> Message[ComputeSettledBody]:
|
|
263
|
+
if self.role != 'buyer':
|
|
264
|
+
raise ComputeWireError('not a buyer')
|
|
265
|
+
self.sealed_msg = dict(sealed_msg)
|
|
266
|
+
self.payment_preimage = preimage_hex
|
|
267
|
+
|
|
268
|
+
settled = make_settled(
|
|
269
|
+
from_did=did_of(self.my_pk),
|
|
270
|
+
to_did=did_of(self.peer_pk),
|
|
271
|
+
thid=self.thid,
|
|
272
|
+
payment_preimage=preimage_hex,
|
|
273
|
+
)
|
|
274
|
+
self.settled_msg = settled
|
|
275
|
+
self.state = 'SETTLED'
|
|
276
|
+
return settled
|
|
277
|
+
|
|
278
|
+
def buyer_verify_and_settle(
|
|
279
|
+
self,
|
|
280
|
+
receipt_event: NostrEvent,
|
|
281
|
+
registry: Optional[compute_executors.ExecutorRegistry] = None,
|
|
282
|
+
pinned_vk_digests: Optional[Collection[str]] = None,
|
|
283
|
+
) -> tuple[bool, Optional[Message[ComputeRefuteBody]]]:
|
|
284
|
+
"""Verify delivery and result integrity. If invalid, returns (False, refute_msg)."""
|
|
285
|
+
if self.role != 'buyer':
|
|
286
|
+
raise ComputeWireError('not a buyer')
|
|
287
|
+
if self.job_payload is None:
|
|
288
|
+
# A buyer always carries the job it asked for; without it there is
|
|
289
|
+
# no declared verify mode to check the receipt against.
|
|
290
|
+
raise ComputeWireError('buyer has no job payload to verify against')
|
|
291
|
+
job_payload = self.job_payload
|
|
292
|
+
self.receipt_event = receipt_event
|
|
293
|
+
|
|
294
|
+
sealed_body = self.sealed_msg['body'] if self.sealed_msg else {}
|
|
295
|
+
ciphertext = sealed_body.get('ciphertext', '')
|
|
296
|
+
claimed_result = sealed_body.get('result', '')
|
|
297
|
+
|
|
298
|
+
# 1. Unseal ciphertext with preimage
|
|
299
|
+
ok_open, dec_bytes, fail_reason = open_sealed(
|
|
300
|
+
ciphertext=ciphertext,
|
|
301
|
+
preimage_hex=self.payment_preimage or '',
|
|
302
|
+
expected_payment_hash=self.payment_hash,
|
|
303
|
+
expected_result=claimed_result,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
if not ok_open or dec_bytes is None:
|
|
307
|
+
# Delivery violation! (S3)
|
|
308
|
+
refute = make_refute(
|
|
309
|
+
from_did=did_of(self.my_pk),
|
|
310
|
+
to_did=did_of(self.peer_pk),
|
|
311
|
+
thid=self.thid,
|
|
312
|
+
job_receipt=receipt_event['id'],
|
|
313
|
+
mode=job_payload.get('verify', {}).get('mode', 'none'),
|
|
314
|
+
claimed_result=claimed_result,
|
|
315
|
+
actual_result='',
|
|
316
|
+
)
|
|
317
|
+
self.refute_msg = refute
|
|
318
|
+
self.state = 'REFUTED'
|
|
319
|
+
return False, refute
|
|
320
|
+
|
|
321
|
+
# 2. Verify according to declared mode
|
|
322
|
+
decl_mode = job_payload.get('verify', {}).get('mode', 'none')
|
|
323
|
+
valid, fail_kind, fail_detail = evaluate_job_evidence(
|
|
324
|
+
declared_mode=decl_mode,
|
|
325
|
+
sealed_body=sealed_body,
|
|
326
|
+
decrypted_output=dec_bytes,
|
|
327
|
+
job_payload=job_payload,
|
|
328
|
+
registry=registry,
|
|
329
|
+
pinned_vk_digests=pinned_vk_digests,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
if not valid:
|
|
333
|
+
refute = make_refute(
|
|
334
|
+
from_did=did_of(self.my_pk),
|
|
335
|
+
to_did=did_of(self.peer_pk),
|
|
336
|
+
thid=self.thid,
|
|
337
|
+
job_receipt=receipt_event['id'],
|
|
338
|
+
mode=decl_mode,
|
|
339
|
+
claimed_result=claimed_result,
|
|
340
|
+
actual_result=fail_detail.get('actual_result') if fail_detail else '',
|
|
341
|
+
wasi_profile_digest=fail_detail.get('wasi_profile_digest') if fail_detail else '',
|
|
342
|
+
engine=fail_detail.get('engine') if fail_detail else '',
|
|
343
|
+
# Under proof the detail IS the verifier's rejection (proof_system,
|
|
344
|
+
# reason). It used to be looked up under a key it never had, so the
|
|
345
|
+
# refute's proof_failure slot went out empty on every disputed proof.
|
|
346
|
+
proof_failure=cast(ProofFailure, dict(fail_detail)) if fail_detail and fail_kind in ('proof-invalid', 'commitment-mismatch') else None,
|
|
347
|
+
)
|
|
348
|
+
self.refute_msg = refute
|
|
349
|
+
self.state = 'REFUTED'
|
|
350
|
+
return False, refute
|
|
351
|
+
|
|
352
|
+
self.state = 'COMPLETED'
|
|
353
|
+
return True, None
|
|
354
|
+
|
|
355
|
+
# compute/1.1 Streaming & Progress Extensions
|
|
356
|
+
def seller_send_stream_chunk(
|
|
357
|
+
self,
|
|
358
|
+
seq: int,
|
|
359
|
+
delta: str,
|
|
360
|
+
is_final: bool = False,
|
|
361
|
+
counters: Optional[Mapping[str, Any]] = None,
|
|
362
|
+
encrypted: Optional[bool] = None,
|
|
363
|
+
) -> Message[ComputeStreamBody]:
|
|
364
|
+
"""Produce and emit sequential compute/1.1/stream chunk frame."""
|
|
365
|
+
if self.role != 'seller':
|
|
366
|
+
raise ComputeWireError('not a seller')
|
|
367
|
+
if seq < 0:
|
|
368
|
+
raise ComputeWireError(f'stream seq negative ({seq})')
|
|
369
|
+
if seq <= self.last_seq and self.last_seq != -1:
|
|
370
|
+
raise ComputeWireError(f'stream seq out of order ({seq} <= {self.last_seq})')
|
|
371
|
+
self.last_seq = seq
|
|
372
|
+
|
|
373
|
+
do_encrypt = self.encrypted if encrypted is None else encrypted
|
|
374
|
+
frame_delta = delta
|
|
375
|
+
if do_encrypt:
|
|
376
|
+
frame_delta = encrypt_stream_frame(self.session_key(), seq, delta)
|
|
377
|
+
|
|
378
|
+
msg = make_stream_chunk(
|
|
379
|
+
from_did=did_of(self.my_pk),
|
|
380
|
+
to_did=did_of(self.peer_pk),
|
|
381
|
+
thid=self.thid,
|
|
382
|
+
seq=seq,
|
|
383
|
+
delta=frame_delta,
|
|
384
|
+
is_final=is_final,
|
|
385
|
+
counters=counters,
|
|
386
|
+
)
|
|
387
|
+
self.stream_chunks.append(msg)
|
|
388
|
+
self.stream_buffer.append(delta)
|
|
389
|
+
if not is_final:
|
|
390
|
+
self.state = 'STREAMING'
|
|
391
|
+
else:
|
|
392
|
+
self.state = 'STREAM_COMPLETED'
|
|
393
|
+
return msg
|
|
394
|
+
|
|
395
|
+
def buyer_receive_stream_chunk(
|
|
396
|
+
self,
|
|
397
|
+
stream_msg: Mapping[str, Any],
|
|
398
|
+
encrypted: Optional[bool] = None,
|
|
399
|
+
) -> Mapping[str, Any]:
|
|
400
|
+
"""Process incoming sequential stream chunk and reassemble output."""
|
|
401
|
+
if self.role != 'buyer':
|
|
402
|
+
raise ComputeWireError('not a buyer')
|
|
403
|
+
body = stream_msg.get('body', {})
|
|
404
|
+
seq = int(body.get('seq', 0))
|
|
405
|
+
raw_delta = str(body.get('delta', ''))
|
|
406
|
+
is_final = bool(body.get('is_final', False))
|
|
407
|
+
|
|
408
|
+
if seq < 0:
|
|
409
|
+
raise ComputeWireError(f'stream seq negative ({seq})')
|
|
410
|
+
if seq <= self.last_seq and self.last_seq != -1:
|
|
411
|
+
raise ComputeWireError(f'stream seq out of order ({seq} <= {self.last_seq})')
|
|
412
|
+
self.last_seq = seq
|
|
413
|
+
|
|
414
|
+
do_encrypt = self.encrypted if encrypted is None else encrypted
|
|
415
|
+
if do_encrypt:
|
|
416
|
+
delta = decrypt_stream_frame(self.session_key(), seq, raw_delta)
|
|
417
|
+
else:
|
|
418
|
+
delta = raw_delta
|
|
419
|
+
|
|
420
|
+
self.stream_chunks.append(dict(stream_msg))
|
|
421
|
+
self.stream_buffer.append(delta)
|
|
422
|
+
if not is_final:
|
|
423
|
+
self.state = 'STREAMING'
|
|
424
|
+
else:
|
|
425
|
+
self.state = 'STREAM_COMPLETED'
|
|
426
|
+
return {
|
|
427
|
+
'seq': seq,
|
|
428
|
+
'delta': delta,
|
|
429
|
+
'is_final': is_final,
|
|
430
|
+
'cumulative_text': ''.join(self.stream_buffer),
|
|
431
|
+
'counters': body.get('counters', {}),
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
def seller_send_progress(
|
|
435
|
+
self,
|
|
436
|
+
state: ProgressState,
|
|
437
|
+
progress: Optional[float] = None,
|
|
438
|
+
elapsed_seconds: Optional[int] = None,
|
|
439
|
+
logs_tail: Optional[str] = None,
|
|
440
|
+
) -> Message[ComputeProgressBody]:
|
|
441
|
+
"""Emit compute/1.1/progress status update for long-running batch work."""
|
|
442
|
+
if self.role != 'seller':
|
|
443
|
+
raise ComputeWireError('not a seller')
|
|
444
|
+
msg = make_progress(
|
|
445
|
+
from_did=did_of(self.my_pk),
|
|
446
|
+
to_did=did_of(self.peer_pk),
|
|
447
|
+
thid=self.thid,
|
|
448
|
+
state=state,
|
|
449
|
+
progress=progress,
|
|
450
|
+
elapsed_seconds=elapsed_seconds,
|
|
451
|
+
logs_tail=logs_tail,
|
|
452
|
+
)
|
|
453
|
+
self.progress_history.append(msg)
|
|
454
|
+
return msg
|
|
455
|
+
|
|
456
|
+
def buyer_receive_progress(
|
|
457
|
+
self,
|
|
458
|
+
progress_msg: Mapping[str, Any],
|
|
459
|
+
) -> Mapping[str, Any]:
|
|
460
|
+
"""Record progress telemetry from seller."""
|
|
461
|
+
if self.role != 'buyer':
|
|
462
|
+
raise ComputeWireError('not a buyer')
|
|
463
|
+
self.progress_history.append(dict(progress_msg))
|
|
464
|
+
return dict(progress_msg.get('body', {}))
|
|
465
|
+
|
|
466
|
+
def create_progress_tracker(self) -> Callable[[str, float, Mapping[str, Any]], None]:
|
|
467
|
+
"""Return a ProgressCallback that forwards telemetry updates to seller_send_progress."""
|
|
468
|
+
start_time = time.time()
|
|
469
|
+
|
|
470
|
+
def _tracker(state: str, progress: float, metadata: Mapping[str, Any]) -> None:
|
|
471
|
+
elapsed = int(time.time() - start_time)
|
|
472
|
+
logs_tail = str(metadata.get('logs_tail') or metadata.get('status') or '')
|
|
473
|
+
self.seller_send_progress(
|
|
474
|
+
state=cast(ProgressState, state),
|
|
475
|
+
progress=progress,
|
|
476
|
+
elapsed_seconds=elapsed,
|
|
477
|
+
logs_tail=logs_tail,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
return _tracker
|
|
481
|
+
|
|
482
|
+
def register_cancellation_hook(self, hook: Callable[[str], None]) -> None:
|
|
483
|
+
"""Register a callback to be invoked when the session is cancelled."""
|
|
484
|
+
self._cancellation_hooks.append(hook)
|
|
485
|
+
|
|
486
|
+
def cancel_session(
|
|
487
|
+
self,
|
|
488
|
+
reason: str,
|
|
489
|
+
penalty_msat: int = 0,
|
|
490
|
+
) -> Message[ComputeCancelBody]:
|
|
491
|
+
"""Abort ongoing compute session with reason and penalty terms."""
|
|
492
|
+
msg = make_cancel(
|
|
493
|
+
from_did=did_of(self.my_pk),
|
|
494
|
+
to_did=did_of(self.peer_pk),
|
|
495
|
+
thid=self.thid,
|
|
496
|
+
reason=reason,
|
|
497
|
+
penalty_msat=penalty_msat,
|
|
498
|
+
)
|
|
499
|
+
self.cancel_msg = msg
|
|
500
|
+
self.state = 'CANCELLED'
|
|
501
|
+
for hook in self._cancellation_hooks:
|
|
502
|
+
try:
|
|
503
|
+
hook(reason)
|
|
504
|
+
except Exception:
|
|
505
|
+
pass
|
|
506
|
+
return msg
|
|
507
|
+
|
|
508
|
+
def stream_over_quic(
|
|
509
|
+
self,
|
|
510
|
+
quic_engine: Any,
|
|
511
|
+
conn: Any,
|
|
512
|
+
chunks: Sequence[str],
|
|
513
|
+
encrypted: Optional[bool] = None,
|
|
514
|
+
) -> list[Message[ComputeStreamBody]]:
|
|
515
|
+
"""Stream a sequence of tokens directly over an active QUIC connection."""
|
|
516
|
+
sent_messages: list[Message[ComputeStreamBody]] = []
|
|
517
|
+
for i, delta in enumerate(chunks):
|
|
518
|
+
is_final = (i == len(chunks) - 1)
|
|
519
|
+
msg = self.seller_send_stream_chunk(seq=i, delta=delta, is_final=is_final, encrypted=encrypted)
|
|
520
|
+
if hasattr(quic_engine, 'send_stream') and conn is not None:
|
|
521
|
+
payload = json.dumps(msg).encode('utf-8')
|
|
522
|
+
quic_engine.send_stream(conn, payload)
|
|
523
|
+
sent_messages.append(msg)
|
|
524
|
+
return sent_messages
|
|
525
|
+
|
|
526
|
+
def stream_over_fips(
|
|
527
|
+
self,
|
|
528
|
+
flow_sock: Any,
|
|
529
|
+
chunks: Sequence[str],
|
|
530
|
+
fips_client: Optional[Any] = None,
|
|
531
|
+
encrypted: Optional[bool] = None,
|
|
532
|
+
) -> list[Message[ComputeStreamBody]]:
|
|
533
|
+
"""Stream sequential token frames directly over an active FIPS FSP datagram flow."""
|
|
534
|
+
sent_messages: list[Message[ComputeStreamBody]] = []
|
|
535
|
+
for i, delta in enumerate(chunks):
|
|
536
|
+
is_final = (i == len(chunks) - 1)
|
|
537
|
+
msg = self.seller_send_stream_chunk(seq=i, delta=delta, is_final=is_final, encrypted=encrypted)
|
|
538
|
+
if flow_sock is not None:
|
|
539
|
+
if fips_client is not None and hasattr(fips_client, 'send_framed'):
|
|
540
|
+
fips_client.send_framed(flow_sock, payload=msg, seq=i, is_final=is_final)
|
|
541
|
+
elif hasattr(flow_sock, 'send'):
|
|
542
|
+
from node.transport import fips_rung as fips_transport
|
|
543
|
+
packet = fips_transport.encode_frame(
|
|
544
|
+
seq=i,
|
|
545
|
+
payload=json.dumps(msg, sort_keys=True).encode('utf-8'),
|
|
546
|
+
is_final=is_final,
|
|
547
|
+
is_didcomm=True,
|
|
548
|
+
)
|
|
549
|
+
flow_sock.send(packet)
|
|
550
|
+
sent_messages.append(msg)
|
|
551
|
+
return sent_messages
|
|
552
|
+
|
|
553
|
+
@property
|
|
554
|
+
def cumulative_text(self) -> str:
|
|
555
|
+
"""Assembled stream content received/sent so far."""
|
|
556
|
+
return "".join(self.stream_buffer)
|
|
557
|
+
|
|
558
|
+
def buyer_receive_fips_chunk(
|
|
559
|
+
self,
|
|
560
|
+
raw_frame: Union[bytes, Mapping[str, Any]],
|
|
561
|
+
) -> Mapping[str, Any]:
|
|
562
|
+
"""Receive and decode a binary-framed FSP datagram chunk or payload."""
|
|
563
|
+
if isinstance(raw_frame, Mapping):
|
|
564
|
+
msg = raw_frame
|
|
565
|
+
else:
|
|
566
|
+
from node.transport import fips_rung as fips_transport
|
|
567
|
+
try:
|
|
568
|
+
seq, flags, payload = fips_transport.decode_frame(raw_frame)
|
|
569
|
+
msg = json.loads(payload.decode('utf-8'))
|
|
570
|
+
except Exception:
|
|
571
|
+
msg = json.loads(raw_frame.decode('utf-8'))
|
|
572
|
+
return self.buyer_receive_stream_chunk(msg)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Types and records for the ComputeWire transport compartment."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, TypedDict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ComputeDisputeRecord(TypedDict):
|
|
8
|
+
"""Refute received from a peer, stored in the disputes ring."""
|
|
9
|
+
peer: str
|
|
10
|
+
refute: Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ComputeSessionSummary(TypedDict):
|
|
14
|
+
"""Summary of an active bilateral compute session."""
|
|
15
|
+
role: str
|
|
16
|
+
peer: str
|
|
17
|
+
state: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ComputeWireStateReport(TypedDict):
|
|
21
|
+
"""State report for operator status and telemetry."""
|
|
22
|
+
active_sessions: int
|
|
23
|
+
disputes_filed: int
|
|
24
|
+
refusals: int
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class EvidenceDetail(TypedDict, total=False):
|
|
28
|
+
"""Why evaluate_job_evidence said no, keyed by failure kind."""
|
|
29
|
+
claimed_result: str
|
|
30
|
+
actual_result: str
|
|
31
|
+
wasi_profile_digest: str
|
|
32
|
+
engine: str
|
|
33
|
+
engine_version: str
|
|
34
|
+
proof_system: str
|
|
35
|
+
reason: str
|
|
36
|
+
detail: str
|
|
37
|
+
path: list[str]
|
|
38
|
+
schema_path: list[str]
|
|
39
|
+
pattern: str
|
|
40
|
+
error: str
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SealedResult(TypedDict, total=False):
|
|
44
|
+
"""Result of sealing a compute job output under a preimage."""
|
|
45
|
+
ciphertext: str
|
|
46
|
+
payment_hash: str
|
|
47
|
+
payment_preimage: str
|
|
48
|
+
result: str
|
|
49
|
+
result_hash: str
|