Skip to content

Commit 6b1b934

Browse files
authored
Publish v0.1 (#14)
* Updates for hex package * Add CI and Hex badges
1 parent 8c7d465 commit 6b1b934

File tree

8 files changed

+123
-71
lines changed

8 files changed

+123
-71
lines changed

README.md

+33-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,38 @@
11
# Xander
22

3-
Elixir client for Cardano's Ouroboros networking.
3+
![CI Status](https://github.com/wowica/xander/actions/workflows/ci.yml/badge.svg)
4+
[![Version](https://img.shields.io/hexpm/v/xander.svg)](https://hex.pm/packages/xander)
5+
6+
Elixir client for Cardano's Ouroboros networking protocol.
47

58
⚠️ This project is under active development. For a more stable solution to connect to a Cardano node using Elixir, see [Xogmios](https://github.com/wowica/xogmios).
69

7-
There are two ways to run this project:
10+
## Quickstart
11+
12+
Below is an example of how to connect to a Cardano node and run a query that returns the current tip of the ledger, similar to a `cardano-cli query tip` command.
13+
14+
```elixir
15+
$ iex
16+
> Mix.install([{:xander, "~> 0.1.0"}])
17+
> # Must be a valid Unix socket path
18+
> # to a fully synced Cardano node
19+
> socket_path = "/path/to/cardano-node.socket"
20+
> config = Xander.Config.default_config!(socket_path)
21+
[
22+
network: :mainnet,
23+
path: [socket_path: "/tmp/cardano-node.socket"],
24+
port: 0,
25+
type: :socket
26+
]
27+
> {:ok, pid} = Xander.Query.start_link(config)
28+
{:ok, #PID<0.207.0>}
29+
> Xander.Query.run(pid, :get_current_tip)
30+
{:ok,
31+
{147911158, "b2a4f78539559866281d6089143fa4c99db90b7efc4cf7787777f927967f0c8a"}}
32+
```
33+
34+
For a more detailed description of different ways to use this library, read the following sections:
35+
836

937
1. [Running via Docker](#running-via-docker)
1038
2. [Running natively with an Elixir local dev environment](#running-natively-with-an-elixir-local-dev-environment)
@@ -31,6 +59,8 @@ docker compose up --build
3159

3260
#### Via local UNIX socket
3361

62+
🚨 **Note:** Socket files mapped via socat/ssh tunnels **DO NOT WORK** when using containers on OS X.
63+
3464
Uncomment the `volumes` section in the `compose.yml` file to mount the local UNIX socket to the container's socket path.
3565

3666
```yml
@@ -57,7 +87,7 @@ CARDANO_NODE_PATH=/your/cardano/node.socket elixir run.exs
5787

5888
This is useful if you want to run the application on a server different from your Cardano node.
5989

60-
🚨 **Note:** Socket files mapped via socat/ssh tunnels do not work when using containers on OS X.
90+
🚨 **Note:** Socket files mapped via socat/ssh tunnels **DO NOT WORK** when using containers on OS X.
6191

6292
1. Run socat on the remote server with the following command:
6393

lib/xander/handshake/response.ex

+8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
defmodule Xander.Handshake.Response do
2+
@moduledoc """
3+
This module is responsible for validating and parsing the
4+
handshake response from a Cardano node.
5+
"""
6+
27
defstruct [:type, :version_number, :network_magic, :query]
38

49
alias Xander.Util
510

11+
@doc """
12+
Validates the handshake response from a Cardano node.
13+
"""
614
def validate(response) do
715
%{payload: payload} = Util.plex(response)
816

lib/xander/handshake_response.ex

-49
This file was deleted.

lib/xander/query.ex

+49
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
defmodule Xander.Query do
22
@moduledoc """
33
Issues ledger queries to a Cardano node using the Node-to-Client (n2c) protocol.
4+
This module implements the `gen_statem` behaviour.
45
"""
6+
57
@behaviour :gen_statem
68

79
alias Xander.Handshake
@@ -19,10 +21,31 @@ defmodule Xander.Query do
1921
# Public API #
2022
##############
2123

24+
@doc """
25+
Sends a query to the connected Cardano node.
26+
27+
Available queries are:
28+
29+
* `:get_current_era`
30+
* `:get_current_block_height`
31+
* `:get_epoch_number`
32+
* `:get_current_tip`
33+
34+
For example:
35+
36+
```elixir
37+
Xander.Query.run(pid, :get_epoch_number)
38+
```
39+
"""
40+
@spec run(pid() | atom(), atom()) :: {atom(), any()}
2241
def run(pid \\ __MODULE__, query_name) do
2342
:gen_statem.call(pid, {:request, query_name})
2443
end
2544

45+
@doc """
46+
Starts a new query process.
47+
"""
48+
@spec start_link(Keyword.t()) :: GenServer.on_start()
2649
def start_link(network: network, path: path, port: port, type: type) do
2750
data = %__MODULE__{
2851
client: tcp_lib(type),
@@ -39,6 +62,10 @@ defmodule Xander.Query do
3962
# Callbacks #
4063
#############
4164

65+
@doc """
66+
Returns a child specification for the process. This determines the
67+
configuration of the OTP process when it is started by its parent.
68+
"""
4269
def child_spec(opts) do
4370
%{
4471
id: __MODULE__,
@@ -58,6 +85,11 @@ defmodule Xander.Query do
5885
{:ok, :disconnected, data, actions}
5986
end
6087

88+
@doc """
89+
Emits events when in the `disconnected` state.
90+
"""
91+
def disconnected(_event_type, _event_content, _data)
92+
6193
def disconnected(
6294
:internal,
6395
:connect,
@@ -84,6 +116,12 @@ defmodule Xander.Query do
84116
{:keep_state, data, actions}
85117
end
86118

119+
@doc """
120+
Emits events when in the `connected` state. Must transition to the
121+
`established_has_agency` state prior to sending queries to the node.
122+
"""
123+
def connected(_event_type, _event_content, _data)
124+
87125
def connected(
88126
:internal,
89127
:establish,
@@ -108,6 +146,11 @@ defmodule Xander.Query do
108146
end
109147
end
110148

149+
@doc """
150+
Emits events when in the `established_no_agency` state.
151+
"""
152+
def established_no_agency(_event_type, _event_content, _data)
153+
111154
def established_no_agency(
112155
:internal,
113156
:acquire_agency,
@@ -123,6 +166,12 @@ defmodule Xander.Query do
123166
{:next_state, :disconnected, data}
124167
end
125168

169+
@doc """
170+
Emits events when in the `established_has_agency` state. Can send queries
171+
to the node when in this state.
172+
"""
173+
def established_has_agency(_event_type, _event_content, _data)
174+
126175
def established_has_agency(
127176
{:call, from},
128177
{:request, request},

lib/xander/query/response.ex

+9-3
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@ defmodule Xander.Query.Response do
44
@message_response 4
55
@slot_timeline 1
66

7-
def parse_response(full_response) do
8-
%{payload: response_payload} = Xander.Util.plex(full_response)
7+
@type cbor :: binary()
98

10-
case CBOR.decode(response_payload) do
9+
@doc """
10+
Parses the response from a query. Accepts CBOR encoded responses.
11+
"""
12+
@spec parse_response(cbor()) :: {:ok, any()} | {:error, atom()}
13+
def parse_response(cbor_response) do
14+
%{payload: cbor_response_payload} = Xander.Util.plex(cbor_response)
15+
16+
case CBOR.decode(cbor_response_payload) do
1117
{:ok, decoded, ""} -> parse_cbor(decoded)
1218
{:error, _reason} -> {:error, :error_decoding_cbor}
1319
end

mix.exs

+16-6
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
defmodule Xander.MixProject do
22
use Mix.Project
33

4+
@description "An Elixir library for communicating with a Cardano node"
5+
@source_url "https://github.com/wowica/xander"
6+
@version "0.1.0"
7+
48
def project do
59
[
610
app: :xander,
7-
version: "0.1.0",
11+
version: @version,
812
elixir: "~> 1.16",
913
start_permanent: Mix.env() == :prod,
1014
deps: deps(),
1115
description: description(),
1216
package: package(),
1317
name: "Xander",
14-
source_url: "https://github.com/wowica/xander",
15-
docs: [
16-
main: "Xander",
17-
extras: ["README.md"]
18-
]
18+
source_url: @source_url,
19+
description: @description,
20+
docs: docs()
1921
]
2022
end
2123

@@ -41,9 +43,17 @@ defmodule Xander.MixProject do
4143
defp package do
4244
[
4345
name: "xander",
46+
maintainers: ["Carlos Souza", "Dave Miner"],
4447
files: ~w(lib .formatter.exs mix.exs README* LICENSE*),
4548
licenses: ["Apache-2.0"],
4649
links: %{"GitHub" => "https://github.com/wowica/xander"}
4750
]
4851
end
52+
53+
defp docs do
54+
[
55+
main: "readme",
56+
extras: ["README.md"]
57+
]
58+
end
4959
end

run.exs

+4-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ Mix.install([
33
{:xander, path: Path.expand(".")}
44
])
55

6-
76
socket_path = System.get_env("CARDANO_NODE_SOCKET_PATH")
87

98
if socket_path == nil do
@@ -26,20 +25,19 @@ queries = [
2625

2726
case Query.start_link(config) do
2827
{:ok, pid} ->
29-
IO.puts("Successfully connected to Cardano node")
28+
IO.puts("Successfully connected to Cardano node 🎉\n")
3029

3130
for query <- queries do
3231
case Query.run(pid, query) do
33-
{:ok, result} ->
34-
IO.puts("Query #{query} result: #{inspect(result)}")
32+
{:ok, result} ->
33+
IO.puts("Query #{query} result: #{inspect(result)}")
3534

36-
{:error, reason} ->
35+
{:error, reason} ->
3736
IO.puts("Error querying #{inspect(query)}: #{inspect(reason)}")
3837
System.halt(1)
3938
end
4039
end
4140

42-
4341
{:error, reason} ->
4442
IO.puts("Failed to connect to Cardano node: #{inspect(reason)}")
4543
System.halt(1)

run_with_demeter.exs

+4-4
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ queries = [
2929

3030
case Query.start_link(config) do
3131
{:ok, pid} ->
32-
IO.puts("Successfully connected to Demeter node")
32+
IO.puts("Successfully connected to Demeter node 🎉\n")
3333

3434
for query <- queries do
3535
case Query.run(pid, query) do
36-
{:ok, result} ->
37-
IO.puts("Query #{query} result: #{inspect(result)}")
36+
{:ok, result} ->
37+
IO.puts("Query #{query} result: #{inspect(result)}")
3838

39-
{:error, reason} ->
39+
{:error, reason} ->
4040
IO.puts("Error querying #{inspect(query)}: #{inspect(reason)}")
4141
System.halt(1)
4242
end

0 commit comments

Comments
 (0)