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,99 @@
|
|
|
1
|
+
"""Types, records, and protocols for the PaidFetch compartment."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Any, Mapping, Optional, Protocol, TypedDict, Union
|
|
6
|
+
|
|
7
|
+
from fleetmesh_core.accounting.paidfetch import Item
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PriceSetResult(TypedDict):
|
|
11
|
+
sha256: str
|
|
12
|
+
account: str
|
|
13
|
+
msat: int
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PriceGetResult(TypedDict):
|
|
17
|
+
sha256: str
|
|
18
|
+
per_item_msat: Optional[int]
|
|
19
|
+
default_msat: int
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PaidFetchOffer(TypedDict, total=False):
|
|
23
|
+
rail: str
|
|
24
|
+
request: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BalanceReport(TypedDict, total=False):
|
|
28
|
+
unit: str
|
|
29
|
+
by_mint: dict[str, int]
|
|
30
|
+
total: int
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SweepReport(TypedDict, total=False):
|
|
34
|
+
swept: int
|
|
35
|
+
why: str
|
|
36
|
+
balance: int
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RailDescriptor(TypedDict, total=False):
|
|
40
|
+
rail: str
|
|
41
|
+
mints: list[str]
|
|
42
|
+
unit: str
|
|
43
|
+
sweep_above_sat: int
|
|
44
|
+
trust: str
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class PriceEntry(TypedDict, total=False):
|
|
48
|
+
sha256: str
|
|
49
|
+
account: str
|
|
50
|
+
msat: int
|
|
51
|
+
set_by: Optional[str]
|
|
52
|
+
at: int
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class StoredProof(TypedDict, total=False):
|
|
56
|
+
y: str
|
|
57
|
+
mint: str
|
|
58
|
+
unit: str
|
|
59
|
+
id: str
|
|
60
|
+
amount: int
|
|
61
|
+
secret: str
|
|
62
|
+
C: str
|
|
63
|
+
witness: Optional[str]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class Paid:
|
|
68
|
+
rail: str
|
|
69
|
+
amount_msat: int
|
|
70
|
+
detail: Mapping[str, Any] = field(default_factory=dict)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class Unpaid:
|
|
75
|
+
reason: str # short and stable: a refusal category
|
|
76
|
+
detail: str = ''
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class MintBackend(Protocol):
|
|
80
|
+
"""Where a Cashu payment is trusted and settled. One node, one backend."""
|
|
81
|
+
|
|
82
|
+
def offer(self, item: Item, quote_id: str, expires: int) -> Optional[PaidFetchOffer]:
|
|
83
|
+
"""The rail's entry in a 402 `offers` list for this item, or None if
|
|
84
|
+
this backend cannot take payment for it (no mint configured)."""
|
|
85
|
+
...
|
|
86
|
+
|
|
87
|
+
def verify(self, item: Item, token: str) -> Union[Paid, Unpaid]:
|
|
88
|
+
"""Final: after Paid the node holds value it can spend. Never optimistic."""
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
def balance(self) -> BalanceReport: ...
|
|
92
|
+
|
|
93
|
+
def sweep(self, now: int, force: bool = False) -> Optional[SweepReport]:
|
|
94
|
+
"""Move earnings out (to Lightning) when above threshold. None: nothing done."""
|
|
95
|
+
...
|
|
96
|
+
|
|
97
|
+
def describe(self) -> RailDescriptor:
|
|
98
|
+
"""What the descriptor and `rails` command say about this backend."""
|
|
99
|
+
...
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Settlement Engine and Compartment for Fleetmesh.
|
|
2
|
+
|
|
3
|
+
Spec reference: spec.html section "Modular Settlement Rails & Zero-Custody Payments (PaymentRail)"
|
|
4
|
+
and "Bridge 4: NIP-60 / NIP-61 Cashu Nutzap Asymmetric Settlement" (lines 7266-7270).
|
|
5
|
+
|
|
6
|
+
Provides:
|
|
7
|
+
- `SettlementEngine`: persistent escrow management across Lightning Hold Invoices and Cashu Nutzap rails.
|
|
8
|
+
- Asymmetric peering rate-bucket expansion using ecash tokens.
|
|
9
|
+
- `Settlement`: Compartment compartment managing payments and accounting.
|
|
10
|
+
|
|
11
|
+
Laid out as a package: store.py (escrows + spent proofs), engine.py (the
|
|
12
|
+
lifecycle and the nutzap expansion), compartment.py (the compartment). Every
|
|
13
|
+
name the module used to export is re-exported here.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from .engine import PAID_CEILING_MULTIPLIER, SettlementEngine, paid_ceilings
|
|
18
|
+
from .store import PersistentSpentProofs, SettlementStore
|
|
19
|
+
from .compartment import Settlement
|
|
20
|
+
from .types import HoldInvoiceDict, PaymentRailStats, SettleShowDict
|
|
21
|
+
from . import gateways, payment
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
'PAID_CEILING_MULTIPLIER', 'SettlementEngine', 'paid_ceilings', 'PersistentSpentProofs',
|
|
25
|
+
'SettlementStore', 'Settlement', 'gateways', 'payment',
|
|
26
|
+
'HoldInvoiceDict', 'PaymentRailStats', 'SettleShowDict',
|
|
27
|
+
]
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"""The Settlement compartment: the operator verbs over the engine and the
|
|
2
|
+
rails. Built from the operator's `settlement` config block."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any, Mapping, Optional, Union
|
|
7
|
+
|
|
8
|
+
from node import channels as ch_mod
|
|
9
|
+
from node import metrics as mx_mod
|
|
10
|
+
from fleetmesh_core.accounting.cashu import create_nutzap_event
|
|
11
|
+
from . import payment
|
|
12
|
+
from node import compartment as compartment_mod
|
|
13
|
+
from node.compartment import stores as st
|
|
14
|
+
from node.compartment import Command, Need
|
|
15
|
+
from node.config.parameter import Parameter
|
|
16
|
+
from node.config import SettlementConfig
|
|
17
|
+
from .engine import SettlementEngine
|
|
18
|
+
from .store import SettlementStore
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
from .types import SettleShowDict
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Settlement(compartment_mod.Compartment):
|
|
25
|
+
"""The settlement compartment of meshnode (NIP-61 Nutzap & Lightning hold-invoices)."""
|
|
26
|
+
|
|
27
|
+
name = 'settle'
|
|
28
|
+
attr: Optional[str] = 'settlement'
|
|
29
|
+
DB: str = 'settlements.sqlite'
|
|
30
|
+
reads = ('settlement', 'compute') # grants is a node attribute it reaches, not a config key
|
|
31
|
+
config_cls = SettlementConfig
|
|
32
|
+
SCHEMA = SettlementStore.SCHEMA
|
|
33
|
+
STORES = (
|
|
34
|
+
st.Store(key='escrows', description='settlement escrows held in sqlite database',
|
|
35
|
+
kind=st.RING, shape=dict, bound=256),
|
|
36
|
+
)
|
|
37
|
+
NEEDS = (
|
|
38
|
+
Need('db_path', path='DB'),
|
|
39
|
+
Need('node_pk', 'pubkey'),
|
|
40
|
+
Need('settings', block=SettlementConfig),
|
|
41
|
+
Need('ks', 'ks'),
|
|
42
|
+
Need('grants', 'stores.grants'),
|
|
43
|
+
)
|
|
44
|
+
CHANNELS = (
|
|
45
|
+
ch_mod.Channel('escrow_created', 'A settlement escrow was created for a compute ask',
|
|
46
|
+
level=ch_mod.INFO, say='created escrow {ask} for {amount_msat} msat (rail: {rail})',
|
|
47
|
+
tags=('settle', 'escrow')),
|
|
48
|
+
ch_mod.Channel('escrow_held', 'A settlement escrow was locked in held state',
|
|
49
|
+
level=ch_mod.INFO, say='escrow {ask} held',
|
|
50
|
+
tags=('settle', 'escrow')),
|
|
51
|
+
ch_mod.Channel('escrow_settled', 'A settlement escrow was settled upon preimage disclosure',
|
|
52
|
+
level=ch_mod.INFO, say='escrow {ask} settled',
|
|
53
|
+
tags=('settle', 'escrow')),
|
|
54
|
+
ch_mod.Channel('escrow_cancelled', 'A settlement escrow was cancelled and released',
|
|
55
|
+
level=ch_mod.NOTE, say='escrow {ask} cancelled (reason: {reason})',
|
|
56
|
+
tags=('settle', 'escrow')),
|
|
57
|
+
ch_mod.Channel('grant_expanded', 'Peer rate-bucket grant limits expanded with Nutzap',
|
|
58
|
+
level=ch_mod.INFO, say='grant expanded for peer {peer} via nutzap',
|
|
59
|
+
tags=('settle', 'nutzap')),
|
|
60
|
+
)
|
|
61
|
+
METRICS = (
|
|
62
|
+
mx_mod.Metric(name='settlement_escrows_created', description='Escrows created for compute tasks.',
|
|
63
|
+
kind=mx_mod.COUNTER, counts='escrow_created', tags=('settle', 'escrow')),
|
|
64
|
+
mx_mod.Metric(name='settlement_escrows_held', description='Escrows locked into held state.',
|
|
65
|
+
kind=mx_mod.COUNTER, counts='escrow_held', tags=('settle', 'escrow')),
|
|
66
|
+
mx_mod.Metric(name='settlement_escrows_settled', description='Escrows settled with preimage.',
|
|
67
|
+
kind=mx_mod.COUNTER, counts='escrow_settled', tags=('settle', 'escrow')),
|
|
68
|
+
mx_mod.Metric(name='settlement_escrows_cancelled', description='Escrows cancelled.',
|
|
69
|
+
kind=mx_mod.COUNTER, counts='escrow_cancelled', tags=('settle', 'escrow')),
|
|
70
|
+
mx_mod.Metric(name='settlement_grants_expanded', description='Grants expanded via ecash Nutzap.',
|
|
71
|
+
kind=mx_mod.COUNTER, counts='grant_expanded', tags=('settle', 'nutzap')),
|
|
72
|
+
)
|
|
73
|
+
COMMANDS = (
|
|
74
|
+
Command(
|
|
75
|
+
key='show',
|
|
76
|
+
name='Settlement Status',
|
|
77
|
+
description='display active settlement rails and escrows',
|
|
78
|
+
tags=('settle', 'status'),
|
|
79
|
+
permission='settle/show',
|
|
80
|
+
run='show_cmd',
|
|
81
|
+
reply='dict',
|
|
82
|
+
),
|
|
83
|
+
Command(
|
|
84
|
+
key='create',
|
|
85
|
+
name='Create Escrow',
|
|
86
|
+
description='create conditional hold escrow for a compute ask',
|
|
87
|
+
tags=('settle', 'escrow'),
|
|
88
|
+
parameters=(
|
|
89
|
+
Parameter(key='ask', name='ask', description='ask identifier', type=str, default='', required=True),
|
|
90
|
+
Parameter(key='buyer', name='buyer', description='buyer public key', type=str, default='', required=True),
|
|
91
|
+
Parameter(key='seller', name='seller', description='seller public key', type=str, default='', required=True),
|
|
92
|
+
Parameter(key='amount_msat', name='amount_msat', description='escrow amount in millisatoshis', type=int, default=0, required=True),
|
|
93
|
+
Parameter(key='hash', name='hash', description='payment hash', type=str, default='', required=True),
|
|
94
|
+
Parameter(key='rail', name='rail', description='settlement rail name', type=str, default='lightning', required=False),
|
|
95
|
+
),
|
|
96
|
+
permission='settle/create',
|
|
97
|
+
run='create_cmd',
|
|
98
|
+
reply='dict',
|
|
99
|
+
),
|
|
100
|
+
Command(
|
|
101
|
+
key='hold',
|
|
102
|
+
name='Hold Escrow',
|
|
103
|
+
description='transition escrow to HELD state upon locked payment',
|
|
104
|
+
tags=('settle', 'hold'),
|
|
105
|
+
parameters=(
|
|
106
|
+
Parameter(key='ask', name='ask', description='ask identifier', type=str, default='', required=True),
|
|
107
|
+
Parameter(key='proof', name='proof', description='proof or token payload', type=object, default=None, required=False),
|
|
108
|
+
),
|
|
109
|
+
permission='settle/hold',
|
|
110
|
+
run='hold_cmd',
|
|
111
|
+
reply='bool',
|
|
112
|
+
),
|
|
113
|
+
Command(
|
|
114
|
+
key='settle',
|
|
115
|
+
name='Settle Escrow',
|
|
116
|
+
description='settle escrow upon disclosed preimage',
|
|
117
|
+
tags=('settle', 'settle'),
|
|
118
|
+
parameters=(
|
|
119
|
+
Parameter(key='ask', name='ask', description='ask identifier', type=str, default='', required=True),
|
|
120
|
+
Parameter(key='preimage', name='preimage', description='payment preimage hex', type=str, default='', required=True),
|
|
121
|
+
),
|
|
122
|
+
permission='settle/settle',
|
|
123
|
+
run='settle_cmd',
|
|
124
|
+
reply='bool',
|
|
125
|
+
),
|
|
126
|
+
Command(
|
|
127
|
+
key='reconcile',
|
|
128
|
+
name='Reconcile Escrow',
|
|
129
|
+
description='reconcile variable token escrow with delta refund',
|
|
130
|
+
tags=('settle', 'reconcile'),
|
|
131
|
+
parameters=(
|
|
132
|
+
Parameter(key='ask', name='ask', description='ask identifier', type=str, default='', required=True),
|
|
133
|
+
Parameter(key='cost_msat', name='cost_msat', description='actual cost in millisatoshis', type=int, default=0, required=True),
|
|
134
|
+
Parameter(key='preimage', name='preimage', description='payment preimage hex', type=str, default='', required=True),
|
|
135
|
+
),
|
|
136
|
+
permission='settle/reconcile',
|
|
137
|
+
run='reconcile_cmd',
|
|
138
|
+
reply='dict',
|
|
139
|
+
),
|
|
140
|
+
Command(
|
|
141
|
+
key='stream_pay',
|
|
142
|
+
name='Stream Payment',
|
|
143
|
+
description='record pay-as-you-stream Nutzap token milestone',
|
|
144
|
+
tags=('settle', 'stream'),
|
|
145
|
+
parameters=(
|
|
146
|
+
Parameter(key='ask', name='ask', description='ask identifier', type=str, default='', required=True),
|
|
147
|
+
Parameter(key='tokens', name='tokens', description='delta tokens', type=int, default=0, required=True),
|
|
148
|
+
Parameter(key='rate', name='rate', description='millisatoshis per token', type=int, default=0, required=True),
|
|
149
|
+
Parameter(key='proof', name='proof', description='cashu token proof', type=object, default=None, required=False),
|
|
150
|
+
),
|
|
151
|
+
permission='settle/stream_pay',
|
|
152
|
+
run='stream_pay_cmd',
|
|
153
|
+
reply='dict',
|
|
154
|
+
),
|
|
155
|
+
Command(
|
|
156
|
+
key='cancel',
|
|
157
|
+
name='Cancel Escrow',
|
|
158
|
+
description='cancel escrow and refund locked funds',
|
|
159
|
+
tags=('settle', 'cancel'),
|
|
160
|
+
parameters=(
|
|
161
|
+
Parameter(key='ask', name='ask', description='ask identifier', type=str, default='', required=True),
|
|
162
|
+
Parameter(key='reason', name='reason', description='cancellation reason', type=str, default='', required=False),
|
|
163
|
+
),
|
|
164
|
+
permission='settle/cancel',
|
|
165
|
+
run='cancel_cmd',
|
|
166
|
+
reply='bool',
|
|
167
|
+
),
|
|
168
|
+
Command(
|
|
169
|
+
key='nutzap',
|
|
170
|
+
name='Create Nutzap',
|
|
171
|
+
description='create signed NIP-61 Kind 9321 Nutzap event',
|
|
172
|
+
tags=('settle', 'nutzap'),
|
|
173
|
+
parameters=(
|
|
174
|
+
Parameter(key='recipient', name='recipient', description='recipient public key', type=str, default='', required=True),
|
|
175
|
+
Parameter(key='amount_msat', name='amount_msat', description='payment amount in millisatoshis', type=int, default=0, required=True),
|
|
176
|
+
Parameter(key='proofs', name='proofs', description='Cashu proofs list or json', type=object, default=list, required=True),
|
|
177
|
+
Parameter(key='mint', name='mint', description='mint url', type=str, default=None, required=False),
|
|
178
|
+
Parameter(key='target_event', name='target_event', description='target event id', type=str, default=None, required=False),
|
|
179
|
+
),
|
|
180
|
+
permission='settle/nutzap',
|
|
181
|
+
run='nutzap_cmd',
|
|
182
|
+
reply='dict',
|
|
183
|
+
),
|
|
184
|
+
Command(
|
|
185
|
+
key='expand_grant',
|
|
186
|
+
name='Expand Grant',
|
|
187
|
+
description='expand peer rate-bucket limits with verified Nutzap proofs',
|
|
188
|
+
tags=('settle', 'grants'),
|
|
189
|
+
parameters=(
|
|
190
|
+
Parameter(key='peer', name='peer', description='peer public key', type=str, default='', required=True),
|
|
191
|
+
Parameter(key='nutzap', name='nutzap', description='nutzap event dict or json', type=object, default=dict, required=True),
|
|
192
|
+
),
|
|
193
|
+
permission='settle/expand_grant',
|
|
194
|
+
run='expand_grant_cmd',
|
|
195
|
+
reply='dict',
|
|
196
|
+
),
|
|
197
|
+
Command(
|
|
198
|
+
key='nwc_pay',
|
|
199
|
+
name='Pay NWC Invoice',
|
|
200
|
+
description='pay a Lightning invoice via NWC to unlock escrow',
|
|
201
|
+
tags=('settle', 'nwc'),
|
|
202
|
+
parameters=(
|
|
203
|
+
Parameter(key='hash', name='hash', description='payment hash hex', type=str, default='', required=True),
|
|
204
|
+
Parameter(key='bolt11', name='bolt11', description='bolt11 payment invoice', type=str, default='', required=True),
|
|
205
|
+
),
|
|
206
|
+
permission='settle/nwc_pay',
|
|
207
|
+
run='nwc_pay_cmd',
|
|
208
|
+
reply='dict',
|
|
209
|
+
),
|
|
210
|
+
Command(
|
|
211
|
+
key='cashu_swap',
|
|
212
|
+
name='Swap Cashu Proofs',
|
|
213
|
+
description='swap held Cashu proofs for fresh blinded tokens at the live mint',
|
|
214
|
+
tags=('settle', 'cashu'),
|
|
215
|
+
parameters=(
|
|
216
|
+
Parameter(key='hash', name='hash', description='payment hash hex', type=str, default='', required=True),
|
|
217
|
+
Parameter(key='outputs', name='outputs', description='blinded outputs list or json', type=object, default=list, required=True),
|
|
218
|
+
),
|
|
219
|
+
permission='settle/cashu_swap',
|
|
220
|
+
run='cashu_swap_cmd',
|
|
221
|
+
reply='dict',
|
|
222
|
+
),
|
|
223
|
+
Command(
|
|
224
|
+
key='cashu_melt',
|
|
225
|
+
name='Melt Cashu Proofs',
|
|
226
|
+
description='melt held Cashu proofs to pay an external Lightning invoice',
|
|
227
|
+
tags=('settle', 'cashu'),
|
|
228
|
+
parameters=(
|
|
229
|
+
Parameter(key='hash', name='hash', description='payment hash hex', type=str, default='', required=True),
|
|
230
|
+
Parameter(key='bolt11', name='bolt11', description='bolt11 payment invoice', type=str, default='', required=True),
|
|
231
|
+
),
|
|
232
|
+
permission='settle/cashu_melt',
|
|
233
|
+
run='cashu_melt_cmd',
|
|
234
|
+
reply='dict',
|
|
235
|
+
),
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def __init__(self, node: Any = None, db_path: Optional[str] = None, node_pk: Optional[str] = None,
|
|
239
|
+
settings: Optional[Union[SettlementConfig, Mapping[str, Any]]] = None, clock: Optional[Any] = None,
|
|
240
|
+
ks: Optional[Any] = None, grants: Optional[Any] = None, db: Optional[Any] = None) -> None:
|
|
241
|
+
"""Built by `from_context` in a node. `Settlement()` and
|
|
242
|
+
`Settlement(node=fake)` still work: what is not passed is read off
|
|
243
|
+
`node` (dir, ks, cfg['settlement']) when it has it, else defaulted.
|
|
244
|
+
|
|
245
|
+
`settings` is the operator's `settlement` config block: `nwc_uri`,
|
|
246
|
+
`live_cashu`, `mint_url`."""
|
|
247
|
+
if db is None and db_path is not None:
|
|
248
|
+
from node.storage.adapter import SqliteAdapter
|
|
249
|
+
db = SqliteAdapter(db_path, schema=self.SCHEMA, clock=clock)
|
|
250
|
+
super().__init__(node, clock, db=db)
|
|
251
|
+
# The keystore that signs a Nutzap and the grant store a Nutzap
|
|
252
|
+
# expands: named from ctx in a node; read off `node` for the suites
|
|
253
|
+
# that hand a fake node and nothing else.
|
|
254
|
+
self.ks: Optional[Any] = ks if ks is not None else getattr(node, 'ks', None)
|
|
255
|
+
self.grants: Optional[Any] = grants if grants is not None else getattr(node, 'grants', None)
|
|
256
|
+
if node_pk is None and self.ks is not None:
|
|
257
|
+
node_pk = self.ks.node_pk
|
|
258
|
+
if settings is None:
|
|
259
|
+
cfg = (getattr(node, 'cfg', None) or {}) if node is not None else {}
|
|
260
|
+
settings = cfg.get('settlement', {}) if isinstance(cfg, dict) else {}
|
|
261
|
+
block = settings if isinstance(settings, SettlementConfig) else SettlementConfig.from_dict(settings or {})
|
|
262
|
+
if db_path is not None:
|
|
263
|
+
self.engine: SettlementEngine = SettlementEngine(
|
|
264
|
+
db_path=db_path,
|
|
265
|
+
clock=clock,
|
|
266
|
+
nwc_uri=block.nwc_uri,
|
|
267
|
+
use_live_cashu=block.live_cashu,
|
|
268
|
+
mint_url=block.mint_url or payment.DEFAULT_MINT_URL,
|
|
269
|
+
node_pk=node_pk,
|
|
270
|
+
db=self.db,
|
|
271
|
+
)
|
|
272
|
+
else:
|
|
273
|
+
self.engine = SettlementEngine(
|
|
274
|
+
clock=clock,
|
|
275
|
+
nwc_uri=block.nwc_uri,
|
|
276
|
+
use_live_cashu=block.live_cashu,
|
|
277
|
+
mint_url=block.mint_url or payment.DEFAULT_MINT_URL,
|
|
278
|
+
node_pk=node_pk,
|
|
279
|
+
db=self.db,
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
def show_cmd(self) -> SettleShowDict:
|
|
283
|
+
return {
|
|
284
|
+
'rails': list(self.engine.rails.keys()),
|
|
285
|
+
'escrows': self.engine.list_escrows(limit=20),
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
def create_cmd(self, args: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
289
|
+
res = self.engine.create_escrow(
|
|
290
|
+
ask_id=args['ask'],
|
|
291
|
+
buyer_pk=args['buyer'],
|
|
292
|
+
seller_pk=args['seller'],
|
|
293
|
+
amount_msat=int(args['amount_msat']),
|
|
294
|
+
payment_hash=args['hash'],
|
|
295
|
+
rail_id=args.get('rail') or 'lightning',
|
|
296
|
+
)
|
|
297
|
+
self.say('escrow_created', ask=args['ask'], amount_msat=int(args['amount_msat']), rail=args.get('rail') or 'lightning')
|
|
298
|
+
return res
|
|
299
|
+
|
|
300
|
+
def hold_cmd(self, args: Mapping[str, Any]) -> bool:
|
|
301
|
+
ok_hold = self.engine.hold_escrow(args['ask'], token_or_proof=args.get('proof'))
|
|
302
|
+
if not ok_hold:
|
|
303
|
+
self.refusals.refuse('settlement', 'hold failed', detail={'ask': args['ask']})
|
|
304
|
+
else:
|
|
305
|
+
self.say('escrow_held', ask=args['ask'])
|
|
306
|
+
return ok_hold
|
|
307
|
+
|
|
308
|
+
def settle_cmd(self, args: Mapping[str, Any]) -> bool:
|
|
309
|
+
ok_settle = self.engine.settle_escrow(args['ask'], args['preimage'])
|
|
310
|
+
if not ok_settle:
|
|
311
|
+
self.refusals.refuse('settlement', 'settle failed', detail={'ask': args['ask']})
|
|
312
|
+
else:
|
|
313
|
+
self.say('escrow_settled', ask=args['ask'])
|
|
314
|
+
return ok_settle
|
|
315
|
+
|
|
316
|
+
def reconcile_cmd(self, args: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
317
|
+
return self.engine.reconcile_escrow(
|
|
318
|
+
ask_id=args['ask'],
|
|
319
|
+
actual_cost_msat=int(args['cost_msat']),
|
|
320
|
+
preimage=args['preimage'],
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
def stream_pay_cmd(self, args: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
324
|
+
return self.engine.stream_micropayment(
|
|
325
|
+
ask_id=args['ask'],
|
|
326
|
+
delta_tokens=int(args['tokens']),
|
|
327
|
+
rate_msat_per_token=int(args['rate']),
|
|
328
|
+
cashu_token=args.get('proof'),
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
def cancel_cmd(self, args: Mapping[str, Any]) -> bool:
|
|
332
|
+
ok_cancel = self.engine.cancel_escrow(args['ask'], reason=args.get('reason') or '')
|
|
333
|
+
if ok_cancel:
|
|
334
|
+
self.say('escrow_cancelled', ask=args['ask'], reason=args.get('reason') or '')
|
|
335
|
+
return ok_cancel
|
|
336
|
+
|
|
337
|
+
def nutzap_cmd(self, args: Mapping[str, Any]) -> Any:
|
|
338
|
+
if self.ks is None:
|
|
339
|
+
raise ValueError("no node keystore available to sign Nutzap")
|
|
340
|
+
proofs = args['proofs'] if isinstance(args['proofs'], list) else json.loads(args['proofs'])
|
|
341
|
+
return create_nutzap_event(
|
|
342
|
+
sender_sk=self.ks.node_sk,
|
|
343
|
+
recipient_pk=args['recipient'],
|
|
344
|
+
proofs=proofs,
|
|
345
|
+
mint_url=args.get('mint') or payment.DEFAULT_MINT_URL,
|
|
346
|
+
amount_msat=int(args['amount_msat']),
|
|
347
|
+
target_event_id=args.get('target_event'),
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
def expand_grant_cmd(self, args: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
351
|
+
if self.grants is None:
|
|
352
|
+
raise ValueError("no node grant store available")
|
|
353
|
+
nutzap_raw = args['nutzap']
|
|
354
|
+
nutzap: Mapping[str, Any] = (
|
|
355
|
+
nutzap_raw if isinstance(nutzap_raw, dict) else json.loads(nutzap_raw)
|
|
356
|
+
)
|
|
357
|
+
res = self.engine.expand_grant_with_nutzap(self.grants, args['peer'], nutzap)
|
|
358
|
+
self.say('grant_expanded', peer=args['peer'])
|
|
359
|
+
return res
|
|
360
|
+
|
|
361
|
+
def nwc_pay_cmd(self, args: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
362
|
+
r = self.engine.get_rail('nwc')
|
|
363
|
+
if not hasattr(r, 'pay_and_settle'):
|
|
364
|
+
raise ValueError("configured nwc rail does not support pay_and_settle")
|
|
365
|
+
return r.pay_and_settle(args['hash'], args['bolt11'])
|
|
366
|
+
|
|
367
|
+
def cashu_swap_cmd(self, args: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
368
|
+
r = self.engine.get_rail('cashu')
|
|
369
|
+
if not hasattr(r, 'swap_and_settle'):
|
|
370
|
+
raise ValueError("configured cashu rail does not support live swap_and_settle")
|
|
371
|
+
outputs = args['outputs'] if isinstance(args['outputs'], list) else json.loads(args['outputs'])
|
|
372
|
+
return r.swap_and_settle(args['hash'], outputs)
|
|
373
|
+
|
|
374
|
+
def cashu_melt_cmd(self, args: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
375
|
+
r = self.engine.get_rail('cashu')
|
|
376
|
+
if not hasattr(r, 'melt_to_lightning'):
|
|
377
|
+
raise ValueError("configured cashu rail does not support live melt_to_lightning")
|
|
378
|
+
return r.melt_to_lightning(args['hash'], args['bolt11'])
|
|
379
|
+
|
|
380
|
+
def stop(self) -> Any:
|
|
381
|
+
self.engine.close()
|
|
382
|
+
super().stop()
|
|
383
|
+
return None
|
|
384
|
+
|