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

Convert to a Flutter FFI plugin #1

Closed
icota opened this issue Apr 18, 2023 · 31 comments
Closed

Convert to a Flutter FFI plugin #1

icota opened this issue Apr 18, 2023 · 31 comments
Labels
enhancement New feature or request

Comments

@icota
Copy link
Collaborator

icota commented Apr 18, 2023

As per https://codelabs.developers.google.com/codelabs/flutter-ffigen#2

Shoehorning cargo into a cmake/cocoapods build might be a challenge

@icota icota mentioned this issue Apr 18, 2023
@icota icota added the enhancement New feature or request label Apr 18, 2023
@jeandudey
Copy link

There's also https://github.com/corrosion-rs/corrosion 👀

@icota
Copy link
Collaborator Author

icota commented Apr 19, 2023

@jeandudey I tested it but I found it an overkill and it doesn't even work for Android. I think for the Linux build it's enough to do vanilla cmake like:

add_custom_target(
        tor_rust
        COMMAND cargo build --manifest-path ../../src/tor-ffi/Cargo.toml
)

add_dependencies(tor tor_rust)

Android should work with: https://github.com/mozilla/rust-android-gradle
iOS: ?
macOS: ? (but probably same as iOS)
Windows: ?????

@sneurlax
Copy link
Collaborator

sneurlax commented Apr 19, 2023

The CypherStack team managed to get a cargo-built rust plugin working in flutter_libepiccash, so I am referencing that repo while following your first link to try and get this building as a Flutter FFI plugin.

It should work on iOS, Android, linux and mac desktop, and I think I even got it working on Windows (Monero was a blocker for Windows so the plugin mentioned before didn't undergo extensive testing and use on that platform)

I can only test Android and linux and Windows desktop platforms

@icota
Copy link
Collaborator Author

icota commented Apr 20, 2023

Thanks @sneurlax! We can test on iOS/macOS

@sneurlax
Copy link
Collaborator

sneurlax commented Apr 20, 2023

OK I have got it building but not tested working on android and linux in https://github.com/sneurlax/flutter_libtor/tree/flutter-plugin

I just renamed the repo to flutter_libtor to match other Stack Wallet plugins for our use because I'll be testing the tor-ffi plugin as part of that, but after getting it working I'll make a branch with the changes necessary so y'all don't have to rename anything on your end and can look over the changes without them being attached with a repo name change

You can follow README.md and run the scripts in eg scripts/android or scripts/linux to test, maybe it will be easier for you to test if either works than it will be for me to get the example app working?

@icota
Copy link
Collaborator Author

icota commented Apr 21, 2023

Thank you @sneurlax. So with this approach you still need to manually run the script?

I'd prefer for this to build automatically (as long as user has cargo and other dependencies) by triggering cargo (or the scripts themselves) from cmake/cocoapods like in the Flutter FFI template.

Imagine if we publish this on pub.dev and user does an flutter pub add tor. How does one find the script then?

@sneurlax
Copy link
Collaborator

sneurlax commented Apr 21, 2023

Thank you @sneurlax. So with this approach you still need to manually run the script?

I'd prefer for this to build automatically (as long as user has cargo and other dependencies) by triggering cargo (or the scripts themselves) from cmake/cocoapods like in the Flutter FFI template.

Imagine if we publish this on pub.dev and user does an flutter pub add tor. How does one find the script then?

Yes, you have to manually run the script. That's how CypherStack's plugins are set up. That's a very good point and that makes sense, I'll try to do that now but I'm not familiar with it. If the Flutter FFI template does it, though, I should be able to follow that example and get it building automatically. Not sure if I can get to that today or tomorrow, have something else needing addressing

@sneurlax
Copy link
Collaborator

sneurlax commented Apr 28, 2023

@jeandudey I tested it but I found it an overkill and it doesn't even work for Android. I think for the Linux build it's enough to do vanilla cmake like:

add_custom_target(
        tor_rust
        COMMAND cargo build --manifest-path ../../src/tor-ffi/Cargo.toml
)

add_dependencies(tor tor_rust)

Android should work with: https://github.com/mozilla/rust-android-gradle iOS: ? macOS: ? (but probably same as iOS) Windows: ?????

I was working along these lines and got linux working, but while addressing android I found https://github.com/fzyzcjy/flutter_rust_bridge and hope it offers a better approach overall

@icota
Copy link
Collaborator Author

icota commented Apr 29, 2023

Their approach is outlined in https://cjycode.com/flutter_rust_bridge/library/platform_setup.html#how-it-works:

We have a series of build scripts (/scripts/build-*.sh) that build all of our platform specific binaries into /platform-build and package them up appropriately, based on the target platform. Example: on iOS/macOS, this bundle is an XCFramework, on Windows/Linux, it is a .tar.gz.

These binaries are uploaded to somewhere online; as mentioned previously, we will use GitHub releases in this guide (which is automated in ci).

When the Dart tooling builds our library (such as when an application consuming our library is built), it invokes the platform specific build process. We hijack this build process by downloading a copy of the binaries for the needed platform, if not already present on the filesystem. This last part is the key; it allows us to run integration tests locally and in CI by providing our own copy of the binaries instead of forcing our build process to always fetch the binaries from GitHub releases.

After the binaries are stored locally (either by being copied to the proper folder(s) or by fetching them from online), we extract them and place them in the needed locations.

Basically they suggest building the binaries on the CI and uploading somewhere. Flutter plugin build then fetches the binaries from the internet.

I'm not sure if this approach works for us (Foundation Devices) since repoducible builds is one of our aims. With this apporach if the user wanted to verify the build he'd have to build the rust libs himself and then change the code to point to whereever he put those binaries. Seems pretty involved and I'm not even sure if it would work.

@sneurlax give me like a week to retry the trigger-the-script-from-cmake-gradle-cocoa-whatever approach again and I'll report back here.

Executing a custom command in the build process, how hard can it be? (famous last words)

@icota icota linked a pull request May 1, 2023 that will close this issue
@sneurlax
Copy link
Collaborator

sneurlax commented May 2, 2023

With this apporach if the user wanted to verify the build he'd have to build the rust libs himself and then change the code to point to whereever he put those binaries. Seems pretty involved and I'm not even sure if it would work.

How we are approaching this is that there are two scripts, build and download, basically. We will still default to building it from source, but the download script downloads tagged binaries from github releases. If your builds are reproducible and will produce the same binary as a tagged release, I only see upsides to this approach: you don't have to build, but if you do, you can verify that the output is identical. Reproducibility is still an outstanding issue for us, so not all local builds will match tagged releases, but if yours are reproducible, then downloading and verifying should be just as safe as building locally, right? We're missing that feature but offering downloading binaries as an option for the convenience of higher-level developers that don't want to bother with sometimes-lengthy builds (we build Monero from source... twice, ha!)

As to

wherever he put those binaries

you have the build and download script put their outputs in the same place: wherever they'd go to get built or bundled into your binary, package, etc.

I'm not even sure if it would work

We have been wanting to follow the example of isar, which wraps rust as a dart ffi plugin and is distributed as tagged binaries per-platform similar to the approach described by flutter_rust_bridge... I'm not sure if they've achieved reproducibility, but the number of contributors they have communicates to me that their system for modifying the source and testing the binaries is coherent, at least

Let me know how I can help. For the time being I'm going to test and see how flutter_rust_bridge works if all downloading is disabled and we just build from source every time (that's gotta be possible... right?)

@icota
Copy link
Collaborator Author

icota commented May 7, 2023

After some thinking I'd say you are right.

It's totally okay to have the default be to download - as long as the "power-user" can still build from source locally. As the CI would probably just be GH actions it would be obvious what source the binaries are built from (as long as you trust GitHub - but we all implicitly do here).

Since the approach in your branch is somewhat similar to what flutter_rust_bridge is doing for the build (keeping the native build scripts in a separate folder) I think it makes sense to move in that direction. I won't have the time to tackle this myself in the coming weeks but would be more than happy to assist.

I have closed #3 but maybe it can be used later on, in case we decide to do some cool way of triggering the local build (maybe with build flavours?).

@sneurlax
Copy link
Collaborator

@sneurlax
Copy link
Collaborator

PS I forked this and pushed changes on top of your #ffi-plugin branch pushing things forward a bit. I generated C headers and dart bindings and I think this is ready to be used, but I had trouble testing. I'll have to revisit it. I wanted to see if libtor (vs libtor-sys) would be any better, but it wasn't much better...

@sneurlax
Copy link
Collaborator

Here's another example of a rust plugin done well: https://github.com/LtbLightning/bdk-flutter

I am just trying to use the generated dart bindings now 🤞

@sneurlax
Copy link
Collaborator

OK! I got it building and apparently working on a fork of your ffi-plugin branch in the tor branch of stack_wallet. when I use start I see:

flutter: Instance of Tor created!
May 17 17:26:32.655 [notice] Tor 0.4.7.13 running on Linux with Libevent 2.1.12-stable, OpenSSL 1.1.1t, Zlib 1.2.13, Liblzma N/A, Libzstd N/A and Glibc 2.31 as libc.
May 17 17:26:32.655 [notice] Tor can't help you if you use it wrong! Learn how to be safe at https://support.torproject.org/faq/staying-anonymous/
May 17 17:26:32.655 [notice] Read configuration file "/home/user/Documents/tor/torrc".
May 17 17:26:32.656 [notice] Opening Socks listener on 127.0.0.1:64179
May 17 17:26:32.656 [notice] Opened Socks listener connection (ready) on 127.0.0.1:64179
May 17 17:26:32.656 [notice] Opening Control listener on 127.0.0.1:8795
May 17 17:26:32.656 [notice] Opened Control listener connection (ready) on 127.0.0.1:8795

so it seems to be working, but I need to test it better. The scripts need cleaning and they don't run automatically yet. I'd like to polish this package up a bit and maybe get the example app running if that'd be helpful--what would you say are the next steps here?

@sneurlax
Copy link
Collaborator

One thing I notice is that I'm going to need to configure the directory used for tor files, as right now they're put into ~/Documents by default

@icota
Copy link
Collaborator Author

icota commented May 18, 2023

One thing I notice is that I'm going to need to configure the directory used for tor files, as right now they're put into ~/Documents by default

By all means! As long as AppDocumentsDir or whatever we are using right now can stay the default. I don't think most users (especially on mobile) care where Tor files are.

@icota
Copy link
Collaborator Author

icota commented May 18, 2023

what would you say are the next steps here?

Like you said if we could get a simple start/stop Tor example app running that would be grand.

@sneurlax
Copy link
Collaborator

I got the start/stop Tor example up, but am wondering how to actually use it :P

I'll get the example app working vs how I'm demoing use now: cypherstack#2

I'll post here again later when I get the standalone example working

@sneurlax
Copy link
Collaborator

sneurlax commented Jul 13, 2023

I updated the ffi-plugin branch's flutter example app to show start/stop: https://github.com/sneurlax/tor/blob/ffi-plugin/example/lib/main.dart

@sneurlax
Copy link
Collaborator

sneurlax commented Jul 13, 2023

I'm looking into usage beyond start/stop, got any tips? will try connecting to daemon as socks5 proxy tomorrow

I still need to make/fix and demonstrate builds for android

@sneurlax
Copy link
Collaborator

also note that I got another Rust-to-Dart example working well in https://github.com/sneurlax/libxmr after having gone through this process

a key takeway is to generate C bindings from Rust, not C++

@sneurlax
Copy link
Collaborator

sneurlax commented Jul 14, 2023

I connected to it as a normal, authenticated socks5 proxy and successfully showed a Tor exit node ip, so at this point I'm going to work on adding a configuration model

I am having issues with host lookups for .onion services

@icota
Copy link
Collaborator Author

icota commented Jul 15, 2023

We connect to .onion services through BDK and that "just works" by passing the socks proxy parameters. What client are you using?

@sneurlax
Copy link
Collaborator

sneurlax commented Sep 6, 2023

I had to make a custom client in order to support sockets over the socks5 proxy: https://github.com/cypherstack/tor/blob/main/example/lib/socks_socket.dart but this doesn't necessarily address .onion lookups; I'll have to look at it again. Right now we're focusing on trying to connect to clearnet nodes through the proxy vs .onion-hosted nodes


@julian-CStack made significant progress removing the requirement for manual or scripted building using cargokit, but an open issue prevents some platforms from working. That code is here: https://github.com/cypherstack/tor/tree/no_scripts_updated_cargokit

but when it works, it's really cool to just flutter run it and go. The author of cargokit has already filed an issue and a fix, so we should eventually be able to remove all the manual setup instructions and scripts alike


Now we need a way to stop the daemon. Got any suggestions for approaching that @icota ? I haven't even really looked into Arti's rust or API since the change, but I'll dive in--just wanted to report progress on removing the manual build steps and replacing scripted setups and see if you had an easy stop method handy

@icota
Copy link
Collaborator Author

icota commented Sep 7, 2023

@sneurlax one way to stop the "daemon" would be to set_dormant on TorClient and we could also kill the proxy it is connected to for good measure (they are separate).

Let's investigate and compare notes

@knopp
Copy link

knopp commented Sep 12, 2023

Just a small heads-up, Cargokit now has workaround for the pub bug so the symlink is resolved beforehand. I also added github action that builds example project for all possible platforms / configurations to detect regressions like this earlier.

@sneurlax
Copy link
Collaborator

sneurlax commented Sep 13, 2023

Thank you @knopp!

@icota we have Linux, iOS, and Android working now thanks to @julian-CStack, but macOS uses a precompiled binary right now. I haven't tested Windows yet, but it should work and here's that PR

I'll be testing Windows next but like I said, it should work if you want to give it a try :)

I haven't worked on adding a stop or status function, stopping could save lots of battery so that will probably be next. I'm tracking it in issues over on our fork

@sneurlax
Copy link
Collaborator

@knopp and @julian-CStack helpfully resolved the macOS build issue, so we will make another PR for that once it's polished. I've yet to test Windows, still working on fixing certain aspects of our usage of this plugin

@sneurlax
Copy link
Collaborator

macOS just uses a recompiled binary currently, otherwise this issue could be closed or it could be closed by #17 (which currently has issues to resolve of its own)

@sneurlax
Copy link
Collaborator

sneurlax commented Sep 29, 2023

#20 solved the macOS precompiled binary issue. So this should be good or close to publishing, right?

The only issue might be the SOCKSSocket class, which I did find has an issue: cypherstack#24

I had to write it custom because many other packages I tried wouldn't work for our use case (raw socket use to connect to ElectrumX). It could and maybe should be spun out as a separate package. We moved it into the main lib dir and exported it so that we wouldn't have to manually sync fixes between repos, rather just using one source for the class, but it probably ought to get published as a standalone package because--as far as I tested--none of the top 6 or so major SOCKS5 Dart packages support HTTP, HTTPS, and socket connections (at least insofar as we needed).

It isn't even needed except for its use in the example app. I had to put a lot of work into that to make sure our use case was covered tho. You could also just remove it and not use it in the example even

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants