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,46 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""The reference out-of-process plugin: follow a node, print, ask.
|
|
3
|
+
|
|
4
|
+
python3 -m node.examples.follower ~/.meshnode [topic ...]
|
|
5
|
+
|
|
6
|
+
Everything it does goes through the control socket (docs/extending.md,
|
|
7
|
+
"Out-of-process plugins"). It holds no keys, runs as whoever started it,
|
|
8
|
+
and when a grant is issued it asks the node who the peer is - the one
|
|
9
|
+
"act" in the example, chosen because it reads rather than changes.
|
|
10
|
+
"""
|
|
11
|
+
import json
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
from node.app import socket as control
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def follow(node_dir, topics=(), out=sys.stdout, limit=None):
|
|
18
|
+
"""Print envelopes until the node goes away, `limit` envelopes or ^C.
|
|
19
|
+
|
|
20
|
+
Returns how many envelopes it printed; the test counts them.
|
|
21
|
+
"""
|
|
22
|
+
args = {'topics': list(topics)} if topics else {}
|
|
23
|
+
it = control.stream(node_dir, 'subscribe', timeout=None, **args)
|
|
24
|
+
ack = next(it)
|
|
25
|
+
print('following from seq %s' % ack['seq'], file=sys.stderr)
|
|
26
|
+
seen = 0
|
|
27
|
+
for env in it:
|
|
28
|
+
line = {'seq': env['seq'], 'topic': env['topic'], 'data': env['data']}
|
|
29
|
+
if env['topic'] == 'grant.issued':
|
|
30
|
+
# A plugin acts with the operator's verbs and no others.
|
|
31
|
+
peers = control.call(node_dir, 'peers')
|
|
32
|
+
line['peer_known'] = any(p.get('peer') == env['data']['peer'] for p in peers)
|
|
33
|
+
print(json.dumps(line), file=out, flush=True)
|
|
34
|
+
seen += 1
|
|
35
|
+
if limit and seen >= limit:
|
|
36
|
+
break
|
|
37
|
+
return seen
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == '__main__':
|
|
41
|
+
if len(sys.argv) < 2:
|
|
42
|
+
sys.exit(__doc__)
|
|
43
|
+
try:
|
|
44
|
+
follow(sys.argv[1], sys.argv[2:])
|
|
45
|
+
except KeyboardInterrupt:
|
|
46
|
+
pass
|
node/interfaces.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Formal interfaces, protocols, and data contracts for meshnode.
|
|
2
|
+
|
|
3
|
+
Standard library only. All wire types reflect the normative JSON Schemas in
|
|
4
|
+
schemas/ and the protocol invariants verified in fleetmesh_core/.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import (
|
|
9
|
+
Any,
|
|
10
|
+
Counter,
|
|
11
|
+
Mapping,
|
|
12
|
+
Optional,
|
|
13
|
+
Protocol,
|
|
14
|
+
Sequence,
|
|
15
|
+
TypedDict,
|
|
16
|
+
runtime_checkable,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# Core wire, event, filter, and DIDComm shapes are defined once in
|
|
20
|
+
# fleetmesh_core.wire.interfaces and re-exported here to ensure exact parity.
|
|
21
|
+
from fleetmesh_core.wire.interfaces import (
|
|
22
|
+
DIDCommAttachment,
|
|
23
|
+
DIDCommAttachmentData,
|
|
24
|
+
DIDCommMessage,
|
|
25
|
+
DeliveryEnvelope,
|
|
26
|
+
DeliveryHop,
|
|
27
|
+
EventLike,
|
|
28
|
+
GrantLimits,
|
|
29
|
+
GrantPayload,
|
|
30
|
+
GrantScope,
|
|
31
|
+
NostrEvent,
|
|
32
|
+
NostrFilter,
|
|
33
|
+
Tag,
|
|
34
|
+
TagList,
|
|
35
|
+
UnsignedNostrEvent,
|
|
36
|
+
ProtocolHealth,
|
|
37
|
+
RouteMatch,
|
|
38
|
+
ProtocolHostRouter,
|
|
39
|
+
ProtocolIdentityAdapter,
|
|
40
|
+
ProtocolPackage,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
'DeliveryEnvelope', 'DeliveryHop', 'GrantLimits', 'GrantPayload', 'GrantScope',
|
|
45
|
+
'Tag', 'TagList', 'NostrEvent', 'UnsignedNostrEvent', 'EventLike', 'NostrFilter',
|
|
46
|
+
'DIDCommAttachmentData', 'DIDCommAttachment', 'DIDCommMessage',
|
|
47
|
+
'RelayHealthRow', 'AwaitingAckRow', 'ThrottledRow', 'ReceiptRefusedRow',
|
|
48
|
+
'GrantStatusRow', 'PendingGrantRow', 'EvaluationRow', 'RepricedRow',
|
|
49
|
+
'HealthStatusDict', 'NodeStatusDict', 'CommandSpec',
|
|
50
|
+
'KeyStoreProtocol', 'TransportProtocol', 'EventStoreProtocol', 'CompartmentProtocol',
|
|
51
|
+
'ProtocolHealth', 'RouteMatch', 'ProtocolHostRouter', 'ProtocolIdentityAdapter', 'ProtocolPackage',
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@runtime_checkable
|
|
56
|
+
class KeyStoreProtocol(Protocol):
|
|
57
|
+
"""The node keys and identity."""
|
|
58
|
+
node_sk: str
|
|
59
|
+
node_pk: str
|
|
60
|
+
did: str
|
|
61
|
+
|
|
62
|
+
def multikeys(self) -> dict[str, str]: ...
|
|
63
|
+
def kid(self, which: str = 'agreement') -> str: ...
|
|
64
|
+
def save(self, directory: Any) -> str: ...
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@runtime_checkable
|
|
68
|
+
class TransportProtocol(Protocol):
|
|
69
|
+
"""The transport adapter interface: sends and receives DIDComm envelopes."""
|
|
70
|
+
scheme: str
|
|
71
|
+
health: Mapping[str, Mapping[str, Any]]
|
|
72
|
+
drop_counts: Counter[str]
|
|
73
|
+
|
|
74
|
+
def send(self, peer_pk: str, message: DIDCommMessage | Mapping[str, Any], authenticated: bool = True) -> Any: ...
|
|
75
|
+
def request(
|
|
76
|
+
self,
|
|
77
|
+
peer_pk: str,
|
|
78
|
+
message: DIDCommMessage | Mapping[str, Any],
|
|
79
|
+
timeout: Optional[float] = None,
|
|
80
|
+
authenticated: bool = True,
|
|
81
|
+
) -> Optional[DIDCommMessage | Mapping[str, Any]]: ...
|
|
82
|
+
def close(self) -> None: ...
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@runtime_checkable
|
|
86
|
+
class EventStoreProtocol(Protocol):
|
|
87
|
+
"""Storage for Nostr events and provenance."""
|
|
88
|
+
def insert_event(self, event: NostrEvent) -> str: ...
|
|
89
|
+
def get_event(self, event_id: str) -> Optional[NostrEvent]: ...
|
|
90
|
+
def has_event(self, event_id: str) -> bool: ...
|
|
91
|
+
def note_origin(self, event_id: str, origin: str) -> None: ...
|
|
92
|
+
def origin_of(self, event_id: str) -> Optional[str]: ...
|
|
93
|
+
def any_replicated(self, event_ids: Sequence[str]) -> bool: ...
|
|
94
|
+
def fingerprint_range(self, filt: NostrFilter | Mapping[str, Any], since: int, until: int, now: int) -> str: ...
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# ---- what `meshnode status` and `meshnode health` format -------------------
|
|
98
|
+
#
|
|
99
|
+
# A compartment's state() is free-form: it is JSON-serialised the moment it is
|
|
100
|
+
# read, and the CLI prints most of it with .get(). These are the rows whose
|
|
101
|
+
# fields the CLI formats by name, so a compartment that renames one is caught
|
|
102
|
+
# where it is written rather than where an operator reads it. total=False
|
|
103
|
+
# because status.json on disk predates every one of them.
|
|
104
|
+
|
|
105
|
+
class RelayHealthRow(TypedDict, total=False):
|
|
106
|
+
connected: bool
|
|
107
|
+
confirmed: bool # the relay answered the REQ, not merely received it
|
|
108
|
+
since: Optional[int]
|
|
109
|
+
reconnects: int
|
|
110
|
+
last_event_at: Optional[int]
|
|
111
|
+
last_error: Optional[str]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class AwaitingAckRow(TypedDict, total=False):
|
|
115
|
+
peer: str
|
|
116
|
+
type: str
|
|
117
|
+
at: int
|
|
118
|
+
relays: list[str]
|
|
119
|
+
heard_at: Optional[int] # unacknowledged rows only: when the peer last spoke
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class ThrottledRow(TypedDict, total=False):
|
|
123
|
+
peer: str
|
|
124
|
+
shard: str
|
|
125
|
+
why: str
|
|
126
|
+
until: int
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class ReceiptRefusedRow(TypedDict, total=False):
|
|
130
|
+
peer: Optional[str]
|
|
131
|
+
window: Optional[int]
|
|
132
|
+
named: Optional[str]
|
|
133
|
+
acceptable: list[str]
|
|
134
|
+
at: int
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class GrantStatusRow(TypedDict, total=False):
|
|
138
|
+
peer: str
|
|
139
|
+
direction: str
|
|
140
|
+
tier: Optional[str]
|
|
141
|
+
status: str
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class PendingGrantRow(TypedDict, total=False):
|
|
145
|
+
peer: str
|
|
146
|
+
tier: Optional[str]
|
|
147
|
+
from_epoch: int
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class EvaluationRow(TypedDict, total=False):
|
|
151
|
+
peer: str
|
|
152
|
+
epoch: int
|
|
153
|
+
severity: Optional[str] # None for a silent window: nothing was priced
|
|
154
|
+
state: str
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class RepricedRow(TypedDict, total=False):
|
|
158
|
+
peer: str
|
|
159
|
+
epoch: int
|
|
160
|
+
was: str
|
|
161
|
+
severity: str
|
|
162
|
+
remedy_cancelled: bool
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class HealthStatusDict(TypedDict, total=False):
|
|
166
|
+
subsystem_trouble: list[str]
|
|
167
|
+
config_error: Optional[str]
|
|
168
|
+
handler_failures: int
|
|
169
|
+
tick_failures: int
|
|
170
|
+
last_tick_error: Optional[str]
|
|
171
|
+
rejected: Mapping[str, int]
|
|
172
|
+
relays: Mapping[str, RelayHealthRow]
|
|
173
|
+
sessions_refused: int
|
|
174
|
+
receipts_refused: list[ReceiptRefusedRow]
|
|
175
|
+
awaiting_ack: list[AwaitingAckRow]
|
|
176
|
+
throttled: list[ThrottledRow]
|
|
177
|
+
grants: list[GrantStatusRow]
|
|
178
|
+
evaluations: list[EvaluationRow]
|
|
179
|
+
repriced: list[RepricedRow]
|
|
180
|
+
subsystems: Mapping[str, Any]
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
NodeStatusDict = TypedDict('NodeStatusDict', {
|
|
184
|
+
'at': int,
|
|
185
|
+
'did': str,
|
|
186
|
+
'node_did': str,
|
|
187
|
+
'pubkey': str,
|
|
188
|
+
'class': str,
|
|
189
|
+
'release': Mapping[str, Any],
|
|
190
|
+
'face': str,
|
|
191
|
+
'relays': list[str],
|
|
192
|
+
'descriptor': str,
|
|
193
|
+
'events_stored': int,
|
|
194
|
+
'grants': list[GrantStatusRow],
|
|
195
|
+
'pending_grants': list[PendingGrantRow],
|
|
196
|
+
'evaluations': list[EvaluationRow],
|
|
197
|
+
'repriced': list[RepricedRow],
|
|
198
|
+
'reports_published': int,
|
|
199
|
+
'pending_findings': int,
|
|
200
|
+
'refused_sessions': int,
|
|
201
|
+
'last_tick': list[list[str]],
|
|
202
|
+
'uptime': int | float,
|
|
203
|
+
'health': HealthStatusDict,
|
|
204
|
+
'compartments': Mapping[str, Any],
|
|
205
|
+
'subsystems': Mapping[str, Any],
|
|
206
|
+
}, total=False)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class CommandSpec(TypedDict, total=False):
|
|
210
|
+
"""One control-socket command as the socket documents it under `commands`:
|
|
211
|
+
the dispatch table is the documentation, so it cannot drift from it."""
|
|
212
|
+
args: Mapping[str, str] # name -> required | optional | a short note
|
|
213
|
+
does: str
|
|
214
|
+
reply: str
|
|
215
|
+
subsystem: str # set by the registry when it prefixes the name
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@runtime_checkable
|
|
219
|
+
class CompartmentProtocol(Protocol):
|
|
220
|
+
"""A compartment of the node, assembled and ticked uniformly."""
|
|
221
|
+
name: Optional[str]
|
|
222
|
+
reads: tuple[str, ...]
|
|
223
|
+
live: tuple[str, ...]
|
|
224
|
+
commands: Mapping[str, CommandSpec]
|
|
225
|
+
|
|
226
|
+
def start(self) -> None: ...
|
|
227
|
+
def tick(self, now: int) -> list[tuple[Any, ...]]: ...
|
|
228
|
+
def stop(self) -> Any: ...
|
|
229
|
+
def state(self) -> Mapping[str, Any]: ...
|
|
230
|
+
def trouble(self) -> list[str]: ...
|
|
231
|
+
def notes(self) -> list[str]: ...
|
|
232
|
+
def report(self) -> Mapping[str, Any]: ...
|
|
233
|
+
def command(self, name: str, args: Mapping[str, Any]) -> Any: ...
|
|
234
|
+
def capability_protocols(self) -> Sequence[str]: ...
|
|
235
|
+
def descriptor_tags(self) -> Sequence[Sequence[str]]: ...
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""The node's cryptographic keys and hardware enclave on disk."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from .core import FILE, Keystore
|
|
5
|
+
from .enclave import (
|
|
6
|
+
DEFAULT_ORIGIN,
|
|
7
|
+
DEFAULT_RP_ID,
|
|
8
|
+
EnclaveKey,
|
|
9
|
+
EnclaveKeystore,
|
|
10
|
+
detect_hardware_enclave,
|
|
11
|
+
verify_detached_attestation,
|
|
12
|
+
verify_enclave_attestation,
|
|
13
|
+
verify_signature,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
'FILE',
|
|
18
|
+
'Keystore',
|
|
19
|
+
'EnclaveKey',
|
|
20
|
+
'EnclaveKeystore',
|
|
21
|
+
'detect_hardware_enclave',
|
|
22
|
+
'verify_detached_attestation',
|
|
23
|
+
'verify_enclave_attestation',
|
|
24
|
+
'verify_signature',
|
|
25
|
+
'DEFAULT_RP_ID',
|
|
26
|
+
'DEFAULT_ORIGIN',
|
|
27
|
+
]
|
node/keystore/core.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""The node's keys on disk.
|
|
2
|
+
|
|
3
|
+
One JSON file, mode 0600, holding the three secrets constants.json names: the
|
|
4
|
+
secp256k1 node key (the nostr identity and did:nostr subject), the X25519
|
|
5
|
+
agreement key, and the Ed25519 transport key. Generated once, read every
|
|
6
|
+
start. Nothing else is stored here: grants, receipts and the event store have
|
|
7
|
+
their own files, and an operator who rotates the node key has a new node.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import stat
|
|
14
|
+
from typing import Literal
|
|
15
|
+
|
|
16
|
+
from cryptography.hazmat.primitives.asymmetric import ed25519, x25519
|
|
17
|
+
|
|
18
|
+
from fleetmesh_core.crypto import crypto, keys # noqa: E402
|
|
19
|
+
|
|
20
|
+
FILE = 'keys.json'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Keystore:
|
|
24
|
+
node_sk: str
|
|
25
|
+
node_pk: str
|
|
26
|
+
did: str
|
|
27
|
+
agreement_priv: x25519.X25519PrivateKey
|
|
28
|
+
agreement_pub: x25519.X25519PublicKey
|
|
29
|
+
transport_priv: ed25519.Ed25519PrivateKey
|
|
30
|
+
transport_pub: ed25519.Ed25519PublicKey
|
|
31
|
+
_seeds: tuple[bytes, bytes]
|
|
32
|
+
|
|
33
|
+
def __init__(self, node_sk: str, agreement_seed: bytes, transport_seed: bytes) -> None:
|
|
34
|
+
# A secret that is not a scalar in [1, N) is not a key. Below 1 the
|
|
35
|
+
# curve arithmetic returned None and the node died on a TypeError;
|
|
36
|
+
# at or above N it was silently reduced, so the node signed with a
|
|
37
|
+
# key that was not the one in its file. Refuse both by name.
|
|
38
|
+
try:
|
|
39
|
+
value = int(node_sk, 16)
|
|
40
|
+
except (TypeError, ValueError):
|
|
41
|
+
raise ValueError('node_sk is 64 hex characters')
|
|
42
|
+
if not isinstance(node_sk, str) or len(node_sk) != 64 or not (1 <= value < crypto.N):
|
|
43
|
+
raise ValueError('node_sk must be a 32-byte scalar in [1, N)')
|
|
44
|
+
if len(agreement_seed) != 32 or len(transport_seed) != 32:
|
|
45
|
+
raise ValueError('agreement and transport seeds are 32 bytes')
|
|
46
|
+
self.node_sk = node_sk # hex
|
|
47
|
+
self.node_pk = crypto.pubkey(node_sk) # hex, x-only
|
|
48
|
+
self.did = 'did:nostr:' + self.node_pk
|
|
49
|
+
self.agreement_priv, self.agreement_pub = keys.agreement_keypair(agreement_seed)
|
|
50
|
+
self.transport_priv, self.transport_pub = keys.transport_keypair(transport_seed)
|
|
51
|
+
self._seeds = (agreement_seed, transport_seed)
|
|
52
|
+
|
|
53
|
+
# ---- encodings the descriptor and DID document carry ----------------
|
|
54
|
+
def multikeys(self) -> dict[str, str]:
|
|
55
|
+
return {'node': keys.multikey('node', bytes.fromhex(self.node_pk)),
|
|
56
|
+
'agreement': keys.multikey('agreement', keys.raw_public(self.agreement_pub)),
|
|
57
|
+
'transport': keys.multikey('transport', keys.raw_public(self.transport_pub))}
|
|
58
|
+
|
|
59
|
+
def kid(self, which: Literal['agreement', 'transport', 'node'] = 'agreement') -> str:
|
|
60
|
+
return '%s#%s' % (self.did, {'agreement': 'agree', 'transport': 'wire', 'node': 'nostr'}[which])
|
|
61
|
+
|
|
62
|
+
# ---- persistence -----------------------------------------------------
|
|
63
|
+
@classmethod
|
|
64
|
+
def generate(cls) -> Keystore:
|
|
65
|
+
sk = os.urandom(32).hex()
|
|
66
|
+
while not (1 <= int(sk, 16) < crypto.N):
|
|
67
|
+
sk = os.urandom(32).hex()
|
|
68
|
+
return cls(sk, keys.fresh_seed(), keys.fresh_seed())
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def load(cls, directory: str | os.PathLike[str]) -> Keystore:
|
|
72
|
+
path = os.path.join(directory, FILE)
|
|
73
|
+
mode = stat.S_IMODE(os.stat(path).st_mode)
|
|
74
|
+
if mode & 0o077:
|
|
75
|
+
raise PermissionError('%s is readable by others (mode %o); refusing to load' % (path, mode))
|
|
76
|
+
with open(path, encoding='utf-8') as _fh:
|
|
77
|
+
d = json.load(_fh)
|
|
78
|
+
if d.get('format') != 'meshnode-keys/1':
|
|
79
|
+
raise ValueError('not a meshnode key file')
|
|
80
|
+
return cls(d['node_sk'], bytes.fromhex(d['agreement_seed']), bytes.fromhex(d['transport_seed']))
|
|
81
|
+
|
|
82
|
+
def save(self, directory: str | os.PathLike[str]) -> str:
|
|
83
|
+
os.makedirs(directory, mode=0o700, exist_ok=True)
|
|
84
|
+
path = os.path.join(directory, FILE)
|
|
85
|
+
if os.path.exists(path):
|
|
86
|
+
raise FileExistsError('%s exists; a node does not overwrite its own keys' % path)
|
|
87
|
+
body = json.dumps({'format': 'meshnode-keys/1', 'node_sk': self.node_sk,
|
|
88
|
+
'agreement_seed': self._seeds[0].hex(),
|
|
89
|
+
'transport_seed': self._seeds[1].hex(),
|
|
90
|
+
'node_pk': self.node_pk, 'did': self.did}, indent=2)
|
|
91
|
+
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
92
|
+
with os.fdopen(fd, 'w', encoding='utf-8') as f:
|
|
93
|
+
f.write(body + '\n')
|
|
94
|
+
return path
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def load_or_generate(cls, directory: str | os.PathLike[str]) -> Keystore:
|
|
98
|
+
if os.path.exists(os.path.join(directory, FILE)):
|
|
99
|
+
return cls.load(directory)
|
|
100
|
+
ks = cls.generate()
|
|
101
|
+
ks.save(directory)
|
|
102
|
+
return ks
|
|
103
|
+
|