Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions lib/transaction/hash.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
defmodule Xander.Transaction.Hash do
@moduledoc """
Extracts a transaction ID from a transaction CBOR hex.
"""

require Logger

@doc """
Extracts a transaction ID from a transaction CBOR hex
Returns the transaction ID as a string, or nil if there's an error.

## Examples

# Valid transaction hash
iex> Xander.Transaction.Hash.get_id("84a500d901028182582015e6ff205f08b2a29371e97b876e62ef942e5b8b74331d0e1cc735b5945d32bc02018282583900f9a2fa2e3fb59bbf8cfda38437c391fc3047e56a3b7c37e5f7a5fbd12d2915e78d60e700f34154074b6ec1cd365531f196276748ba498f251a00b71b0082583900f9a2fa2e3fb59bbf8cfda38437c391fc3047e56a3b7c37e5f7a5fbd12d2915e78d60e700f34154074b6ec1cd365531f196276748ba498f251a246d92ef021a000292dd031a0481bdd00800a100d9010281825820439c9a6aa2b4533d7a8309326f644998b8e8b42f22b8850fa858a93fc7e2888e584089bc8da32942758efdba2779daa6252e00c06bfc76b5a1b144d33e5d01b3f5c75ca45e784005ce73f839cf8c6bf0851552daabe13b94ad95997de91c66ae5b06f5f6")
"dc2d59c0188d55a94fc4780b930276d49303b558534dedbdfe7aca1c995cf463"

# Invalid transaction hash
iex> Xander.Transaction.Hash.get_id("84a500d9123")
nil
"""
@spec get_id(String.t()) :: String.t() | nil
def get_id(cbor_hash) do
try do
cbor_hash = String.upcase(cbor_hash)
{:ok, transaction} = Base.decode16(cbor_hash)
{:ok, [tx_body | _], ""} = CBOR.decode(transaction)

tx_body
|> CBOR.encode()
# Blake2b-256 is currently not supported by the built-in :crypto module
# so we must use the :blake2 dependency.
|> Blake2.hash2b(32)
|> Base.encode16(case: :lower)
rescue
error ->
Logger.warning("Error extracting tx id: #{inspect(error)}")
nil
end
end
end
21 changes: 11 additions & 10 deletions lib/transaction/response.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ defmodule Xander.Transaction.Response do

require Logger

@type error_reason ::
:cannot_decode_non_binary_values
| :cbor_function_clause_error
| :cbor_match_error
| :invalid_format
| :invalid_input
| :disconnected

@type t :: {:ok, :accepted} | {:rejected, any()} | {:error, error_reason}

@doc """
Parses the response from a transaction. Accepts CBOR encoded responses.

Expand Down Expand Up @@ -34,16 +44,7 @@ defmodule Xander.Transaction.Response do
iex> Xander.Transaction.Response.parse_response(nil)
{:error, :invalid_input}
"""
@spec parse_response(binary() | nil) ::
{:error,
:cannot_decode_non_binary_values
| :cbor_function_clause_error
| :cbor_match_error
| :invalid_format
| :invalid_input
| :disconnected}
| {:rejected, any()}
| {:ok, :accepted}
@spec parse_response(binary() | nil) :: t()
def parse_response(cbor_response) do
case Xander.Util.plex(cbor_response) do
{:ok, %{payload: cbor_response_payload}} ->
Expand Down
21 changes: 18 additions & 3 deletions lib/xander/transaction.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ defmodule Xander.Transaction do

alias Xander.Handshake
alias Xander.Messages
alias Xander.Transaction.Hash
alias Xander.Transaction.Response

require Logger
Expand All @@ -27,6 +28,7 @@ defmodule Xander.Transaction do
network: any(),
queue: :queue.queue()
}
@type tx_id :: binary()

##############
# Public API #
Expand All @@ -41,7 +43,8 @@ defmodule Xander.Transaction do
Xander.Transaction.send(tx_hash)
```
"""
@spec send(atom() | pid(), binary()) :: :ok | {:error, atom()}
@spec send(atom() | pid(), binary()) ::
{:accepted, tx_id()} | {:rejected, binary()} | {:error, binary()}
def send(pid \\ __MODULE__, tx_hash) do
:gen_statem.call(pid, {:request, :send_tx, tx_hash})
end
Expand Down Expand Up @@ -267,6 +270,7 @@ defmodule Xander.Transaction do
# Track the caller and transaction in our queue. A queue is kept to handle
# transactions that are sent from the dependent process while the current
# transaction is being processed.

data = update_in(data.queue, &:queue.in({from, tx_hash}, &1))

{:next_state, :busy, data, [{:next_event, :internal, :send_tx}]}
Expand All @@ -279,8 +283,19 @@ defmodule Xander.Transaction do
end

defp process_queue_item(data, result) do
{{:value, {caller, _tx_hash}}, new_data} = get_and_update_in(data.queue, &:queue.out/1)
actions = [{:reply, caller, result}]
{{:value, {caller, tx_hash}}, new_data} =
get_and_update_in(data.queue, &:queue.out/1)

submission_result =
case result do
# If transaction was accepted, then include the tx id
# in the result tuple for the client
{:ok, :accepted} -> {:accepted, Hash.get_id(tx_hash)}
# If not accepted, then return the result as is
_ -> result
end

actions = [{:reply, caller, submission_result}]

# Check if there are more items in the queue.
# These are from calls that came in while the current transaction was being processed.
Expand Down
2 changes: 2 additions & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ defmodule Xander.MixProject do

defp deps do
[
{:blake2, "~> 1.0"},
{:cbor, "~> 1.0"},
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
{:ex_doc, "~> 0.29", only: :dev, runtime: false}
]
end
Expand Down
3 changes: 3 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
%{
"blake2": {:hex, :blake2, "1.0.4", "8263c69a191142922bc2510f1ffc0de0ae96e8c3bd5e2ad3fac7e87aed94c8b1", [:mix], [], "hexpm", "e9f4120d163ba14d86304195e50745fa18483e6ad2be94c864ae449bbdd6a189"},
"cbor": {:hex, :cbor, "1.0.1", "39511158e8ea5a57c1fcb9639aaa7efde67129678fee49ebbda780f6f24959b0", [:mix], [], "hexpm", "5431acbe7a7908f17f6a9cd43311002836a34a8ab01876918d8cfb709cd8b6a2"},
"dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"},
"earmark_parser": {:hex, :earmark_parser, "1.4.43", "34b2f401fe473080e39ff2b90feb8ddfeef7639f8ee0bbf71bb41911831d77c5", [:mix], [], "hexpm", "970a3cd19503f5e8e527a190662be2cee5d98eed1ff72ed9b3d1a3d466692de8"},
"erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
"ex_doc": {:hex, :ex_doc, "0.36.1", "4197d034f93e0b89ec79fac56e226107824adcce8d2dd0a26f5ed3a95efc36b1", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d7d26a7cf965dacadcd48f9fa7b5953d7d0cfa3b44fa7a65514427da44eafd89"},
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
"makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
Expand Down
1 change: 1 addition & 0 deletions run.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Install Xander from local path
Mix.install([
{:blake2, "~> 1.0"},
{:xander, path: Path.expand(".")}
])

Expand Down
6 changes: 3 additions & 3 deletions run_submit_tx.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Install Xander from local path
Mix.install([
{:xander, path: Path.expand(".")},
{:cbor, "~> 1.0.0"}
{:blake2, "~> 1.0.0"},
{:xander, path: Path.expand(".")}
])

socket_path = System.get_env("CARDANO_NODE_SOCKET_PATH", "/tmp/cardano-node-preview.socket")
Expand All @@ -24,7 +24,7 @@ case Transaction.start_link(config) do
tx_hex = ""

case Transaction.send(pid, tx_hex) do
{:ok, :accepted} -> IO.puts("Transaction submitted successfully")
{:accepted, tx_id} -> IO.puts("\nTransaction submitted successfully ✅\nTx ID: #{tx_id}")
_ -> IO.puts("Error submitting transaction")
end

Expand Down
1 change: 1 addition & 0 deletions run_with_demeter.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Install Xander from local path
Mix.install([
{:blake2, "~> 1.0"},
{:xander, path: Path.expand(".")}
])

Expand Down
8 changes: 3 additions & 5 deletions test/integration/transaction_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -291,12 +291,10 @@ defmodule Xander.Integration.TransactionTest do
Enum.map(signed_transactions, fn {index, tx_cbor, _tx_signed} ->
Task.async(fn ->
case Xander.Transaction.send(tx_cbor) do
{:ok, response} ->
IO.puts(
"Received successful response for transaction #{index}: #{inspect(response)}"
)
{:accepted, tx_id} ->
IO.puts("Received successful response for transaction #{index}: #{inspect(tx_id)}")

assert response == :accepted
assert tx_id == Xander.Transaction.Hash.get_id(tx_cbor)
{index, :ok}

{:rejected, reason} ->
Expand Down
2 changes: 1 addition & 1 deletion test/test_helper.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Application.stop(:xander)
Logger.configure(level: :error)

# Configure ExUnit to exclude integration tests by default
ExUnit.configure(exclude: [:integration])
Expand Down
5 changes: 5 additions & 0 deletions test/xander/transaction/hash_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule Xander.Transaction.HashTest do
use ExUnit.Case

doctest Xander.Transaction.Hash
end