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.
132 lines
4.1 KiB
Dart
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();
|
|
});
|
|
}
|