tane/spike/block2_spike/test/roundtrip_test.dart
vjrj 49b872b405 spike(block2): de-risk social layer — key derivation, OfferTransport, NIP-99 round-trip
Throwaway research spike (spike/ branch, outside pub workspace, no prod deps
touched). Validates the open Block 2 decisions before committing a funded round:

1. Deterministic one-way secp256k1 (Nostr) key derived from the Ğ1 root seed
   via HKDF — user still backs up ONE thing. Reproducible + signs (BIP340).
2. OfferTransport abstraction over Nostr NIP-99 (kind 30402); Offer stays
   inventory/location-agnostic, geohash coarsened on the wire (tested).
3. publish->discover-by-geohash proven end-to-end against an in-process
   hermetic mini-relay (~34ms round-trip).

Findings + risks + recommendation in docs/design/spike-block2-findings.md.
Block 1 suite untouched. 14 tests green, analyzer clean.
2026-07-10 01:57:25 +02:00

132 lines
4.1 KiB
Dart

import 'dart:typed_data';
import 'package:block2_spike/block2_spike.dart';
import 'package:test/test.dart';
/// Q3 — publish → discover a real offer by geohash against a running relay
/// (in-process, hermetic). Proves the flow works end to end and measures it.
void main() {
late MiniRelay relay;
setUp(() async => relay = await MiniRelay.start());
tearDown(() async => relay.stop());
NostrKey keyFor(int fill) =>
NostrKey.deriveFromSeed(Uint8List(32)..fillRange(0, 32, fill));
test('offer published by one identity is discovered by another via geohash',
() async {
final alice = keyFor(1);
final bob = keyFor(2);
final aliceTransport = await NostrOfferTransport.connect(
relay.url,
privateKeyHex: alice.privateKeyHex,
pubkeyHex: alice.publicKeyHex,
);
final bobTransport = await NostrOfferTransport.connect(
relay.url,
privateKeyHex: bob.privateKeyHex,
pubkeyHex: bob.publicKeyHex,
);
final offer = Offer(
id: 'tomate-1',
authorPubkeyHex: alice.publicKeyHex,
summary: 'Tomate rosa de Barbastro',
type: OfferType.gift,
category: 'Solanaceae',
approxGeohash: 'sp3e9xyz', // will be coarsened to sp3e9
);
final published = await aliceTransport.publish(offer);
expect(published.accepted, isTrue,
reason: 'relay OK: ${published.message}');
// Bob queries a coarser area and still finds it (ladder match).
final found = await bobTransport.discoverUntilEose(
const DiscoveryQuery(geohashPrefix: 'sp3'),
);
expect(found, hasLength(1));
expect(found.single.summary, 'Tomate rosa de Barbastro');
expect(found.single.authorPubkeyHex, alice.publicKeyHex);
await aliceTransport.close();
await bobTransport.close();
});
test('a query for a different area returns nothing (geohash scoping works)',
() async {
final alice = keyFor(1);
final t = await NostrOfferTransport.connect(
relay.url,
privateKeyHex: alice.privateKeyHex,
pubkeyHex: alice.publicKeyHex,
);
await t.publish(Offer(
id: 'x',
authorPubkeyHex: alice.publicKeyHex,
summary: 'Pimiento',
type: OfferType.gift,
approxGeohash: 'sp3e9',
));
final elsewhere = await t.discoverUntilEose(
const DiscoveryQuery(geohashPrefix: 'u09'),
);
expect(elsewhere, isEmpty);
await t.close();
});
test('re-publishing the same offer id replaces (addressable), not duplicates',
() async {
final alice = keyFor(3);
final t = await NostrOfferTransport.connect(
relay.url,
privateKeyHex: alice.privateKeyHex,
pubkeyHex: alice.publicKeyHex,
);
Offer o(String summary) => Offer(
id: 'lot-42',
authorPubkeyHex: alice.publicKeyHex,
summary: summary,
type: OfferType.exchange,
exchangeTerms: 'a cambio de legumbre',
approxGeohash: 'sp3e9',
);
await t.publish(o('v1'));
await t.publish(o('v2'));
final found = await t.discoverUntilEose(
const DiscoveryQuery(geohashPrefix: 'sp3e9'),
);
expect(found, hasLength(1), reason: 'addressable replace by (kind,pubkey,d)');
expect(found.single.summary, 'v2');
await t.close();
});
test('measures publish→discover latency (informational)', () async {
final alice = keyFor(4);
final t = await NostrOfferTransport.connect(
relay.url,
privateKeyHex: alice.privateKeyHex,
pubkeyHex: alice.publicKeyHex,
);
final sw = Stopwatch()..start();
await t.publish(Offer(
id: 'latency',
authorPubkeyHex: alice.publicKeyHex,
summary: 'Calabaza',
type: OfferType.gift,
approxGeohash: 'sp3e9',
));
final found = await t.discoverUntilEose(
const DiscoveryQuery(geohashPrefix: 'sp3e9'),
);
sw.stop();
expect(found, hasLength(1));
// Not an assertion on wall-time (CI is noisy); just surface the number.
printOnFailure('publish+discover round-trip: ${sw.elapsedMilliseconds} ms');
// ignore: avoid_print
print('[metric] publish+discover round-trip: ${sw.elapsedMilliseconds} ms');
await t.close();
});
}