lib/transaction/validator/prevout.ex

defmodule BitcoinLib.Transaction.Validator.Prevout do
  @moduledoc """
  Correspond to an UTXO used as an input in a given transaction
  """

  alias BitcoinLib.Transaction
  alias BitcoinLib.Transaction.Input

  @spec from_transaction(Transaction.t(), (binary() -> Transaction.t())) ::
          {:ok, list()} | {:error, binary()}
  def from_transaction(%Transaction{inputs: inputs}, get_transaction_by_id) do
    from_inputs(inputs, get_transaction_by_id)
  end

  @spec from_inputs(list(Input.t()), (binary() -> Transaction.t())) ::
          {:ok, list()} | {:error, binary()}
  def from_inputs(inputs, get_transaction_by_id) do
    with {:ok, reversed_prevouts} <- apply_extraction(inputs, get_transaction_by_id) do
      {:ok, Enum.reverse(reversed_prevouts)}
    else
      {:error, message} -> {:error, message}
    end
  end

  @spec from_input(Input.t(), (binary() -> Transaction.t())) ::
          {:ok, bitstring()} | {:error, binary()}
  def from_input(%Input{txid: txid, vout: vout}, get_transaction_by_id) do
    with {:ok, transaction} <- get_transaction_by_id.(txid) do
      {:ok, transaction |> Map.get(:outputs) |> Enum.at(vout)}
    else
      {:error, message} -> {:error, message}
    end
  end

  defp apply_extraction(inputs, get_transaction_by_id) do
    inputs
    |> Enum.map(&from_input(&1, get_transaction_by_id))
    |> Enum.reduce({:ok, []}, &validate_prevout/2)
  end

  defp validate_prevout({:error, message}, _acc), do: {:error, message}

  defp validate_prevout({:ok, prevout}, {:ok, prevouts}) do
    {:ok, [prevout | prevouts]}
  end
end