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 BdkActor #750

Closed
wants to merge 2 commits into from
Closed

Add BdkActor #750

wants to merge 2 commits into from

Conversation

luckysori
Copy link
Contributor

@luckysori luckysori commented Jun 8, 2023

Fixes #730.

An actor that manages the bdk::Wallet resource. It allows us to use the wallet whilst the inevitably expensive on-chain sync is happening in the background. But only for those things that can be cached, such as the balance, wallet history and addresses.

We would like to use the async version of EsploraBlockchain, but bitcoindevkit/bdk#165 is still an issue.

The only hacky bit stems from the fact that we still have to implement certain non-async foreign traits and to access any bdk::Wallet resource we now have to go via async methods, since the actor is async.

To do so, at some point we have to call an async function "from a sync context". The natural way to do so (for me) would be to use
runtime.block_on, which schedules an async task and waits for it to resolve, blocking the thread. But, these non-async foreign trait methods are actually called elswhere in async contexts. This leads to runtime.block_on panicking because tokio is trying to prevent us from blocking a thread in an async context. This is analogous to us misusing async and doing an expensive CPU-bound computation in an async context, but here tokio is able to "aid" us since block_on is provided by tokio.

The solution is to use the following pattern:

tokio::task::block_in_place(|| {
    tokio::runtime::Handle::current().block_on(async {
        // async code
    })
});

From the documentation of block_in_place, we are able to run "the provided blocking function on the current thread without blocking the executor". We therefore avoid the panic, as we no longer block the executor when calling block_on.

This has one final side-effect, which is that all ln-dlc-node async tests now need to go back to using the multi_thread flavour.
block_in_place works by moving all scheduled tasks to a different worker thread and without multi_thread there is only one worker thread, so it just panics.

klochowicz and others added 2 commits June 8, 2023 23:37
A factory function is good enough, we were not using the struct for anything
other than calling new()
An actor that manages the `bdk::Wallet` resource. It allows us to use
the wallet whilst the inevitably expensive on-chain sync is happening
in the background.

We would like to use the async version of `EsploraBlockchain`, but
bitcoindevkit/bdk#165 is still an issue.

The only hacky bit stems from the fact that we still have to implement
certain non-async foreign traits and to access any `bdk::Wallet`
resource we now have to go via async methods, since the actor is
async.

To do so, at some point we have to call an async function "from a sync
context". The natural way to do so (for me) would be to use
`runtime.block_on`, which schedules an async task and waits for it to
resolve, blocking the thread. *But*, these non-async foreign trait
methods are actually called elswhere in _async_ contexts. This leads
to `runtime.block_on` panicking because `tokio` is trying to prevent
us from blocking a thread in an async context. This is analogous to us
misusing async and doing an expensive CPU-bound computation in an
async context, but here `tokio` is able to "aid" us since `block_on`
is provided by `tokio`.

The solution is to use the following pattern:

```rust
tokio::task::block_in_place(|| {
    tokio::runtime::Handle::current().block_on(async {
        // async code
    })
});
```

From the documentation of `block_in_place`, we are able to run "the
provided blocking function on the current thread without blocking the
executor". We therefore avoid the panic, as we no longer block the
executor when calling `block_on`.

This has one final side-effect, which is that all `ln-dlc-node` async
tests now need to go back to using the `multi_thread` flavour.
`block_in_place` works by moving all scheduled tasks to a different
worker thread and without `multi_thread` there is only one worker
thread, so it just panics.

Co-authored-by: Mariusz Klochowicz <mariusz@klochowicz.com>
@luckysori
Copy link
Contributor Author

Thanks to @holzeis for pointing out that we will still have to wait for the sync to handle any other actor message, as we are holding a mutable reference to the whole actor every time we handle the Sync message (or any other message for that matter).

At this stage I am questioning if this even helps at all, as even if I reintroduced the cache (it was there in a previous version of this PR) one would not be able to access the cache until each sync finishes regardless.

I think this is probably a reasonable refactor of the code, but maybe we should hold off until the next version of bdk to see if this structure helps with that transition. What do you think? @klochowicz @holzeis

@holzeis
Copy link
Contributor

holzeis commented Jun 15, 2023

@luckysori @klochowicz Although I think its a reasonable refactor, I wouldn't do that now and rather wait for bdk 1.0.0. I am a bit afraid of regressions.

@luckysori luckysori marked this pull request as draft June 15, 2023 07:57
@holzeis
Copy link
Contributor

holzeis commented Jun 28, 2023

@luckysori Can we close that PR?

@luckysori luckysori closed this Jun 28, 2023
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.

Refactor away Mutex around BDK
3 participants