feat(v2): reject settlements that fail nostr attribution cross-check (S5 G5)
When LNbits' nostr-transport stamps `nostr_sender_pubkey` and `nostr_event_id` onto Payment.extra (post aiolabs/lnbits PR #4), the listener now cross-checks the signer against the resolved machine's `machine_npub` before any distribution. Mismatch / absence / unparseable pubkey → settlement is recorded with `status='rejected'` and the reason in `error_message`, distribution is skipped. Wire shape: bitspire.SettlementAttributionError + assert_nostr_attribution() Raises on absence, mismatch, or unparseable pubkey on either side. Normalises both `machine.machine_npub` (operator UI accepts hex or `npub1...`) and the stamped sender through `lnbits.utils.nostr.normalize_public_key` so the comparison is canonical-hex on both sides. tasks._handle_payment parse_settlement -> stamp nostr_event_id onto bitspire_event_id -> try assert_nostr_attribution: on failure, insert row with initial_status='rejected' + error_message, return without spawning process_settlement. crud.create_settlement_idempotent Now takes `initial_status` (required) and `error_message`. Normal path passes 'pending'; rejected path passes 'rejected' with the reason. Single-statement insert — no two-step pending-> errored dance. crud.get_stuck_settlements_for_operator New `rejected` bucket alongside `errored` / `stuck_pending` / `stuck_processing`. Distinct because retry is wrong for these: the row was misrouted, not operationally failed. models.DcaSettlement.status enum extended with 'rejected'. Worklist response model carries the new bucket; API + UI plumbed end-to-end. static/js/index.js + templates/satmachineadmin/index.html New 'rejected' worklist bucket (deep-orange, gpp_bad icon). Force-reset button now scoped to stuck_pending / stuck_processing only — was 'not errored' which would have shown on rejected too. 10 unit tests in tests/test_nostr_attribution.py cover hex<->hex, hex<->bech32, case-insensitivity, every absent variant, mismatch, and unparseable on either side. All pass. Closes the consumer-side of aiolabs/satmachineadmin#19 (G5). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
47916bdddd
commit
9414a18f82
8 changed files with 301 additions and 75 deletions
|
|
@ -33,7 +33,8 @@ const SETTLEMENT_STATUS_COLOR = {
|
|||
processed: 'green',
|
||||
partial: 'orange',
|
||||
refunded: 'purple',
|
||||
errored: 'red'
|
||||
errored: 'red',
|
||||
rejected: 'deep-orange'
|
||||
}
|
||||
|
||||
window.app = Vue.createApp({
|
||||
|
|
@ -69,6 +70,7 @@ window.app = Vue.createApp({
|
|||
|
||||
// Worklist (P9g)
|
||||
worklist: {
|
||||
rejected: [],
|
||||
errored: [],
|
||||
stuck_pending: [],
|
||||
stuck_processing: [],
|
||||
|
|
@ -262,6 +264,13 @@ window.app = Vue.createApp({
|
|||
},
|
||||
worklistBuckets() {
|
||||
return [
|
||||
{
|
||||
key: 'rejected',
|
||||
label: 'Rejected — Nostr attribution failed; investigate machine',
|
||||
icon: 'gpp_bad',
|
||||
color: 'deep-orange',
|
||||
rows: this.worklist.rejected
|
||||
},
|
||||
{
|
||||
key: 'errored',
|
||||
label: 'Errored — needs retry',
|
||||
|
|
@ -443,6 +452,7 @@ window.app = Vue.createApp({
|
|||
try {
|
||||
const {data} = await LNbits.api.request('GET', STUCK_PATH)
|
||||
this.worklistCount =
|
||||
(data?.rejected?.length || 0) +
|
||||
(data?.errored?.length || 0) +
|
||||
(data?.stuck_pending?.length || 0) +
|
||||
(data?.stuck_processing?.length || 0)
|
||||
|
|
@ -457,10 +467,12 @@ window.app = Vue.createApp({
|
|||
const {data} = await LNbits.api.request(
|
||||
'GET', `${STUCK_PATH}?threshold_minutes=${this.worklistThreshold}`
|
||||
)
|
||||
this.worklist.rejected = data?.rejected || []
|
||||
this.worklist.errored = data?.errored || []
|
||||
this.worklist.stuck_pending = data?.stuck_pending || []
|
||||
this.worklist.stuck_processing = data?.stuck_processing || []
|
||||
this.worklist.totalCount =
|
||||
this.worklist.rejected.length +
|
||||
this.worklist.errored.length +
|
||||
this.worklist.stuck_pending.length +
|
||||
this.worklist.stuck_processing.length
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue