Skip to content

Instantly share code, notes, and snippets.

@AdamISZ
Created July 31, 2020 15:16
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save AdamISZ/f062c7453c6973a8287897fe506b9d19 to your computer and use it in GitHub Desktop.
Save AdamISZ/f062c7453c6973a8287897fe506b9d19 to your computer and use it in GitHub Desktop.
Thoughts on an RPC API for Joinmarket
Some blue sky thinking about Joinmarket architecture (this is about the "client" side of "client-server" in the sense used in the name of the repo; there, the "server" is the communications daemon which can run somewhere else, and I'm not suggesting changing that).
Though the idea has cropped up several times (because it is a very natural concept in software engineering, and is widely used), I started today thinking again about having something like an RPC API for a Joinmarket long-running daemon (this would be *another* daemon to the one mentioned above). Let's call it jmwalletd to distinguish from existing joinmarketd (badly named in retrospect), and called that because as you see below the idea is to bundle the services around the wallet.
The reason I started thinking about it today again, was because of SNICKER; and I'll use that as an example of how this might create benefits (along with some benefits that would exist without new functionality).
Imagine then that jmwalletd.py runs as a service or similar. It exposes a grpc interface. The functions it exposes would be (just an initial list):
1. `generate` - creates a new wallet, with flags like p2sh/native, fidelity bond, mnemonic extension, SNICKER support. And so on.
2. `unlock` - give a wallet that's been created and decrypt it.
3. `maker` - switch on/off yield generator function for the specified wallet.
4. `wallet-display` - leaving this deliberately vague for now, but just all the various ways we can display coins, there could be e.g. a separate `listunspent`, so I don't mean this has to be one call.
5. `maker-stats` - could display info about completed coinjoins as per `yigen-statement.csv` today or similar
6. `deposit` - retrieve a suitable deposit address for a specified mixdepth/account
7. `sendpayment` - this could have several parameters so we can do everything as currently in the script/Qt: send without coinjoin, send with coinjoin, send with payjoin and/or with BIP21 URI.
8. `run-schedule` - architecturally it may or may not make sense to have `generate-tumbler-schedule` as an RPC, but in any case, sending a schedule over the API can instruct the jmwalletd daemon to run that whole schedule as a Taker. Note that this would "lock" the wallet for the duration and you wouldn't be able to do `maker` or `sendpayment` (but you would still be able to call `wallet-display`
9. `taker-stats` could return the current status of the running schedule (see current TUMBLE.log and TUMBLE.schedule files).
10. `shutdown-wallet` - self explanatory, call `WalletService.stopService()`.
I won't list all of them as it gets kinda boring, but we'd also have recover, import key, dumpprivkey and a few more housekeeping ones, ideally.
But how does this get implemented?
For the RPC part, I mention grpc because I already did that in some PoC Lightning Python code, connecting to lnd, and it's pretty easy and well structured it seems to me. Other RPC code I found for Python was more problematic (but: vague memory here).
For the exposure of services, I believe #359 was the critical step that makes this kinda work fairly easily, although I wouldn't oversell that, there will still be quite a lot of coding to do!
Basically the main loop can still be started in `jmclient.client_protocol.start_reactor()` as now, and we could start services like the `WalletService` on the fly as and when requested by authenticated RPC calls (that would happen on `unlock`; after that point the service for that wallet object starts, it syncs then it starts its main `transaction_monitor()` loop).
(Multiple wallet services at once is obviously a desirable feature, and *should* work, but it'll need checking out).
So the SNICKER example is instructive in this case. Joinmarket currently has support for a `SNICKERWalletMixin` in any given `Wallet` instance, and that has a function `parse_proposal_to_signed_tx` which will do decryption, PSBT parsing and signing under the hood; all you need to do, currently, is define an `acceptance_callback` function that tells it whether or not this transaction is OK for signing. So I think with simple wallet commands on the RPC client side, it seems like it would allow for an almost stupidly simple SNICKER client, which only knows those few RPC calls (such software wouldn't need all our weird imports and networking stuff and whatnot - notice it wouldn't need to know about bitcoind connections, though that requirement of course still exists, it's remote to this client). The other job such a piece of software would have to do is poll some *.onion to find lists of encrypted proposals.
To be clear, I'm not suggesting that's the best way to do SNICKER, but it illustrates the point.
I would imagine shifting to this architecture for three distinct reasons: (1) it will allow people to write clients in forms that are more standardised nowadays (perhaps browser based, perhaps others) and naturally cross platform with better user interfaces, (2) it will make the usage of *existing* Joinmarket functionality more intuitive (switching different roles and functions on and off by communicating with one "master" process) and (3) it will make easier what I hope is the further development of this privacy wallet software by adding several more "power-user" features, of which Payjoin is the first one.
I'm sure there are negatives to going this way too. I would guess this is worth doing though, as long as it can be done reasonably well.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment