Current section

Files

Jump to
elixium_core lib store utxo.ex
Raw

lib/store/utxo.ex

defmodule Elixium.Store.Utxo do
use Elixium.Store
require IEx
@moduledoc """
Provides an interface for interacting with the UTXOs stored in level db
"""
@store_dir ".utxo"
@ets_name :utxo
@type utxo() :: %{
txoid: String.t(),
addr: String.t(),
amount: number,
signature: String.t() | none()
}
def initialize do
initialize(@store_dir)
:ets.new(@ets_name, [:ordered_set, :public, :named_table])
end
@doc """
Add a utxo to leveldb, indexing it by its txoid
"""
@spec add_utxo(utxo()) :: :ok | {:error, any}
def add_utxo(utxo) do
transact @store_dir do
&Exleveldb.put(&1, String.to_atom(utxo.txoid), :erlang.term_to_binary(utxo))
end
:ets.insert(@ets_name, {utxo.txoid, utxo.addr, utxo})
end
@spec remove_utxo(String.t()) :: :ok | {:error, any}
def remove_utxo(txoid) do
transact @store_dir do
&Exleveldb.delete(&1, String.to_atom(txoid))
end
:ets.delete(@ets_name, txoid)
end
@doc """
Retrieve a UTXO by its txoid
"""
@spec retrieve_utxo(String.t()) :: map
def retrieve_utxo(txoid) do
case :ets.lookup(@ets_name, txoid) do
[] ->
transact @store_dir do
fn ref ->
{:ok, utxo} = Exleveldb.get(ref, String.to_atom(txoid))
:erlang.binary_to_term(utxo)
end
end
[{_txoid, _addr, utxo}] -> utxo
end
end
@doc """
Check if a UTXO is currently in the pool
"""
@spec in_pool?(utxo()) :: true | false
def in_pool?(%{txoid: txoid}), do: retrieve_utxo(txoid) != []
@spec retrieve_all_utxos :: list(utxo())
def retrieve_all_utxos do
# It might be better to get from ets here, but there might be the issue
# that ets wont have an UTXO that the store does, causing a block to be
# invalidated somewhere down the line even if the inputs are all valid.
transact @store_dir do
&Exleveldb.map(&1, fn {_, utxo} -> :erlang.binary_to_term(utxo) end)
end
end
@spec update_with_transactions(list) :: :ok | {:error, any}
def update_with_transactions(transactions) do
transact @store_dir do
fn ref ->
remove =
transactions
|> Enum.flat_map(& &1.inputs)
|> Enum.map(&{:delete, &1.txoid})
add =
transactions
|> Enum.flat_map(& &1.outputs)
|> Enum.map(&{:put, &1.txoid, :erlang.term_to_binary(&1)})
Exleveldb.write(ref, Enum.concat(remove, add))
transactions
|> Enum.flat_map(& &1.inputs)
|> Enum.each(&(:ets.delete(@ets_name, &1.txoid)))
transactions
|> Enum.flat_map(& &1.outputs)
|> Enum.each(&(:ets.insert(@ets_name, {&1.txoid, &1.addr, &1})))
end
end
end
@doc """
Return a list of UTXOs that a given address (public key) can use as inputs
"""
@spec find_by_address(String.t()) :: list(utxo())
def find_by_address(public_key) do
case :ets.match(@ets_name, {'_', public_key, '$1'}) do
[] -> do_find_by_address_from_store(public_key)
utxos -> utxos
end
end
defp do_find_by_address_from_store(public_key) do
transact @store_dir do
fn ref ->
ref
|> Exleveldb.map(fn {_, utxo} -> :erlang.binary_to_term(utxo) end)
|> Enum.filter(&(&1.addr == public_key))
end
end
end
end