mirror of
https://github.com/tompro/sattle.git
synced 2026-08-27 07:15:59 +00:00
fix: await NWC payment bearer commits
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
// Stale-owner fencing end to end: a service accepted work while its owner
|
||||
// was still installed, and a second tab replaced the saved key AFTER the
|
||||
// fence's last safe point - mid-melt, past the boundary where aborting is
|
||||
// no longer possible. What must not happen is any stale-owner WRITE:
|
||||
// no bearer changeset, no budget debit, no success response.
|
||||
|
||||
import {describe, expect, it} from 'vitest'
|
||||
import {bytesToHex} from '@noble/hashes/utils.js'
|
||||
|
||||
import {readNwcConnections} from './nwc'
|
||||
import {
|
||||
methodRequest,
|
||||
OTHER_LINKING_KEY,
|
||||
OTHER_OWNER_ID,
|
||||
OWNER_ID,
|
||||
waitFor,
|
||||
} from './nwc.testProtocol'
|
||||
import {makeBearer, mint, readResponse, startTestService} from './nwc.testService'
|
||||
|
||||
describe('service: post-boundary stale-owner fencing', () => {
|
||||
it('makes no stale-owner writes when the saved key is replaced mid-melt', async () => {
|
||||
// Given a running service whose next mint call coincides with a second
|
||||
// tab installing its own wallet (the first fetch is the melt itself:
|
||||
// the exact-match carve performs no mint call of its own)
|
||||
const m = await mint()
|
||||
let swapped = false
|
||||
const swappingFetch: typeof fetch = (input, init) => {
|
||||
if (!swapped) {
|
||||
swapped = true
|
||||
localStorage.setItem(
|
||||
'sattle_linking_key',
|
||||
JSON.stringify({
|
||||
enc: false,
|
||||
value: bytesToHex(OTHER_LINKING_KEY),
|
||||
ownerId: OTHER_OWNER_ID,
|
||||
version: 1,
|
||||
}),
|
||||
)
|
||||
}
|
||||
return fetch(input, init)
|
||||
}
|
||||
const {relay, walletServicePubkey, state, stop} = await startTestService({
|
||||
kit: {fetch: swappingFetch},
|
||||
})
|
||||
state.bearers = [await makeBearer(m, 'd7'.repeat(32), 21_000)]
|
||||
const request = methodRequest(walletServicePubkey, 'pay_invoice', {
|
||||
invoice: 'lnbc210n1pjqrstuvwxyz',
|
||||
})
|
||||
|
||||
// When the pay flows past the irreversible boundary and reaches the
|
||||
// first owner-bound persistence (the conservative budget debit)
|
||||
relay.emit(request)
|
||||
await waitFor(() => state.errors.length > 0)
|
||||
|
||||
// Then the melt genuinely happened (we are past the boundary) ...
|
||||
expect(swapped).toBe(true)
|
||||
expect(m.state.noteState('d7'.repeat(32))).toBe('burned')
|
||||
// ... but nothing under the stale owner moved: no budget debit, no
|
||||
// bearer changeset, no success response (the failure surfaced through
|
||||
// onError instead)
|
||||
expect(readNwcConnections(OWNER_ID)[0]?.spent.msat).toBe(0)
|
||||
expect(state.changesets).toEqual([])
|
||||
expect(readResponse(relay.published, request.id, 'nip44_v2')).toBeNull()
|
||||
await stop()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user