Packages
elixium_core
0.2.3
0.6.3
0.6.2
0.6.1
0.6.0
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.1
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.4
0.1.3
The core package for the Elixium blockchain, containing all the modules needed to run the chain
Current section
Files
Jump to
Current section
Files
lib/validator.ex
defmodule Elixium.Validator do
alias Elixium.Blockchain.Block
alias Elixium.Utilities
alias Elixium.KeyPair
alias Decimal, as: D
@moduledoc """
Responsible for implementing the consensus rules to all blocks and transactions
"""
@doc """
A block is considered valid if the index is greater than the index of the previous block,
the previous_hash is equal to the hash of the previous block, and the hash of the block,
when recalculated, is the same as what the listed block hash is
"""
@spec is_block_valid?(Block, list, number) :: :ok | {:error, any}
def is_block_valid?(block, chain, difficulty) do
last_block = List.first(chain)
with :ok <- valid_index(block.index, last_block.index),
:ok <- valid_prev_hash?(block.previous_hash, last_block.hash),
:ok <- valid_hash?(block),
:ok <- valid_coinbase?(block),
:ok <- valid_transactions?(block),
:ok <- valid_difficulty?(block, difficulty) do
:ok
else
err -> err
end
end
@spec valid_index(number, number) :: :ok | {:error, {:invalid_index, number, number}}
defp valid_index(index, prev_index) when index > prev_index, do: :ok
defp valid_index(index, prev_index) when index <= prev_index,
do: {:error, {:invalid_index, prev_index, index}}
@spec valid_prev_hash?(String.t(), String.t()) ::
:ok | {:error, {:wrong_hash, {:doesnt_match_last, String.t(), String.t()}}}
defp valid_prev_hash?(prev_hash, last_block_hash) when prev_hash == last_block_hash, do: :ok
defp valid_prev_hash?(prev_hash, last_block_hash) when prev_hash != last_block_hash,
do: {:error, {:wrong_hash, {:doesnt_match_last, prev_hash, last_block_hash}}}
@spec valid_hash?(Block) :: :ok | {:error, {:wrong_hash, {:too_high, String.t(), number}}}
defp valid_hash?(%{
index: index,
previous_hash: previous_hash,
timestamp: timestamp,
nonce: nonce,
hash: hash,
merkle_root: merkle_root,
difficulty: difficulty
}) do
with :ok <- compare_hash({index, previous_hash, timestamp, nonce, merkle_root}, hash),
:ok <- (fn -> if Block.hash_beat_target?(%{hash: hash, difficulty: difficulty}), do: :ok, else: {:error, {:wrong_hash, {:too_high, hash, difficulty}}} end).()
do
:ok
else
err -> err
end
end
@spec compare_hash({number, String.t(), String.t(), number, String.t()}, String.t()) ::
:ok | {:error, {:wrong_hash, {:doesnt_match_provided, String.t(), String.t()}}}
defp compare_hash({index, previous_hash, timestamp, nonce, merkle_root}, hash) do
computed =
[Integer.to_string(index), previous_hash, timestamp, Integer.to_string(nonce), merkle_root]
|> Utilities.sha3_base16()
if computed == hash do
:ok
else
{:error, {:wrong_hash, {:doesnt_match_provided, computed, hash}}}
end
end
@spec valid_coinbase?(Block) :: :ok | {:error, :no_coinbase}
def valid_coinbase?(%{transactions: transactions, index: block_index}) do
coinbase = List.first(transactions)
with :ok <- (&if(&1 != nil, do: :ok, else: {:error, :no_coinbase})).(coinbase),
:ok <- is_coinbase?(coinbase),
:ok <- appropriate_coinbase_output?(transactions, block_index) do
:ok
else
err -> err
end
end
@spec valid_transaction?(Transaction) :: boolean
def valid_transaction?(%{inputs: inputs}) do
inputs
|> Enum.map(fn input ->
case {Base.decode16(input.addr), Base.decode16(input.signature)} do
{{:ok, pub}, {:ok, sig}} -> KeyPair.verify_signature(pub, sig, input.txoid)
_ -> false
end
end)
|> Enum.all?(&(&1 == true))
end
@spec valid_transactions?(Block) :: :ok | {:error, :invalid_inputs}
def valid_transactions?(%{transactions: transactions}) do
if Enum.all?(transactions, &valid_transaction?(&1)), do: :ok, else: {:error, :invalid_inputs}
end
@spec is_coinbase?(Transaction) :: :ok | {:error, {:not_coinbase, String.t()}}
defp is_coinbase?(tx) do
if tx.txtype == "COINBASE", do: :ok, else: {:error, {:not_coinbase, tx.txtype}}
end
@spec appropriate_coinbase_output?(list, number) :: :ok | {:error, :invalid_coinbase}
defp appropriate_coinbase_output?([coinbase | transactions], block_index) do
total_fees = Block.total_block_fees(transactions)
reward = Block.calculate_block_reward(block_index)
amount = List.first(coinbase.outputs).amount
if D.equal?(D.add(total_fees, reward), amount) do
:ok
else
{:error, {:invalid_coinbase, total_fees, reward, amount}}
end
end
@spec valid_difficulty?(Block, number) :: :ok | {:error, {:invalid_difficulty, number, number}}
def valid_difficulty?(%{difficulty: difficulty}, diff) do
if difficulty == diff, do: :ok, else: {:error, {:invalid_difficulty, difficulty, diff}}
end
end