fleetmesh-node 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fleetmesh_node-0.2.0.dist-info/METADATA +57 -0
- fleetmesh_node-0.2.0.dist-info/RECORD +338 -0
- fleetmesh_node-0.2.0.dist-info/WHEEL +5 -0
- fleetmesh_node-0.2.0.dist-info/entry_points.txt +2 -0
- fleetmesh_node-0.2.0.dist-info/licenses/LICENSE +121 -0
- fleetmesh_node-0.2.0.dist-info/top_level.txt +2 -0
- node/RELEASE +1 -0
- node/__init__.py +35 -0
- node/app/__init__.py +49 -0
- node/app/commands.py +265 -0
- node/app/config.py +131 -0
- node/app/control.py +670 -0
- node/app/core.py +867 -0
- node/app/plugins.py +76 -0
- node/app/router.py +83 -0
- node/app/socket.py +431 -0
- node/app/systemd.py +36 -0
- node/app/views.py +204 -0
- node/channels/__init__.py +378 -0
- node/channels/bus.py +331 -0
- node/channels/journal.py +46 -0
- node/commands/__init__.py +140 -0
- node/commands/adapter.py +210 -0
- node/commands/base.py +278 -0
- node/commands/cluster_cmds.py +183 -0
- node/commands/compute_cmds.py +401 -0
- node/commands/core.py +474 -0
- node/commands/gossip_cmds.py +106 -0
- node/commands/nsite_cmds.py +141 -0
- node/commands/peering.py +402 -0
- node/commands/plugin_cmds.py +135 -0
- node/commands/sidecar_cmds.py +120 -0
- node/commands/sync.py +388 -0
- node/commands/transport_cmds.py +451 -0
- node/commands/video_cmds.py +99 -0
- node/compartment/__init__.py +124 -0
- node/compartment/base.py +868 -0
- node/compartment/command.py +458 -0
- node/compartment/context.py +163 -0
- node/compartment/every.py +108 -0
- node/compartment/handler.py +131 -0
- node/compartment/lifecycle.py +460 -0
- node/compartment/lint.py +388 -0
- node/compartment/need.py +158 -0
- node/compartment/problem.py +521 -0
- node/compartment/refusals.py +120 -0
- node/compartment/registry.py +790 -0
- node/compartment/schema.py +290 -0
- node/compartment/sidecar.py +499 -0
- node/compartment/stores.py +662 -0
- node/compartment/wire.py +225 -0
- node/compartment/worker.py +61 -0
- node/config/__init__.py +43 -0
- node/config/base.py +116 -0
- node/config/change.py +441 -0
- node/config/changestore.py +201 -0
- node/config/compartments.py +1144 -0
- node/config/node.py +318 -0
- node/config/parameter.py +563 -0
- node/config/protocols.py +274 -0
- node/config/records.py +171 -0
- node/examples/__init__.py +1 -0
- node/examples/demo.py +226 -0
- node/examples/follower.py +46 -0
- node/interfaces.py +235 -0
- node/keystore/__init__.py +27 -0
- node/keystore/core.py +103 -0
- node/keystore/enclave.py +688 -0
- node/meshnode.py +111 -0
- node/metrics/__init__.py +396 -0
- node/metrics/adapters.py +102 -0
- node/metrics/face.py +110 -0
- node/planes/__init__.py +21 -0
- node/planes/identity/__init__.py +20 -0
- node/planes/identity/announce/__init__.py +9 -0
- node/planes/identity/announce/compartment.py +447 -0
- node/planes/identity/announce/types.py +26 -0
- node/planes/identity/discovery/__init__.py +9 -0
- node/planes/identity/discovery/compartment.py +522 -0
- node/planes/identity/discovery/types.py +13 -0
- node/planes/identity/linked/__init__.py +1 -0
- node/planes/identity/linked/compartment.py +270 -0
- node/planes/identity/linked/types.py +25 -0
- node/planes/identity/presence/__init__.py +7 -0
- node/planes/identity/presence/compartment.py +409 -0
- node/planes/identity/presence/types.py +14 -0
- node/planes/operations/__init__.py +13 -0
- node/planes/operations/scheduler/__init__.py +9 -0
- node/planes/operations/scheduler/compartment.py +353 -0
- node/planes/operations/scheduler/types.py +21 -0
- node/planes/operations/updates/__init__.py +17 -0
- node/planes/operations/updates/core.py +310 -0
- node/planes/operations/updates/store.py +79 -0
- node/planes/operations/updates/types.py +20 -0
- node/planes/peering/__init__.py +28 -0
- node/planes/peering/budget/__init__.py +43 -0
- node/planes/peering/budget/chain.py +43 -0
- node/planes/peering/budget/core.py +537 -0
- node/planes/peering/budget/evaluation.py +80 -0
- node/planes/peering/budget/meter.py +100 -0
- node/planes/peering/budget/receipts.py +439 -0
- node/planes/peering/budget/shape.py +101 -0
- node/planes/peering/budget/tick.py +110 -0
- node/planes/peering/budget/types.py +67 -0
- node/planes/peering/budget/windows.py +346 -0
- node/planes/peering/capacity/__init__.py +7 -0
- node/planes/peering/capacity/compartment.py +342 -0
- node/planes/peering/capacity/types.py +24 -0
- node/planes/peering/peering/__init__.py +25 -0
- node/planes/peering/peering/core.py +464 -0
- node/planes/peering/peering/envelope.py +142 -0
- node/planes/peering/peering/handshake.py +198 -0
- node/planes/peering/peering/shape.py +80 -0
- node/planes/peering/peering/types.py +39 -0
- node/planes/peering/reporter/__init__.py +42 -0
- node/planes/peering/reporter/constants.py +29 -0
- node/planes/peering/reporter/core.py +299 -0
- node/planes/peering/reporter/finding.py +74 -0
- node/planes/peering/reporter/findings.py +171 -0
- node/planes/peering/reporter/publish.py +285 -0
- node/planes/peering/reporter/reading.py +193 -0
- node/planes/peering/reporter/shape.py +86 -0
- node/planes/peering/reporter/types.py +50 -0
- node/planes/peering/strain/__init__.py +9 -0
- node/planes/peering/strain/compartment.py +204 -0
- node/planes/peering/strain/types.py +21 -0
- node/planes/peering/trust/__init__.py +9 -0
- node/planes/peering/trust/compartment.py +239 -0
- node/planes/peering/trust/types.py +30 -0
- node/planes/services/__init__.py +33 -0
- node/planes/services/compute/__init__.py +160 -0
- node/planes/services/compute/common.py +134 -0
- node/planes/services/compute/compartment.py +681 -0
- node/planes/services/compute/executors/__init__.py +121 -0
- node/planes/services/compute/executors/agent.py +840 -0
- node/planes/services/compute/executors/bacalhau.py +605 -0
- node/planes/services/compute/executors/base.py +127 -0
- node/planes/services/compute/executors/harness.py +439 -0
- node/planes/services/compute/executors/mcp.py +454 -0
- node/planes/services/compute/executors/media.py +898 -0
- node/planes/services/compute/executors/nondet.py +283 -0
- node/planes/services/compute/executors/providers.py +499 -0
- node/planes/services/compute/executors/registry.py +140 -0
- node/planes/services/compute/executors/wasm.py +244 -0
- node/planes/services/compute/executors/zk.py +604 -0
- node/planes/services/compute/jobs.py +227 -0
- node/planes/services/compute/load.py +180 -0
- node/planes/services/compute/market.py +433 -0
- node/planes/services/compute/sandbox.py +282 -0
- node/planes/services/compute/types.py +84 -0
- node/planes/services/compute_wire/__init__.py +58 -0
- node/planes/services/compute_wire/compartment.py +256 -0
- node/planes/services/compute_wire/messages.py +320 -0
- node/planes/services/compute_wire/sealing.py +147 -0
- node/planes/services/compute_wire/session.py +572 -0
- node/planes/services/compute_wire/types.py +49 -0
- node/planes/services/compute_wire/verify.py +331 -0
- node/planes/services/dvm/__init__.py +24 -0
- node/planes/services/dvm/gateway.py +526 -0
- node/planes/services/dvm/protocol.py +35 -0
- node/planes/services/dvm/translate.py +15 -0
- node/planes/services/dvm/types.py +25 -0
- node/planes/services/gateway/__init__.py +40 -0
- node/planes/services/gateway/settle.py +279 -0
- node/planes/services/gateway/translate.py +644 -0
- node/planes/services/gossip/__init__.py +22 -0
- node/planes/services/gossip/core.py +929 -0
- node/planes/services/gossip/envelope.py +154 -0
- node/planes/services/gossip/mesh.py +127 -0
- node/planes/services/gossip/types.py +50 -0
- node/planes/services/nsite/__init__.py +7 -0
- node/planes/services/nsite/client.py +62 -0
- node/planes/services/nsite/compartment.py +752 -0
- node/planes/services/nsite/types.py +44 -0
- node/planes/services/paidfetch/__init__.py +7 -0
- node/planes/services/paidfetch/compartment.py +862 -0
- node/planes/services/paidfetch/types.py +99 -0
- node/planes/services/settle/__init__.py +27 -0
- node/planes/services/settle/compartment.py +384 -0
- node/planes/services/settle/engine.py +456 -0
- node/planes/services/settle/gateways.py +541 -0
- node/planes/services/settle/payment.py +512 -0
- node/planes/services/settle/store.py +74 -0
- node/planes/services/settle/types.py +58 -0
- node/planes/services/sidecars/__init__.py +8 -0
- node/planes/services/sidecars/compartment.py +461 -0
- node/planes/services/sidecars/manifest.py +304 -0
- node/planes/services/sidecars/manifests/10-grasp.json +37 -0
- node/planes/services/sidecars/manifests/20-ncontainer.json +31 -0
- node/planes/services/sidecars/manifests/30-magpie.json +28 -0
- node/planes/services/sidecars/types.py +43 -0
- node/planes/services/video/__init__.py +106 -0
- node/planes/services/video/barter.py +407 -0
- node/planes/services/video/cli.py +158 -0
- node/planes/services/video/compartment.py +932 -0
- node/planes/services/video/ingest.py +524 -0
- node/planes/services/video/ledger.py +705 -0
- node/planes/services/video/packager.py +847 -0
- node/planes/services/video/player.py +456 -0
- node/planes/services/video/timed_segments.py +124 -0
- node/planes/services/video/transit.py +478 -0
- node/planes/store/__init__.py +24 -0
- node/planes/store/blob_replication/__init__.py +7 -0
- node/planes/store/blob_replication/compartment.py +496 -0
- node/planes/store/blob_replication/types.py +35 -0
- node/planes/store/blob_wire/__init__.py +48 -0
- node/planes/store/blob_wire/client.py +57 -0
- node/planes/store/blob_wire/core.py +359 -0
- node/planes/store/blob_wire/intake.py +74 -0
- node/planes/store/blob_wire/messages.py +85 -0
- node/planes/store/projected/__init__.py +7 -0
- node/planes/store/projected/compartment.py +258 -0
- node/planes/store/projected/types.py +29 -0
- node/planes/store/retention/__init__.py +7 -0
- node/planes/store/retention/compartment.py +214 -0
- node/planes/store/retention/types.py +12 -0
- node/planes/store/sync/__init__.py +35 -0
- node/planes/store/sync/core.py +311 -0
- node/planes/store/sync/envelope.py +106 -0
- node/planes/store/sync/handover.py +206 -0
- node/planes/store/sync/initiator.py +128 -0
- node/planes/store/sync/responder.py +114 -0
- node/planes/store/sync/rules.py +179 -0
- node/planes/store/sync/shape.py +80 -0
- node/planes/store/sync/types.py +51 -0
- node/planes/transport/__init__.py +23 -0
- node/planes/transport/bridge/__init__.py +13 -0
- node/planes/transport/bridge/compartment.py +785 -0
- node/planes/transport/bridge/ratelimit.py +44 -0
- node/planes/transport/bridge/types.py +43 -0
- node/planes/transport/lan/__init__.py +9 -0
- node/planes/transport/lan/compartment.py +446 -0
- node/planes/transport/lan/types.py +25 -0
- node/planes/transport/link/__init__.py +7 -0
- node/planes/transport/link/compartment.py +257 -0
- node/planes/transport/link/types.py +27 -0
- node/planes/transport/mediation/__init__.py +45 -0
- node/planes/transport/mediation/compartment.py +238 -0
- node/planes/transport/mediation/leaf.py +107 -0
- node/planes/transport/mediation/messages.py +302 -0
- node/planes/transport/mediation/service.py +233 -0
- node/planes/transport/mediation/store.py +37 -0
- node/planes/transport/mediation/types.py +20 -0
- node/planes/transport/p2p/__init__.py +7 -0
- node/planes/transport/p2p/compartment.py +349 -0
- node/planes/transport/p2p/types.py +22 -0
- node/protocols/__init__.py +277 -0
- node/protocols/activitypub/__init__.py +56 -0
- node/protocols/activitypub/actor.py +230 -0
- node/protocols/activitypub/federation.py +490 -0
- node/protocols/activitypub/media.py +145 -0
- node/protocols/activitypub/models.py +286 -0
- node/protocols/activitypub/package.py +271 -0
- node/protocols/activitypub/store.py +564 -0
- node/protocols/arcade/__init__.py +26 -0
- node/protocols/atproto/__init__.py +13 -0
- node/protocols/atproto/firehose.py +126 -0
- node/protocols/atproto/metering.py +165 -0
- node/protocols/atproto/package.py +155 -0
- node/protocols/atproto/repo.py +250 -0
- node/protocols/atproto/xrpc.py +285 -0
- node/protocols/base.py +109 -0
- node/protocols/circles/__init__.py +29 -0
- node/protocols/dm/__init__.py +50 -0
- node/protocols/grasp/__init__.py +22 -0
- node/protocols/isotime.py +15 -0
- node/protocols/nostr/__init__.py +51 -0
- node/protocols/nostr/blobstore.py +383 -0
- node/protocols/nostr/blossom.py +627 -0
- node/protocols/nostr/client.py +377 -0
- node/protocols/nostr/descriptor.py +211 -0
- node/protocols/nostr/eventstore.py +304 -0
- node/protocols/nostr/package.py +182 -0
- node/protocols/nostr/peers.py +109 -0
- node/protocols/nostr/relayserver.py +656 -0
- node/protocols/p2panda/__init__.py +22 -0
- node/protocols/p2panda/identity.py +19 -0
- node/protocols/p2panda/package.py +236 -0
- node/protocols/p2panda/rung.py +152 -0
- node/protocols/p2panda/store.py +273 -0
- node/protocols/rhio/__init__.py +33 -0
- node/protocols/rhio/package.py +275 -0
- node/protocols/rhio/rung.py +136 -0
- node/protocols/rhio/store.py +1423 -0
- node/protocols/ssb/muxrpc.py +256 -0
- node/protocols/ssb/package.py +267 -0
- node/protocols/textile/__init__.py +111 -0
- node/protocols/textile/transducers.py +259 -0
- node/release.py +119 -0
- node/storage/__init__.py +88 -0
- node/storage/accounts.py +305 -0
- node/storage/adapter.py +161 -0
- node/storage/base.py +385 -0
- node/storage/dialect.py +209 -0
- node/storage/grants.py +317 -0
- node/storage/locator.py +150 -0
- node/storage/postgres.py +294 -0
- node/storage/projected.py +953 -0
- node/storage/sharding.py +448 -0
- node/storage/standing.py +568 -0
- node/testnet/__init__.py +9 -0
- node/testnet/cluster.py +731 -0
- node/testnet/fips_daemon.py +230 -0
- node/transport/__init__.py +94 -0
- node/transport/const.py +110 -0
- node/transport/didcomm_inbound.py +251 -0
- node/transport/dnstxt.py +169 -0
- node/transport/fips_rung/__init__.py +67 -0
- node/transport/fips_rung/core.py +666 -0
- node/transport/fips_rung/listener.py +96 -0
- node/transport/fips_rung/types.py +12 -0
- node/transport/host.py +334 -0
- node/transport/iroh_rung/__init__.py +29 -0
- node/transport/iroh_rung/core.py +477 -0
- node/transport/iroh_rung/quic.py +942 -0
- node/transport/iroh_rung/types.py +22 -0
- node/transport/ladder.py +145 -0
- node/transport/lineproto.py +49 -0
- node/transport/mdns.py +303 -0
- node/transport/netfetch.py +283 -0
- node/transport/relay_rung/__init__.py +23 -0
- node/transport/relay_rung/cooldown.py +87 -0
- node/transport/relay_rung/core.py +138 -0
- node/transport/relay_rung/inbound.py +161 -0
- node/transport/relay_rung/ledger.py +104 -0
- node/transport/relay_rung/listen.py +121 -0
- node/transport/relay_rung/outbound.py +181 -0
- node/transport/relay_rung/shape.py +71 -0
- node/transport/relay_rung/types.py +58 -0
- node/transport/relay_rung/wire.py +63 -0
- node/transport/router.py +761 -0
- node/transport/rung.py +276 -0
- node/transport/ssb_rung.py +166 -0
- node/transport/ws.py +502 -0
- sidecar/fleetmesh_announce.py +338 -0
- sidecar/fleetmesh_p2p.py +373 -0
- sidecar/fleetmesh_rhio.py +352 -0
- sidecar/fleetmesh_ssb.py +603 -0
node/storage/accounts.py
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"""What a node must not forget when it restarts: its accounting.
|
|
2
|
+
|
|
3
|
+
Grants and events were on disk from the start. The meters, the receipts a
|
|
4
|
+
node has received, and the chain state of the receipts it sends were not: they
|
|
5
|
+
lived in memory, and a restart lost them. That is not a lost convenience, it
|
|
6
|
+
is a correctness bug, and a two-machine run is what showed its shape.
|
|
7
|
+
|
|
8
|
+
A receipt chain is `seq` and `prev`, and the issuer checks both. A subject
|
|
9
|
+
that restarts mid-window begins again at seq 0 with a zero `prev`, while the
|
|
10
|
+
issuer is still expecting seq N chained onto what it last held. The issuer
|
|
11
|
+
reads that as a broken chain and files an S2 misreport. Its counters are
|
|
12
|
+
cumulative within a window, too, so the restarted subject also reports fewer
|
|
13
|
+
than it did before, which is a counter going backwards: the second half of the
|
|
14
|
+
same finding. An honest node that was restarted is priced exactly as a liar.
|
|
15
|
+
|
|
16
|
+
So the accounting is persisted here, in the node's own directory, beside the
|
|
17
|
+
grants it already kept.
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import time
|
|
23
|
+
from typing import Any, Mapping, Optional, Sequence
|
|
24
|
+
|
|
25
|
+
from fleetmesh_core.wire.interfaces import ReceiptBody
|
|
26
|
+
from fleetmesh_core.wire.wire_types import HandoverLink
|
|
27
|
+
from ..planes.peering.budget.types import OutChainDict
|
|
28
|
+
from ..planes.peering.reporter.types import FindingDict
|
|
29
|
+
from ..compartment.schema import Column, Migration, Table
|
|
30
|
+
from . import SqliteStore
|
|
31
|
+
|
|
32
|
+
BY: str = 'by'
|
|
33
|
+
OF_ME: str = 'of_me'
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AccountStore(SqliteStore):
|
|
37
|
+
SCHEMA = (
|
|
38
|
+
Table('meters', (
|
|
39
|
+
Column('peer', 'TEXT', not_null=True),
|
|
40
|
+
Column('epoch', 'INTEGER', not_null=True),
|
|
41
|
+
Column('side', 'TEXT', not_null=True),
|
|
42
|
+
Column('counters', 'TEXT'),
|
|
43
|
+
Column('at', 'INTEGER'),
|
|
44
|
+
), primary_key=('peer', 'epoch', 'side')),
|
|
45
|
+
Table('receipts', (
|
|
46
|
+
Column('peer', 'TEXT', not_null=True),
|
|
47
|
+
Column('epoch', 'INTEGER', not_null=True),
|
|
48
|
+
Column('seq', 'INTEGER', not_null=True),
|
|
49
|
+
Column('body', 'TEXT'),
|
|
50
|
+
), primary_key=('peer', 'epoch', 'seq')),
|
|
51
|
+
Table('chain', (
|
|
52
|
+
Column('peer', 'TEXT', not_null=True),
|
|
53
|
+
Column('epoch', 'INTEGER', not_null=True),
|
|
54
|
+
Column('state', 'TEXT'),
|
|
55
|
+
), primary_key=('peer', 'epoch')),
|
|
56
|
+
Table('evaluated', (
|
|
57
|
+
Column('peer', 'TEXT', not_null=True),
|
|
58
|
+
Column('epoch', 'INTEGER', not_null=True),
|
|
59
|
+
Column('severity', 'TEXT'),
|
|
60
|
+
), primary_key=('peer', 'epoch')),
|
|
61
|
+
Table('findings', (
|
|
62
|
+
Column('fid', 'TEXT', primary_key=True),
|
|
63
|
+
Column('peer', 'TEXT'),
|
|
64
|
+
Column('epoch', 'INTEGER'),
|
|
65
|
+
Column('body', 'TEXT'),
|
|
66
|
+
)),
|
|
67
|
+
Table('seen_wraps', (
|
|
68
|
+
Column('wid', 'TEXT', primary_key=True),
|
|
69
|
+
Column('at', 'INTEGER'),
|
|
70
|
+
)),
|
|
71
|
+
Table('awaiting_acks', (
|
|
72
|
+
Column('mid', 'TEXT', primary_key=True),
|
|
73
|
+
Column('peer', 'TEXT'),
|
|
74
|
+
Column('type', 'TEXT'),
|
|
75
|
+
Column('at', 'INTEGER'),
|
|
76
|
+
Column('relays', 'TEXT'),
|
|
77
|
+
)),
|
|
78
|
+
Table('reports_seen', (
|
|
79
|
+
Column('reporter', 'TEXT', not_null=True),
|
|
80
|
+
Column('report_id', 'TEXT', not_null=True),
|
|
81
|
+
Column('contradicted', 'INTEGER'),
|
|
82
|
+
), primary_key=('reporter', 'report_id')),
|
|
83
|
+
Table('strain', (
|
|
84
|
+
Column('peer', 'TEXT', not_null=True),
|
|
85
|
+
Column('shard', 'TEXT', not_null=True),
|
|
86
|
+
Column('transport', 'TEXT', not_null=True),
|
|
87
|
+
Column('kind', 'TEXT', not_null=True),
|
|
88
|
+
Column('n', 'INTEGER'),
|
|
89
|
+
Column('last_at', 'INTEGER'),
|
|
90
|
+
), primary_key=('peer', 'shard', 'transport', 'kind')),
|
|
91
|
+
Table('handovers', (
|
|
92
|
+
Column('shard', 'TEXT', primary_key=True),
|
|
93
|
+
Column('frm', 'TEXT'),
|
|
94
|
+
Column('body', 'TEXT'),
|
|
95
|
+
Column('at', 'INTEGER'),
|
|
96
|
+
)),
|
|
97
|
+
)
|
|
98
|
+
MIGRATIONS = (
|
|
99
|
+
Migration(1, up='ALTER TABLE evaluated ADD COLUMN tier TEXT', name='evaluated_tier'),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# ---- writes ---------------------------------------------------------------
|
|
103
|
+
def put_meter(
|
|
104
|
+
self,
|
|
105
|
+
peer: str,
|
|
106
|
+
epoch: int,
|
|
107
|
+
side: str,
|
|
108
|
+
counters: Mapping[str, int],
|
|
109
|
+
now: Optional[int] = None,
|
|
110
|
+
) -> None:
|
|
111
|
+
with self._lock:
|
|
112
|
+
self.execute('INSERT OR REPLACE INTO meters VALUES (?,?,?,?,?)',
|
|
113
|
+
(peer, int(epoch), side, json.dumps(counters, sort_keys=True),
|
|
114
|
+
int(now or time.time())))
|
|
115
|
+
self.db.commit()
|
|
116
|
+
|
|
117
|
+
def put_receipt(self, peer: str, epoch: int, body: ReceiptBody) -> None:
|
|
118
|
+
with self._lock:
|
|
119
|
+
self.execute('INSERT OR REPLACE INTO receipts VALUES (?,?,?,?)',
|
|
120
|
+
(peer, int(epoch), int(body.get('seq', 0)),
|
|
121
|
+
json.dumps(body, sort_keys=True)))
|
|
122
|
+
self.db.commit()
|
|
123
|
+
|
|
124
|
+
def put_chain(self, peer: str, epoch: int, state: OutChainDict | Mapping[str, Any]) -> None:
|
|
125
|
+
"""An OutChain, as its to_dict renders it; chains() hands the mapping back for from_dict."""
|
|
126
|
+
with self._lock:
|
|
127
|
+
self.execute('INSERT OR REPLACE INTO chain VALUES (?,?,?)',
|
|
128
|
+
(peer, int(epoch), json.dumps(state, sort_keys=True)))
|
|
129
|
+
self.db.commit()
|
|
130
|
+
|
|
131
|
+
def put_finding(self, fid: str, peer: str, epoch: int, body: FindingDict | Mapping[str, Any]) -> None:
|
|
132
|
+
"""A reporter Finding, as its to_dict renders it; findings() hands the mapping back for from_dict."""
|
|
133
|
+
with self._lock:
|
|
134
|
+
self.execute('INSERT OR REPLACE INTO findings VALUES (?,?,?,?)',
|
|
135
|
+
(fid, peer, int(epoch), json.dumps(body, sort_keys=True)))
|
|
136
|
+
self.db.commit()
|
|
137
|
+
|
|
138
|
+
def drop_finding(self, fid: str) -> None:
|
|
139
|
+
with self._lock:
|
|
140
|
+
self.execute('DELETE FROM findings WHERE fid = ?', (fid,))
|
|
141
|
+
self.db.commit()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def findings(self) -> list[FindingDict]:
|
|
146
|
+
"""Every pending finding, oldest opened first."""
|
|
147
|
+
rows = self._all('SELECT body FROM findings')
|
|
148
|
+
out: list[FindingDict] = [json.loads(r[0]) for r in rows]
|
|
149
|
+
out.sort(key=lambda b: (b.get('opened_at', 0), b.get('fid', '')))
|
|
150
|
+
return out
|
|
151
|
+
|
|
152
|
+
def seen_wrap(self, wid: str) -> bool:
|
|
153
|
+
with self._lock:
|
|
154
|
+
return self._one('SELECT 1 FROM seen_wraps WHERE wid = ?', (wid,)) is not None
|
|
155
|
+
|
|
156
|
+
def put_seen_wrap(self, wid: str, at: int) -> None:
|
|
157
|
+
with self._lock:
|
|
158
|
+
self.execute('INSERT OR IGNORE INTO seen_wraps VALUES (?,?)', (wid, int(at)))
|
|
159
|
+
# three days: a day past the replay horizon, so nothing replayable is forgotten
|
|
160
|
+
self.execute('DELETE FROM seen_wraps WHERE at < ?', (int(at) - 3 * 86400,))
|
|
161
|
+
self.db.commit()
|
|
162
|
+
|
|
163
|
+
def put_awaiting(self, mid: str, peer: str, mtype: str, at: int, relays: list[str]) -> None:
|
|
164
|
+
with self._lock:
|
|
165
|
+
self.execute('INSERT OR REPLACE INTO awaiting_acks VALUES (?,?,?,?,?)',
|
|
166
|
+
(mid, peer, mtype, int(at), json.dumps(list(relays))))
|
|
167
|
+
self.db.commit()
|
|
168
|
+
|
|
169
|
+
def drop_awaiting(self, mid: str) -> None:
|
|
170
|
+
with self._lock:
|
|
171
|
+
self.execute('DELETE FROM awaiting_acks WHERE mid = ?', (mid,))
|
|
172
|
+
self.db.commit()
|
|
173
|
+
|
|
174
|
+
def awaiting(self) -> list[tuple[str, str, str, int, list[str]]]:
|
|
175
|
+
with self._lock:
|
|
176
|
+
return [(m, p, t, a, json.loads(r)) for m, p, t, a, r in
|
|
177
|
+
self._all('SELECT mid, peer, type, at, relays FROM awaiting_acks ORDER BY at')]
|
|
178
|
+
|
|
179
|
+
def put_report_seen(self, reporter: str, report_id: str, contradicted: bool) -> None:
|
|
180
|
+
with self._lock:
|
|
181
|
+
self.execute('INSERT OR REPLACE INTO reports_seen VALUES (?,?,?)',
|
|
182
|
+
(reporter, report_id, 1 if contradicted else 0))
|
|
183
|
+
self.db.commit()
|
|
184
|
+
|
|
185
|
+
def reports_seen(self) -> list[tuple[str, str, bool]]:
|
|
186
|
+
with self._lock:
|
|
187
|
+
return [(r[0], r[1], bool(r[2])) for r in
|
|
188
|
+
self.execute('SELECT reporter, report_id, contradicted FROM reports_seen')]
|
|
189
|
+
|
|
190
|
+
def put_evaluated(
|
|
191
|
+
self,
|
|
192
|
+
peer: str,
|
|
193
|
+
epoch: int,
|
|
194
|
+
severity: Optional[str],
|
|
195
|
+
tier: Optional[str] = None,
|
|
196
|
+
) -> None:
|
|
197
|
+
with self._lock:
|
|
198
|
+
self.execute('INSERT OR REPLACE INTO evaluated (peer, epoch, severity, tier) VALUES (?,?,?,?)',
|
|
199
|
+
(peer, int(epoch), severity or '', tier))
|
|
200
|
+
self.db.commit()
|
|
201
|
+
|
|
202
|
+
def put_strain(
|
|
203
|
+
self,
|
|
204
|
+
peer: Optional[str],
|
|
205
|
+
shard: Optional[str],
|
|
206
|
+
transport: Optional[str],
|
|
207
|
+
kind: str,
|
|
208
|
+
at: int,
|
|
209
|
+
n: int = 1,
|
|
210
|
+
) -> None:
|
|
211
|
+
with self._lock:
|
|
212
|
+
self.execute(
|
|
213
|
+
'INSERT INTO strain (peer, shard, transport, kind, n, last_at) VALUES (?,?,?,?,?,?) '
|
|
214
|
+
'ON CONFLICT(peer, shard, transport, kind) DO UPDATE SET '
|
|
215
|
+
'n = n + excluded.n, last_at = excluded.last_at',
|
|
216
|
+
(peer or '', shard or '', transport or '', kind, int(n), int(at)))
|
|
217
|
+
self.db.commit()
|
|
218
|
+
|
|
219
|
+
def put_handover(self, shard: str, chain: Sequence[HandoverLink]) -> None:
|
|
220
|
+
"""The whole chain, ordered from the first-hand root. `frm` is the last
|
|
221
|
+
link's signer: the node that handed it to this one."""
|
|
222
|
+
art = chain[-1]
|
|
223
|
+
with self._lock:
|
|
224
|
+
self.execute('INSERT OR REPLACE INTO handovers VALUES (?,?,?,?)',
|
|
225
|
+
(shard, art['from'], json.dumps(chain, sort_keys=True), int(art['at'])))
|
|
226
|
+
self.db.commit()
|
|
227
|
+
|
|
228
|
+
def drop_handover(self, shard: str) -> None:
|
|
229
|
+
with self._lock:
|
|
230
|
+
self.execute('DELETE FROM handovers WHERE shard = ?', (shard,))
|
|
231
|
+
self.db.commit()
|
|
232
|
+
|
|
233
|
+
def handover(self, shard: str) -> Optional[list[HandoverLink]]:
|
|
234
|
+
"""The chain this node holds for a shard, root first, or None."""
|
|
235
|
+
row = self._one('SELECT body FROM handovers WHERE shard = ?', (shard,))
|
|
236
|
+
if not row:
|
|
237
|
+
return None
|
|
238
|
+
chain = json.loads(row[0])
|
|
239
|
+
return chain if isinstance(chain, list) else [chain]
|
|
240
|
+
|
|
241
|
+
def handovers(self) -> dict[str, list[HandoverLink]]:
|
|
242
|
+
return {r[0]: (json.loads(r[1]) if isinstance(json.loads(r[1]), list) else [json.loads(r[1])])
|
|
243
|
+
for r in self._all('SELECT shard, body FROM handovers')}
|
|
244
|
+
|
|
245
|
+
def strain(self) -> list[tuple[str, str, str, str, int, int]]:
|
|
246
|
+
"""[(peer, shard, transport, kind, n, last_at)] - everything survived."""
|
|
247
|
+
with self._lock:
|
|
248
|
+
return [(str(r[0]), str(r[1]), str(r[2]), str(r[3]), int(r[4]), int(r[5]))
|
|
249
|
+
for r in self.execute('SELECT peer, shard, transport, kind, n, last_at FROM strain')]
|
|
250
|
+
|
|
251
|
+
# ---- reads ----------------------------------------------------------------
|
|
252
|
+
def meters(self) -> dict[str, dict[str, dict[int, dict[str, int]]]]:
|
|
253
|
+
# Every write here took the lock and every read did not, on a
|
|
254
|
+
# connection opened with check_same_thread=False and shared by the
|
|
255
|
+
# tick, sync, peering and transport threads. sqlite answers that with
|
|
256
|
+
# an intermittent "bad parameter or other API misuse", which is what a
|
|
257
|
+
# sweep caught in grants.get after the capacity ladder added a reader
|
|
258
|
+
# on the peering path. A read is as much a use of the connection as a
|
|
259
|
+
# write.
|
|
260
|
+
with self._lock:
|
|
261
|
+
out: dict[str, dict[str, dict[int, dict[str, int]]]] = {BY: {}, OF_ME: {}}
|
|
262
|
+
for peer, epoch, side, counters in self.execute(
|
|
263
|
+
'SELECT peer, epoch, side, counters FROM meters'):
|
|
264
|
+
out.setdefault(side, {}).setdefault(peer, {})[epoch] = json.loads(counters)
|
|
265
|
+
return out
|
|
266
|
+
|
|
267
|
+
def receipts(self) -> dict[str, dict[int, list[ReceiptBody]]]:
|
|
268
|
+
with self._lock:
|
|
269
|
+
out: dict[str, dict[int, list[ReceiptBody]]] = {}
|
|
270
|
+
for peer, epoch, body in self.execute(
|
|
271
|
+
'SELECT peer, epoch, body FROM receipts ORDER BY peer, epoch, seq'):
|
|
272
|
+
out.setdefault(peer, {}).setdefault(epoch, []).append(json.loads(body))
|
|
273
|
+
return out
|
|
274
|
+
|
|
275
|
+
def chains(self) -> dict[str, dict[int, OutChainDict]]:
|
|
276
|
+
with self._lock:
|
|
277
|
+
out: dict[str, dict[int, OutChainDict]] = {}
|
|
278
|
+
for peer, epoch, state in self.execute('SELECT peer, epoch, state FROM chain'):
|
|
279
|
+
out.setdefault(peer, {})[epoch] = json.loads(state)
|
|
280
|
+
return out
|
|
281
|
+
|
|
282
|
+
def evaluated(self) -> set[tuple[str, int]]:
|
|
283
|
+
with self._lock:
|
|
284
|
+
return {(peer, epoch) for peer, epoch in
|
|
285
|
+
self.execute('SELECT peer, epoch FROM evaluated')}
|
|
286
|
+
|
|
287
|
+
def evaluations(self) -> list[tuple[str, int, Optional[str], Optional[str]]]:
|
|
288
|
+
"""(peer, epoch, severity) in window order, severity None for a window
|
|
289
|
+
that was not evaluated. The standing ledger is rebuilt from this on a
|
|
290
|
+
restart: it lived in memory, so every streak began again at zero."""
|
|
291
|
+
with self._lock:
|
|
292
|
+
return [(p, e, s or None, t) for p, e, s, t in
|
|
293
|
+
self.execute('SELECT peer, epoch, severity, tier FROM evaluated ORDER BY epoch, peer')]
|
|
294
|
+
|
|
295
|
+
def forget_before(self, epoch: int) -> None:
|
|
296
|
+
"""Windows old enough that nothing can still be said about them.
|
|
297
|
+
|
|
298
|
+
A closed window that has been evaluated is history; keeping every one
|
|
299
|
+
forever would grow without bound on a node that never restarts.
|
|
300
|
+
"""
|
|
301
|
+
with self._lock:
|
|
302
|
+
for table in ('meters', 'receipts', 'chain', 'evaluated', 'findings'):
|
|
303
|
+
self.execute('DELETE FROM %s WHERE epoch < ?' % table, (int(epoch),))
|
|
304
|
+
self.db.commit()
|
|
305
|
+
|
node/storage/adapter.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""The database adapter module: provides typed schema binding, query execution,
|
|
2
|
+
transactions, and leases across database backends (SQLite, PostgreSQL, etc.).
|
|
3
|
+
|
|
4
|
+
Instantiated with the compartment into a database adapter module.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Any, Callable, Optional, Sequence
|
|
9
|
+
|
|
10
|
+
from .base import LEASE_SECONDS, BaseStore
|
|
11
|
+
from .dialect import Dialect, SqliteDialect
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DatabaseAdapter(BaseStore):
|
|
15
|
+
"""The database adapter module: binds a compartment's declared SCHEMA to a
|
|
16
|
+
database backend and provides query execution, transactions, and leases."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
target: str = ':memory:',
|
|
21
|
+
schema: Sequence[Any] = (),
|
|
22
|
+
migrations: Sequence[Any] = (),
|
|
23
|
+
timeout: float = 5.0,
|
|
24
|
+
dialect: Optional[Dialect] = None,
|
|
25
|
+
owner: Optional[str] = None,
|
|
26
|
+
lease_seconds: int = LEASE_SECONDS,
|
|
27
|
+
clock: Optional[Callable[[], int]] = None,
|
|
28
|
+
readonly: bool = False,
|
|
29
|
+
) -> None:
|
|
30
|
+
self.raw_schema: tuple[Any, ...] = tuple(schema)
|
|
31
|
+
# If schema is provided, override SCHEMA attribute before base _ensure_schema runs
|
|
32
|
+
if self.raw_schema:
|
|
33
|
+
self.SCHEMA = self.raw_schema
|
|
34
|
+
self.raw_migrations: tuple[Any, ...] = tuple(migrations)
|
|
35
|
+
if self.raw_migrations:
|
|
36
|
+
self.MIGRATIONS = tuple(
|
|
37
|
+
m.to_tuple() if hasattr(m, 'to_tuple') else m
|
|
38
|
+
for m in self.raw_migrations
|
|
39
|
+
)
|
|
40
|
+
super().__init__(
|
|
41
|
+
path=target,
|
|
42
|
+
timeout=timeout,
|
|
43
|
+
dialect=dialect,
|
|
44
|
+
owner=owner,
|
|
45
|
+
lease_seconds=lease_seconds,
|
|
46
|
+
clock=clock,
|
|
47
|
+
readonly=readonly,
|
|
48
|
+
schema=self.SCHEMA,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def one(self, q: str, args: Sequence[Any] = ()) -> Optional[tuple[Any, ...]]:
|
|
52
|
+
"""Execute query and fetch one row, closing the cursor before returning."""
|
|
53
|
+
return self._one(q, args)
|
|
54
|
+
|
|
55
|
+
def all(self, q: str, args: Sequence[Any] = ()) -> list[tuple[Any, ...]]:
|
|
56
|
+
"""Execute query and fetch all rows, closing the cursor before returning."""
|
|
57
|
+
return self._all(q, args)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class SqliteAdapter(DatabaseAdapter):
|
|
61
|
+
"""SQLite database adapter module."""
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
path: str = ':memory:',
|
|
66
|
+
schema: Sequence[Any] = (),
|
|
67
|
+
migrations: Sequence[Any] = (),
|
|
68
|
+
timeout: float = 5.0,
|
|
69
|
+
dialect: Optional[Dialect] = None,
|
|
70
|
+
owner: Optional[str] = None,
|
|
71
|
+
lease_seconds: int = LEASE_SECONDS,
|
|
72
|
+
clock: Optional[Callable[[], int]] = None,
|
|
73
|
+
readonly: bool = False,
|
|
74
|
+
) -> None:
|
|
75
|
+
super().__init__(
|
|
76
|
+
target=path,
|
|
77
|
+
schema=schema,
|
|
78
|
+
migrations=migrations,
|
|
79
|
+
timeout=timeout,
|
|
80
|
+
dialect=dialect or SqliteDialect(),
|
|
81
|
+
owner=owner,
|
|
82
|
+
lease_seconds=lease_seconds,
|
|
83
|
+
clock=clock,
|
|
84
|
+
readonly=readonly,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class PsqlAdapter(DatabaseAdapter):
|
|
89
|
+
"""PostgreSQL database adapter module."""
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
url: str,
|
|
94
|
+
schema: Sequence[Any] = (),
|
|
95
|
+
migrations: Sequence[Any] = (),
|
|
96
|
+
timeout: float = 5.0,
|
|
97
|
+
dialect: Optional[Dialect] = None,
|
|
98
|
+
owner: Optional[str] = None,
|
|
99
|
+
lease_seconds: int = LEASE_SECONDS,
|
|
100
|
+
clock: Optional[Callable[[], int]] = None,
|
|
101
|
+
readonly: bool = False,
|
|
102
|
+
) -> None:
|
|
103
|
+
if dialect is None:
|
|
104
|
+
from .postgres import PostgresDialect
|
|
105
|
+
dialect = PostgresDialect()
|
|
106
|
+
super().__init__(
|
|
107
|
+
target=url,
|
|
108
|
+
schema=schema,
|
|
109
|
+
migrations=migrations,
|
|
110
|
+
timeout=timeout,
|
|
111
|
+
dialect=dialect,
|
|
112
|
+
owner=owner,
|
|
113
|
+
lease_seconds=lease_seconds,
|
|
114
|
+
clock=clock,
|
|
115
|
+
readonly=readonly,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
PostgresAdapter = PsqlAdapter
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def create_adapter(
|
|
123
|
+
backend: Optional[str] = None,
|
|
124
|
+
target: Optional[str] = None,
|
|
125
|
+
schema: Sequence[Any] = (),
|
|
126
|
+
migrations: Sequence[Any] = (),
|
|
127
|
+
**kw: Any,
|
|
128
|
+
) -> DatabaseAdapter:
|
|
129
|
+
"""Create a DatabaseAdapter for the specified backend or target."""
|
|
130
|
+
tgt = target or ':memory:'
|
|
131
|
+
b = (backend or '').lower()
|
|
132
|
+
if b in ('psql', 'postgres', 'postgresql') or tgt.startswith(('postgresql://', 'postgres://')):
|
|
133
|
+
return PsqlAdapter(url=tgt, schema=schema, migrations=migrations, **kw)
|
|
134
|
+
return SqliteAdapter(path=tgt, schema=schema, migrations=migrations, **kw)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def adapter_from_locator(
|
|
138
|
+
locator: Any,
|
|
139
|
+
name: str,
|
|
140
|
+
schema: Sequence[Any] = (),
|
|
141
|
+
migrations: Sequence[Any] = (),
|
|
142
|
+
**kw: Any,
|
|
143
|
+
) -> DatabaseAdapter:
|
|
144
|
+
"""Instantiate a DatabaseAdapter using the node's storage Locator."""
|
|
145
|
+
target = locator.target(name)
|
|
146
|
+
dialect = getattr(locator, 'dialect', None)
|
|
147
|
+
owner = kw.pop('owner', getattr(locator, 'owner', None))
|
|
148
|
+
if dialect is not None and dialect.name == 'postgres':
|
|
149
|
+
return PsqlAdapter(url=target, schema=schema, migrations=migrations, dialect=dialect, owner=owner, **kw)
|
|
150
|
+
return SqliteAdapter(path=target, schema=schema, migrations=migrations, dialect=dialect, owner=owner, **kw)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
__all__ = [
|
|
155
|
+
'DatabaseAdapter',
|
|
156
|
+
'SqliteAdapter',
|
|
157
|
+
'PsqlAdapter',
|
|
158
|
+
'PostgresAdapter',
|
|
159
|
+
'create_adapter',
|
|
160
|
+
'adapter_from_locator',
|
|
161
|
+
]
|