Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/aecore/config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ bytes_per_token = case System.get_env("BYTES_PER_TOKEN") do
end

config :aecore, :tx_data,
lock_time_coinbase: 10,
miner_fee_bytes_per_token: bytes_per_token,
pool_fee_bytes_per_token: 100
1 change: 1 addition & 0 deletions apps/aecore/config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ config :aecore, :peers,
peers_max_count: 4

config :aecore, :tx_data,
lock_time_coinbase: 0,
miner_fee_bytes_per_token: 100,
pool_fee_bytes_per_token: 100
4 changes: 2 additions & 2 deletions apps/aecore/lib/aecore/chain/block_validation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ defmodule Aecore.Chain.BlockValidation do

cond do
tx_has_valid_nonce && from_account_has_necessary_balance ->
from_acc_new_state = %{balance: -(tx.data.value + tx.data.fee), nonce: 1}
to_acc_new_state = %{balance: tx.data.value, nonce: 0}
from_acc_new_state = %{balance: -(tx.data.value + tx.data.fee), nonce: 1, locked: []}
to_acc_new_state = %{balance: tx.data.value, nonce: 0, locked: []}
chain_state_changes = %{tx.data.from_acc => from_acc_new_state, tx.data.to_acc => to_acc_new_state}
updated_chain_state = ChainState.calculate_chain_state(chain_state_changes, chain_state)
{true, updated_chain_state}
Expand Down
87 changes: 71 additions & 16 deletions apps/aecore/lib/aecore/chain/chain_state.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ defmodule Aecore.Chain.ChainState do
The chain state is a map, telling us what amount of tokens each account has.
"""

require Logger

@doc """
Calculates the balance of each account mentioned
in the transactions a single block, returns a map with the
accounts as key and their balance as value.
"""
@spec calculate_block_state(list()) :: map()
def calculate_block_state(txs) do
@spec calculate_block_state(list(), integer()) :: map()
def calculate_block_state(txs, latest_block_height) do
block_state = %{}

block_state =
Expand All @@ -20,13 +22,16 @@ defmodule Aecore.Chain.ChainState do
transaction.data.from_acc != nil ->
update_block_state(block_state, transaction.data.from_acc,
-(transaction.data.value + transaction.data.fee),
transaction.data.nonce)
transaction.data.nonce, transaction.data.lock_time_block, false)

true ->
block_state
end

update_block_state(updated_block_state, transaction.data.to_acc, transaction.data.value, 0)
add_to_locked = latest_block_height + 1 <= transaction.data.lock_time_block

update_block_state(updated_block_state, transaction.data.to_acc, transaction.data.value,
0, transaction.data.lock_time_block, add_to_locked)
end

reduce_map_list(block_state)
Expand Down Expand Up @@ -68,38 +73,86 @@ defmodule Aecore.Chain.ChainState do

@spec calculate_total_tokens(map()) :: integer()
def calculate_total_tokens(chain_state) do
chain_state
|> Enum.map(fn{_account, data} -> data.balance end)
|> Enum.sum()
Enum.reduce(chain_state, {0, 0, 0}, fn({_account, data}, acc) ->
{total_tokens, total_unlocked_tokens, total_locked_tokens} = acc
locked_tokens =
Enum.reduce(data.locked, 0, fn(%{amount: amount}, locked_sum) ->
locked_sum + amount
end)
new_total_tokens = total_tokens + data.balance + locked_tokens
new_total_unlocked_tokens = total_unlocked_tokens + data.balance
new_total_locked_tokens = total_locked_tokens + locked_tokens

{new_total_tokens, new_total_unlocked_tokens, new_total_locked_tokens}
end)
end

@spec validate_chain_state(map()) :: boolean()
def validate_chain_state(chain_state) do
chain_state
chain_state
|> Enum.map(fn{_account, data} -> Map.get(data, :balance, 0) >= 0 end)
|> Enum.all?()
end

@spec update_block_state(map(), binary(), integer(), integer()) :: map()
defp update_block_state(block_state, account, value, nonce) do
@spec update_chain_state_locked(map(), integer()) :: map()
def update_chain_state_locked(chain_state, new_block_height) do
Enum.reduce(chain_state, %{}, fn({account, %{balance: balance, nonce: nonce, locked: locked}}, acc) ->
{unlocked_amount, updated_locked} =
Enum.reduce(locked, {0, []}, fn(%{amount: amount, block: lock_time_block}, {amount_update_value, updated_locked}) ->
cond do
lock_time_block > new_block_height ->
{amount_update_value, updated_locked ++ [%{amount: amount, block: lock_time_block}]}
lock_time_block == new_block_height ->
{amount_update_value + amount, updated_locked}

true ->
Logger.error(fn ->
"Update chain state locked:
new block height (#{new_block_height}) greater than lock time block (#{lock_time_block})"
end)

{amount_update_value, updated_locked}
end
end)

Map.put(acc, account, %{balance: balance + unlocked_amount, nonce: nonce, locked: updated_locked})
end)
end

@spec update_block_state(map(), binary(), integer(), integer(), integer(), boolean()) :: map()
defp update_block_state(block_state, account, value, nonce, lock_time_block, add_to_locked) do
block_state_filled_empty =
cond do
!Map.has_key?(block_state, account) ->
Map.put(block_state, account, %{balance: 0, nonce: 0})
Map.put(block_state, account, %{balance: 0, nonce: 0, locked: []})

true ->
block_state
end

new_nonce =
cond do
new_balance = if(add_to_locked) do
block_state_filled_empty[account].balance
else
block_state_filled_empty[account].balance + value
end

new_nonce = cond do
block_state_filled_empty[account].nonce < nonce ->
nonce
true ->
block_state_filled_empty[account].nonce
end

new_account_state = %{balance: block_state_filled_empty[account].balance + value,
nonce: new_nonce}
new_locked = if(add_to_locked) do
block_state_filled_empty[account].locked ++ [%{amount: value, block: lock_time_block}]
else
block_state_filled_empty[account].locked
end

new_account_state = %{balance: new_balance,
nonce: new_nonce,
locked: new_locked}

Map.put(block_state_filled_empty, account, new_account_state)
end

Expand All @@ -123,7 +176,9 @@ defmodule Aecore.Chain.ChainState do
v1.nonce
end

%{balance: v1.balance + v2.balance, nonce: new_nonce}
%{balance: v1.balance + v2.balance,
nonce: new_nonce,
locked: v1.locked ++ v2.locked}
end)
end
end
26 changes: 14 additions & 12 deletions apps/aecore/lib/aecore/chain/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ defmodule Aecore.Chain.Worker do
def init(_) do
genesis_block_hash = BlockValidation.block_header_hash(Block.genesis_block().header)
genesis_block_map = %{genesis_block_hash => Block.genesis_block()}
genesis_chain_state = ChainState.calculate_block_state(Block.genesis_block().txs)
genesis_chain_state =
ChainState.calculate_block_state(Block.genesis_block().txs, Block.genesis_block().header.height)
chain_states = %{genesis_block_hash => genesis_chain_state}
txs_index = calculate_block_acc_txs_info(Block.genesis_block())

Expand All @@ -44,7 +45,7 @@ defmodule Aecore.Chain.Worker do
GenServer.call(__MODULE__, :top_block_hash)
end

@spec top_height() :: integer()
@spec top_height() :: integer()
def top_height() do
GenServer.call(__MODULE__, :top_height)
end
Expand Down Expand Up @@ -74,12 +75,14 @@ defmodule Aecore.Chain.Worker do
def add_block(%Block{} = block) do
prev_block = get_block(block.header.prev_hash) #TODO: catch error
prev_block_chain_state = chain_state(block.header.prev_hash)
new_block_state = ChainState.calculate_block_state(block.txs)
new_block_state = ChainState.calculate_block_state(block.txs, prev_block.header.height)
new_chain_state = ChainState.calculate_chain_state(new_block_state, prev_block_chain_state)
new_chain_state_locked_amounts =
ChainState.update_chain_state_locked(new_chain_state, prev_block.header.height + 1)

blocks_for_difficulty_calculation = get_blocks(block.header.prev_hash, Difficulty.get_number_of_blocks())
BlockValidation.validate_block!(block, prev_block, new_chain_state, blocks_for_difficulty_calculation)
add_validated_block(block, new_chain_state)
BlockValidation.validate_block!(block, prev_block, new_chain_state_locked_amounts, blocks_for_difficulty_calculation)
add_validated_block(block, new_chain_state_locked_amounts)
end

@spec add_validated_block(%Block{}, map()) :: :ok
Expand Down Expand Up @@ -142,9 +145,9 @@ defmodule Aecore.Chain.Worker do
{:reply, has_block, state}
end

def handle_call({:add_validated_block, %Block{} = new_block, new_chain_state},
_from,
%{blocks_map: blocks_map, chain_states: chain_states,
def handle_call({:add_validated_block, %Block{} = new_block, new_chain_state},
_from,
%{blocks_map: blocks_map, chain_states: chain_states,
txs_index: txs_index, top_height: top_height} = state) do
new_block_txs_index = calculate_block_acc_txs_info(new_block)
new_txs_index = update_txs_index(txs_index, new_block_txs_index)
Expand All @@ -160,13 +163,13 @@ defmodule Aecore.Chain.Worker do
end)
## Store new block to disk
Persistence.write_block_by_hash(new_block)
state_update1 = %{state | blocks_map: updated_blocks_map,
chain_states: updated_chain_states,
state_update1 = %{state | blocks_map: updated_blocks_map,
chain_states: updated_chain_states,
txs_index: new_txs_index}
if top_height < new_block.header.height do
## We send the block to others only if it extends the longest chain
Peers.broadcast_block(new_block)
{:reply, :ok, %{state_update1 | top_hash: new_block_hash,
{:reply, :ok, %{state_update1 | top_hash: new_block_hash,
top_height: new_block.header.height}}
else
{:reply, :ok, state_update1}
Expand All @@ -184,7 +187,6 @@ defmodule Aecore.Chain.Worker do
def terminate(_, state) do
Persistence.store_state(state)
Logger.warn("Terminting, state was stored on disk ...")

end

defp calculate_block_acc_txs_info(block) do
Expand Down
6 changes: 3 additions & 3 deletions apps/aecore/lib/aecore/keys/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ defmodule Aecore.Keys.Worker do
- value: The amount of a transaction

"""
@spec sign_tx(binary(), integer(), integer(), integer()) :: {:ok, %SignedTx{}}
def sign_tx(to_acc, value, nonce, fee) do
@spec sign_tx(binary(), integer(), integer(), integer(), integer()) :: {:ok, %SignedTx{}}
def sign_tx(to_acc, value, nonce, fee, lock_time_block \\ 0) do
{:ok, from_acc} = pubkey()
{:ok, tx_data} = TxData.create(from_acc, to_acc, value, nonce, fee)
{:ok, tx_data} = TxData.create(from_acc, to_acc, value, nonce, fee, lock_time_block)
{:ok, signature} = sign(tx_data)
signed_tx = %SignedTx{data: tx_data, signature: signature}
{:ok, signed_tx}
Expand Down
16 changes: 11 additions & 5 deletions apps/aecore/lib/aecore/miner/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,14 @@ defmodule Aecore.Miner.Worker do
Application.put_env(:aecore, :tx_data, miner_fee_bytes_per_token: bytes)
end

def get_coinbase_transaction(to_acc, total_fees) do
def get_coinbase_transaction(to_acc, total_fees, lock_time_block) do
tx_data = %TxData{
from_acc: nil,
to_acc: to_acc,
value: @coinbase_transaction_value + total_fees,
nonce: 0,
fee: 0
fee: 0,
lock_time_block: lock_time_block
}

%SignedTx{data: tx_data, signature: nil}
Expand Down Expand Up @@ -182,12 +183,17 @@ defmodule Aecore.Miner.Worker do
{_, pubkey} = Keys.pubkey()

total_fees = calculate_total_fees(valid_txs_by_fee)
valid_txs = [get_coinbase_transaction(pubkey, total_fees) | valid_txs_by_fee]
valid_txs = [get_coinbase_transaction(pubkey, total_fees,
top_block.header.height + 1 +
Application.get_env(:aecore, :tx_data)[:lock_time_coinbase]) | valid_txs_by_fee]
root_hash = BlockValidation.calculate_root_hash(valid_txs)

new_block_state = ChainState.calculate_block_state(valid_txs)
new_block_state =
ChainState.calculate_block_state(valid_txs, top_block.header.height)
new_chain_state = ChainState.calculate_chain_state(new_block_state, chain_state)
chain_state_hash = ChainState.calculate_chain_state_hash(new_chain_state)
new_chain_state_locked_amounts =
ChainState.update_chain_state_locked(new_chain_state, top_block.header.height + 1)
chain_state_hash = ChainState.calculate_chain_state_hash(new_chain_state_locked_amounts)

top_block_hash = BlockValidation.block_header_hash(top_block.header)

Expand Down
14 changes: 10 additions & 4 deletions apps/aecore/lib/aecore/structures/tx_data.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ defmodule Aecore.Structures.TxData do
"""

alias Aecore.Structures.TxData

@type tx_data() :: %TxData{}

@doc """
Expand All @@ -15,11 +16,16 @@ defmodule Aecore.Structures.TxData do
- to_acc: To account is the public address of the account receiving the transaction
- value: The amount of a transaction
"""
defstruct [:nonce, :from_acc, :to_acc, :value, :fee]
defstruct [:nonce, :from_acc, :to_acc, :value, :fee, :lock_time_block]
use ExConstructor

@spec create(binary(), binary(), integer(), integer(), integer()) :: {:ok, %TxData{}}
def create(from_acc, to_acc, value, nonce, fee) do
{:ok, %TxData{from_acc: from_acc, to_acc: to_acc, value: value, nonce: nonce, fee: fee}}
@spec create(binary(), binary(), integer(), integer(), integer(), integer()) :: {:ok, %TxData{}}
def create(from_acc, to_acc, value, nonce, fee, lock_time_block \\ 0) do
{:ok, %TxData{from_acc: from_acc,
to_acc: to_acc,
value: value,
nonce: nonce,
fee: fee,
lock_time_block: lock_time_block}}
end
end
Loading