nostr-protocol / nips

Nostr Implementation Possibilities
2.39k stars 580 forks source link

Ephemeral NIP-47 is too ephemeral #537

Open TonyGiorgio opened 1 year ago

TonyGiorgio commented 1 year ago

According to NIP-16, ephemeral events MUST NOT be stored. These seem to only be useful for synchronous communication, which conflicts with the asynchronous nature of nostr.

One of the use cases makes sense:

Typing indicators: A chat application may use ephemeral events as a typing indicator.

However, this example given in NIP-16 does not:

Messaging: Two pubkeys can message over nostr using ephemeral events.

It is extremely unreliable to use this as a messaging protocol between two users if the events must not be stored and are discarded if one of the users even momentarily has a disconnection from a relay. However, there's another use case that is using ephemeral events when it's not strictly needed.

NIP-47 seems to have been created with the assumption that nostr users should be using custodians or are running an always online lightning node at home or in the cloud. These are two opposite extremes and it does not well support the middle ground which is non-custodial mobile wallets.

I would like to start some conversation around how to best support this use case with some adaptations to either NIP-16, NIP-47, or a new NIP all together. Essentially the way a non-custodial mobile wallet could take advantage of NWC is by letting a relay queue up the pay_invoice requests until they come online again. This should allow for the same one-click experience that is finality-delayed until the wallet application is opened again. Once opened, all the pay_invoice requests could be retrieved from the relay (something that does not seem possible with ephemeral events today) and paid all at once.


Here's some options I've thought of:

Semi-ephemeral events

  1. Could be an adaptation to NIP-16 that specifies that ephemeral events behave more like semi-ephemeral events. IE, MUST NOT be stored, but SHOULD BE in memory for a temporary amount of time (10 minutes?) in order to allow some time for the recipient to come online to get the event.
  2. Could be a new event range. IE, 30000 <= n < 40000, but that seems wasteful.

queue_pay_invoice

A new NIP for a new NWC command type. This event could look almost identical to pay_invoice except it is in the "Regular Event" range so it is stored by the relays and allowed to be fetched when the wallet comes back online. Wallet services (ie, mobile wallets) could advertise this one instead of pay_invoice in their NIP-47 13194 event type.

These events would require more relay resources than semi-ephemeral events, so I suggest finding some balance and flexibility with ephemeral events instead.


Curious if there's some middle ground we can find in order to support paying invoices in a similar one-click fashion with non-custodial mobile wallets. The main caveat is if the user never opens up their wallet in time to pay all the bulk invoices. However, if this is a concern, some extra metadata could be transmitted with something like the queue_pay_invoice approach so that a new zap payment request is made for each of the queued up zaps if an invoice times out. Thus resuming all queued up zaps when the wallet is opened again (perhaps with some long 24-hour time limit or something).

CC'ing a few people that might be interested in this: @benthecarman @jb55 @Semisol @kiwiidb @bumi

fiatjaf commented 1 year ago

Both https://github.com/hoytech/strfry and https://github.com/fiatjaf/relayer used to store ephemeral events for some minutes, but @Semisol harassed us until we changed. Strfry may still do it, I'm not sure.

This is solved by a relay policy like that. Some relays may choose to store NWC events (or all ephemeral events) for some minutes, and NWC users that expect some unreliability on their wallet providers may choose to use those relays.

TonyGiorgio commented 1 year ago

Looks like strfry still does it, specifically deletes after 5 minutes: https://github.com/hoytech/strfry/blob/e4e79af1214e38ea90f4d099bd1f057a688abdb6/strfry.conf#L116-L117

This may work, but still puts a tight limit on needing to immediately open the app before then. Maybe another approach could be some sort of "one time read" event or putting a TTL on events.

cmdruid commented 1 year ago

Kind 001-10k: store all Kind 10k-20k: store latest (kind) Kind 20k-30k: store none Kind 30k-40k: store latest (tag)

The core issue for me is an inability to negotiate a better retention policy with the relay.

If a client makes a good faith attempt to communicate their desired retention policy, then the next step is for the relay to use it (or naively store everything).

Is there a NIP for setting an "expires" tag? This seems like the most obvious solution.

Semisol commented 1 year ago

Use the event expiry NIP. Any implementations storing ephemeral events are non-compliant. Though I have advocated multiple times to make the expiry tag work on ephemeral events. And it was dropped eventually.

I propose that expiry applies to ephemeral events instead of persisting all of them.

fiatjaf commented 1 year ago

Using expiry tags in ephemeral events is a sensible idea.

TonyGiorgio commented 1 year ago

I like the idea of using expiry tags, looks like that's NIP-40.

Would there be a reason to use both the ephemeral event range and the expiry tag? Or does NIP-40 alone solve the same issue?

cmdruid commented 1 year ago

I like the idea of using expiry tags, looks like that's NIP-40.

Thanks for looking up the NIP number. I could not recall it myself.

I would recommend using NIP40 with regular event kinds, so that the fallback (if NIP40 is not implemented) would be a persistent event. Otherwise with ephemeral events the fallback is a dropped event.

I think using expiry tags solves the issue from the client side. It's up to the relay to implement expiry tags in order to optimize their storage and prune useless events.

bu5hm4nn commented 8 months ago

I think it's quite simple: the ephemeral event should exist as long as the emitter of the event is connected to the relay.

I was just testing NIP-46 connections and ran into multiple fails due the 'service' not being subscribed at the time of the client request. This makes anything using ephemeral events extremely unreliable when used between consumer devices.

mikedilger commented 8 months ago

That could work to make them more reliable, but I didn't hear the argument from @semisol as to why they shouldn't persist at all (unless perhaps explicitly marked with expiry). Could someone link to it, or summarize it briefly?

BTW currently chorus relay doesn't persist them at all, they flow in and get sent to current REQs and then are instantly forgotten. But I can change that if needed.