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,198 @@
|
|
|
1
|
+
"""The handshake over the wire: the proposer (`propose`), the acceptor
|
|
2
|
+
(`handle`, one inbound peering/1.0 message) and the loop that serves them.
|
|
3
|
+
What each message *means* is fleetmesh_core/peering.py's; this is only the carrying."""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import queue
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
from typing import Any, Mapping, Optional
|
|
10
|
+
|
|
11
|
+
from fleetmesh_core.peering import peering
|
|
12
|
+
from fleetmesh_core.storage import replication
|
|
13
|
+
from fleetmesh_core.wire.wire_types import PeeringAcceptBody
|
|
14
|
+
from fleetmesh_core.identity.did import did_nostr
|
|
15
|
+
from node.storage import grants as grants_mod
|
|
16
|
+
from .envelope import PeeringError
|
|
17
|
+
from .shape import PeeringShape
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
from .types import PeeringResultDetail
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class HandshakeMixin:
|
|
24
|
+
# ---- proposer ----------------------------------------------------------
|
|
25
|
+
def propose(
|
|
26
|
+
self: PeeringShape,
|
|
27
|
+
peer_pk: str,
|
|
28
|
+
want: Any,
|
|
29
|
+
offer: Optional[list[Any]] = None,
|
|
30
|
+
budget_self: Optional[Mapping[str, Any]] = None,
|
|
31
|
+
timeout: float = 30.0,
|
|
32
|
+
) -> tuple[str, PeeringResultDetail | Mapping[str, Any]]:
|
|
33
|
+
"""Run the whole handshake against one peer. Returns ('peered', detail) or ('declined', body)."""
|
|
34
|
+
budget_self = budget_self or {'events_in': 600, 'bytes_in': 8388608,
|
|
35
|
+
'window': peering.CONSTANTS['grant']['window_seconds_default']}
|
|
36
|
+
want_list = want if isinstance(want, list) else [want]
|
|
37
|
+
proposal = self.host.propose(did_nostr(peer_pk), want_list, offer or [], budget_self,
|
|
38
|
+
pow_event=self.descriptor['id'])
|
|
39
|
+
reply = self.wire_client.request(peer_pk, proposal, timeout=timeout)
|
|
40
|
+
if reply is None:
|
|
41
|
+
raise PeeringError('no answer from %s within %.0fs' % (peer_pk[:16], timeout))
|
|
42
|
+
msg = reply.message
|
|
43
|
+
if msg['type'].endswith('/decline'):
|
|
44
|
+
self.log.append(('declined by', peer_pk, msg['body']))
|
|
45
|
+
return 'declined', msg['body']
|
|
46
|
+
if not msg['type'].endswith('/offer'):
|
|
47
|
+
raise PeeringError('expected an offer, got %s' % msg['type'])
|
|
48
|
+
held = self.grants.get(peer_pk, grants_mod.ISSUED)
|
|
49
|
+
if held is not None and held['status'] == 'live':
|
|
50
|
+
# Already granting this peer: proposing again is a check that the
|
|
51
|
+
# peering stands, not a reason to reissue at the opening tier and
|
|
52
|
+
# throw away what the peer's clean windows earned.
|
|
53
|
+
acc_body: PeeringAcceptBody = {'grant_envelope': held['envelope_id'],
|
|
54
|
+
'selected_transport': msg['body']['transports'][0]}
|
|
55
|
+
# The row from get() never carried the event - it lives in the
|
|
56
|
+
# history table - so this used to read a key that was never there
|
|
57
|
+
# and the re-accept always went out without the envelope.
|
|
58
|
+
held_event = self.grants.envelope_event(peer_pk, grants_mod.ISSUED, held['envelope_id'])
|
|
59
|
+
if held_event:
|
|
60
|
+
acc_body['envelope_event'] = held_event
|
|
61
|
+
accept = self.host._msg('accept', acc_body,
|
|
62
|
+
did_nostr(peer_pk), thid=proposal['id'])
|
|
63
|
+
published: dict[str, dict[str, tuple[bool, str]]] = {}
|
|
64
|
+
else:
|
|
65
|
+
accept = dict(self.host.accept(msg, did_nostr(peer_pk), want_list, self.clock()))
|
|
66
|
+
published = self._flush_envelopes()
|
|
67
|
+
accept_body = dict(accept.get('body', {}))
|
|
68
|
+
if self.host.envelopes.get(peer_pk):
|
|
69
|
+
accept_body['envelope_event'] = self.host.envelopes[peer_pk]
|
|
70
|
+
accept['body'] = accept_body
|
|
71
|
+
mine = self.host.envelopes.get(peer_pk)
|
|
72
|
+
self.grants.put(peer_pk, grants_mod.ISSUED, self.host.issued[peer_pk], accept['body']['grant_envelope'],
|
|
73
|
+
self.clock(), event=mine, env_created_at=mine['created_at'] if mine else None)
|
|
74
|
+
self.wire_client.send(peer_pk, accept)
|
|
75
|
+
received = self._take_received(msg['body']['grant_envelope'], peer_pk, msg['body'].get('envelope_event'))
|
|
76
|
+
issued = self.host.issued.get(peer_pk) or (held['payload'] if held else None)
|
|
77
|
+
if held is None or held['status'] != 'live':
|
|
78
|
+
for cb in self.on_grant_issued:
|
|
79
|
+
cb(peer_pk, 'accept', accept['body']['grant_envelope'], (issued or {}).get('tier'))
|
|
80
|
+
for peered in self.on_peered:
|
|
81
|
+
peered(peer_pk, 'proposer', accept['body']['selected_transport'])
|
|
82
|
+
return 'peered', {'issued': issued, 'received': received,
|
|
83
|
+
'transport': accept['body']['selected_transport'], 'published': published}
|
|
84
|
+
|
|
85
|
+
# ---- acceptor ----------------------------------------------------------
|
|
86
|
+
def handle_propose(self: PeeringShape, inbound: Any, sender: str) -> tuple[Any, ...]:
|
|
87
|
+
msg = getattr(inbound, 'message', inbound)
|
|
88
|
+
body = msg.get('body')
|
|
89
|
+
if not isinstance(body, dict):
|
|
90
|
+
return ('ignored', sender, 'malformed body')
|
|
91
|
+
held = self.grants.get(sender, grants_mod.ISSUED)
|
|
92
|
+
if held is not None and held['status'] == 'live':
|
|
93
|
+
theirs = {tr: i for i, tr in enumerate(body.get('transports', []))}
|
|
94
|
+
ladder = replication.negotiate(self.host.policy.transports, theirs) or [next(iter(self.host.policy.transports))]
|
|
95
|
+
self.host.threads[sender] = msg['id']
|
|
96
|
+
reply = self.host._msg('offer', {'tier': held['payload']['tier'], 'limits': held['payload']['limits'],
|
|
97
|
+
'transports': ladder, 'grant_envelope': held['envelope_id']},
|
|
98
|
+
msg['from'], thid=msg['id'])
|
|
99
|
+
self.wire_client.send(sender, reply)
|
|
100
|
+
return ('re-offered', sender, held['envelope_id'])
|
|
101
|
+
if self.capacity is not None and self.capacity.refusing():
|
|
102
|
+
reply = self.host._msg('decline',
|
|
103
|
+
{'code': peering.CAPACITY,
|
|
104
|
+
'message': self.capacity.refuse_peering(sender)},
|
|
105
|
+
msg['from'], thid=msg['id'])
|
|
106
|
+
self.wire_client.send(sender, reply)
|
|
107
|
+
return ('declined', sender, reply['body'])
|
|
108
|
+
peer_desc = self.peers.descriptor(sender)
|
|
109
|
+
reply = self.host.offer(msg, peer_desc, self.clock())
|
|
110
|
+
published = self._flush_envelopes()
|
|
111
|
+
if not reply['type'].endswith('/offer'):
|
|
112
|
+
self.wire_client.send(sender, reply)
|
|
113
|
+
return ('declined', sender, reply['body'])
|
|
114
|
+
reply = dict(reply)
|
|
115
|
+
reply_body = dict(reply.get('body', {}))
|
|
116
|
+
if self.host.envelopes.get(sender):
|
|
117
|
+
reply_body['envelope_event'] = self.host.envelopes[sender]
|
|
118
|
+
reply['body'] = reply_body
|
|
119
|
+
mine = self.host.envelopes.get(sender)
|
|
120
|
+
self.grants.put(sender, grants_mod.ISSUED, self.host.issued[sender],
|
|
121
|
+
reply['body']['grant_envelope'], self.clock(),
|
|
122
|
+
event=mine, env_created_at=mine['created_at'] if mine else None)
|
|
123
|
+
self.wire_client.send(sender, reply)
|
|
124
|
+
for cb in self.on_grant_issued:
|
|
125
|
+
cb(sender, 'offer', reply['body']['grant_envelope'], (self.host.issued.get(sender) or {}).get('tier'))
|
|
126
|
+
return ('offered', sender, published)
|
|
127
|
+
|
|
128
|
+
def handle_accept(self: PeeringShape, inbound: Any, sender: str) -> tuple[Any, ...]:
|
|
129
|
+
msg = getattr(inbound, 'message', inbound)
|
|
130
|
+
body = msg.get('body')
|
|
131
|
+
if not isinstance(body, dict):
|
|
132
|
+
return ('ignored', sender, 'malformed body')
|
|
133
|
+
held = self.grants.get(sender, grants_mod.ISSUED)
|
|
134
|
+
if held is None or held['status'] != 'live':
|
|
135
|
+
return ('ignored', sender, 'accept from a node this node did not offer to')
|
|
136
|
+
grant = self._take_received(msg['body']['grant_envelope'], sender, msg['body'].get('envelope_event'))
|
|
137
|
+
for peered in self.on_peered:
|
|
138
|
+
peered(sender, 'acceptor', msg['body'].get('selected_transport'))
|
|
139
|
+
return ('peered', sender, grant)
|
|
140
|
+
|
|
141
|
+
def handle_amend(self: PeeringShape, inbound: Any, sender: str) -> tuple[Any, ...]:
|
|
142
|
+
msg = getattr(inbound, 'message', inbound)
|
|
143
|
+
body = msg.get('body')
|
|
144
|
+
if not isinstance(body, dict):
|
|
145
|
+
return ('ignored', sender, 'malformed body')
|
|
146
|
+
new_id = msg['body'].get('new_grant_envelope')
|
|
147
|
+
if not new_id:
|
|
148
|
+
self._refuse('amend', 'no new grant named', sender)
|
|
149
|
+
return ('ignored', sender, 'amend without a new grant')
|
|
150
|
+
held = self.grants.get(sender, grants_mod.RECEIVED)
|
|
151
|
+
if held is None or held['status'] != 'live':
|
|
152
|
+
self._refuse('amend', 'from a node whose grant this node does not hold', sender,
|
|
153
|
+
{'named': new_id[:16]})
|
|
154
|
+
return ('ignored', sender, 'amend from a node whose grant this node does not hold')
|
|
155
|
+
was = held['envelope_id']
|
|
156
|
+
try:
|
|
157
|
+
grant = self._take_received(new_id, sender, msg['body'].get('envelope_event'))
|
|
158
|
+
except Exception as e:
|
|
159
|
+
self._refuse('amend', 'the grant it names could not be taken up', sender,
|
|
160
|
+
{'named': new_id[:16], 'error': '%s: %s' % (type(e).__name__, e)})
|
|
161
|
+
return ('ignored', sender, 'amend names a grant this node could not take up: %s' % e)
|
|
162
|
+
now_held = self.grants.get(sender, grants_mod.RECEIVED)
|
|
163
|
+
if now_held is not None and now_held['envelope_id'] == was and was != new_id:
|
|
164
|
+
self._refuse('amend', 'refused as older than the grant already held', sender,
|
|
165
|
+
{'named': new_id[:16], 'holding': was[:16]})
|
|
166
|
+
return ('ignored', sender, 'amend is older than the grant held')
|
|
167
|
+
return ('amended', sender, grant)
|
|
168
|
+
|
|
169
|
+
def handle_terminate(self: PeeringShape, inbound: Any, sender: str) -> tuple[Any, ...]:
|
|
170
|
+
msg = getattr(inbound, 'message', inbound)
|
|
171
|
+
body = msg.get('body')
|
|
172
|
+
if not isinstance(body, dict):
|
|
173
|
+
return ('ignored', sender, 'malformed body')
|
|
174
|
+
self.grants.zero(sender, self.clock())
|
|
175
|
+
if sender in self.host.issued:
|
|
176
|
+
self.host.terminate(sender, reason='peer terminated')
|
|
177
|
+
return ('terminated', sender, msg['body'])
|
|
178
|
+
|
|
179
|
+
def serve(self: PeeringShape, stop: threading.Event, poll: float = 0.5) -> None:
|
|
180
|
+
"""Handle inbound peering messages until stop is set; others go back to the inbox."""
|
|
181
|
+
while not stop.is_set():
|
|
182
|
+
inbound = self.transport.receive(timeout=poll)
|
|
183
|
+
if inbound is None:
|
|
184
|
+
continue
|
|
185
|
+
try:
|
|
186
|
+
done = self.handle(inbound)
|
|
187
|
+
except Exception as e:
|
|
188
|
+
sender = getattr(inbound, 'sender', '') if inbound is not None else ''
|
|
189
|
+
self.log.append(('error', sender, str(e)))
|
|
190
|
+
continue
|
|
191
|
+
if done is None:
|
|
192
|
+
try:
|
|
193
|
+
self.transport.inbox.put_nowait(inbound) # not peering; leave it for whoever is
|
|
194
|
+
except queue.Full:
|
|
195
|
+
pass
|
|
196
|
+
time.sleep(poll)
|
|
197
|
+
else:
|
|
198
|
+
self.log.append(done)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""The shape of a Peering as its mixins see it.
|
|
2
|
+
|
|
3
|
+
The one class is split across core.py and handshake.py, the same way
|
|
4
|
+
node/reporter/ is: each mixin method annotates `self` as this Protocol so
|
|
5
|
+
the checker knows what is on it without the mixins inheriting from one
|
|
6
|
+
another. A declaration only; Peering satisfies it structurally.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, Callable, Mapping, Optional, Protocol, Sequence
|
|
11
|
+
|
|
12
|
+
from fleetmesh_core.wire.interfaces import GrantPayload, NostrEvent, UnsignedNostrEvent
|
|
13
|
+
|
|
14
|
+
from node.compartment import stores as st
|
|
15
|
+
from node.storage import grants as grants_mod
|
|
16
|
+
from node.compartment import Refusals
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PeeringHost(Protocol):
|
|
20
|
+
"""What the wire needs from the peering state machine - exactly this.
|
|
21
|
+
|
|
22
|
+
fleetmesh_core.peering.Node satisfies it, and so does WireNode, which is
|
|
23
|
+
what Peering builds when none is given. The members are the whole of the
|
|
24
|
+
reach: the four tables the handshake reads and writes, the five message
|
|
25
|
+
writers, and the one method WireNode makes real (`_publish_envelope`).
|
|
26
|
+
A test may hand in anything with these members; nothing else on the
|
|
27
|
+
core Node is touched (review R11).
|
|
28
|
+
"""
|
|
29
|
+
policy: Any
|
|
30
|
+
issued: dict[str, GrantPayload]
|
|
31
|
+
envelopes: dict[str, NostrEvent | UnsignedNostrEvent]
|
|
32
|
+
threads: dict[str, str]
|
|
33
|
+
digest_mismatch: dict[str, str]
|
|
34
|
+
published: list[NostrEvent]
|
|
35
|
+
|
|
36
|
+
def _msg(self, kind: str, body: Mapping[str, Any], to: str, thid: Optional[str] = None) -> Mapping[str, Any]: ...
|
|
37
|
+
def propose(self, to_did: str, want: Sequence[Mapping[str, Any]], offer: Sequence[Mapping[str, Any]],
|
|
38
|
+
budget_self: Mapping[str, Any], pow_event: Optional[str] = None,
|
|
39
|
+
pow_bits: Optional[int] = None) -> Mapping[str, Any]: ...
|
|
40
|
+
def offer(self, proposal: Mapping[str, Any], peer_descriptor: Optional[NostrEvent], now: int) -> Mapping[str, Any]: ...
|
|
41
|
+
def accept(self, offer: Mapping[str, Any], peer_did: str, want: Sequence[Mapping[str, Any]], now: int) -> Mapping[str, Any]: ...
|
|
42
|
+
def terminate(self, peer_pubkey: str, reason: str = 'operator', grace_seconds: int = 0) -> Mapping[str, Any]: ...
|
|
43
|
+
def disclose(self, query: Optional[list[dict[str, str]]] = None) -> dict[str, list[dict[str, str]]]: ...
|
|
44
|
+
def _publish_envelope(self, subject: str, grant: GrantPayload, now: Optional[int]) -> NostrEvent | UnsignedNostrEvent: ...
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class PeeringShape(Protocol):
|
|
48
|
+
TROUBLE_WINDOW: int
|
|
49
|
+
refusals: Refusals
|
|
50
|
+
capacity: Optional[Any]
|
|
51
|
+
_reconciled: Any
|
|
52
|
+
ks: Any
|
|
53
|
+
descriptor: Any
|
|
54
|
+
peers: Any
|
|
55
|
+
transport: Any
|
|
56
|
+
wire_client: Any
|
|
57
|
+
relays: list[str]
|
|
58
|
+
grants: grants_mod.GrantStore
|
|
59
|
+
clock: Callable[[], int]
|
|
60
|
+
timeout: float
|
|
61
|
+
host: PeeringHost
|
|
62
|
+
log: st.Ring[tuple[Any, ...]]
|
|
63
|
+
on_grant_issued: list[Callable[[str, str, str, Optional[str]], Any]]
|
|
64
|
+
on_peered: list[Callable[[str, str, Optional[str]], Any]]
|
|
65
|
+
|
|
66
|
+
# core.py
|
|
67
|
+
def _flush_envelopes(self) -> dict[str, dict[str, tuple[bool, str]]]: ...
|
|
68
|
+
def _fetch_envelope(self, envelope_id: Optional[str], issuer_pk: str) -> NostrEvent: ...
|
|
69
|
+
def _refuse(self, kind: str, why: str, subject: Optional[str] = None,
|
|
70
|
+
detail: Optional[Mapping[str, Any]] = None) -> Any: ...
|
|
71
|
+
def _take_received(self, envelope_id: Optional[str], issuer_pk: str,
|
|
72
|
+
envelope_event: Optional[Mapping[str, Any]] = None) -> GrantPayload: ...
|
|
73
|
+
|
|
74
|
+
# handshake.py
|
|
75
|
+
def handle(self, inbound: Any) -> Optional[tuple[Any, ...]]: ...
|
|
76
|
+
def handle_discover(self, inbound: Any) -> Optional[tuple[Any, ...]]: ...
|
|
77
|
+
def handle_propose(self, inbound: Any, sender: str) -> tuple[Any, ...]: ...
|
|
78
|
+
def handle_accept(self, inbound: Any, sender: str) -> tuple[Any, ...]: ...
|
|
79
|
+
def handle_amend(self, inbound: Any, sender: str) -> tuple[Any, ...]: ...
|
|
80
|
+
def handle_terminate(self, inbound: Any, sender: str) -> tuple[Any, ...]: ...
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Types and records for the Peering compartment."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Mapping, Optional, TypedDict
|
|
5
|
+
|
|
6
|
+
from fleetmesh_core.wire.interfaces import GrantPayload, NostrEvent
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Issued(TypedDict):
|
|
10
|
+
"""What `Peering.issue` hands back: the signed kind 30801, the grant it
|
|
11
|
+
wraps, what each relay said to it, and why it was written."""
|
|
12
|
+
envelope: NostrEvent
|
|
13
|
+
payload: GrantPayload
|
|
14
|
+
published: dict[str, dict[str, tuple[bool, str]]]
|
|
15
|
+
cause: str
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PeeringDecision(TypedDict, total=False):
|
|
19
|
+
"""Result of deciding a grant pending next epoch boundary."""
|
|
20
|
+
payload: Mapping[str, Any]
|
|
21
|
+
effective_from: int
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PeeringStateReport(TypedDict, total=False):
|
|
25
|
+
"""The report Peering.state() gives of itself."""
|
|
26
|
+
peers: int
|
|
27
|
+
reconciled: dict[str, int]
|
|
28
|
+
refused: int
|
|
29
|
+
digest_mismatch: dict[str, str]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class PeeringResultDetail(TypedDict, total=False):
|
|
33
|
+
"""Result of running propose against a peer."""
|
|
34
|
+
envelope: Optional[NostrEvent]
|
|
35
|
+
payload: Optional[GrantPayload]
|
|
36
|
+
published: dict[str, dict[str, tuple[bool, str]]]
|
|
37
|
+
cause: str
|
|
38
|
+
selected_transport: Optional[str]
|
|
39
|
+
reason: Optional[str]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Reports and standing over the wire.
|
|
2
|
+
|
|
3
|
+
One rule shapes all of this and is worth stating first: a report a reader will
|
|
4
|
+
discard must not be published. The specification makes a kind 30802 admissible
|
|
5
|
+
only if every evidence tag resolves to an event the subject itself signed, and
|
|
6
|
+
readers count an inadmissible report as unsupported against the reporter. So
|
|
7
|
+
publish() checks its own report against fleetmesh_core.peering.reports.admissible() before it goes
|
|
8
|
+
out, and keeps it local if it fails. See node/README.md for what that rules
|
|
9
|
+
out today, which is most of it.
|
|
10
|
+
|
|
11
|
+
Findings come from two places: a window the budget evaluated, and a session
|
|
12
|
+
the sync layer ran. Each is a verdict of fleetmesh_core/'s arithmetic against one peer
|
|
13
|
+
under one grant this node issued. What happens next is the specification's
|
|
14
|
+
complaint rule, executed: S1 and S2 are presented to the subject as a
|
|
15
|
+
complaint/1.0/notice and published only after a window has passed; S3 and S4
|
|
16
|
+
are published at once. The report is the kind 30802 fleetmesh_core.peering.reports.build()
|
|
17
|
+
makes, signed by this node and put on the relays with its evidence.
|
|
18
|
+
|
|
19
|
+
The other side of the same coin is reading: fetch the reports about a node,
|
|
20
|
+
keep the admissible ones, weight each by what this node itself knows of the
|
|
21
|
+
reporter, and fold them into a view with fleetmesh_core.accounting.window. A reader that holds no
|
|
22
|
+
grant to the reporter weights it at zero, so a stranger's report moves
|
|
23
|
+
nothing.
|
|
24
|
+
|
|
25
|
+
standing/1.0 rides here too: an ask goes out through the transport, a tell
|
|
26
|
+
comes back from the peer's ledger, and inbound asks are answered from ours
|
|
27
|
+
only about nodes we hold a grant to.
|
|
28
|
+
|
|
29
|
+
Split the way node/budget/ and node/compute/ are: core.py holds construction
|
|
30
|
+
and the Compartment contract, findings.py/publish.py/reading.py are the mixins
|
|
31
|
+
for each concern, and shape.py is the structural Protocol the checker types
|
|
32
|
+
`self` against inside each mixin.
|
|
33
|
+
"""
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
from .constants import FINDING_WINDOWS, KEEP_UNPUBLISHABLE, LIMIT_METRICS, PUBLISH_TRIES, REPORT_TTL
|
|
37
|
+
from .core import Reporter
|
|
38
|
+
from .finding import Finding
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
'FINDING_WINDOWS', 'KEEP_UNPUBLISHABLE', 'LIMIT_METRICS', 'PUBLISH_TRIES', 'REPORT_TTL', 'Reporter', 'Finding',
|
|
42
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""The reporter's fixed numbers, in one place so the mixins and core import
|
|
2
|
+
them without importing each other.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
REPORT_TTL: int = 30 * 86400
|
|
7
|
+
# A finding that never became a report is dropped when the accounting under it
|
|
8
|
+
# is gone. `Budget.KEEP_WINDOWS` is how long a window's receipts, meters and
|
|
9
|
+
# evaluation are kept, and past that this node cannot substantiate a finding
|
|
10
|
+
# about that window to anyone, including itself. The same number, stated here
|
|
11
|
+
# rather than imported, because budget imports nothing from this module and the
|
|
12
|
+
# dependency must not start running the other way.
|
|
13
|
+
FINDING_WINDOWS: int = 24 * 7
|
|
14
|
+
# How many times a finding that could not be published is tried again, and how
|
|
15
|
+
# many of those refusals are kept for the operator to read.
|
|
16
|
+
PUBLISH_TRIES: int = 3
|
|
17
|
+
KEEP_UNPUBLISHABLE: int = 200
|
|
18
|
+
# What the reporter keeps of what it signed: the newest KEEP_SIGNED of each
|
|
19
|
+
# kind (30802 reports, 30385 relay scores, 1985 labels). The relays hold them;
|
|
20
|
+
# these copies are for `state` and the operator. KEEP_THREADS complaint
|
|
21
|
+
# threads of each side, the bound notices_in was cut to by hand. KEEP_LOG of
|
|
22
|
+
# the log, as many as the node's own.
|
|
23
|
+
KEEP_SIGNED: int = 256
|
|
24
|
+
KEEP_THREADS: int = 1000
|
|
25
|
+
KEEP_LOG: int = 500
|
|
26
|
+
LIMIT_METRICS: tuple[str, ...] = (
|
|
27
|
+
'conn_rate', 'req', 'events_in', 'bytes_in', 'bytes_out',
|
|
28
|
+
'blob_bytes', 'sync_ranges', 'errors', 'dup_ratio'
|
|
29
|
+
)
|