defmodule BitcoinLib.Block.Transactions do
@moduledoc """
Converts a list of bytes into a list of transactions
"""
alias BitcoinLib.Signing.Psbt.CompactInteger
alias BitcoinLib.Transaction
@spec decode(binary()) :: {:ok, list()} | {:error, binary()}
def decode(transactions_data) do
%CompactInteger{
remaining: remaining,
value: transaction_count
} = CompactInteger.extract_from(transactions_data)
with {:ok, transactions} <- scan(transaction_count, remaining) do
{:ok, transactions}
else
{:error, message} -> {:error, message}
end
end
defp scan(expected_transaction_count, remaining) do
with {:ok, transactions} <- extract_transactions(remaining) do
validate(expected_transaction_count, transactions)
else
{:error, message} -> {:error, message}
end
end
defp extract_transactions(remaining) do
with {:ok, coinbase, remaining} <- extract_coinbase(remaining) do
extract_next_transaction([coinbase], remaining)
else
{:error, message} -> {:error, message}
end
end
defp extract_coinbase(remaining) do
Transaction.decode(remaining)
end
defp extract_next_transaction(transactions, <<>>), do: {:ok, transactions |> Enum.reverse()}
defp extract_next_transaction(transactions, remaining) do
with {:ok, transaction, remaining} <- Transaction.decode(remaining) do
extract_next_transaction([transaction | transactions], remaining)
else
{:error, message} ->
transaction_count = Enum.count(transactions)
{:error, "#{transaction_count} transactions in the block, #{message}"}
end
end
defp validate(expected_transaction_count, transactions) do
case Enum.count(transactions) do
^expected_transaction_count ->
{:ok, transactions}
transaction_count ->
{:error, "got #{transaction_count} transactions, expected #{expected_transaction_count}"}
end
end
end