Skip to content

Commit

Permalink
Integrate quorum reads in the top archethic module
Browse files Browse the repository at this point in the history
  • Loading branch information
Samuel committed Jun 14, 2022
1 parent 3f51dc4 commit 16b6a63
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 210 deletions.
221 changes: 63 additions & 158 deletions lib/archethic.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,13 @@ defmodule Archethic do
alias __MODULE__.P2P

alias __MODULE__.P2P.Message.Balance
alias __MODULE__.P2P.Message.Error
alias __MODULE__.P2P.Message.GetBalance
alias __MODULE__.P2P.Message.GetLastTransaction
alias __MODULE__.P2P.Message.GetLastTransactionAddress
alias __MODULE__.P2P.Message.GetTransaction
alias __MODULE__.P2P.Message.GetTransactionChain
alias __MODULE__.P2P.Message.GetTransactionChainLength
alias __MODULE__.P2P.Message.GetTransactionInputs
alias __MODULE__.P2P.Message.LastTransactionAddress
alias __MODULE__.P2P.Message.NewTransaction
alias __MODULE__.P2P.Message.NotFound
alias __MODULE__.P2P.Message.Ok
alias __MODULE__.P2P.Message.StartMining
alias __MODULE__.P2P.Message.TransactionChainLength
alias __MODULE__.P2P.Message.TransactionInputList
alias __MODULE__.P2P.Message.TransactionList
alias __MODULE__.P2P.Node

alias __MODULE__.TransactionChain
alias __MODULE__.TransactionChain.Transaction
alias __MODULE__.TransactionChain.TransactionInput

Expand All @@ -47,30 +36,14 @@ defmodule Archethic do
def search_transaction(address) when is_binary(address) do
storage_nodes = Election.chain_storage_nodes(address, P2P.available_nodes())

storage_nodes
|> P2P.nearest_nodes()
|> Enum.filter(&Node.locally_available?/1)
|> get_transaction(address)
end

defp get_transaction([node | rest], address) do
case P2P.send_message(node, %GetTransaction{address: address}) do
{:ok, tx = %Transaction{}} ->
{:ok, tx}

{:ok, %NotFound{}} ->
{:error, :transaction_not_exists}

{:ok, %Error{}} ->
{:error, :transaction_invalid}
nodes =
storage_nodes
|> P2P.nearest_nodes()
|> Enum.filter(&Node.locally_available?/1)

{:error, _} ->
get_transaction(rest, address)
end
TransactionChain.fetch_transaction_remotely(address, nodes)
end

defp get_transaction([], _), do: {:error, :network_issue}

@doc """
Send a new transaction in the network to be mined. The current node will act as welcome node
"""
Expand Down Expand Up @@ -138,61 +111,32 @@ defmodule Archethic do
| {:error, :transaction_not_exists}
| {:error, :transaction_invalid}
| {:error, :network_issue}
def get_last_transaction(address) do
address
|> Election.chain_storage_nodes(P2P.available_nodes())
|> P2P.nearest_nodes()
|> Enum.filter(&Node.locally_available?/1)
|> get_last_transaction(address)
end

defp get_last_transaction([node | rest], address) do
case P2P.send_message(node, %GetLastTransaction{address: address}) do
{:ok, tx = %Transaction{}} ->
{:ok, tx}

{:ok, %NotFound{}} ->
{:error, :transaction_not_exists}

{:ok, %Error{}} ->
{:error, :transaction_invalid}

{:error, _} ->
get_last_transaction(rest, address)
def get_last_transaction(address) when is_binary(address) do
case get_last_transaction_address(address) do
{:ok, last_address} ->
nodes =
last_address
|> Election.chain_storage_nodes(P2P.available_nodes())
|> P2P.nearest_nodes()
|> Enum.filter(&Node.locally_available?/1)

TransactionChain.fetch_transaction_remotely(last_address, nodes)

{:error, :network_issue} = e ->
e
end
end

defp get_last_transaction([], _), do: {:error, :network_issue}

@doc """
Retrieve the last transaction address for a chain from the closest nodes
"""
@spec get_last_transaction_address(address :: binary()) ::
{:ok, binary()}
| {:error, :network_issue}
def get_last_transaction_address(address) do
address
|> Election.chain_storage_nodes(P2P.available_nodes())
|> P2P.nearest_nodes()
|> Enum.filter(&Node.locally_available?/1)
|> get_last_transaction_address(address)
end

defp get_last_transaction_address([node | rest], address) do
case P2P.send_message(node, %GetLastTransactionAddress{
address: address,
timestamp: DateTime.utc_now()
}) do
{:ok, %LastTransactionAddress{address: last_address}} ->
{:ok, last_address}

{:error, _} ->
get_last_transaction_address(rest, address)
end
def get_last_transaction_address(address) when is_binary(address) do
TransactionChain.resolve_last_address(address)
end

defp get_last_transaction_address([], _), do: {:error, :network_issue}

@doc """
Retrieve the balance from an address from the closest nodes
"""
Expand Down Expand Up @@ -223,117 +167,78 @@ defmodule Archethic do
@spec get_transaction_inputs(binary()) ::
{:ok, list(TransactionInput.t())} | {:error, :network_issue}
def get_transaction_inputs(address) when is_binary(address) do
address
|> Election.chain_storage_nodes(P2P.available_nodes())
|> P2P.nearest_nodes()
|> Enum.filter(&Node.locally_available?/1)
|> get_transaction_inputs(address)
end

defp get_transaction_inputs([node | rest], address) do
case P2P.send_message(node, %GetTransactionInputs{address: address}) do
{:ok, %TransactionInputList{inputs: inputs}} ->
{:ok, inputs}
nodes =
address
|> Election.chain_storage_nodes(P2P.available_nodes())
|> P2P.nearest_nodes()
|> Enum.filter(&Node.locally_available?/1)

{:error, _} ->
get_transaction_inputs(rest, address)
end
TransactionChain.fetch_inputs_remotely(address, nodes, DateTime.utc_now())
end

defp get_transaction_inputs([], _), do: {:error, :network_issue}

@doc """
Retrieve a transaction chain based on an address from the closest nodes
"""
@spec get_transaction_chain(binary()) :: {:ok, list(Transaction.t())} | {:error, :network_issue}
def get_transaction_chain(address) when is_binary(address) do
local_available_nodes = locally_available_nodes(address)
get_transaction_chain(local_available_nodes, address)
end

defp get_transaction_chain(nodes, address, opts \\ [], acc \\ [])

defp get_transaction_chain([node | rest], address, opts, acc) do
case P2P.send_message(node, %GetTransactionChain{
address: address,
paging_state: Keyword.get(opts, :paging_state)
}) do
{:ok, %TransactionList{transactions: transactions, more?: false}} ->
{:ok, Enum.uniq_by(acc ++ transactions, & &1.address)}

{:ok, %TransactionList{transactions: transactions, more?: true, paging_state: paging_state}} ->
get_transaction_chain(
[node | rest],
address,
[paging_state: paging_state],
Enum.uniq_by(acc ++ transactions, & &1.address)
)
nodes =
address
|> Election.chain_storage_nodes(P2P.available_nodes())
|> P2P.nearest_nodes()
|> Enum.filter(&Node.locally_available?/1)

{:error, _} ->
get_transaction_chain(rest, address, opts, acc)
try do
chain =
address
|> TransactionChain.stream_remotely(nodes)
|> Enum.to_list()
|> List.flatten()

{:ok, chain}
catch
_ ->
{:error, :network_issue}
end
end

defp get_transaction_chain([], _, _, _), do: {:error, :network_issue}

defp locally_available_nodes(address) do
address
|> Election.chain_storage_nodes(P2P.available_nodes())
|> P2P.nearest_nodes()
|> Enum.filter(&Node.locally_available?/1)
end

@doc """
Retrieve a transaction chain based on an address from the closest nodes
by setting `paging_address as an offset address.
"""
@spec get_transaction_chain_by_paging_address(binary(), binary()) ::
{:ok, list(Transaction.t())} | {:error, :network_issue}
def get_transaction_chain_by_paging_address(address, paging_address) when is_binary(address) do
options = [paging_state: paging_address]
local_available_nodes = locally_available_nodes(address)
transaction_chain_by_paging_address(local_available_nodes, address, options)
end
nodes =
address
|> Election.chain_storage_nodes(P2P.available_nodes())
|> P2P.nearest_nodes()
|> Enum.filter(&Node.locally_available?/1)

defp transaction_chain_by_paging_address([node | rest], address, options) do
case P2P.send_message(node, %GetTransactionChain{
address: address,
paging_state: Keyword.get(options, :paging_state)
}) do
{:ok, %TransactionList{transactions: transactions}} ->
{:ok, transactions}
try do
chain_page =
address
|> TransactionChain.stream_remotely(nodes, paging_address)
|> Enum.at(0)

{:error, _} ->
transaction_chain_by_paging_address(rest, address, options)
{:ok, chain_page}
catch
_ ->
{:error, :network_issue}
end
end

defp transaction_chain_by_paging_address([], _address, _options) do
{:error, :network_issue}
end

@doc """
Retrieve the number of transaction in a transaction chain from the closest nodes
"""
@spec get_transaction_chain_length(binary()) ::
{:ok, non_neg_integer()} | {:error, :network_issue}
def get_transaction_chain_length(address) when is_binary(address) do
address
|> Election.chain_storage_nodes(P2P.available_nodes())
|> P2P.nearest_nodes()
|> Enum.filter(&Node.locally_available?/1)
|> get_transaction_chain_length(address)
end

defp get_transaction_chain_length([node | rest], address) do
case P2P.send_message(node, %GetTransactionChainLength{address: address}) do
{:ok, %TransactionChainLength{length: length}} ->
{:ok, length}
nodes =
address
|> Election.chain_storage_nodes(P2P.available_nodes())
|> P2P.nearest_nodes()
|> Enum.filter(&Node.locally_available?/1)

{:error, _} ->
get_transaction_chain_length(rest, address)
end
TransactionChain.fetch_size_remotely(address, nodes)
end

defp get_transaction_chain_length([], _), do: {:error, :network_issue}
end
4 changes: 2 additions & 2 deletions lib/archethic_web/controllers/explorer_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ defmodule ArchethicWeb.ExplorerController do
{:ok, %{uco: uco_balance}} <- Archethic.get_balance(addr),
uco_price <- DateTime.utc_now() |> OracleChain.get_uco_price() do
render(conn, "chain.html",
transaction_chain: chain,
transaction_chain: List.flatten(chain),
chain_size: Enum.count(chain),
address: addr,
uco_balance: uco_balance,
Expand Down Expand Up @@ -92,7 +92,7 @@ defmodule ArchethicWeb.ExplorerController do
{:ok, %{uco: uco_balance}} <- Archethic.get_balance(addr),
uco_price <- DateTime.utc_now() |> OracleChain.get_uco_price() do
render(conn, "chain.html",
transaction_chain: chain,
transaction_chain: List.flatten(chain),
address: addr,
chain_size: Enum.count(chain),
uco_balance: uco_balance,
Expand Down
19 changes: 14 additions & 5 deletions test/archethic_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ defmodule ArchethicTest do
alias Archethic.P2P
alias Archethic.P2P.Message.Balance
alias Archethic.P2P.Message.GetBalance
alias Archethic.P2P.Message.GetLastTransaction
alias Archethic.P2P.Message.GetLastTransactionAddress
alias Archethic.P2P.Message.GetTransaction
alias Archethic.P2P.Message.GetTransactionChain
alias Archethic.P2P.Message.GetTransactionChainLength
alias Archethic.P2P.Message.GetTransactionInputs
alias Archethic.P2P.Message.LastTransactionAddress
alias Archethic.P2P.Message.NotFound
alias Archethic.P2P.Message.Ok
alias Archethic.P2P.Message.StartMining
Expand Down Expand Up @@ -144,8 +145,12 @@ defmodule ArchethicTest do
})

MockClient
|> expect(:send_message, fn _, %GetLastTransaction{}, _ ->
{:ok, %Transaction{previous_public_key: "Alice1"}}
|> stub(:send_message, fn
_, %GetLastTransactionAddress{address: address}, _ ->
{:ok, %LastTransactionAddress{address: address}}

_, %GetTransaction{}, _ ->
{:ok, %Transaction{previous_public_key: "Alice1"}}
end)

assert {:ok, %Transaction{previous_public_key: "Alice1"}} =
Expand Down Expand Up @@ -177,8 +182,12 @@ defmodule ArchethicTest do
})

MockClient
|> expect(:send_message, fn _, %GetLastTransaction{}, _ ->
{:ok, %NotFound{}}
|> stub(:send_message, fn
_, %GetLastTransactionAddress{address: address}, _ ->
{:ok, %LastTransactionAddress{address: address}}

_, %GetTransaction{}, _ ->
{:ok, %NotFound{}}
end)

assert {:error, :transaction_not_exists} =
Expand Down
Loading

0 comments on commit 16b6a63

Please sign in to comment.