This is a large refactor that aims to improve performance by offloading
RelayPool computations into a separate actor outside the main thread.
This should reduce congestion on the main thread and thus improve UI
performance.
Also, the internal subscription callback mechanism was changed to use
AsyncStreams to prevent race conditions newly found in that area of the
code.
Changelog-Fixed: Added performance improvements to timeline scrolling
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This attempts to improve the performance of InnerTimelineView by
performing event filtering computations on "EventHolder.insert" instead
of on each view body re-render, to improve SwiftUI performance.
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This feature is not production-ready, and is not essential for the
current scope of work, so descoping it and hiding it behind a feature
flag until it is ready.
Changelog-Removed: Removed "Load new content" button
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This improves upon a temporary fix we had for the RelayPool race
condition that would cause timeline staleness.
The root cause was that during app launch, the HomeModel would subscribe
to some filters, and the subscribe function would filter out any relays
not yet connected to avoid unnecessary waiting for EOSEs from disconnected relays.
However, that filtering would cause the subscribe request to not be
queued up or sent back to the relays once connected, causing the relays
to never receive those subscription requests and causing timeline
staleness.
This was fixed by separating the relay list used for the subcription
request from the relay list used for waiting for network EOSEs. This
allows other mechanisms to ensure the subscription will go through even
when the app is initializing and relays are not yet fully connected.
Fixes: 61eb833239
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Previously, we combined the ndb and network stream within a "session
subscription" stream, which was teared down and rebuilt every time the
app went into the background and back to the foreground (This was done to
prevent crashes related to access to Ndb memory when Ndb is closed).
However, this caused complications and instability on the network
stream, leading to timeline staleness.
To address this, the pipeline was modified to merge the ndb and network
streams further upstream, on the multi-session stage, allowing the
session subscription streams to be completely split between Ndb and the
network.
For the ndb stream, we still tear it down and bring it up along the app
foreground state, to prevent memory crashes. However, the network stream
is kept intact between sessions, since RelayPool will now automatically
handle resubscription on websocket reconnection. This prevents
complexity and potential race conditions that could lead to timeline
staleness.
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
- Resend subscription requests to relays when websocket connection is
re-established
- More safeguard checks on whether Ndb is opened before accessing its
memory
- Cancel queued unsubscribe requests on app backgrounding to avoid race
conditions with subscribe requests when app enters the foreground
- Call Ndb re-open when Damus is active (not only on active notify), as
experimentally there have been instances where active notify code has
not been run. The operation is idempotent, so there should be no risk
of it being called twice.
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Use Apple's unified logging system, and specify proper privacy levels
for each piece of information.
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit adds more safeguards to prevent RUNNINGBOARD 0xdead10cc
crashes, by:
1. Using the `beginBackgroundTask(withName:expirationHandler:)` to
request additional background execution time before completely
suspending the app. See https://developer.apple.com/documentation/xcode/sigkill
2. Reorganizing app closing/cleanup tasks to be done in parallel when
possible to decrease time needed to cleanup resources.
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This should prevent RUNNINGBOARD 0xdead10cc crashes related to
ProfileManager and app background states.
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This should prevent background crashes caused by race conditions between
usages of Ndb and the Ndb/app lifecycle operations.
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Through some local experimentation, it seems that network relays can support higher subscription limits.
Increase internal limits to avoid hitting issues with subscriptions
waiting on subscription pool to clear and appearing stale.
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This reduces the overall subscription usage throughout the app, thus
reducing issues associated with too many subscriptions being used at
once, and the resulting staleness.
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit improves the loading speed for the home timeline (and likely
other areas of the app) by employing various techniques and changes:
- Network EOSE timeout reduced from 10 seconds down to 5 seconds
- Network EOSE does not wait on relays with broken connections
- Offload HomeModel handler event processing to separate tasks to
avoid a large backlog
- Give SubscriptionManager streamers more fine-grained EOSE signals for
local optimization
- Only wait for Ndb EOSE on the home timeline for faster loading
- Add logging with time elapsed measurements for easier identification of
loading problems
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This is done to prevent hang ups when the device is offline.
Changelog-Added: Added the ability to load saved notes if device is offline
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit takes a step back from the full local relay model by
treating NostrDB as one of the many relays streamed from, instead of the
one exclusive relay that other classes rely on.
This was done to reduce regression risk from the local relay model
migration, without discarding the migration work already done.
The full "local relay model" behavior (exclusive NDB streaming) was
hidden behind a feature flag for easy migration later on.
Closes: https://github.com/damus-io/damus/issues/3225
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit improves NostrNetworkManager interfaces to be easier to use,
and with more options on how to read data from the Nostr network
This reduces the amount of duplicate logic in handling streams, and also
prevents possible common mistakes when using the standard subscribe method.
This fixes an issue with the mute list manager (which prompted for this
interface improvement, as the root cause is similar to other similar
issues).
Closes: https://github.com/damus-io/damus/issues/3221
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit implements nostr network subscriptions that survive between
sessions, as well as improved handling of RelayPool opening/closing with
respect to the app lifecycle.
This prevents stale data after users swap out and back into Damus.
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Previously, HomeModel could listen to all subscriptions throughout the
app, and it would handle reaction and repost counting.
Once moved to the local relay model, HomeModel no longer had access to
all subscriptions, causing those counts to disappear.
The issue was fixed by doing the counting from ThreadModel itself, which
better isolates concerns throughout the app.
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit fixes a crash that caused the app to crash when getting all
the follows from a profile.
This issue was caused by a use-after-free memory error on inherited
transactions after the original transaction is deinitialized.
The issue was fixed by introducing a reference count on all transactions
and only deallocating the C transaction when the ref count goes to zero.
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
The widespread usage of the SubscriptionManager caused new crashes to
occur when swapping apps.
This was caused due to an access to Ndb memory after Ndb has been closed
from the app background signal.
The issue was fixed with improved task management logic and ensuring all
subscription tasks are finished before closing Ndb.
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit introduces a verification step at the relay connection
level, to help ensure notes get validated at the source and prevent
security issues associated with untrusted relays.
`RelayConnection.swift` — the source that initially handles WebSocket
messages — was analyzed, and measures were put in place to prevent
(or at least minimize) unverified nostr event data being spread
throughout the app.
The following measures were taken:
1. A note verification step was added prior to the `self.handleEvent(.nostr_event(ev))` call (which sends a Nostr response to the rest of the app for logical handling).
a. From code analysis, there is only one such call in `RelayConnection.swift`.
2. `NostrConnectionEvent`, the object that gets passed to event handlers, had its interface modified to remove the "message" case, since:
a. that could be a source of unverified nostr events.
b. it is redundant an unneeded due to the `.nostr_event` case.
c. there were no usages of it around the codebase
3. The raw websocket event handler had its label renamed to "handleUnverifiedWSEvent", to make it clear to the caller about the verification status of the data.
a. Usages of this were inspected and no significant risk was detected.
4. A new `verify` method in NdbNote was created to verify Nostr notes, and unit tests were added to confirm tampering detections around all the major fields in a Nostr note.
5. Care was taken to ensure the performance regression is as little as
possible.
It is worth noting that we will not need this once the local relay model
architecture is introduced, since that architecture ensures note
validation before it reaches the rest of the application and the user.
In other words, this is a temporary fix.
However, since the migration to that new architecture is a major
undertaking that will take some time to be completed, this fix was written
in order to address security concerns while the migration is unfinished.
This fix was written in a way that attempts to be as effective as
possible in reducing security risks without a risky and lenghty
refactor of the code that would delay the fix from being published.
Changelog-Fixed: Improved security around note validation
Closes: https://github.com/damus-io/damus/issues/1341
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit fixes a crash that occurred when swapping between Damus and
other apps.
When Damus enters background mode, NostrDB is closed and its resources
released. When Damus re-enters foreground mode, NostrDB is reopened.
However, an issue with the transaction inheritance logic
caused a race condition where a side menu profile lookup would get an
obsolete transaction containing pointers that have been freedwhen
NostrDB was closed, causing a "use-after-free" memory error.
The issue was fixed by improving the transaction inheritance logic to
double-check if the "generation" counter (which auto increments when
Damus closes and re-opens) matches the generation marked on the
thread-specific transaction. This effectively prevents lookups from
inheriting an obsolete transaction from a previous NostrDB generation.
Closes: https://github.com/damus-io/damus/issues/3167
Changelog-Fixed: Fixed an issue where the app would crash when swapping between apps
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
It was decided on a standup meeting that this feature is not important
and failing tests can be disabled.
Changelog-None
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit fixes a stack corruption issue caused by
an off-by-one error in one of the functions responsible
for parsing bech32 entities.
Changelog-None
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Previously two addresses from different memory regions were being
subtracted, which will lead to the incorrect number. This commit
improves the calculation.
Changelog-None
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Currently NostrDB does not seem to handle encryption/decryption of DMs.
Since NostrDB now controls the block parsing process and fetches note
contents directly from the database, we have to add a specific condition
that injects decrypted content directly to the ndb content parser.
This is done in conjunction with some minor refactoring to `NdbBlocks`
and associated structs, as in C those are separated between the content
string and the offsets for each block, but in Swift this is more
ergonomically represented as a standalone/self-containing object.
No changelog entry is added because the previously broken version was
never released to the public, and therefore this fix produces no
user-facing changes compared to the last released version.
Changelog-None
Closes: https://github.com/damus-io/damus/issues/3106
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
NostrDB relies on manual memory management, so it is a good idea to
enable the address sanitizer on debug configurations, as it helps find
memory-related issues on the app, which will allow us to identify memory
issues and potential crashes earlier in the development process.
Changelog-None
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit introduces new interfaces for working with NostrDB from
Swift, including `NostrFilter` conversion, subscription streaming via
AsyncStreams and lookup/wait functions.
No user-facing changes.
Changelog-None
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
The Address Sanitizer detected a heap buffer overflow during a memcpy operation
in nostrdb.c associated with note parsing.
It was found that not enough memory was being allocated to the buffer to
support all the content parsing.
Allocation size was increased to support the memory needed for the
parsing operations. However, the new number was not carefully calculated
as we will not run into this code path once we switch to the local relay
model.
Changelog-Fixed: Fixed memory error in nostrdb
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit enhances the ImageCarousel component to maintain a consistent
height when navigating between images of different aspect ratios. The
changes prevent the UI from "jumping" during carousel navigation, which
improves the overall user experience.
Key improvements:
- Added `first_image_fill` property to store dimensions of the first image
- Modified height calculation to prioritize the first image's dimensions
- Refactored image fill calculation into a reusable `compute_item_fill` method
- Added proper view clipping to prevent content overflow
- Simplified filling behavior for more predictable layout
These changes provide a smoother, more stable carousel experience by
maintaining consistent dimensions throughout image navigation.
Changelog-Changed: Improved the image sizing behavior on the image carousel for a smoother experience
Closes: https://github.com/damus-io/damus/issues/2724
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit improves error handling in the wallet's "send" feature, by
displaying more specific wallet response error messages when available.
Closes: https://github.com/damus-io/damus/issues/3095
Changelog-Fixed: Improve error handling on wallet send feature
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
During the implementation of the "hide balance" feature, the balance
view was refactored in a way that caused it to not be redacted anymore,
making it show the "??" instead of the intended skeleton loader.
This commit fixes that issue without reverting the hide balance feature.
Changelog-Fixed: Fixed issue where the text "??" would appear on the balance while loading
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>