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,33 @@
|
|
|
1
|
+
"""Rhio cluster sync & P2P JetStream protocol suite for Fleetmesh.
|
|
2
|
+
|
|
3
|
+
Native and sidecar integration for HIRO Rhio:
|
|
4
|
+
- NATS JetStream messaging and stream replication
|
|
5
|
+
- Bao-compatible chunked tree hashing for blob integrity
|
|
6
|
+
- S3 / MinIO cluster storage synchronization
|
|
7
|
+
- Outbound RhioRung for TransportRouter ladder
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from .package import RhioProtocolPackage
|
|
12
|
+
from .rung import RhioRung
|
|
13
|
+
from .store import (
|
|
14
|
+
FiberAccessError,
|
|
15
|
+
RhioBaoTree,
|
|
16
|
+
RhioBlobSync,
|
|
17
|
+
RhioStoreError,
|
|
18
|
+
binary_http_response,
|
|
19
|
+
handle_fiber_http,
|
|
20
|
+
is_fiber_route,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
'FiberAccessError',
|
|
25
|
+
'RhioBaoTree',
|
|
26
|
+
'RhioBlobSync',
|
|
27
|
+
'RhioProtocolPackage',
|
|
28
|
+
'RhioRung',
|
|
29
|
+
'RhioStoreError',
|
|
30
|
+
'binary_http_response',
|
|
31
|
+
'handle_fiber_http',
|
|
32
|
+
'is_fiber_route',
|
|
33
|
+
]
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""fleetmesh-protocol-rhio: Rhio cluster sync protocol package for Fleetmesh.
|
|
2
|
+
|
|
3
|
+
Conforms to ProtocolPackage (fleetmesh_core.wire.interfaces.ProtocolPackage).
|
|
4
|
+
Mounts Rhio cluster blob sync, NATS JetStream bridge, and Bao tree verification
|
|
5
|
+
onto ProtocolHostRouter and provides RhioRung for the TransportRouter ladder.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import base64
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
import urllib.parse
|
|
14
|
+
from typing import Any, Mapping, Optional
|
|
15
|
+
|
|
16
|
+
from fleetmesh_core.wire.interfaces import (
|
|
17
|
+
ProtocolHealth,
|
|
18
|
+
ProtocolHostRouter,
|
|
19
|
+
ProtocolIdentityAdapter,
|
|
20
|
+
)
|
|
21
|
+
from node.protocols.base import BaseProtocolPackage
|
|
22
|
+
from node.protocols.p2panda.identity import P2PandaIdentityAdapter
|
|
23
|
+
from .rung import RhioRung
|
|
24
|
+
from .store import RhioBlobSync
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
'RhioProtocolPackage',
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
log = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class RhioProtocolPackage(BaseProtocolPackage):
|
|
34
|
+
"""The Rhio cluster synchronization suite for Fleetmesh."""
|
|
35
|
+
|
|
36
|
+
name: str = 'rhio'
|
|
37
|
+
version: str = '0.1.0'
|
|
38
|
+
standards: tuple[str, ...] = (
|
|
39
|
+
'rhio-nats-v1',
|
|
40
|
+
'p2panda-v0.4.0',
|
|
41
|
+
'bao-tree-hash',
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
node_dir: Optional[str] = None,
|
|
47
|
+
keystore: Optional[Any] = None,
|
|
48
|
+
blob_store: Optional[Any] = None,
|
|
49
|
+
store: Optional[RhioBlobSync] = None,
|
|
50
|
+
sidecar_client: Optional[Any] = None,
|
|
51
|
+
auto_start_sidecar: bool = False,
|
|
52
|
+
**kwargs: Any,
|
|
53
|
+
) -> None:
|
|
54
|
+
self.node_dir = node_dir
|
|
55
|
+
self.keystore = keystore
|
|
56
|
+
self.blob_store = blob_store
|
|
57
|
+
self.auto_start_sidecar = auto_start_sidecar
|
|
58
|
+
self.config: Mapping[str, Any] = {}
|
|
59
|
+
self._enabled: bool = False
|
|
60
|
+
self._running: bool = False
|
|
61
|
+
self._messages_in: int = 0
|
|
62
|
+
self._messages_out: int = 0
|
|
63
|
+
self._daemon: Optional[Any] = None
|
|
64
|
+
|
|
65
|
+
# Identity adapter (p2panda author identity used by Rhio)
|
|
66
|
+
if keystore is not None:
|
|
67
|
+
self.identity: Optional[P2PandaIdentityAdapter] = P2PandaIdentityAdapter.from_keystore(keystore)
|
|
68
|
+
else:
|
|
69
|
+
self.identity = None
|
|
70
|
+
|
|
71
|
+
# Blob & Tree Hash Store
|
|
72
|
+
if store is not None:
|
|
73
|
+
self.store: RhioBlobSync = store
|
|
74
|
+
elif node_dir:
|
|
75
|
+
db_path = os.path.join(node_dir, "rhio_blobs.db")
|
|
76
|
+
cas_dir = os.path.join(node_dir, "rhio_blobs")
|
|
77
|
+
self.store = RhioBlobSync(db_path=db_path, cas_dir=cas_dir)
|
|
78
|
+
else:
|
|
79
|
+
self.store = RhioBlobSync(":memory:")
|
|
80
|
+
|
|
81
|
+
# Outbound Transport Rung
|
|
82
|
+
self.socket_path: Optional[str] = os.path.join(node_dir, "rhio.sock") if node_dir else None
|
|
83
|
+
self.rung: RhioRung = RhioRung(
|
|
84
|
+
sidecar_client=sidecar_client,
|
|
85
|
+
socket_path=self.socket_path,
|
|
86
|
+
enabled=self._enabled,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
self._host: Optional[ProtocolHostRouter] = None
|
|
90
|
+
|
|
91
|
+
def configure(self, config: Mapping[str, Any]) -> None:
|
|
92
|
+
self.config = dict(config)
|
|
93
|
+
self._enabled = bool(self.config.get('enabled', False))
|
|
94
|
+
if 'auto_start_sidecar' in self.config:
|
|
95
|
+
self.auto_start_sidecar = bool(self.config['auto_start_sidecar'])
|
|
96
|
+
if 'socket_path' in self.config and self.config['socket_path']:
|
|
97
|
+
self.socket_path = str(self.config['socket_path'])
|
|
98
|
+
self.rung._socket_path = self.socket_path
|
|
99
|
+
self.rung.enabled = self._enabled
|
|
100
|
+
|
|
101
|
+
def identity_adapter(self, keystore: Any) -> Optional[ProtocolIdentityAdapter]:
|
|
102
|
+
if keystore is not None:
|
|
103
|
+
return P2PandaIdentityAdapter.from_keystore(keystore)
|
|
104
|
+
return self.identity
|
|
105
|
+
|
|
106
|
+
def mount_ingress(self, host: ProtocolHostRouter) -> None:
|
|
107
|
+
if not self._enabled:
|
|
108
|
+
return
|
|
109
|
+
self._host = host
|
|
110
|
+
|
|
111
|
+
from node.transport import ws
|
|
112
|
+
|
|
113
|
+
# 1. GET /rhio/status
|
|
114
|
+
def _handle_status(conn: Any, req: Any) -> bool:
|
|
115
|
+
body_dict = {
|
|
116
|
+
'status': 'online' if self._running else 'configured',
|
|
117
|
+
'name': self.name,
|
|
118
|
+
'version': self.version,
|
|
119
|
+
'network_id': self.config.get('network_id', 'rhio-default-network-1'),
|
|
120
|
+
'nats_endpoint': self.config.get('nats_endpoint', 'localhost:4222'),
|
|
121
|
+
's3_endpoint': self.config.get('s3_endpoint', 'http://localhost:9000'),
|
|
122
|
+
'blobs_count': self.store.count_blobs(),
|
|
123
|
+
}
|
|
124
|
+
body = json.dumps(body_dict, indent=2).encode('utf-8')
|
|
125
|
+
ws.http_response(conn, "200 OK", body, "application/json")
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
# 2. GET & POST /rhio/blobs
|
|
129
|
+
def _handle_blobs(conn: Any, req: Any) -> bool:
|
|
130
|
+
method = getattr(req, 'method', 'GET').upper()
|
|
131
|
+
uri = getattr(req, 'path', '')
|
|
132
|
+
parsed = urllib.parse.urlparse(uri)
|
|
133
|
+
|
|
134
|
+
if method == 'GET':
|
|
135
|
+
qparams = urllib.parse.parse_qs(parsed.query)
|
|
136
|
+
blob_hash = qparams.get('hash', [None])[0]
|
|
137
|
+
target_route = parsed.path
|
|
138
|
+
if not blob_hash and target_route.startswith('/rhio/blobs/'):
|
|
139
|
+
tail = target_route[len('/rhio/blobs/'):].strip('/')
|
|
140
|
+
if tail:
|
|
141
|
+
blob_hash = tail
|
|
142
|
+
|
|
143
|
+
if blob_hash:
|
|
144
|
+
blob = self.store.get_blob(blob_hash)
|
|
145
|
+
if blob is None and self.blob_store is not None and hasattr(self.blob_store, 'get'):
|
|
146
|
+
try:
|
|
147
|
+
blob = self.blob_store.get(blob_hash)
|
|
148
|
+
except Exception:
|
|
149
|
+
blob = None
|
|
150
|
+
if blob is None:
|
|
151
|
+
ws.http_response(conn, "404 Not Found", b'{"error":"not_found"}', "application/json")
|
|
152
|
+
return True
|
|
153
|
+
ws.http_response(conn, "200 OK", blob, "application/octet-stream")
|
|
154
|
+
return True
|
|
155
|
+
else:
|
|
156
|
+
blobs = self.store.list_blobs(limit=50)
|
|
157
|
+
body = json.dumps({'blobs': blobs, 'total': self.store.count_blobs()}).encode('utf-8')
|
|
158
|
+
ws.http_response(conn, "200 OK", body, "application/json")
|
|
159
|
+
return True
|
|
160
|
+
|
|
161
|
+
elif method == 'POST':
|
|
162
|
+
try:
|
|
163
|
+
payload = ws.read_body(conn, req)
|
|
164
|
+
except Exception as e:
|
|
165
|
+
ws.http_response(conn, "400 Bad Request", json.dumps({'error': str(e)}).encode('utf-8'))
|
|
166
|
+
return True
|
|
167
|
+
|
|
168
|
+
headers = getattr(req, 'headers', {})
|
|
169
|
+
content_type = headers.get(b'content-type', b'').decode('utf-8', errors='ignore').lower()
|
|
170
|
+
|
|
171
|
+
# If JSON payload with data_b64
|
|
172
|
+
if 'json' in content_type:
|
|
173
|
+
try:
|
|
174
|
+
obj = json.loads(payload.decode('utf-8'))
|
|
175
|
+
blob_bytes = base64.b64decode(obj['data_b64'])
|
|
176
|
+
except Exception as e:
|
|
177
|
+
ws.http_response(conn, "400 Bad Request", json.dumps({'error': str(e)}).encode('utf-8'))
|
|
178
|
+
return True
|
|
179
|
+
else:
|
|
180
|
+
blob_bytes = payload
|
|
181
|
+
|
|
182
|
+
blob_hash, tree_root, size = self.store.store_blob(blob_bytes)
|
|
183
|
+
if self.blob_store is not None and hasattr(self.blob_store, 'put'):
|
|
184
|
+
try:
|
|
185
|
+
self.blob_store.put(blob_bytes)
|
|
186
|
+
except Exception:
|
|
187
|
+
pass
|
|
188
|
+
self._messages_in += 1
|
|
189
|
+
resp_data = {
|
|
190
|
+
'hash': blob_hash,
|
|
191
|
+
'tree_root': tree_root,
|
|
192
|
+
'size': size,
|
|
193
|
+
}
|
|
194
|
+
ws.http_response(conn, "201 Created", json.dumps(resp_data).encode('utf-8'), "application/json")
|
|
195
|
+
return True
|
|
196
|
+
|
|
197
|
+
return False
|
|
198
|
+
|
|
199
|
+
# 3. GET /rhio/sync
|
|
200
|
+
def _handle_sync(conn: Any, req: Any) -> bool:
|
|
201
|
+
body = json.dumps({
|
|
202
|
+
'network_id': self.config.get('network_id', 'rhio-default-network-1'),
|
|
203
|
+
'synced': True,
|
|
204
|
+
'blobs_stored': self.store.count_blobs(),
|
|
205
|
+
}).encode('utf-8')
|
|
206
|
+
ws.http_response(conn, "200 OK", body, "application/json")
|
|
207
|
+
return True
|
|
208
|
+
|
|
209
|
+
host.mount_route('/rhio/status', _handle_status, methods=('GET',), owner=self.name)
|
|
210
|
+
host.mount_route('/rhio/blobs', _handle_blobs, methods=('GET', 'POST'), owner=self.name)
|
|
211
|
+
host.mount_route('/rhio/sync', _handle_sync, methods=('GET',), owner=self.name)
|
|
212
|
+
|
|
213
|
+
def transport_rung(self, context: Any) -> Optional[Any]:
|
|
214
|
+
if not self._enabled:
|
|
215
|
+
return None
|
|
216
|
+
return self.rung
|
|
217
|
+
|
|
218
|
+
def compartments(self) -> tuple[type[Any], ...]:
|
|
219
|
+
return ()
|
|
220
|
+
|
|
221
|
+
def start(self) -> None:
|
|
222
|
+
if not self._enabled:
|
|
223
|
+
return
|
|
224
|
+
if self.auto_start_sidecar and self._daemon is None and self.node_dir:
|
|
225
|
+
from sidecar.fleetmesh_rhio import RhioSidecarClient, RhioSidecarDaemon
|
|
226
|
+
self._daemon = RhioSidecarDaemon(
|
|
227
|
+
node_dir=self.node_dir,
|
|
228
|
+
uds_path=self.socket_path,
|
|
229
|
+
network_id=str(self.config.get('network_id', 'rhio-default-network-1')),
|
|
230
|
+
nats_endpoint=str(self.config.get('nats_endpoint', 'localhost:4222')),
|
|
231
|
+
s3_endpoint=str(self.config.get('s3_endpoint', 'http://localhost:9000')),
|
|
232
|
+
)
|
|
233
|
+
self._daemon.start()
|
|
234
|
+
client = RhioSidecarClient(self._daemon.uds_path)
|
|
235
|
+
client.connect()
|
|
236
|
+
self.rung._client = client
|
|
237
|
+
self._running = True
|
|
238
|
+
|
|
239
|
+
def stop(self) -> None:
|
|
240
|
+
self._running = False
|
|
241
|
+
if self._daemon is not None:
|
|
242
|
+
if self.rung._client is not None:
|
|
243
|
+
try:
|
|
244
|
+
self.rung._client.close()
|
|
245
|
+
except Exception:
|
|
246
|
+
pass
|
|
247
|
+
self.rung._client = None
|
|
248
|
+
self._daemon.stop()
|
|
249
|
+
self._daemon = None
|
|
250
|
+
if self.store is not None:
|
|
251
|
+
self.store.close()
|
|
252
|
+
|
|
253
|
+
def health(self) -> ProtocolHealth:
|
|
254
|
+
if not self._enabled:
|
|
255
|
+
return ProtocolHealth(
|
|
256
|
+
healthy=True,
|
|
257
|
+
status="disabled",
|
|
258
|
+
connections_active=0,
|
|
259
|
+
messages_in=0,
|
|
260
|
+
messages_out=0,
|
|
261
|
+
errors=(),
|
|
262
|
+
)
|
|
263
|
+
return ProtocolHealth(
|
|
264
|
+
healthy=True,
|
|
265
|
+
status="serving" if self._running else "configured",
|
|
266
|
+
connections_active=0,
|
|
267
|
+
messages_in=self._messages_in,
|
|
268
|
+
messages_out=self._messages_out,
|
|
269
|
+
errors=(),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
def descriptor_capabilities(self) -> tuple[tuple[str, str], ...]:
|
|
273
|
+
if not self._enabled:
|
|
274
|
+
return ()
|
|
275
|
+
return (('c', 'rhio'), ('c', 'nats-jetstream'), ('c', 'bao'))
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Rhio cluster transport ladder rung for TransportRouter.
|
|
2
|
+
|
|
3
|
+
Integrates Rhio NATS JetStream and cluster blob replication into the transport ladder:
|
|
4
|
+
- Checks sidecar availability over local Unix domain socket (rhio.sock).
|
|
5
|
+
- Resolves peer Rhio endpoints from node descriptors (scheme 'rhio').
|
|
6
|
+
- Delivers messages to remote cluster peers via NATS JetStream pub/sub.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
from typing import Any, Callable, Mapping, Optional
|
|
13
|
+
|
|
14
|
+
from node.transport.const import TransportError
|
|
15
|
+
from node.transport.ladder import FaultLog
|
|
16
|
+
from node.transport.rung import Rung, Verdict, endpoints_of
|
|
17
|
+
from sidecar.fleetmesh_rhio import RhioSidecarClient
|
|
18
|
+
|
|
19
|
+
log = logging.getLogger('rhio_rung')
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
'RhioRung',
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RhioRung(Rung):
|
|
27
|
+
"""Transport rung delegating outbound deliveries to the Rhio cluster sidecar daemon."""
|
|
28
|
+
|
|
29
|
+
name: str = 'rhio'
|
|
30
|
+
rank: int = 12 # Cluster sync tier (below FIPS 5, above P2Panda 15)
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
router: Optional[Any] = None,
|
|
35
|
+
sidecar_client: Optional[RhioSidecarClient] = None,
|
|
36
|
+
socket_path: Optional[str] = None,
|
|
37
|
+
enabled: bool = True,
|
|
38
|
+
clock: Optional[Callable[[], int]] = None,
|
|
39
|
+
):
|
|
40
|
+
self.router = router
|
|
41
|
+
self._client = sidecar_client
|
|
42
|
+
self._socket_path = socket_path
|
|
43
|
+
self.enabled = enabled
|
|
44
|
+
clk = clock or (getattr(router, 'clock', None) if router else None)
|
|
45
|
+
self._faults = FaultLog(self.name, clock=clk)
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def faults(self) -> FaultLog:
|
|
49
|
+
return self._faults
|
|
50
|
+
|
|
51
|
+
def fault_log(self) -> Optional[FaultLog]:
|
|
52
|
+
return self._faults
|
|
53
|
+
|
|
54
|
+
def _get_client(self) -> Optional[RhioSidecarClient]:
|
|
55
|
+
if self._client is not None:
|
|
56
|
+
return self._client
|
|
57
|
+
sock_path = self._socket_path
|
|
58
|
+
if sock_path is None and self.router is not None:
|
|
59
|
+
node_dir = getattr(self.router, 'dir', None) or getattr(self.router, 'node_dir', None)
|
|
60
|
+
if node_dir:
|
|
61
|
+
sock_path = os.path.join(node_dir, 'rhio.sock')
|
|
62
|
+
if sock_path and os.path.exists(sock_path):
|
|
63
|
+
self._client = RhioSidecarClient(sock_path)
|
|
64
|
+
return self._client
|
|
65
|
+
|
|
66
|
+
def available(self) -> bool:
|
|
67
|
+
"""Returns True if the Rhio sidecar socket is reachable and healthy."""
|
|
68
|
+
if not self.enabled:
|
|
69
|
+
return False
|
|
70
|
+
client = self._get_client()
|
|
71
|
+
if client is None:
|
|
72
|
+
return False
|
|
73
|
+
try:
|
|
74
|
+
st = client.status()
|
|
75
|
+
return bool(st.get('ok'))
|
|
76
|
+
except Exception:
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
def target_of(self, desc: Optional[Mapping[str, Any]], theirs: Mapping[str, int]) -> Optional[str]:
|
|
80
|
+
"""Extract Rhio endpoint target from descriptor."""
|
|
81
|
+
if desc is None:
|
|
82
|
+
return None
|
|
83
|
+
_, where = endpoints_of(desc)
|
|
84
|
+
if 'rhio' in where:
|
|
85
|
+
return where['rhio']
|
|
86
|
+
|
|
87
|
+
# Check raw tags
|
|
88
|
+
for t in desc.get('tags', []):
|
|
89
|
+
if len(t) > 2 and t[0] in ('endpoint', 'address') and t[1] == 'rhio':
|
|
90
|
+
return str(t[2])
|
|
91
|
+
if len(t) > 1 and t[0] == 'c' and t[1] == 'rhio':
|
|
92
|
+
return ''
|
|
93
|
+
if 'rhio' in theirs:
|
|
94
|
+
return ''
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
def deliver(
|
|
98
|
+
self,
|
|
99
|
+
message: Mapping[str, Any],
|
|
100
|
+
to_pubkey: str,
|
|
101
|
+
target: str,
|
|
102
|
+
anonymous: bool = False,
|
|
103
|
+
) -> Verdict:
|
|
104
|
+
"""Attempt delivery of message to target Rhio endpoint via JetStream."""
|
|
105
|
+
if not self.enabled:
|
|
106
|
+
raise TransportError("rhio rung is disabled")
|
|
107
|
+
|
|
108
|
+
client = self._get_client()
|
|
109
|
+
if client is None:
|
|
110
|
+
raise TransportError("rhio sidecar client unavailable")
|
|
111
|
+
|
|
112
|
+
subject = f"fleetmesh.{to_pubkey[:16]}" if to_pubkey else "fleetmesh.broadcast"
|
|
113
|
+
try:
|
|
114
|
+
res = client.publish(subject, dict(message))
|
|
115
|
+
if not res.get("ok"):
|
|
116
|
+
raise TransportError(f"rhio publish failed: {res.get('error', 'unknown error')}")
|
|
117
|
+
msg_id = res.get("id", "")
|
|
118
|
+
return {self.name: (True, f"published to {subject} id={msg_id[:16]} via JetStream")}
|
|
119
|
+
except Exception as e:
|
|
120
|
+
self._faults.record(f"deliver failed: {e}", ident=to_pubkey[:16] if to_pubkey else None)
|
|
121
|
+
raise TransportError(f"rhio delivery failed: {e}") from e
|
|
122
|
+
|
|
123
|
+
def health(self) -> Optional[Mapping[str, Any]]:
|
|
124
|
+
client = self._get_client()
|
|
125
|
+
st = {}
|
|
126
|
+
if client:
|
|
127
|
+
try:
|
|
128
|
+
st = dict(client.status())
|
|
129
|
+
except Exception:
|
|
130
|
+
pass
|
|
131
|
+
return {
|
|
132
|
+
'available': self.available(),
|
|
133
|
+
'sidecar_connected': bool(st.get('connected', False)),
|
|
134
|
+
'messages_count': st.get('messages_count', 0),
|
|
135
|
+
'blobs_count': st.get('blobs_count', 0),
|
|
136
|
+
}
|