Current section

Files

Jump to
elixium_core lib blockchain.ex
Raw

lib/blockchain.ex

defmodule Elixium.Blockchain do
alias Elixium.Blockchain.Block
alias Elixium.Store.Ledger
alias Elixium.Store.Utxo
@target_blocktime 120
@diff_rebalance_offset 10_080
@moduledoc """
Wrapper functions for interacting with the blockchain on a high level
"""
@doc """
Creates a List with a genesis block in it or returns the existing blockchain
"""
@spec initialize :: list
def initialize do
if Ledger.empty?() do
add_block(Block.initialize())
else
Ledger.retrieve_chain()
end
end
@doc """
Adds the latest block to the beginning of the blockchain
"""
@spec add_block(Block) :: none
def add_block(block) do
Ledger.append_block(block)
Utxo.update_with_transactions(block.transactions)
end
@doc """
Based on the canonical chain, recalculate the difficulty at which blocks should
be mined. Returns a modifier which must be added to the current difficulty.
For example, if the current difficulty is 4.0, this function may return something
like 0.43, and the new difficulty should become 4.43. Conversely, this may also
return a negative number, which should be treated the same way: a current
difficulty of 4.0 combined with a return value of -0.43 should produce a new
difficulty of 3.57
"""
@spec recalculate_difficulty :: number
def recalculate_difficulty do
count = Ledger.count_blocks() - 1
if count >= @diff_rebalance_offset - 1 do
last_block = Ledger.last_block
{:ok, last_time, _} = DateTime.from_iso8601(last_block.timestamp)
{:ok, first_time, _} =
last_block.index - rem(last_block.index, @diff_rebalance_offset)
|> Ledger.block_at_height()
|> Map.get(:timestamp)
|> DateTime.from_iso8601()
do_calculate_difficulty(first_time, last_time)
else
0
end
end
@doc """
Same as recalculate_difficulty/0 except that instead of reading values from
the ledger, the blocks at the start and end of an epoch are explicitly passed
in. Although this will work properly otherwise, it is intended that blocks
passed in have an index which is a multiple of the diff_rebalance_offset
"""
@spec recalculate_difficulty(Block, Block) :: number
def recalculate_difficulty(epoch_start, epoch_end) do
if epoch_end >= @diff_rebalance_offset - 1 do
{:ok, epoch_end_time, _} = DateTime.from_iso8601(epoch_end.timestamp)
{:ok, epoch_start_time, _} = DateTime.from_iso8601(epoch_start.timestamp)
do_calculate_difficulty(epoch_start_time, epoch_end_time)
else
0
end
end
def do_calculate_difficulty(epoch_start, epoch_end) do
avg_secs_per_block =
epoch_end
|> DateTime.diff(epoch_start, :microseconds)
|> Kernel./(1_000_000)
|> Kernel./(@diff_rebalance_offset)
@target_blocktime
|> div(avg_secs_per_block)
|> :math.log()
|> Kernel./(:math.log(16))
end
def diff_rebalance_offset, do: @diff_rebalance_offset
end