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
node/config/node.py
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"""NodeConfig: the whole of config.json as one frozen dataclass.
|
|
2
|
+
|
|
3
|
+
`DEFAULTS` in node.app.config is generated from `NodeConfig()` so the file's
|
|
4
|
+
defaults have one source. `check` names every problem; `from_dict` builds
|
|
5
|
+
the typed view. The dict the compartments still read is kept beside it
|
|
6
|
+
(see node.app.config.load_config) until every compartment reads its block
|
|
7
|
+
through `Registry.assemble`.
|
|
8
|
+
|
|
9
|
+
Unknown top-level keys are refused by name: a misspelt block (`capacitty`)
|
|
10
|
+
is exactly the silent failure this exists to catch. Out-of-tree compartments
|
|
11
|
+
put theirs under `extensions`, which is carried as-is and checked by the
|
|
12
|
+
compartment that owns it. Keys beginning with `_` are the node's own
|
|
13
|
+
runtime notes and never saved.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import dataclasses
|
|
18
|
+
from typing import Any, ClassVar, Mapping, MutableMapping, Optional, TypedDict, cast
|
|
19
|
+
|
|
20
|
+
from .base import Block, Problems, all_str, is_hex64, is_int, is_relay_url, positive_int
|
|
21
|
+
from .compartments import (AnnounceConfig, BlobReplicationConfig, BlobWireConfig, BlobsConfig, BridgeConfig,
|
|
22
|
+
CapacityConfig, ComputeConfig, DVMConfig, FetchPricingConfig, NsiteConfig, GossipConfig, LanConfig,
|
|
23
|
+
MediationConfig, P2PConfig, PluginsConfig, SettlementConfig, SidecarsConfig, SIDECAR_KEYS_MOVED, sidecars_of, StorageConfig,
|
|
24
|
+
TransportConfig, UpdatesConfig, HostMultiplexerConfig, NostrProtocolConfig, ProtocolSuitesConfig)
|
|
25
|
+
from .records import (CLASSES, HANDOVER, CoverageClaim, Endpoint, FaceConfig, PeerPull, PolicyConfig,
|
|
26
|
+
TrustDecision)
|
|
27
|
+
|
|
28
|
+
DEFAULT_PROTOCOLS: tuple[str, ...] = ('https://fleetmesh.org/sync/1.0',)
|
|
29
|
+
DEFAULT_CAPABILITIES: tuple[str, ...] = ('relay', 'negentropy')
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class NodeConfigDict(TypedDict, total=False):
|
|
33
|
+
cls_: str
|
|
34
|
+
name: str
|
|
35
|
+
relays: list[str]
|
|
36
|
+
face: Mapping[str, Any]
|
|
37
|
+
endpoints: list[Mapping[str, Any]]
|
|
38
|
+
protocols: list[str]
|
|
39
|
+
capabilities: list[str]
|
|
40
|
+
operator: Optional[str]
|
|
41
|
+
pow_bits: Optional[int]
|
|
42
|
+
policy: Mapping[str, Any]
|
|
43
|
+
coverage: list[Mapping[str, Any]]
|
|
44
|
+
handover: str
|
|
45
|
+
retention: dict[int, int]
|
|
46
|
+
trust: Mapping[str, Any]
|
|
47
|
+
peers: list[Mapping[str, Any]]
|
|
48
|
+
tick_seconds: int
|
|
49
|
+
heartbeat: bool
|
|
50
|
+
projection: bool
|
|
51
|
+
telemetry: bool
|
|
52
|
+
fetch_pricing: Mapping[str, Any]
|
|
53
|
+
bridge: Optional[Mapping[str, Any]]
|
|
54
|
+
announce: Mapping[str, Any]
|
|
55
|
+
lan: Mapping[str, Any]
|
|
56
|
+
p2p: Optional[Mapping[str, Any]]
|
|
57
|
+
gossip: Optional[Mapping[str, Any]]
|
|
58
|
+
linked: Mapping[str, Any]
|
|
59
|
+
capacity: Mapping[str, Any]
|
|
60
|
+
compute: Optional[Mapping[str, Any]]
|
|
61
|
+
dvm: Optional[Mapping[str, Any]]
|
|
62
|
+
settlement: Mapping[str, Any]
|
|
63
|
+
mediation: Mapping[str, Any]
|
|
64
|
+
transport: Mapping[str, Any]
|
|
65
|
+
blob_wire: Mapping[str, Any]
|
|
66
|
+
blob_replication: Mapping[str, Any]
|
|
67
|
+
storage: Mapping[str, Any]
|
|
68
|
+
blobs: Mapping[str, Any]
|
|
69
|
+
sidecars: Mapping[str, Any]
|
|
70
|
+
plugins: Mapping[str, Any]
|
|
71
|
+
updates: Optional[Mapping[str, Any]]
|
|
72
|
+
protocol_suites: Optional[Mapping[str, Any]]
|
|
73
|
+
extensions: Mapping[str, Any]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclasses.dataclass(frozen=True)
|
|
77
|
+
class NodeConfig(Block):
|
|
78
|
+
# --- the node -----------------------------------------------------------
|
|
79
|
+
cls_: str = 'edge' # `class` in the file
|
|
80
|
+
name: str = 'meshnode'
|
|
81
|
+
relays: tuple[str, ...] = () # the control plane's relays; empty allowed
|
|
82
|
+
face: FaceConfig = FaceConfig()
|
|
83
|
+
endpoints: tuple[Endpoint, ...] = ()
|
|
84
|
+
protocols: tuple[str, ...] = DEFAULT_PROTOCOLS
|
|
85
|
+
capabilities: tuple[str, ...] = DEFAULT_CAPABILITIES
|
|
86
|
+
operator: Optional[str] = None
|
|
87
|
+
pow_bits: Optional[int] = None # None: the constants' default
|
|
88
|
+
policy: PolicyConfig = PolicyConfig()
|
|
89
|
+
coverage: tuple[CoverageClaim, ...] = ()
|
|
90
|
+
handover: str = 'offer'
|
|
91
|
+
retention: dict[int, int] = dataclasses.field(default_factory=dict) # {kind: seconds}; empty keeps everything
|
|
92
|
+
trust: dict[str, TrustDecision] = dataclasses.field(default_factory=dict)
|
|
93
|
+
peers: tuple[PeerPull, ...] = ()
|
|
94
|
+
tick_seconds: int = 30
|
|
95
|
+
heartbeat: bool = True
|
|
96
|
+
projection: bool = True
|
|
97
|
+
telemetry: bool = False # kind 21802, opt-in: coarse counters about this node, no per-peer attribution
|
|
98
|
+
# --- the compartments ------------------------------------------------------
|
|
99
|
+
fetch_pricing: FetchPricingConfig = FetchPricingConfig()
|
|
100
|
+
bridge: Optional[BridgeConfig] = None # None: the block is absent (bridge on with defaults)
|
|
101
|
+
announce: AnnounceConfig = AnnounceConfig()
|
|
102
|
+
lan: LanConfig = LanConfig()
|
|
103
|
+
p2p: Optional[P2PConfig] = None # None: absent; the compartment is off
|
|
104
|
+
gossip: Optional[GossipConfig] = None # None: absent; the compartment is off
|
|
105
|
+
capacity: CapacityConfig = CapacityConfig()
|
|
106
|
+
compute: Optional[ComputeConfig] = None
|
|
107
|
+
dvm: Optional[DVMConfig] = None
|
|
108
|
+
nsite: Optional[NsiteConfig] = None
|
|
109
|
+
settlement: SettlementConfig = SettlementConfig()
|
|
110
|
+
mediation: MediationConfig = MediationConfig()
|
|
111
|
+
transport: TransportConfig = TransportConfig()
|
|
112
|
+
blob_wire: BlobWireConfig = BlobWireConfig()
|
|
113
|
+
blob_replication: BlobReplicationConfig = BlobReplicationConfig()
|
|
114
|
+
storage: StorageConfig = StorageConfig()
|
|
115
|
+
blobs: BlobsConfig = BlobsConfig()
|
|
116
|
+
sidecars: SidecarsConfig = SidecarsConfig()
|
|
117
|
+
plugins: PluginsConfig = PluginsConfig()
|
|
118
|
+
updates: Optional[UpdatesConfig] = None # optional compartment; presence is the switch
|
|
119
|
+
protocol_suites: ProtocolSuitesConfig = ProtocolSuitesConfig()
|
|
120
|
+
extensions: Mapping[str, Any] = dataclasses.field(default_factory=dict)
|
|
121
|
+
|
|
122
|
+
# Keys the file carries that are written by default. Absent-by-default
|
|
123
|
+
# blocks (bridge, p2p, compute, dvm, and every other compartment's) are
|
|
124
|
+
# not written: their presence has meaning (`enabled_by`), and a default
|
|
125
|
+
# file should ask for nothing. `defaults_dict` is derived from this list
|
|
126
|
+
# and the dataclass defaults, so the two cannot drift.
|
|
127
|
+
WRITTEN_BY_DEFAULT: ClassVar[tuple[str, ...]] = ('class', 'relays', 'face', 'endpoints', 'protocols', 'capabilities', 'operator',
|
|
128
|
+
'pow_bits', 'policy', 'coverage', 'handover', 'retention', 'trust', 'peers',
|
|
129
|
+
'tick_seconds', 'heartbeat', 'projection', 'fetch_pricing')
|
|
130
|
+
|
|
131
|
+
# File key -> dataclass field, where they differ.
|
|
132
|
+
FIELD_OF: ClassVar[dict[str, str]] = {'class': 'cls_'}
|
|
133
|
+
|
|
134
|
+
# Every key the file may carry, with the block that checks it (None: checked inline).
|
|
135
|
+
BLOCKS: ClassVar[dict[str, Optional[type[Block]]]] = {
|
|
136
|
+
'class': None, 'name': None, 'relays': None, 'face': FaceConfig, 'endpoints': None, 'protocols': None,
|
|
137
|
+
'capabilities': None, 'operator': None, 'pow_bits': None, 'policy': PolicyConfig, 'coverage': None,
|
|
138
|
+
'handover': None, 'retention': None, 'trust': None, 'peers': None, 'tick_seconds': None,
|
|
139
|
+
'heartbeat': None, 'projection': None, 'telemetry': None,
|
|
140
|
+
'fetch_pricing': FetchPricingConfig, 'bridge': BridgeConfig, 'announce': AnnounceConfig, 'lan': LanConfig,
|
|
141
|
+
'p2p': P2PConfig, 'gossip': GossipConfig, 'capacity': CapacityConfig, 'compute': ComputeConfig, 'dvm': DVMConfig, 'nsite': NsiteConfig,
|
|
142
|
+
# `video` is a known key with its block declared on the compartment
|
|
143
|
+
# (VideoConfig), the way `trust` carries TrustConfig. It was neither:
|
|
144
|
+
# the compartment declared reads=('video',) and config_cls=VideoConfig
|
|
145
|
+
# while NodeConfig had no such key, so the block was never a typed,
|
|
146
|
+
# validated part of the config - which nothing caught, because
|
|
147
|
+
# test_config_contract walks MANIFEST and this compartment was not in it.
|
|
148
|
+
'video': None,
|
|
149
|
+
'settlement': SettlementConfig, 'mediation': MediationConfig, 'transport': TransportConfig,
|
|
150
|
+
'blob_wire': BlobWireConfig, 'blob_replication': BlobReplicationConfig, 'storage': StorageConfig,
|
|
151
|
+
'blobs': BlobsConfig, 'sidecars': SidecarsConfig, 'plugins': PluginsConfig,
|
|
152
|
+
# The sidecar blocks before they moved under `sidecars`: read for one
|
|
153
|
+
# release, folded in by from_dict, written back in the new shape.
|
|
154
|
+
'grasp': None, 'ncontainer': None, 'magpie': None,
|
|
155
|
+
'updates': UpdatesConfig,
|
|
156
|
+
'protocol_suites': ProtocolSuitesConfig,
|
|
157
|
+
'extensions': None,
|
|
158
|
+
# Written by the node, not the operator; carried and never checked.
|
|
159
|
+
'executors': None,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@classmethod
|
|
163
|
+
def check(cls, raw: Any, p: Problems) -> None:
|
|
164
|
+
if not isinstance(raw, Mapping):
|
|
165
|
+
p.add('config is not an object'); return
|
|
166
|
+
if raw.get('class') not in CLASSES:
|
|
167
|
+
p.add('class must be leaf, edge or anchor')
|
|
168
|
+
relays = raw.get('relays', [])
|
|
169
|
+
if not isinstance(relays, list) or not all(is_relay_url(r) for r in relays):
|
|
170
|
+
p.add('relays must be a list of ws:// or wss:// URLs')
|
|
171
|
+
FaceConfig.check(raw.get('face', {}), p)
|
|
172
|
+
eps = raw.get('endpoints', [])
|
|
173
|
+
if not isinstance(eps, list) or not all(Endpoint.well_formed(e) for e in eps):
|
|
174
|
+
p.add('endpoints must be a list of {transport, address[, priority]}')
|
|
175
|
+
if not positive_int(raw.get('tick_seconds', 30)):
|
|
176
|
+
p.add('tick_seconds must be a positive integer')
|
|
177
|
+
if not isinstance(raw.get('heartbeat', True), bool):
|
|
178
|
+
p.add('heartbeat must be true or false')
|
|
179
|
+
if not isinstance(raw.get('projection', True), bool):
|
|
180
|
+
p.add('projection must be true or false')
|
|
181
|
+
if not isinstance(raw.get('telemetry', False), bool):
|
|
182
|
+
p.add('telemetry must be true or false')
|
|
183
|
+
if 'name' in raw and not isinstance(raw['name'], str):
|
|
184
|
+
p.add('name must be a string')
|
|
185
|
+
op = raw.get('operator')
|
|
186
|
+
if op is not None and not is_hex64(op):
|
|
187
|
+
p.add('operator must be a 64-hex pubkey')
|
|
188
|
+
bits = raw.get('pow_bits')
|
|
189
|
+
if bits is not None and (not is_int(bits) or not 0 <= bits <= 40):
|
|
190
|
+
p.add('pow_bits must be an integer from 0 to 40')
|
|
191
|
+
raw_protocols = raw.get('protocols')
|
|
192
|
+
if raw_protocols is not None:
|
|
193
|
+
if isinstance(raw_protocols, Mapping):
|
|
194
|
+
ProtocolSuitesConfig.check(raw_protocols, p)
|
|
195
|
+
elif isinstance(raw_protocols, (list, tuple)):
|
|
196
|
+
if not all_str(raw_protocols):
|
|
197
|
+
p.add('protocols must be a list of identifiers')
|
|
198
|
+
else:
|
|
199
|
+
p.add('protocols must be a list of strings or a protocol suite object')
|
|
200
|
+
if 'protocol_suites' in raw and raw['protocol_suites'] is not None:
|
|
201
|
+
ProtocolSuitesConfig.check(raw['protocol_suites'], p)
|
|
202
|
+
if not all_str(raw.get('capabilities', [])):
|
|
203
|
+
p.add('capabilities must be a list of names')
|
|
204
|
+
peers = raw.get('peers', [])
|
|
205
|
+
if not isinstance(peers, list):
|
|
206
|
+
p.add('peers must be a list')
|
|
207
|
+
else:
|
|
208
|
+
for i, e in enumerate(peers):
|
|
209
|
+
PeerPull.check_one(i, e, p)
|
|
210
|
+
trust = raw.get('trust', {})
|
|
211
|
+
if not isinstance(trust, Mapping):
|
|
212
|
+
p.add('trust must be an object of pubkey -> decision')
|
|
213
|
+
else:
|
|
214
|
+
for pk, e in trust.items():
|
|
215
|
+
TrustDecision.check_one(pk, e, p)
|
|
216
|
+
if raw.get('handover', 'offer') not in HANDOVER:
|
|
217
|
+
p.add('handover must be offer or never')
|
|
218
|
+
cov = raw.get('coverage', [])
|
|
219
|
+
if not isinstance(cov, list):
|
|
220
|
+
p.add('coverage must be a list')
|
|
221
|
+
else:
|
|
222
|
+
for i, e in enumerate(cov):
|
|
223
|
+
CoverageClaim.check_one(i, e, p)
|
|
224
|
+
ret = raw.get('retention', {})
|
|
225
|
+
if not isinstance(ret, Mapping):
|
|
226
|
+
p.add('retention must be an object of {kind: seconds}')
|
|
227
|
+
else:
|
|
228
|
+
for k, v in ret.items():
|
|
229
|
+
if not str(k).lstrip('-').isdigit() or not is_int(v) or int(v) < 0:
|
|
230
|
+
p.add('retention[%s] must be a kind number and a positive number of seconds' % str(k)[:12])
|
|
231
|
+
PolicyConfig.check(raw.get('policy', {}), p)
|
|
232
|
+
if 'extensions' in raw and not isinstance(raw['extensions'], Mapping):
|
|
233
|
+
p.add('extensions must be an object of {name: block}')
|
|
234
|
+
for key, blk in cls.BLOCKS.items():
|
|
235
|
+
if blk is not None and key not in ('face', 'policy') and raw.get(key) is not None:
|
|
236
|
+
blk.check(raw[key], p)
|
|
237
|
+
moved = {k: raw[k] for k in SIDECAR_KEYS_MOVED if raw.get(k) is not None}
|
|
238
|
+
if moved:
|
|
239
|
+
SidecarsConfig.check(moved, p)
|
|
240
|
+
unknown = sorted(k for k in raw if k not in cls.BLOCKS and not str(k).startswith('_'))
|
|
241
|
+
if unknown:
|
|
242
|
+
p.add('no compartment reads: %s (an out-of-tree block goes under extensions)' % ', '.join(unknown))
|
|
243
|
+
|
|
244
|
+
@classmethod
|
|
245
|
+
def from_dict(cls, raw: Any) -> 'NodeConfig':
|
|
246
|
+
def block(key: str, blk: Any, absent: Any = 'default') -> Any:
|
|
247
|
+
v = raw.get(key)
|
|
248
|
+
if v is None:
|
|
249
|
+
return blk() if absent == 'default' else None
|
|
250
|
+
return blk.from_dict(v)
|
|
251
|
+
return cls(
|
|
252
|
+
cls_=raw.get('class', 'edge'), name=raw.get('name', 'meshnode'), relays=tuple(raw.get('relays') or ()),
|
|
253
|
+
face=FaceConfig.from_dict(raw.get('face') or {}),
|
|
254
|
+
endpoints=tuple(Endpoint.from_dict(e) for e in raw.get('endpoints') or ()),
|
|
255
|
+
capabilities=tuple(raw.get('capabilities', DEFAULT_CAPABILITIES)),
|
|
256
|
+
operator=raw.get('operator'), pow_bits=raw.get('pow_bits'),
|
|
257
|
+
policy=PolicyConfig.from_dict(raw.get('policy') or {}),
|
|
258
|
+
coverage=tuple(CoverageClaim.from_dict(c) for c in raw.get('coverage') or ()),
|
|
259
|
+
handover=raw.get('handover', 'offer'),
|
|
260
|
+
retention={int(k): int(v) for k, v in (raw.get('retention') or {}).items()},
|
|
261
|
+
trust={pk: TrustDecision.from_dict(e) for pk, e in (raw.get('trust') or {}).items()},
|
|
262
|
+
peers=tuple(PeerPull.from_dict(e) for e in raw.get('peers') or ()),
|
|
263
|
+
tick_seconds=int(raw.get('tick_seconds', 30)), heartbeat=bool(raw.get('heartbeat', True)),
|
|
264
|
+
projection=bool(raw.get('projection', True)), telemetry=bool(raw.get('telemetry', False)),
|
|
265
|
+
fetch_pricing=block('fetch_pricing', FetchPricingConfig),
|
|
266
|
+
bridge=block('bridge', BridgeConfig, absent=None),
|
|
267
|
+
announce=block('announce', AnnounceConfig), lan=block('lan', LanConfig),
|
|
268
|
+
p2p=block('p2p', P2PConfig, absent=None), gossip=block('gossip', GossipConfig, absent=None),
|
|
269
|
+
capacity=block('capacity', CapacityConfig),
|
|
270
|
+
compute=block('compute', ComputeConfig, absent=None), dvm=block('dvm', DVMConfig, absent=None),
|
|
271
|
+
nsite=block('nsite', NsiteConfig, absent=None),
|
|
272
|
+
settlement=block('settlement', SettlementConfig), mediation=block('mediation', MediationConfig),
|
|
273
|
+
transport=block('transport', TransportConfig), blob_wire=block('blob_wire', BlobWireConfig),
|
|
274
|
+
blob_replication=block('blob_replication', BlobReplicationConfig), storage=block('storage', StorageConfig),
|
|
275
|
+
blobs=block('blobs', BlobsConfig), sidecars=SidecarsConfig.from_dict(sidecars_of(raw)),
|
|
276
|
+
plugins=block('plugins', PluginsConfig),
|
|
277
|
+
updates=block('updates', UpdatesConfig, absent=None),
|
|
278
|
+
protocol_suites=(
|
|
279
|
+
ProtocolSuitesConfig.from_dict(raw['protocols']) if isinstance(raw.get('protocols'), Mapping)
|
|
280
|
+
else ProtocolSuitesConfig.from_dict(raw['protocol_suites']) if isinstance(raw.get('protocol_suites'), Mapping)
|
|
281
|
+
else ProtocolSuitesConfig(
|
|
282
|
+
host=HostMultiplexerConfig(
|
|
283
|
+
listen=str((raw.get('face') or {}).get('host', '127.0.0.1')),
|
|
284
|
+
port=int((raw.get('face') or {}).get('port', 0)),
|
|
285
|
+
),
|
|
286
|
+
nostr=NostrProtocolConfig(
|
|
287
|
+
future_seconds=int((raw.get('face') or {}).get('future_seconds', 900)),
|
|
288
|
+
),
|
|
289
|
+
)
|
|
290
|
+
),
|
|
291
|
+
protocols=(
|
|
292
|
+
DEFAULT_PROTOCOLS if isinstance(raw.get('protocols'), Mapping)
|
|
293
|
+
else tuple(raw.get('protocols', DEFAULT_PROTOCOLS))
|
|
294
|
+
),
|
|
295
|
+
extensions=dict(raw.get('extensions') or {}),
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
@classmethod
|
|
299
|
+
def defaults_dict(cls) -> NodeConfigDict:
|
|
300
|
+
"""What a fresh config.json carries: the file shape of NodeConfig(),
|
|
301
|
+
one entry per WRITTEN_BY_DEFAULT key, each the JSON form of that
|
|
302
|
+
field's dataclass default (blocks via to_dict, tuples as lists)."""
|
|
303
|
+
d = cls()
|
|
304
|
+
out: MutableMapping[str, Any] = {}
|
|
305
|
+
for key in cls.WRITTEN_BY_DEFAULT:
|
|
306
|
+
v = getattr(d, cls.FIELD_OF.get(key, key))
|
|
307
|
+
if isinstance(v, Block):
|
|
308
|
+
out[key] = v.to_dict()
|
|
309
|
+
elif isinstance(v, tuple):
|
|
310
|
+
out[key] = list(v)
|
|
311
|
+
elif isinstance(v, dict):
|
|
312
|
+
out[key] = dict(v)
|
|
313
|
+
else:
|
|
314
|
+
out[key] = v
|
|
315
|
+
return cast(NodeConfigDict, out)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
__all__ = ['NodeConfig', 'DEFAULT_PROTOCOLS', 'DEFAULT_CAPABILITIES']
|