clone

nostr relays on demand at <name>.bind.ws: claim one with your key, pay in zaps past the free allowance

fix: keep fuel receipts working during Git transfers

Dami committed 5d31267
Retain Git operation authority while validated provider receipts are credited, preserving teardown fences and owned nested operations. Vendor ntig 0.6.1 for libgit2 sideband push reports and bounded gzip fetch negotiation. Include complete source provenance and regression coverage.
commit
5d3126716ae5b6bf01d46cdf14fceb05244445e0
parent
7793438373191af8e6c9972a1f4bf51cbcc885a2
author
Dami <dami.etoile@gmail.com>
9 files changed, +139 -15
Mpackage-lock.json8++--
Mpackage.json2+-
Msrc/relay.ts11++++--
Msrc/repository-access.ts21+++++++++++
Mtest/object/fuel.test.ts39++++++++++++++++++++-
Mtest/unit/repository-access.test.ts58++++++++++++++++++++++++++++++
Mvendor/README.md15+++++---
Avendor/ntig-0.6.1.tgzbin
Avendor/ntig-source-0.6.1.tar.gzbin
M package-lock.json +4 -4
M package.json +1 -1
M src/relay.ts +7 -4
M src/repository-access.ts +21 -0
M test/object/fuel.test.ts +38 -1
@@ -3,7 +3,7 @@ import { describe, it, expect } from "vitest";
33 import { finalizeEvent, generateSecretKey, getPublicKey, type Event } from "nostr-tools/pure";44 import { getToken } from "nostr-tools/nip98";55 import type { Relay } from "../../src/relay.ts";6-import { now, ev, rpc } from "../helpers/relay.ts";6+import { now, ev, pk, rpc } from "../helpers/relay.ts";77 import { WS } from "../helpers/ws.ts";88 99 const SERVICE = "ab".repeat(32);
@@ -45,6 +45,43 @@ function receipt(req: Event, bolt11: string, signer = provider, p = SERVICE) {
4545 }4646 4747 describe("fuel", () => {48+ it("credits a provider receipt while a Git response is streaming", async () => {49+ const host = "fuel-stream.bind.ws";50+ await mockProvider("fuel-stream");51+ const owner = generateSecretKey();52+ const payer = generateSecretKey();53+ await claim(host, owner);54+ const req = zapRequest(payer, host, 1_000_000);55+ const good = receipt(req, "lnbc10u1streaminvoice");56+ const forged = receipt(req, "lnbc10u1forgedinvoice", generateSecretKey());57+ const stub = env.RELAY.getByName("fuel-stream");58+59+ await runInDurableObject(stub, async (relay: Relay) => {60+ const response = await relay.repositoryAccess.response(61+ "git",62+ async () => new Response(new ReadableStream<Uint8Array>({ start() {} })),63+ () => new Response("busy", { status: 429 }),64+ );65+ expect(relay.repositoryAccess.busy).toBe(true);66+67+ const credited = await relay.acceptAny(good, relay.virtualConn(host, pk(provider)));68+ expect(credited.ok).toBe(true);69+ expect(credited.msg).toBe("fuel: credited 1000 sats");70+ expect(relay.fuelStatus().creditedMsats).toBe(1_000_000);71+72+ const ordinary = await relay.acceptAny(ev(owner, 1, "during Git"), relay.virtualConn(host, pk(owner)));73+ expect(ordinary.ok).toBe(false);74+ expect(ordinary.msg).toContain("relay operation in progress");75+76+ const invalid = await relay.acceptAny(forged, relay.virtualConn(host, pk(owner)));77+ expect(invalid.ok).toBe(false);78+ expect(invalid.msg).toContain("relay operation in progress");79+80+ await response.body!.cancel();81+ expect(relay.repositoryAccess.busy).toBe(false);82+ });83+ });84+4885 it("issues invoices, credits valid receipts once, ignores forgeries, and lifts the out-of-fuel restriction", async () => {4986 const host = "fuel-a.bind.ws";5087 await mockProvider("fuel-a");
M test/unit/repository-access.test.ts +58 -0
@@ -63,6 +63,64 @@ describe("Repository admission", () => {
6363 expect(access.busy).toBe(false);6464 });6565 66+ it("admits provider fuel receipts during Git but fences them during other work", async () => {67+ const access = new RepositoryAccess();68+ const entered = latch();69+ const finish = latch();70+ const active = access.run<string>("git", async () => {71+ entered.release();72+ await finish.promise;73+ return "done";74+ }, refused);75+ await entered.promise;76+ expect(await access.runFuel(() => "credited", refused)).toBe("credited");77+ finish.release();78+ await active;79+80+ const controlEntered = latch();81+ const controlFinish = latch();82+ const control = access.run<string>("control", async () => {83+ controlEntered.release();84+ await controlFinish.promise;85+ return "done";86+ }, refused);87+ await controlEntered.promise;88+ expect(await access.runFuel(() => "unexpected", refused)).toBe("refused");89+ controlFinish.release();90+ await control;91+ await access.run<void>("control", async () => {92+ expect(await access.runFuel(() => "nested", refused)).toBe("nested");93+ }, () => undefined);94+ expect(access.busy).toBe(false);95+ });96+97+ it("keeps the Git lease alive until a concurrent fuel receipt finishes", async () => {98+ const access = new RepositoryAccess();99+ const gitEntered = latch();100+ const gitFinish = latch();101+ const fuelEntered = latch();102+ const fuelFinish = latch();103+ const git = access.run<string>("git", async () => {104+ gitEntered.release();105+ await gitFinish.promise;106+ return "done";107+ }, refused);108+ await gitEntered.promise;109+ const fuel = access.runFuel(async () => {110+ fuelEntered.release();111+ await fuelFinish.promise;112+ return "credited";113+ }, refused);114+ await fuelEntered.promise;115+ gitFinish.release();116+ await git;117+ expect(access.busy).toBe(true);118+ expect(await access.run("teardown", () => "unexpected", refused)).toBe("refused");119+ fuelFinish.release();120+ expect(await fuel).toBe("credited");121+ expect(access.busy).toBe(false);122+ });123+66124 it("releases synchronous and asynchronous owners after failures", async () => {67125 const access = new RepositoryAccess();68126 expect(() => access.sync("event", () => { throw new Error("sync"); }, refused)).toThrow("sync");
M vendor/README.md +10 -5
@@ -1,6 +1,6 @@
11 # Vendored dependencies22 3-`ntig-0.6.0.tgz` supplies Git pack validation, Smart HTTP and Nostr repository3+`ntig-0.6.1.tgz` supplies Git pack validation, Smart HTTP and Nostr repository44 policy wrappers. Bindws stores Git objects, refs and retry receipts in its55 relay Durable Object's SQLite database. The package's R2 adapters remain66 available to other ntig consumers; bindws does not use them for Git storage.
@@ -11,14 +11,19 @@ adds configurable transfer limits, streamed indexed downloads, incremental
1111 hashing and the large-push HTTP probe. It includes the sibling checkout's1212 preexisting development changes; this is not an upstream registry release.1313 14+Version 0.6.1 adds receive-pack sideband reports for libgit2/ngit and bounded15+gzip decoding for native Git's larger fetch negotiations. Plain Git requests16+retain their existing framing. Both native Git and libgit2 pushes were checked17+against the local handler; the full library suite and package checks passed.18+1419 | Fact | Value |1520 |---|---|1621 | Source repository | [FelineStateMachine/ntig](https://github.com/FelineStateMachine/ntig) |1722 | Checkout base | `caa8258e6cf6ec3220b89312d66860258e096f0f`, plus the preserved 0.5.0 behavior and local changes |18-| Source commit | [`52145156e06cd1d8e360c067bc0271f85d36dbd9`](https://github.com/FelineStateMachine/ntig/commit/52145156e06cd1d8e360c067bc0271f85d36dbd9) |19-| Source archive | `ntig-source-0.6.0.tar.gz`; `SOURCE-SNAPSHOT.json` records the complete source file hashes |20-| Package SHA-256 | `7d517c5acd0de2ecca044dc744a5af275ce3710089be698d9d3aa816c1aac94f` |21-| Source archive SHA-256 | `9f6818e9e603fd1f383141f8a8a481371a08863fa351fddb5a8a73fc39b8e52a` |23+| Source commit | [`7b1a517ef9a1f05a412520d15ec5e13c0aea1adb`](https://github.com/FelineStateMachine/ntig/commit/7b1a517ef9a1f05a412520d15ec5e13c0aea1adb) |24+| Source archive | `ntig-source-0.6.1.tar.gz`; `SOURCE-SNAPSHOT.json` records the source commit and complete source file hashes |25+| Package SHA-256 | `4b61ecd241fa1ce8745e543597aa50e8076c6cc0e2499ff9e15dd91f1a9913eb` |26+| Source archive SHA-256 | `4e790e8120d416f24e543ac3384baea8bf70647565a531748ed2d239434528bc` |2227 | License | MIT, included in both archives |2328 | Library build | `npm ci --ignore-scripts`, then `npm run build:library` in the extracted source |2429 | Package build | `npm pack` in the extracted source |
A vendor/ntig-0.6.1.tgz +0 -0

Binary file.

A vendor/ntig-source-0.6.1.tar.gz +0 -0

Binary file.