Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add websocket support #92

Merged
merged 12 commits into from
Dec 11, 2024
Merged

Conversation

HenrikJannsen
Copy link
Contributor

@HenrikJannsen HenrikJannsen commented Dec 6, 2024

Currently implemented are those topics:

  • MARKET_PRICE (pushes updates for market price data)
  • NUM_OFFERS (pushes updates for number of offers per market)
  • OFFERS (pushes updates of offers)

We use the same patter as in the 'Easybind' observer library we use in JavaFx where you get the existing data delivered when subscribing the observer. That is very handy as otherwise one need to do the extra call (as needed in default JavaFx observers). So when we subscribe to a topic we get returned the available data as response to our subscribe request.

For MARKET_PRICE and NUM_OFFERS we get the full map of quotes or numOffers per market code. It would be wasteful to do a new subscribe and unsubscribe every time the user changes the currency. So we keep the data in a map and take out the actual number depending on the selected market.

For OFFERS we dont want to get the full data set at each change as that could be a significant data size.
Instead we use a ModificationType (REPLACE, ADDED, REMOVED) to signal if data was added, removed or if we want to replace the whole data set. The default for the MARKET_PRICE and NUM_OFFERS is REPLACE. For OFFERS we support ADDED/REMOVED to insert items or remove them.
We can send a parameter for subscribing to a topic like currency code, in which case we only would get updates for that currency. If the parameter is not set we get all data. We also handle here the full map of offers per currency, as it would be wasteful to subscribe/unsubscribe at each change of markets.

The payload we get is deserialized to the expected data type at the consumer who knows what data to expect. The generic code does not deserialize that data due lack of type info. Maybe there are some magic hacks to get that resolved as we store the type info in the topic, but I did not get that working so far.

So adding new topics is just to add the topic with its expected type and the consumer defines what type to expect.
We will see with the next user cases if that covers all we need.

The events are emitted on a single thread executor on the server per subscriber, thus guaranteeing the order, but additionally we use a sequence number to check if the later received events are new as the one we got already.

Rest API calls over Websockets:
To avoid that we need to create new Tor circuits at each rest api call, we use the existing websocket connection to route those http messages to the server and there it will get unpacked to call the local rest api endpoints.
So far most rest api calls have been anyway replaced by the websocket subscribe calls. Only the getMarkets is a GET request to the rest api endpoint (though that could be omitted as the markets are a rather static list and only change if we add a market to the price node).
With POST calls there is still a problem (likely on the server side), so we use the normal http client and not the websocket client for that.

For testing one need to run the HttpApiApp (former we used the RestApiApp, this was renamed to HttpApiApp and covers both RestApi and WebsocketApi).

@HenrikJannsen HenrikJannsen force-pushed the add-websocket-support branch 2 times, most recently from b2d340a to 456aeda Compare December 7, 2024 00:32
Copy link
Collaborator

@rodvar rodvar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@HenrikJannsen great PR overall Henrik 💪 .

I've added these comments on a first pass purely doing code review, I'm gonna do some testing ASAP and put my feedback as well.

This is a foundational PR so we might be a bit more strict on this one I hope you don't hate me for it haha :)

@rodvar
Copy link
Collaborator

rodvar commented Dec 9, 2024

@HenrikJannsen gonna need some help testing this one, I'll reach out on M

@HenrikJannsen
Copy link
Contributor Author

@HenrikJannsen gonna need some help testing this one, I'll reach out on M

Added a comment at the initial post about the renamed HttpApiApp in Bisq2.

@rodvar rodvar force-pushed the add-websocket-support branch from e4e3d53 to ecade78 Compare December 10, 2024 05:17
@rodvar
Copy link
Collaborator

rodvar commented Dec 10, 2024

rebased to main updates

@rodvar rodvar force-pushed the add-websocket-support branch from ecade78 to d25e4e3 Compare December 10, 2024 05:23
@rodvar
Copy link
Collaborator

rodvar commented Dec 10, 2024

android node is working as expected, testing WS clients now

@rodvar
Copy link
Collaborator

rodvar commented Dec 10, 2024

@HenrikJannsen ok awesome, It works on the 3 apps. I'll add some docs on how to setup the env properly so that its easier for everyone to contribute.

There are some things we need to discuss, it looks like each running API instance only supports one client only? This causes issues when using the same API instance with a different device, the app assumes its the other user but obviously the cathash has not been generated in that device and then it won't load properly. We need to think how to solve this and allow support for multiple clients per trusted node ? If this is too complex we can support one for the MVP but soon after work on extending it.

Gonna do the last code review and add docs and merge this one in 💪

@rodvar rodvar force-pushed the add-websocket-support branch from 1c1ea72 to 0d65a0f Compare December 10, 2024 23:04
@rodvar rodvar force-pushed the add-websocket-support branch from e627f7d to e1a24d3 Compare December 10, 2024 23:29
Copy link
Collaborator

@rodvar rodvar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merging as is, please have a look at the comments to discuss/consider for next PR! :)

The main thing to deal with is support for multiple clients per trusted node.
If its fesiable to do for MVP then great, otherwise we just need to refuse connections from any device that is not the one that generated the profile (and therefore the cathash) for security purposes right?

I've added some commits with docs, fix to the crash when it can't connect, some logs and the posibility to configure default networking from gradle.properties.

I tested it though in my LAN but it won't work, let's chat about it when you are avail

Awesome work! 💪

}
}.onFailure { e ->
// TODO give user feedback (we could have a general error screen covering usual
// issues like connection issues and potential solutions)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -24,48 +28,89 @@ class ClientOfferbookListItemService(private val apiGateway: OfferbookApiGateway

// Misc
private var job: Job? = null
private var polling = Polling(10000) { updateOffers() }
private var selectedMarket: MarketListItem? = null
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This "selectedMarket" approach on the markets service limit the market information accessibility for all the software components when any of them selects a market. This is probably not something we want as different components might have different needs to cater for different features.

This is why the architecture suggests to use repositories for holding / saving the data as a separate entity from the services.

Its certainly ok for MVP though just leaving this comment as food-for-thought

Copy link
Contributor Author

@HenrikJannsen HenrikJannsen Dec 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We just use the selectedMarket as trigger to select the market price. If we dont want to be triggered by offer market selection but like in bisq2 with a dropdown at the market price, we just change that.

@@ -19,7 +19,7 @@ package network.bisq.mobile.client.replicated_model.common.currency
import kotlinx.serialization.Serializable

@Serializable
class Market(
data class Market(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably be a BaseModel as most of the models introduced in this PR. Leaving as is for now till the need arises.

throw e
}

return withTimeout(timeoutMillis) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add a log for now and we can improve in further PRs

suspend fun dispose() {
log.i { "Disposing request handler for ID: $requestId" }
mutex.withLock {
deferredWebSocketResponse?.cancel()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leaving this for further PRs

import network.bisq.mobile.utils.Logging
import network.bisq.mobile.utils.createUuid

class WebSocketClient(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leaving this for further PR dev

}

suspend fun unSubscribe(topic: Topic, requestId: String) {
//todo
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leaving this for next PR

@rodvar rodvar force-pushed the add-websocket-support branch from 4fe88d2 to 6c6154a Compare December 11, 2024 02:46
@rodvar rodvar merged commit c643e26 into bisq-network:main Dec 11, 2024
1 check passed
@HenrikJannsen HenrikJannsen deleted the add-websocket-support branch December 12, 2024 13:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants