defmodule Stellar.TxBuild.Preconditions do
@moduledoc """
`Preconditions` struct definition.
"""
alias Stellar.TxBuild.{
TimeBounds,
LedgerBounds,
SequenceNumber,
SignerKey,
OptionalSequenceNumber
}
alias StellarBase.XDR.TimeBounds, as: TimeBoundsXDR
alias StellarBase.XDR.{
Preconditions,
PreconditionsV2,
PreconditionType,
Duration,
SignerKeyList,
TimePoint,
UInt32,
Void
}
@behaviour Stellar.TxBuild.XDR
@type precond_v2 :: [
time_bounds: TimeBounds.t(),
ledger_bounds: LedgerBounds.t(),
min_seq_num: OptionalSequenceNumber.t(),
min_seq_age: non_neg_integer(),
min_seq_ledger_gap: non_neg_integer(),
extra_signers: list(SignerKey.t())
]
@type valid :: non_neg_integer() | struct() | list(struct())
@type validation :: {:ok, valid()} | {:error, atom()}
@type type :: :precond_v2 | :precond_time | :none
@type preconditions :: precond_v2() | TimeBounds.t() | nil
@type t :: %__MODULE__{type: type(), preconditions: preconditions()}
defstruct [:type, :preconditions]
@impl true
def new(preconditions \\ [], opts \\ [])
def new([], _opts) do
%__MODULE__{type: :none, preconditions: nil}
end
def new([time_bounds: %TimeBounds{} = time_bounds], _opts) do
%__MODULE__{type: :precond_time, preconditions: time_bounds}
end
def new(preconditions, _opts) do
time_bounds = Keyword.get(preconditions, :time_bounds)
ledger_bounds = Keyword.get(preconditions, :ledger_bounds)
min_seq_num = Keyword.get(preconditions, :min_seq_num)
min_seq_age = Keyword.get(preconditions, :min_seq_age, 0)
min_seq_ledger_gap = Keyword.get(preconditions, :min_seq_ledger_gap, 0)
extra_signers = Keyword.get(preconditions, :extra_signers, [])
with {:ok, time_bounds} <- validate_time_bounds(time_bounds),
{:ok, ledger_bounds} <- validate_ledger_bounds(ledger_bounds),
{:ok, min_seq_num} <- validate_optional_min_seq_num(min_seq_num),
{:ok, min_seq_age} <- validate_min_seq_age(min_seq_age),
{:ok, min_seq_ledger_gap} <- validate_min_seq_ledger_gap(min_seq_ledger_gap),
{:ok, extra_signers} <- validate_extra_signers([], extra_signers) do
%__MODULE__{
type: :precond_v2,
preconditions: [
time_bounds: time_bounds,
ledger_bounds: ledger_bounds,
min_seq_num: min_seq_num,
min_seq_age: min_seq_age,
min_seq_ledger_gap: min_seq_ledger_gap,
extra_signers: extra_signers
]
}
end
end
@impl true
def to_xdr(%__MODULE__{type: :none, preconditions: nil}) do
preconditions_type = PreconditionType.new(:PRECOND_NONE)
Preconditions.new(Void.new(), preconditions_type)
end
def to_xdr(%__MODULE__{
type: :precond_time,
preconditions: %TimeBounds{min_time: min_time, max_time: max_time}
}) do
preconditions_type = PreconditionType.new(:PRECOND_TIME)
min_time = TimePoint.new(min_time)
max_time = TimePoint.new(max_time)
min_time
|> TimeBoundsXDR.new(max_time)
|> Preconditions.new(preconditions_type)
end
def to_xdr(%__MODULE__{
type: :precond_v2,
preconditions: [
time_bounds: time_bounds,
ledger_bounds: ledger_bounds,
min_seq_num: min_seq_num,
min_seq_age: min_seq_age,
min_seq_ledger_gap: min_seq_ledger_gap,
extra_signers: extra_signers
]
}) do
preconditions_type = PreconditionType.new(:PRECOND_V2)
time_bounds = TimeBounds.to_xdr(time_bounds)
ledger_bounds = LedgerBounds.to_xdr(ledger_bounds)
min_seq_num = OptionalSequenceNumber.to_xdr(min_seq_num)
min_seq_age = Duration.new(min_seq_age)
min_seq_ledger_gap = UInt32.new(min_seq_ledger_gap)
extra_signers_list = get_extra_signers_list(extra_signers)
time_bounds
|> PreconditionsV2.new(
ledger_bounds,
min_seq_num,
min_seq_age,
min_seq_ledger_gap,
extra_signers_list
)
|> Preconditions.new(preconditions_type)
end
@spec get_extra_signers_list(extra_signers :: list(SignerKey.t())) :: SignerKeyList.t()
defp get_extra_signers_list(extra_signers) do
extra_signers
|> Enum.map(&SignerKey.to_xdr(&1))
|> SignerKeyList.new()
end
@spec validate_time_bounds(time_bounds :: TimeBounds.t()) :: validation()
defp validate_time_bounds(%TimeBounds{} = time_bounds), do: {:ok, time_bounds}
defp validate_time_bounds(_time_bounds), do: {:error, :invalid_time_bounds}
@spec validate_ledger_bounds(ledger_bounds :: LedgerBounds.t()) :: validation()
defp validate_ledger_bounds(%LedgerBounds{} = ledger_bounds),
do: {:ok, ledger_bounds}
defp validate_ledger_bounds(_ledger_bounds), do: {:error, :invalid_ledger_bounds}
@spec validate_optional_min_seq_num(min_seq_num :: SequenceNumber.t() | nil) :: validation()
defp validate_optional_min_seq_num(nil),
do: {:ok, OptionalSequenceNumber.new()}
defp validate_optional_min_seq_num(%SequenceNumber{} = min_seq_num),
do:
min_seq_num
|> OptionalSequenceNumber.new()
|> (&{:ok, &1}).()
defp validate_optional_min_seq_num(_min_seq_num), do: {:error, :invalid_min_seq_num}
@spec validate_min_seq_age(min_seq_age :: non_neg_integer()) :: validation()
defp validate_min_seq_age(min_seq_age)
when is_integer(min_seq_age) and min_seq_age >= 0,
do: {:ok, min_seq_age}
defp validate_min_seq_age(_min_seq_age), do: {:error, :invalid_min_seq_age}
@spec validate_min_seq_ledger_gap(min_seq_ledger_gap :: non_neg_integer()) :: validation()
defp validate_min_seq_ledger_gap(min_seq_ledger_gap)
when is_integer(min_seq_ledger_gap) and min_seq_ledger_gap >= 0,
do: {:ok, min_seq_ledger_gap}
defp validate_min_seq_ledger_gap(_min_seq_ledger_gap), do: {:error, :invalid_min_seq_ledger_gap}
@spec validate_extra_signers(
response :: list(String.t()),
extra_signers :: list(String.t())
) :: validation()
def validate_extra_signers(response, []), do: {:ok, response}
def validate_extra_signers(response, [h | t]) do
case SignerKey.new(h) do
%SignerKey{} = signer_key -> validate_extra_signers(response ++ [signer_key], t)
_error -> {:error, :invalid_extra_signers}
end
end
def validate_extra_signers(_response, _extra_signers),
do: {:error, :invalid_extra_signers}
end