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,128 @@
|
|
|
1
|
+
"""The initiator: `pull` runs one sync/1.0 session against a peer under the
|
|
2
|
+
grant it issued us, keeps the evidence a report could carry, and never
|
|
3
|
+
files a finding for a peer that merely went quiet."""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Any, Mapping
|
|
7
|
+
|
|
8
|
+
from fleetmesh_core.storage import store, sync
|
|
9
|
+
from node.storage import grants as grants_mod
|
|
10
|
+
from .envelope import _thid, verified_events
|
|
11
|
+
from .shape import SyncSessionResult, SyncShape
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class InitiatorMixin:
|
|
15
|
+
# ---- initiator ---------------------------------------------------------
|
|
16
|
+
def pull(
|
|
17
|
+
self: SyncShape,
|
|
18
|
+
peer_pk: str,
|
|
19
|
+
shard: str,
|
|
20
|
+
interest: Mapping[str, Any],
|
|
21
|
+
since: int = 0,
|
|
22
|
+
until: int = 2 ** 31,
|
|
23
|
+
timeout: float = 30.0,
|
|
24
|
+
max_messages: int = 64,
|
|
25
|
+
) -> SyncSessionResult:
|
|
26
|
+
"""Reconcile one shard with a peer under the grant it issued us."""
|
|
27
|
+
grant = self._grant(peer_pk, grants_mod.RECEIVED)
|
|
28
|
+
sess = sync.Session(self.store, grant, shard, interest, since, until, self.clock(), is_initiator=True)
|
|
29
|
+
sess.thid = _thid(shard)
|
|
30
|
+
self._initiated.add(sess.thid) # the oldest forgotten at the store's bound
|
|
31
|
+
q = self.transport.listen(sess.thid)
|
|
32
|
+
evidence: list[Mapping[str, Any]] = []
|
|
33
|
+
status: str = 'aborted'
|
|
34
|
+
try:
|
|
35
|
+
m = sess.open()
|
|
36
|
+
for _ in range(max_messages):
|
|
37
|
+
self._send(m, peer_pk)
|
|
38
|
+
try:
|
|
39
|
+
inbound = q.get(timeout=timeout)
|
|
40
|
+
except Exception:
|
|
41
|
+
# No finding. A peer that stops answering mid-session has
|
|
42
|
+
# told us nothing about its honesty: the specification's own
|
|
43
|
+
# partition rule is that a dead node and a quiet one cannot
|
|
44
|
+
# be told apart, and that a peer MUST NOT be reported
|
|
45
|
+
# unreachable until every shared transport rung has failed.
|
|
46
|
+
# This filed an S2, so an operator restarting their node
|
|
47
|
+
# earned a misreport finding at every peer that happened to
|
|
48
|
+
# be mid-sync - ordinary operations priced as misbehaviour.
|
|
49
|
+
# Whatever traffic did move is still measured, and if it
|
|
50
|
+
# went unaccounted the window closes on it as active
|
|
51
|
+
# silence, which is the mechanism that exists for this.
|
|
52
|
+
self._record(self.aborted, (peer_pk, sess.thid))
|
|
53
|
+
# A peer that stopped answering is a partition, never a
|
|
54
|
+
# finding - the specification's own rule. Recorded so the
|
|
55
|
+
# node is readier for the next one rather than discarded.
|
|
56
|
+
#
|
|
57
|
+
# Unless the peer said it was going. A reserve is a
|
|
58
|
+
# function of the directions stress came from, and a
|
|
59
|
+
# departure this node was told about in advance is not
|
|
60
|
+
# stress from that direction: thickening against it would
|
|
61
|
+
# make an orderly retirement cost its peers exactly what
|
|
62
|
+
# the retirement exists to save them.
|
|
63
|
+
presence = self.presence
|
|
64
|
+
announced = presence is not None and presence.is_retiring(peer_pk)
|
|
65
|
+
if self.strain is not None and not announced:
|
|
66
|
+
self.strain.record('partition', peer=peer_pk, shard=getattr(sess, 'shard', None))
|
|
67
|
+
break
|
|
68
|
+
reply = inbound.message
|
|
69
|
+
self._seen(peer_pk, reply)
|
|
70
|
+
kind = reply['type'].rsplit('/', 1)[1]
|
|
71
|
+
if kind == 'done':
|
|
72
|
+
status = reply['body'].get('status')
|
|
73
|
+
break
|
|
74
|
+
if kind == 'deliver':
|
|
75
|
+
events, envelopes = verified_events(reply, sess.findings)
|
|
76
|
+
reply = dict(reply, body=dict(reply['body'], events=events, count=len(events)))
|
|
77
|
+
# What the reference will refuse as outside the interest is
|
|
78
|
+
# the evidence a report carries, so keep it before it does -
|
|
79
|
+
# with its envelope, which the sender signed even when it
|
|
80
|
+
# did not write the event.
|
|
81
|
+
evidence += [{'event': e, 'envelope': envelopes.get(e['id'])}
|
|
82
|
+
for e in events if not store.matches(e, sess.interest)]
|
|
83
|
+
if kind not in ('range', 'deliver'):
|
|
84
|
+
sess._finding('S2', 'unexpected sync message %r' % kind)
|
|
85
|
+
break
|
|
86
|
+
if kind == 'deliver' and hasattr(self.store, 'note_origin'):
|
|
87
|
+
for e in reply['body']['events']:
|
|
88
|
+
self.store.note_origin(e['id'], self.store.MESH)
|
|
89
|
+
m = getattr(sess, 'on_' + kind)(reply)
|
|
90
|
+
if kind == 'deliver' and sess.forged:
|
|
91
|
+
evidence += [{'event': e, 'envelope': envelopes.get(e.get('id'))} for e in sess.forged]
|
|
92
|
+
if m['type'].endswith('/done'):
|
|
93
|
+
self._send(m, peer_pk)
|
|
94
|
+
status = m['body'].get('status')
|
|
95
|
+
break
|
|
96
|
+
else:
|
|
97
|
+
sess._finding('S2', 'session did not terminate in %d messages' % max_messages)
|
|
98
|
+
status = 'aborted'
|
|
99
|
+
finally:
|
|
100
|
+
self.transport.unlisten(sess.thid)
|
|
101
|
+
# A handover follows the done on this same thread, and it can
|
|
102
|
+
# arrive in the gap between the done going out and the listener
|
|
103
|
+
# coming down. Anything still queued there is unsolicited now,
|
|
104
|
+
# so it goes where unsolicited messages go.
|
|
105
|
+
while True:
|
|
106
|
+
try:
|
|
107
|
+
late = q.get_nowait()
|
|
108
|
+
except Exception:
|
|
109
|
+
break
|
|
110
|
+
self.transport.inbox.put(late)
|
|
111
|
+
result: SyncSessionResult = {
|
|
112
|
+
'status': status,
|
|
113
|
+
'counters': dict(sess.counters),
|
|
114
|
+
'findings': list(sess.findings),
|
|
115
|
+
'need': sorted(getattr(sess, 'need', [])),
|
|
116
|
+
'dup_ratio': sess.dup_ratio(),
|
|
117
|
+
'thid': sess.thid,
|
|
118
|
+
'evidence': evidence,
|
|
119
|
+
'grant': grant.event_id,
|
|
120
|
+
}
|
|
121
|
+
with self._lock:
|
|
122
|
+
self._record(self.completed, (peer_pk, 'initiator', result))
|
|
123
|
+
if self.budget is not None:
|
|
124
|
+
self.budget.observe_session(peer_pk, 'initiator', result)
|
|
125
|
+
for cb in self.on_session:
|
|
126
|
+
cb(peer_pk, 'initiator', result)
|
|
127
|
+
return result
|
|
128
|
+
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""The responder: `handle` takes one inbound sync/1.0 message, enforces the
|
|
2
|
+
grant (not just measures it), bounds sessions per peer, and answers in the
|
|
3
|
+
wire form - events in the signed attachment, the count in the body."""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from typing import Any, Mapping, Optional
|
|
8
|
+
|
|
9
|
+
from fleetmesh_core.wire import validate
|
|
10
|
+
from node.storage import grants as grants_mod
|
|
11
|
+
from .envelope import MAX_SESSIONS_PER_PEER, PIURI, SyncError, envelope_attachment
|
|
12
|
+
from .shape import SyncShape
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ResponderMixin:
|
|
16
|
+
def _refuse_open(self: SyncShape, peer_pk: str, thid: Optional[str], why: str) -> tuple[Any, ...]:
|
|
17
|
+
"""Refuse a session with a done, so the peer stops waiting, and say why."""
|
|
18
|
+
self._record(self.refused, (peer_pk, thid, why))
|
|
19
|
+
self.refusals.refuse('session', why, subject=peer_pk)
|
|
20
|
+
reply = {'id': 'sync-refuse-%s' % os.urandom(4).hex(), 'type': PIURI + '/done', 'thid': thid,
|
|
21
|
+
'body': {'status': 'aborted', 'events_transferred': 0}}
|
|
22
|
+
self._send(reply, peer_pk)
|
|
23
|
+
return ('refused', peer_pk, why)
|
|
24
|
+
|
|
25
|
+
def _precheck(self: SyncShape, inbound: Any, kind: str) -> tuple[Optional[Mapping[str, Any]], str, Optional[tuple[Any, ...]]]:
|
|
26
|
+
msg = getattr(inbound, 'message', inbound)
|
|
27
|
+
peer_pk = getattr(inbound, 'sender', msg.get('from', '') if isinstance(msg, dict) else '')
|
|
28
|
+
self._seen(peer_pk, msg)
|
|
29
|
+
why = validate.check_body('sync', kind, msg.get('body'))
|
|
30
|
+
if why is not None:
|
|
31
|
+
self.rejected.append((peer_pk, kind, why))
|
|
32
|
+
return None, peer_pk, ('ignored', peer_pk, 'malformed %s: %s' % (kind, why))
|
|
33
|
+
self._expire(self.clock())
|
|
34
|
+
return msg, peer_pk, None
|
|
35
|
+
|
|
36
|
+
def _send_reply(self: SyncShape, thid: Optional[str], peer_pk: str, sess: Any, reply: Mapping[str, Any]) -> tuple[Any, ...]:
|
|
37
|
+
if reply['type'].endswith('/deliver'):
|
|
38
|
+
events = reply['body']['events']
|
|
39
|
+
reply = dict(reply, body={'count': len(events)},
|
|
40
|
+
attachments=[envelope_attachment(events, self.ks.did,
|
|
41
|
+
self.ks.node_sk, self.clock())])
|
|
42
|
+
if reply['type'].endswith('/done'):
|
|
43
|
+
status = reply['body'].get('status')
|
|
44
|
+
self._finish(thid or '', peer_pk, sess, status)
|
|
45
|
+
self._send(reply, peer_pk)
|
|
46
|
+
offered = self._offer_handover(thid or '', peer_pk, sess, status)
|
|
47
|
+
return ('done', peer_pk, status) + ((offered,) if offered else ())
|
|
48
|
+
self._send(reply, peer_pk)
|
|
49
|
+
return ('replied', peer_pk, reply['type'].rsplit('/', 1)[1])
|
|
50
|
+
|
|
51
|
+
def handle_handover(self: SyncShape, inbound: Any, sender: str) -> Optional[tuple[Any, ...]]:
|
|
52
|
+
msg, peer_pk, err = self._precheck(inbound, 'handover')
|
|
53
|
+
if err is not None or msg is None:
|
|
54
|
+
return err
|
|
55
|
+
return self._accept_handover(peer_pk, msg['body'])
|
|
56
|
+
|
|
57
|
+
def handle_open(self: SyncShape, inbound: Any, sender: str) -> Optional[tuple[Any, ...]]:
|
|
58
|
+
msg, peer_pk, err = self._precheck(inbound, 'open')
|
|
59
|
+
if err is not None or msg is None:
|
|
60
|
+
return err
|
|
61
|
+
thid = str(msg.get('thid') or '')
|
|
62
|
+
try:
|
|
63
|
+
grant = self._grant(peer_pk, grants_mod.ISSUED)
|
|
64
|
+
except SyncError as e:
|
|
65
|
+
return ('ignored', peer_pk, str(e))
|
|
66
|
+
body = msg['body']
|
|
67
|
+
with self._lock:
|
|
68
|
+
mine = sum(1 for v in self.sessions.values() if v[0] == peer_pk)
|
|
69
|
+
if mine >= MAX_SESSIONS_PER_PEER:
|
|
70
|
+
return self._refuse_open(peer_pk, thid, 'too many open sessions')
|
|
71
|
+
refuse_why = self.budget.refuses(peer_pk) if self.budget is not None else None
|
|
72
|
+
if refuse_why is not None:
|
|
73
|
+
return self._refuse_open(peer_pk, thid, refuse_why)
|
|
74
|
+
sess = self.session_cls(self.store, grant, body['shard'], body.get('filter'), body.get('since', 0),
|
|
75
|
+
body.get('until', 2 ** 31), self.clock(), is_initiator=False)
|
|
76
|
+
sess.thid = thid
|
|
77
|
+
with self._lock:
|
|
78
|
+
kept = self.sessions.put(thid, (peer_pk, sess, self.clock()))
|
|
79
|
+
if not kept:
|
|
80
|
+
return self._refuse_open(peer_pk, thid, 'this node has too many open sessions')
|
|
81
|
+
reply = sess.on_open(msg)
|
|
82
|
+
return self._send_reply(thid, peer_pk, sess, reply)
|
|
83
|
+
|
|
84
|
+
def _dispatch_session(self: SyncShape, inbound: Any, kind: str) -> Optional[tuple[Any, ...]]:
|
|
85
|
+
msg, peer_pk, err = self._precheck(inbound, kind)
|
|
86
|
+
if err is not None or msg is None:
|
|
87
|
+
return err
|
|
88
|
+
thid = str(msg.get('thid') or '')
|
|
89
|
+
if not thid:
|
|
90
|
+
return ('ignored', peer_pk, 'no such session')
|
|
91
|
+
with self._lock:
|
|
92
|
+
entry = self.sessions.get(thid)
|
|
93
|
+
if entry is not None:
|
|
94
|
+
self.sessions[thid] = (entry[0], entry[1], self.clock())
|
|
95
|
+
if entry is None or entry[0] != peer_pk:
|
|
96
|
+
return ('ignored', peer_pk, 'no such session')
|
|
97
|
+
sess = entry[1]
|
|
98
|
+
if kind == 'done':
|
|
99
|
+
status = msg['body'].get('status')
|
|
100
|
+
self._finish(thid or '', peer_pk, sess, status)
|
|
101
|
+
offered = self._offer_handover(thid or '', peer_pk, sess, status)
|
|
102
|
+
return ('done', peer_pk, status) + ((offered,) if offered else ())
|
|
103
|
+
reply = getattr(sess, 'on_' + kind)(msg)
|
|
104
|
+
return self._send_reply(thid, peer_pk, sess, reply)
|
|
105
|
+
|
|
106
|
+
def handle_range(self: SyncShape, inbound: Any, sender: str) -> Optional[tuple[Any, ...]]:
|
|
107
|
+
return self._dispatch_session(inbound, 'range')
|
|
108
|
+
|
|
109
|
+
def handle_need(self: SyncShape, inbound: Any, sender: str) -> Optional[tuple[Any, ...]]:
|
|
110
|
+
return self._dispatch_session(inbound, 'need')
|
|
111
|
+
|
|
112
|
+
def handle_done(self: SyncShape, inbound: Any, sender: str) -> Optional[tuple[Any, ...]]:
|
|
113
|
+
return self._dispatch_session(inbound, 'done')
|
|
114
|
+
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""What this node will sign for, and what it will take somebody's word on.
|
|
2
|
+
|
|
3
|
+
The handover is the one shape of `asserted` a node may publish over a set it
|
|
4
|
+
did not receive first-hand, and acting on one touches two compartments: the
|
|
5
|
+
sync wire, which offers and accepts them over the wire, and discovery, which
|
|
6
|
+
publishes the claim that rests on one. The rules were written in both places
|
|
7
|
+
and a third copy sat in `validate`, so "what makes a handover acceptable" had
|
|
8
|
+
three homes and no single answer.
|
|
9
|
+
|
|
10
|
+
They live here, as decisions over plain data. Nothing in this module signs,
|
|
11
|
+
sends, stores or refuses: the compartments keep all of that, because the key,
|
|
12
|
+
the transport, the account store and the refusal channel are theirs. What
|
|
13
|
+
they get back is a verdict and a reason for it, and the reason is what the
|
|
14
|
+
operator reads.
|
|
15
|
+
|
|
16
|
+
`fleetmesh_core/coverage.py` owns the artefact - its shape, its signature, and the chain
|
|
17
|
+
rules every reader applies. This owns the part that is about *this* node: the
|
|
18
|
+
shard it was configured to hold, the set on its own shelves, the session that
|
|
19
|
+
just closed, and the operator's `handover` setting.
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
from typing import Any, Mapping, Optional, Sequence, cast
|
|
25
|
+
|
|
26
|
+
from fleetmesh_core.wire.wire_types import HandoverLink
|
|
27
|
+
from fleetmesh_core.peering import coverage
|
|
28
|
+
from fleetmesh_core.storage import replication
|
|
29
|
+
from .types import CoverageShardConfig
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def canonical(filt: Any) -> str:
|
|
33
|
+
"""A filter as the artefact carries it. Two filters that differ only in
|
|
34
|
+
key order are one filter, and a handover that compared them as written
|
|
35
|
+
would refuse a peer for having sorted its JSON differently."""
|
|
36
|
+
return json.dumps(filt, sort_keys=True, separators=(',', ':'))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# The shape check for a handover body is core's, `validate.check_handover_body`,
|
|
40
|
+
# which the sync responder already applies through validate.check_body. A copy
|
|
41
|
+
# of it lived here, unused by anything, with the boolean-as-integer hole core
|
|
42
|
+
# has since closed (audit 2026-10-15, D1 and B9).
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def offered_set(
|
|
46
|
+
claim: Optional[Mapping[str, Any]],
|
|
47
|
+
session: Any,
|
|
48
|
+
now: int,
|
|
49
|
+
setting: str = 'offer',
|
|
50
|
+
status: Any = 'complete',
|
|
51
|
+
) -> tuple[Optional[str], Optional[str]]:
|
|
52
|
+
"""(fingerprint, why_not) for a session that may have earned a handover.
|
|
53
|
+
|
|
54
|
+
Every condition is about what this node has already said in public. The
|
|
55
|
+
claim is the kind 30803 last published: asserted, with a bounded range
|
|
56
|
+
that has closed, and the session covered the whole of that range under
|
|
57
|
+
that same filter, so the peer now holds the set the claim is about. A node
|
|
58
|
+
that published best-effort, or whose range is still open, has nothing it
|
|
59
|
+
can sign for.
|
|
60
|
+
|
|
61
|
+
`why_not` is for the operator and not for the wire. Most of these are the
|
|
62
|
+
ordinary case - a session that reconciled part of a shard has earned
|
|
63
|
+
nothing and there is nothing wrong with that - so a caller logs at most
|
|
64
|
+
the ones it asked about.
|
|
65
|
+
"""
|
|
66
|
+
if status not in ('complete', 'in_sync'):
|
|
67
|
+
return None, 'the session ended %s' % status
|
|
68
|
+
if setting != 'offer':
|
|
69
|
+
return None, 'the operator does not offer handovers'
|
|
70
|
+
if claim is None or not coverage.reportable(dict(claim)):
|
|
71
|
+
return None, 'this node publishes no asserted claim for the shard'
|
|
72
|
+
from fleetmesh_core.storage import store as ref_store
|
|
73
|
+
until = ref_store.first_tag(claim, 'until')
|
|
74
|
+
if not until or len(until) < 2:
|
|
75
|
+
return None, 'the claim has no until: a handover signs for a closed set'
|
|
76
|
+
filt = ref_store.first_tag(claim, 'filter')
|
|
77
|
+
fp = ref_store.first_tag(claim, 'fingerprint')
|
|
78
|
+
if not filt or len(filt) < 2 or not fp or len(fp) < 2:
|
|
79
|
+
# An asserted claim always carries both; one without them was not
|
|
80
|
+
# written by fleetmesh_core.peering.coverage and nothing can be signed for it.
|
|
81
|
+
return None, 'the claim carries no filter or fingerprint'
|
|
82
|
+
since_tag = ref_store.first_tag(claim, 'since')
|
|
83
|
+
c_since = int(since_tag[1]) if since_tag and len(since_tag) > 1 else 0
|
|
84
|
+
c_until = int(until[1])
|
|
85
|
+
if c_until > now:
|
|
86
|
+
return None, 'the claim range has not closed yet'
|
|
87
|
+
if session.since > c_since or session.until < c_until:
|
|
88
|
+
return None, 'the session did not cover the whole of the claim range'
|
|
89
|
+
if canonical(session.interest) != canonical(json.loads(filt[1])):
|
|
90
|
+
return None, 'the session ran under a different filter'
|
|
91
|
+
return fp[1], None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def extend(held: Sequence[HandoverLink | Mapping[str, Any]], link: HandoverLink | Mapping[str, Any]) -> tuple[Optional[list[HandoverLink]], Optional[str]]:
|
|
95
|
+
"""(chain_to_send, why_not): what this node holds, plus its own link.
|
|
96
|
+
|
|
97
|
+
A node passing on a shard it holds under a handover passes on the whole
|
|
98
|
+
provenance with it. Carrying only its own link would leave the node that
|
|
99
|
+
withheld an event able to report the hole against the node below, liable
|
|
100
|
+
for it and unbarred from reporting it. The chain is checked before it
|
|
101
|
+
goes: one this node cannot stand behind is one it should not sign onto.
|
|
102
|
+
"""
|
|
103
|
+
chain = [dict(h) for h in held] + [dict(link)]
|
|
104
|
+
if len(chain) > coverage.HANDOVER_MAX_CHAIN:
|
|
105
|
+
return None, 'the chain would be %d links, longer than %d' % (len(chain), coverage.HANDOVER_MAX_CHAIN)
|
|
106
|
+
problems = coverage.check_chain(chain)
|
|
107
|
+
if problems:
|
|
108
|
+
return None, problems[0]
|
|
109
|
+
return cast(list[HandoverLink], chain), None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def acceptable(
|
|
113
|
+
chain: Sequence[Mapping[str, Any]],
|
|
114
|
+
me: str,
|
|
115
|
+
sender: str,
|
|
116
|
+
shard_config: Optional[Mapping[str, Any]],
|
|
117
|
+
held_ids: Optional[Sequence[str]],
|
|
118
|
+
) -> Optional[str]:
|
|
119
|
+
"""Why this node must not keep an inbound chain, or None to keep it.
|
|
120
|
+
|
|
121
|
+
Held to more than its signatures. Every link must verify and the chain
|
|
122
|
+
must join up and describe one set, which is `fleetmesh_core/coverage.py`'s business.
|
|
123
|
+
Then: the last link names this node, it was signed by the peer that sent
|
|
124
|
+
it, the shard is one the operator configured with that same filter, and
|
|
125
|
+
the set fingerprints to what this node holds over the handed range right
|
|
126
|
+
now. A chain over any other set could only ever support a false claim,
|
|
127
|
+
and refusing it costs less than publishing one and dropping it next tick.
|
|
128
|
+
|
|
129
|
+
`held_ids` is what this node holds over the handed range, or None when the
|
|
130
|
+
caller could not look - the shard is unconfigured and the answer is
|
|
131
|
+
already no.
|
|
132
|
+
"""
|
|
133
|
+
if not chain:
|
|
134
|
+
return 'the chain is empty'
|
|
135
|
+
problems: list[str] = []
|
|
136
|
+
for link in chain:
|
|
137
|
+
problems += coverage.check_handover(link)
|
|
138
|
+
problems += coverage.check_chain(chain)
|
|
139
|
+
if problems:
|
|
140
|
+
return problems[0]
|
|
141
|
+
art = chain[-1]
|
|
142
|
+
if art['to'] != me:
|
|
143
|
+
return 'names %s, not this node' % art['to'][:16]
|
|
144
|
+
if art['from'] != sender:
|
|
145
|
+
return 'signed by %s and sent by %s' % (art['from'][:16], sender[:16])
|
|
146
|
+
if shard_config is None:
|
|
147
|
+
return 'shard %s is not one this node claims' % art['shard']
|
|
148
|
+
if canonical(shard_config.get('filter')) != art['filter']:
|
|
149
|
+
return 'the handed filter is not the one configured for %s' % art['shard']
|
|
150
|
+
if held_ids is None:
|
|
151
|
+
return 'this node cannot say what it holds for %s' % art['shard']
|
|
152
|
+
if replication.fingerprint(held_ids) != art['fingerprint'] or len(held_ids) != art['count']:
|
|
153
|
+
return ('what this node holds over the handed range is not the handed set (%d held, %d handed)'
|
|
154
|
+
% (len(held_ids), art['count']))
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def still_fits(chain: Sequence[Mapping[str, Any]], filt: Any, held_ids: Sequence[str]) -> Optional[str]:
|
|
159
|
+
"""Why a chain this node holds no longer describes what it holds, or None.
|
|
160
|
+
|
|
161
|
+
Asked every time the claim goes out. A handover fits while the set this
|
|
162
|
+
node holds over the handed range, under the handed filter, fingerprints to
|
|
163
|
+
what was handed; the moment a late event lands in the closed range or
|
|
164
|
+
retention takes one out, the only claim it could support is a false one.
|
|
165
|
+
"""
|
|
166
|
+
art = chain[-1]
|
|
167
|
+
if canonical(filt) != art['filter']:
|
|
168
|
+
return 'the filter is not the handed one'
|
|
169
|
+
if replication.fingerprint(held_ids) != art['fingerprint'] or len(held_ids) != art['count']:
|
|
170
|
+
return 'what this node holds over the handed range is no longer the handed set'
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def shard_config(coverage_config: Any, shard: str) -> Optional[CoverageShardConfig]:
|
|
175
|
+
"""The operator's entry for one shard, or None if they configured none."""
|
|
176
|
+
for c in coverage_config or []:
|
|
177
|
+
if isinstance(c, dict) and c.get('shard') == shard:
|
|
178
|
+
return cast(CoverageShardConfig, c)
|
|
179
|
+
return None
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""The shape of a SyncPeer as its mixins see it.
|
|
2
|
+
|
|
3
|
+
Split across core.py, initiator.py, responder.py and handover.py the way
|
|
4
|
+
node/reporter/ and node/peering_wire/ are: each mixin annotates `self` as
|
|
5
|
+
this Protocol. A declaration only; SyncPeer satisfies it structurally.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import threading
|
|
10
|
+
from typing import Any, Callable, Mapping, Optional, Protocol
|
|
11
|
+
|
|
12
|
+
from fleetmesh_core.wire.interfaces import NostrEvent
|
|
13
|
+
from fleetmesh_core.wire.wire_types import HandoverLink, SyncHandoverBody
|
|
14
|
+
from node.compartment import Refusals
|
|
15
|
+
from node.compartment import stores as st
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .types import (
|
|
19
|
+
SyncSessionResult,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SyncShape(Protocol):
|
|
24
|
+
TROUBLE_WINDOW: int
|
|
25
|
+
refusals: Refusals
|
|
26
|
+
node: Any
|
|
27
|
+
strain: Optional[Any]
|
|
28
|
+
accounts: Optional[Any]
|
|
29
|
+
discovery: Optional[Any]
|
|
30
|
+
presence: Optional[Any]
|
|
31
|
+
session_cls: Any
|
|
32
|
+
ks: Any
|
|
33
|
+
store: Any
|
|
34
|
+
transport: Any
|
|
35
|
+
wire_client: Any
|
|
36
|
+
grants: Any
|
|
37
|
+
clock: Callable[[], int]
|
|
38
|
+
budget: Optional[Any]
|
|
39
|
+
on_session: list[Callable[[str, str, SyncSessionResult], Any]]
|
|
40
|
+
totals: st.Counters
|
|
41
|
+
aborted: st.Ring[tuple[str, str]]
|
|
42
|
+
refused: st.Ring[tuple[str, Optional[str], str]]
|
|
43
|
+
sessions: st.Table[str, tuple[str, Any, int]]
|
|
44
|
+
_initiated: st.Members[str]
|
|
45
|
+
rejected: st.Ring[tuple[str, str, str]]
|
|
46
|
+
unsent: st.Ring[tuple[str, str, str, int]]
|
|
47
|
+
completed: st.Ring[tuple[str, str, SyncSessionResult]]
|
|
48
|
+
handed: st.Ring[tuple[str, str, str, int]]
|
|
49
|
+
held: st.Ring[tuple[str, str, int]]
|
|
50
|
+
_offered: st.Table[tuple[str, str], str]
|
|
51
|
+
_lock: threading.Lock
|
|
52
|
+
|
|
53
|
+
# Compartment
|
|
54
|
+
def config(self, key: str, default: Any = None) -> Any: ...
|
|
55
|
+
def say(self, name: str, *args: Any, **kw: Any) -> Any: ...
|
|
56
|
+
|
|
57
|
+
# core.py
|
|
58
|
+
def _grant(self, peer_pk: str, direction: str) -> Any: ...
|
|
59
|
+
def _send(self, m: Mapping[str, Any], peer_pk: str) -> dict[str, tuple[bool, str]]: ...
|
|
60
|
+
def _seen(self, peer_pk: str, m: Mapping[str, Any]) -> None: ...
|
|
61
|
+
def _expire(self, now: int) -> list[str]: ...
|
|
62
|
+
def _finish(self, thid: str, peer_pk: str, sess: Any, status: Any) -> None: ...
|
|
63
|
+
def _record(self, ring: 'st.Ring[Any]', entry: tuple[Any, ...]) -> None: ...
|
|
64
|
+
|
|
65
|
+
# responder.py
|
|
66
|
+
def _refuse_open(self, peer_pk: str, thid: Optional[str], why: str) -> tuple[Any, ...]: ...
|
|
67
|
+
def _precheck(self, inbound: Any, kind: str) -> tuple[Optional[Mapping[str, Any]], str, Optional[tuple[Any, ...]]]: ...
|
|
68
|
+
def _send_reply(self, thid: Optional[str], peer_pk: str, sess: Any, reply: Mapping[str, Any]) -> tuple[Any, ...]: ...
|
|
69
|
+
def _dispatch_session(self, inbound: Any, kind: str) -> Optional[tuple[Any, ...]]: ...
|
|
70
|
+
def handle_open(self, inbound: Any, sender: str) -> Optional[tuple[Any, ...]]: ...
|
|
71
|
+
def handle_handover(self, inbound: Any, sender: str) -> Optional[tuple[Any, ...]]: ...
|
|
72
|
+
def handle_range(self, inbound: Any, sender: str) -> Optional[tuple[Any, ...]]: ...
|
|
73
|
+
def handle_need(self, inbound: Any, sender: str) -> Optional[tuple[Any, ...]]: ...
|
|
74
|
+
def handle_done(self, inbound: Any, sender: str) -> Optional[tuple[Any, ...]]: ...
|
|
75
|
+
|
|
76
|
+
# handover.py
|
|
77
|
+
def _offer_handover(self, thid: str, peer_pk: str, sess: Any, status: Any) -> Optional[str]: ...
|
|
78
|
+
def _unoffer(self, key: tuple[str, str], previous: Optional[str], entry: tuple[Any, ...]) -> None: ...
|
|
79
|
+
def _chain_for(self, shard: str, claim: NostrEvent, peer_pk: str, now: int) -> Optional[list[HandoverLink]]: ...
|
|
80
|
+
def _accept_handover(self, peer_pk: str, body: SyncHandoverBody) -> tuple[Any, ...]: ...
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Types and records for the Sync compartment."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Mapping, Optional, TypedDict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SyncSessionResult(TypedDict, total=False):
|
|
8
|
+
"""Result of a sync session (initiator or responder)."""
|
|
9
|
+
status: str
|
|
10
|
+
counters: Mapping[str, int]
|
|
11
|
+
findings: list[tuple[Any, ...]]
|
|
12
|
+
need: list[str]
|
|
13
|
+
dup_ratio: float
|
|
14
|
+
thid: str
|
|
15
|
+
evidence: list[Mapping[str, Any]]
|
|
16
|
+
grant: Optional[str]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SyncHandedRow(TypedDict, total=False):
|
|
20
|
+
peer: str
|
|
21
|
+
shard: str
|
|
22
|
+
at: int
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SyncStateReport(TypedDict, total=False):
|
|
26
|
+
"""The report SyncPeer.state() gives of itself."""
|
|
27
|
+
open: int
|
|
28
|
+
refused: int
|
|
29
|
+
aborted: int
|
|
30
|
+
completed: int
|
|
31
|
+
unsent: int
|
|
32
|
+
malformed: int
|
|
33
|
+
handed: list[SyncHandedRow]
|
|
34
|
+
held: list[SyncHandedRow]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class CoverageShardConfig(TypedDict, total=False):
|
|
38
|
+
"""Configuration for a coverage shard."""
|
|
39
|
+
shard: str
|
|
40
|
+
filter: Mapping[str, Any]
|
|
41
|
+
since: int
|
|
42
|
+
until: int
|
|
43
|
+
cadence: int
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class SyncTotals(TypedDict):
|
|
47
|
+
"""Sessions since the process started, by how they ended. `state` has
|
|
48
|
+
always reported these as counts; the rings beside them keep the recent."""
|
|
49
|
+
aborted: int
|
|
50
|
+
refused: int
|
|
51
|
+
completed: int
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Transport plane: link, lan, p2p, mediation, bridge."""
|
|
2
|
+
from . import bridge, lan, link, mediation, p2p
|
|
3
|
+
from .bridge.compartment import DEFAULT_EGRESS_KINDS, DEFAULT_INGRESS_KINDS, RelayBridge
|
|
4
|
+
from .lan.compartment import Lan
|
|
5
|
+
from .link.compartment import Link, LinkState
|
|
6
|
+
from .mediation.compartment import Mediation
|
|
7
|
+
from .p2p.compartment import P2PTransport
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
'DEFAULT_EGRESS_KINDS',
|
|
11
|
+
'DEFAULT_INGRESS_KINDS',
|
|
12
|
+
'Lan',
|
|
13
|
+
'Link',
|
|
14
|
+
'LinkState',
|
|
15
|
+
'Mediation',
|
|
16
|
+
'P2PTransport',
|
|
17
|
+
'RelayBridge',
|
|
18
|
+
'bridge',
|
|
19
|
+
'lan',
|
|
20
|
+
'link',
|
|
21
|
+
'mediation',
|
|
22
|
+
'p2p',
|
|
23
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Universal Nostr Relay Bridge for fleetmesh.
|
|
2
|
+
|
|
3
|
+
One-module compartment: everything is in compartment.py.
|
|
4
|
+
|
|
5
|
+
The package is `relay_bridge` and the compartment is still called `bridge`:
|
|
6
|
+
the name is the config key, the `meshnode bridge` verb and the health key an
|
|
7
|
+
operator already uses, so it stays. The package moved so that "bridge" in a
|
|
8
|
+
path means a compartment fronting a sidecar (node/sidecar_bridge), and this -
|
|
9
|
+
which fronts relays, not a sidecar process - says what it bridges.
|
|
10
|
+
"""
|
|
11
|
+
from .compartment import RelayBridge
|
|
12
|
+
|
|
13
|
+
__all__ = ['RelayBridge']
|