RooSoft

RooSoft

Async TCP subscription through a library, without GenServer

We have a lib that exports two functions:

  • Lib.connect(ip, port) simply calls :gen_tcp.connect(to_charlist(ip), port, [:binary])
  • Lib.subscribe(socket) uses :gen_tcp.send(socket, "init") and expects the server to start sending various messages over time. That data is rather cryptic and needs to be processed by the lib so that the lib’s client can process it easily.

Let’s say that the lib’s calls were encapsulated in a GenServer, we would have an easy solution: create a handle_info/2 function, process the data and send it to a pub/sub.

Now, we think a GenServer would cause a whole lot of unnecessary complexity, so we’re trying to avoid it.

What’s the best way to make sure the lib gets the messages and sends the processed version to the clients back without blocking any of the client’s process?

Most Liked

hst337

hst337

From what you wrote I can see that you want to different libraries:

  1. Lib just for Electrum RPC protocol. You want it process-less
  2. User-friendly Electrum interface with more high-level operations

So I can see 3 layers in this program

  1. TCP connection pool management. To connect to peers and communicate with them.
  2. Electrum JSONRPC protocol
  3. High-level API for the user (without scripthash, etc)

The connection management

Alright, I’ve read the python library and I can see that these RPC calls operate over some state (SessionManager class holds global state of all sessions, and connection to a PeerManager which maintains TCP connection to max 8 peers). And you’ll need to handle this state somehow. In erlang and elixir world we use processes for that.

And there are two options for the process. You can make it a global singleton, or a local process. If you want to maintain exactly the same API as in ElectrumX, you’ll need to create a named singleton process. But I’d suggest making library more flexible and isolating process names from it. So, I’d go with SessionManager as a Supervisor module which starts DynamicSupervisor, Registry and starts ElectrumConnection processes under the DynamicSupervisor. This SessionManager can take a name during the start. Each ElectrumConnection holds connection to one peer

And the module would provide these functions

  • SessionManager.add_peer(session_manager_pid, peer_opts) to add a peer. This will start the new connection process under the dynamicsup, name it in the registry.
  • SessionManager.connect_peer(), SessionManager.disconnect_peer, etc. for peer management
  • SessionManager.pick_connection() – randomly picks one connection (you can chose different strategy)
  • SessionManager.execute(connection, operation, reply_to) – this one just passes an Electrum operation to the connection process which should send the result to reply_to pid

This is basically how the process pool operates. I can suggest you to take a look at libraries like poolboy

Electrum JSONRPC

This part of the system is a thing that you wanted to be a process-less library for just parsing and handling responses. The problem with your approach was that you wanted it to be somehow coupled with tcp connection, which is a bad way to go.

I’d suggest spliting this into two things:

  1. Converting elixir structure for operation into the binary with jsonrpc request/response in it. This is a pretty straightforward thing to write. You can use any data validation library you prefer, but I don’t see any problem in hand-rolling this.

  2. Sending and receiving data over TCP as a JSONRPC spec defines. This was already done in libraries like this: GitHub - fanduel-oss/jsonrpc2-elixir: JSON-RPC 2.0 for Elixir

Highlevel user interface

Just create something like Electrum.subscribe which internally calls already started global SessionManager and awaits the response

RooSoft

RooSoft

The data comes from an Electrum RPC endpoint, which is what most Bitcoin wallets use to transact.

For instance, the blockchain.scripthash.subscribe is mostly what I am talking about. So, the data lib I was talking about would connect to this endpoint and make sure it subscribes to a specific scripthash.

But what’s a scripthash???

It’s jargon that Bitcoin wallet devs might not want to deal with. In other words, it’s another representation of an address. This call, in short, returns transactions relative to a specific Bitcoin address as they happen.

The goal here is to not only enable devs to connect to this thing, but make it easy and relatable. Ideally, they’d just initiate a connection, passing an IP and a port and then subscribe to a specific address. This is, of course, in the Bitcoin layer lib’s language. Under the hood, it would handle the electrum lib and isolate the dev from all the scripthash gibberish we initially talked about. Not to mention there’s some data conversion involved in the process.

Why not combine both in a single lib, you might ask… because the raw Electrum lib

Where Next?

Popular in Questions Top

freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
dokuzbir
Hello, I am trying to convert my lists to string without losing brackets.For start i have 3 map. They look like these buyer = %{ id: ...
New
gazoon
I want to know absolute current module path. In python i could do that: os.path.abspath(__file__) Does elixir have anything similar?
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

Other popular topics Top

Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3268 119930 1237
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
AstonJ
You’re a programmer, so you don’t need spoon feeding with the conventional drivel about “this is an integer.” No. You need to know what’s...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New

We're in Beta

About us Mission Statement