Skip to content

Commit e1561e7

Browse files
authored
fix: issues with invoices (#271)
* fix: issues with invoices * chore: add invoice event tag * chore: add sub limits * chore: cleanup invoices page * chore: use mergeDeepLeft when updating invoice * chore: ignore whitelisted pubkey for adminssion fee * chore: use secp256k1 bytesToHex * fix: insecure derivation from secret * fix: tests * chore: consistent returns * test: fix intg tests * fix: intg tests * chore: set SECRET for intg tests
1 parent f237400 commit e1561e7

28 files changed

+212
-180
lines changed

resources/default-settings.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,14 @@ limits:
8282
- 10
8383
- - 40
8484
- 49
85-
maxLength: 65536
85+
maxLength: 102400
8686
- description: 96 KB for event kind ranges 11-39 and 50-max
8787
kinds:
8888
- - 11
8989
- 39
9090
- - 50
9191
- 9007199254740991
92-
maxLength: 98304
92+
maxLength: 102400
9393
rateLimits:
9494
- description: 6 events/min for event kinds 0, 3, 40 and 41
9595
kinds:
@@ -143,6 +143,10 @@ limits:
143143
subscription:
144144
maxSubscriptions: 10
145145
maxFilters: 10
146+
maxFilterValues: 2500
147+
maxSubscriptionIdLength: 256
148+
maxLimit: 5000
149+
minPrefixLength: 4
146150
message:
147151
rateLimits:
148152
- description: 240 raw messages/min

resources/invoices.html

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,11 @@ <h2 class="text-danger">Invoice expired!</h2>
107107
var timeout
108108
var paid = false
109109
var fallbackTimeout
110+
var now = Math.floor(Date.now()/1000)
110111

111112
console.log('invoice id', reference)
113+
console.log('pubkey', pubkey)
114+
console.log('bolt11', invoice)
112115

113116
function getBackoffTime() {
114117
return 5000 + Math.floor(Math.random() * 5000)
@@ -149,7 +152,8 @@ <h2 class="text-danger">Invoice expired!</h2>
149152
var socket = new WebSocket(relayUrl)
150153
socket.onopen = () => {
151154
console.log('connected')
152-
socket.send(JSON.stringify(['REQ', 'payment', { kinds: [4], authors: [relayPubkey], '#c': [reference], limit: 1 }]))
155+
var subscription = ['REQ', 'payment', { kinds: [402], '#p': [pubkey], since: now - 60 }]
156+
socket.send(JSON.stringify(subscription))
153157
}
154158

155159
socket.onmessage = (raw) => {
@@ -162,16 +166,22 @@ <h2 class="text-danger">Invoice expired!</h2>
162166

163167
switch (message[0]) {
164168
case 'EVENT': {
165-
// TODO: validate event
166169
const event = message[2]
167-
// TODO: validate signature
168-
if (event.pubkey === relayPubkey) {
169-
paid = true
170+
if (
171+
event.pubkey === relayPubkey
172+
&& event.kind === 402
173+
) {
174+
const pubkeyTag = event.tags.find((t) => t[0] === 'p' && t[1] === pubkey)
175+
const invoiceTag = event.tags.find((t) => t[0] === 'bolt11' && t[1] === invoice)
170176

171-
if (expiresAt) clearTimeout(timeout)
177+
if (pubkeyTag && invoiceTag) {
178+
paid = true
172179

173-
hide('pending')
174-
show('paid')
180+
if (expiresAt) clearTimeout(timeout)
181+
182+
hide('pending')
183+
show('paid')
184+
}
175185
}
176186
}
177187
break;

src/@types/base.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
import { Knex } from 'knex'
22
import { SocketAddress } from 'net'
33

4+
import { EventTags } from '../constants/base'
5+
46
export type EventId = string
57
export type Pubkey = string
6-
export type TagName = string
8+
export type TagName = EventTags | string
79
export type Signature = string
810
export type Tag = TagBase & string[]
911

1012
export type Secret = string
1113

12-
export interface TagBase {
13-
0: TagName
14-
[index: number]: string
14+
type ExtraTagValues = {
15+
[index in Range<2, 100>]?: string
16+
}
17+
18+
export interface TagBase extends ExtraTagValues {
19+
0: TagName;
20+
1: string
1521
}
1622

1723
type Enumerate<

src/@types/services.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export interface IPaymentsService {
1313
confirmInvoice(
1414
invoice: Pick<Invoice, 'id' | 'amountPaid' | 'confirmedAt'>,
1515
): Promise<void>
16-
sendNewInvoiceNotification(invoice: Invoice): Promise<void>
1716
sendInvoiceUpdateNotification(invoice: Invoice): Promise<void>
1817
getPendingInvoices(): Promise<Invoice[]>
1918
}

src/@types/settings.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ export interface EventLimits {
8282
export interface ClientSubscriptionLimits {
8383
maxSubscriptions?: number
8484
maxFilters?: number
85+
maxFilterValues?: number
86+
maxLimit?: number
87+
minPrefixLength?: number
88+
maxSubscriptionIdLength?: number
8589
}
8690

8791
export interface ClientLimits {

src/app/maintenance-worker.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import { mergeDeepLeft, path, pipe } from 'ramda'
12
import { IRunnable } from '../@types/base'
2-
import { path } from 'ramda'
33

44
import { createLogger } from '../factories/logger-factory'
55
import { delayMs } from '../utils/misc'
@@ -47,21 +47,27 @@ export class MaintenanceWorker implements IRunnable {
4747
for (const invoice of invoices) {
4848
debug('invoice %s: %o', invoice.id, invoice)
4949
try {
50-
debug('getting invoice %s from payment processor', invoice.id)
50+
debug('getting invoice %s from payment processor: %o', invoice.id, invoice)
5151
const updatedInvoice = await this.paymentsService.getInvoiceFromPaymentsProcessor(invoice)
5252
await delay()
53-
debug('updating invoice status %s: %o', invoice.id, invoice)
53+
debug('updating invoice status %s: %o', updatedInvoice.id, updatedInvoice)
5454
await this.paymentsService.updateInvoiceStatus(updatedInvoice)
5555

5656
if (
5757
invoice.status !== updatedInvoice.status
5858
&& updatedInvoice.status == InvoiceStatus.COMPLETED
59-
&& invoice.confirmedAt
59+
&& updatedInvoice.confirmedAt
6060
) {
6161
debug('confirming invoice %s & notifying %s', invoice.id, invoice.pubkey)
62+
63+
const update = pipe(
64+
mergeDeepLeft(updatedInvoice),
65+
mergeDeepLeft({ amountPaid: invoice.amountRequested }),
66+
)(invoice)
67+
6268
await Promise.all([
63-
this.paymentsService.confirmInvoice(invoice),
64-
this.paymentsService.sendInvoiceUpdateNotification(invoice),
69+
this.paymentsService.confirmInvoice(update),
70+
this.paymentsService.sendInvoiceUpdateNotification(update),
6571
])
6672

6773
await delay()

src/constants/base.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ export enum EventKinds {
2727
PARAMETERIZED_REPLACEABLE_FIRST = 30000,
2828
PARAMETERIZED_REPLACEABLE_LAST = 39999,
2929
USER_APPLICATION_FIRST = 40000,
30-
USER_APPLICATION_LAST = Number.MAX_SAFE_INTEGER,
3130
}
3231

3332
export enum EventTags {
@@ -37,6 +36,7 @@ export enum EventTags {
3736
Delegation = 'delegation',
3837
Deduplication = 'd',
3938
Expiration = 'expiration',
39+
Invoice = 'bolt11',
4040
}
4141

4242
export enum PaymentsProcessors {

src/controllers/invoices/post-invoice-controller.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,12 @@ export class PostInvoiceController implements IController {
136136
}
137137

138138
let invoice: Invoice
139-
const amount = admissionFee.reduce((sum, fee) => sum + BigInt(fee.amount), 0n)
139+
const amount = admissionFee.reduce((sum, fee) => {
140+
return fee.enabled && !fee.whitelists?.pubkeys?.includes(pubkey)
141+
? BigInt(fee.amount) + sum
142+
: sum
143+
}, 0n)
144+
140145
try {
141146
const description = `${relayName} Admission Fee for ${toBech32('npub')(pubkey)}`
142147

@@ -145,8 +150,6 @@ export class PostInvoiceController implements IController {
145150
amount,
146151
description,
147152
)
148-
149-
await this.paymentsService.sendNewInvoiceNotification(invoice)
150153
} catch (error) {
151154
console.error('Unable to create invoice. Reason:', error)
152155
response

src/handlers/event-message-handler.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Event, ExpiringEvent } from '../@types/event'
22
import { EventRateLimit, FeeSchedule, Settings } from '../@types/settings'
3-
import { getEventExpiration, getEventProofOfWork, getPubkeyProofOfWork, isEventIdValid, isEventKindOrRangeMatch, isEventSignatureValid, isExpiredEvent } from '../utils/event'
3+
import { getEventExpiration, getEventProofOfWork, getPubkeyProofOfWork, getPublicKey, getRelayPrivateKey, isEventIdValid, isEventKindOrRangeMatch, isEventSignatureValid, isExpiredEvent } from '../utils/event'
44
import { IEventStrategy, IMessageHandler } from '../@types/message-handlers'
55
import { ContextMetadataKey } from '../constants/base'
66
import { createCommandResult } from '../utils/messages'
@@ -79,7 +79,15 @@ export class EventMessageHandler implements IMessageHandler {
7979
}
8080
}
8181

82+
protected getRelayPublicKey(): string {
83+
const relayPrivkey = getRelayPrivateKey(this.settings().info.relay_url)
84+
return getPublicKey(relayPrivkey)
85+
}
86+
8287
protected canAcceptEvent(event: Event): string | undefined {
88+
if (this.getRelayPublicKey() === event.pubkey) {
89+
return
90+
}
8391
const now = Math.floor(Date.now()/1000)
8492

8593
const limits = this.settings().limits?.event ?? {}
@@ -185,6 +193,10 @@ export class EventMessageHandler implements IMessageHandler {
185193
}
186194

187195
protected async isRateLimited(event: Event): Promise<boolean> {
196+
if (this.getRelayPublicKey() === event.pubkey) {
197+
return false
198+
}
199+
188200
const { whitelists, rateLimits } = this.settings().limits?.event ?? {}
189201
if (!rateLimits || !rateLimits.length) {
190202
return false
@@ -249,6 +261,10 @@ export class EventMessageHandler implements IMessageHandler {
249261
return
250262
}
251263

264+
if (this.getRelayPublicKey() === event.pubkey) {
265+
return
266+
}
267+
252268
const isApplicableFee = (feeSchedule: FeeSchedule) =>
253269
feeSchedule.enabled
254270
&& !feeSchedule.whitelists?.pubkeys?.some((prefix) => event.pubkey.startsWith(prefix))

src/handlers/request-handlers/root-request-handler.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export const rootRequestHandler = (request: Request, response: Response, next: N
1717
paymentsUrl.protocol = paymentsUrl.protocol === 'wss:' ? 'https:' : 'http:'
1818
paymentsUrl.pathname = '/invoices'
1919

20+
const content = settings.limits?.event?.content
21+
2022
const relayInformationDocument = {
2123
name,
2224
description,
@@ -29,12 +31,14 @@ export const rootRequestHandler = (request: Request, response: Response, next: N
2931
limitation: {
3032
max_message_length: settings.network.maxPayloadSize,
3133
max_subscriptions: settings.limits?.client?.subscription?.maxSubscriptions,
32-
max_filters: settings.limits?.client?.subscription?.maxFilters,
33-
max_limit: 5000,
34-
max_subid_length: 256,
35-
min_prefix: 4,
34+
max_filters: settings.limits?.client?.subscription?.maxFilterValues,
35+
max_limit: settings.limits?.client?.subscription?.maxLimit,
36+
max_subid_length: settings.limits?.client?.subscription?.maxSubscriptionIdLength,
37+
min_prefix: settings.limits?.client?.subscription?.minPrefixLength,
3638
max_event_tags: 2500,
37-
max_content_length: 102400,
39+
max_content_length: Array.isArray(content)
40+
? content[0].maxLength // best guess since we have per-kind limits
41+
: content?.maxLength,
3842
min_pow_difficulty: settings.limits?.event?.eventId?.minLeadingZeroBits,
3943
auth_required: false,
4044
payment_required: settings.payments?.enabled,

0 commit comments

Comments
 (0)