lib/bsv/tx_in.ex

defmodule BSV.TxIn do
  @moduledoc """
  A TxIn is a data structure representing a single input in a `t:BSV.Tx.t/0`.

  A TxIn consists of the `t:BSV.OutPoint.t/0` of the output which is being
  spent, a Script known as the unlocking script, and a sequence number.

  A TxIn spends a previous output by concatenating the unlocking script with the
  locking script in the order:

		  unlocking_script <> locking_script

  The entire script is evaluated and if it returns a truthy value, the output is
  unlocked and spent.

  When the sequence value is less that `0xFFFFFFFF` and that transaction
  locktime is set in the future, that transaction is considered non-final and
  will not be mined in a block. This mechanism can be used to build payment
  channels.
  """
  alias BSV.{OutPoint, Script, Serializable, VarInt}
  import BSV.Util, only: [decode: 2, encode: 2]

  @max_sequence 0xFFFFFFFF

  defstruct outpoint: %OutPoint{}, script: %Script{}, sequence: @max_sequence

  @typedoc "TxIn struct"
  @type t() :: %__MODULE__{
    outpoint: OutPoint.t(),
    script: Script.t(),
    sequence: non_neg_integer()
  }

  @typedoc """
  Vin - Vector of an input in a Bitcoin transaction

  In integer representing the index of a TxIn.
  """
  @type vin() :: non_neg_integer()

  @doc """
  Parses the given binary into a `t:BSV.TxIn.t/0`.

  Returns the result in an `:ok` / `:error` tuple pair.

  ## Options

  The accepted options are:

  * `:encoding` - Optionally decode the binary with either the `:base64` or `:hex` encoding scheme.
  """
  @spec from_binary(binary(), keyword()) :: {:ok, t()} | {:error, term()}
  def from_binary(data, opts \\ []) when is_binary(data) do
    encoding = Keyword.get(opts, :encoding)

    with {:ok, data} <- decode(data, encoding),
         {:ok, txin, _rest} <- Serializable.parse(%__MODULE__{}, data)
    do
      {:ok, txin}
    end
  end

  @doc """
  Parses the given binary into a `t:BSV.TxIn.t/0`.

  As `from_binary/2` but returns the result or raises an exception.
  """
  @spec from_binary!(binary(), keyword()) :: t()
  def from_binary!(data, opts \\ []) when is_binary(data) do
    case from_binary(data, opts) do
      {:ok, txin} ->
        txin

      {:error, error} ->
        raise BSV.DecodeError, error
    end
  end

  @doc """
  Returns the number of bytes of the given `t:BSV.TxIn.t/0`.
  """
  @spec get_size(t()) :: non_neg_integer()
  def get_size(%__MODULE__{} = txin),
    do: to_binary(txin) |> byte_size()

  @doc """
  Serialises the given `t:BSV.TxIn.t/0` into a binary.

  ## Options

  The accepted options are:

  * `:encoding` - Optionally encode the binary with either the `:base64` or `:hex` encoding scheme.
  """
  @spec to_binary(t()) :: binary()
  def to_binary(%__MODULE__{} = txin, opts \\ []) do
    encoding = Keyword.get(opts, :encoding)

    txin
    |> Serializable.serialize()
    |> encode(encoding)
  end


  defimpl Serializable do
    @impl true
    def parse(txin, data) do
      with {:ok, outpoint, data} <- Serializable.parse(%OutPoint{}, data),
           {:ok, script, data} <- VarInt.parse_data(data),
           <<sequence::little-32, rest::binary>> = data
      do
        script = case OutPoint.is_null?(outpoint) do
          false -> Script.from_binary!(script)
          true -> %Script{coinbase: script}
        end

        {:ok, struct(txin, [
          outpoint: outpoint,
          script: script,
          sequence: sequence
        ]), rest}
      end
    end

    @impl true
    def serialize(%{outpoint: outpoint, script: script, sequence: sequence}) do
      outpoint_data = Serializable.serialize(outpoint)
      script_data = script
      |> Script.to_binary()
      |> VarInt.encode_binary()

      <<
        outpoint_data::binary,
        script_data::binary,
        sequence::little-32
      >>
    end
  end

end