Skip to content

Commit

Permalink
[GH-#125] Fixed bug when substracting amounts from chain state; Refac…
Browse files Browse the repository at this point in the history
…tored code and tests
  • Loading branch information
cheezus1 committed Nov 30, 2017
1 parent 2284e9a commit 73d6393
Show file tree
Hide file tree
Showing 17 changed files with 235 additions and 102 deletions.
3 changes: 3 additions & 0 deletions apps/aecore/config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,6 @@ config :aecore, :pow,
config :aecore, :peers,
peers_target_count: 3,
peers_max_count: 4

config :aecore, :tx_data,
lock_time_block: 10
4 changes: 3 additions & 1 deletion apps/aecore/config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ end

config :aecore, :persistence,
table: Path.absname(persistence_path)

config :aecore, :pow,
nif_path: Path.absname("apps/aecore/priv/aec_pow_cuckoo20_nif"),
genesis_header: %{
Expand Down Expand Up @@ -64,3 +64,5 @@ config :aecore, :peers,
peers_target_count: 2,
peers_max_count: 4

config :aecore, :tx_data,
lock_time_block: 0
48 changes: 30 additions & 18 deletions apps/aecore/lib/aecore/chain/chain_state.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ defmodule Aecore.Chain.ChainState do
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,14 +20,17 @@ 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.lock_time_block)
transaction.data.nonce, transaction.data.lock_time_block, true)

true ->
block_state
end

update_block_state(updated_block_state, transaction.data.to_acc,
transaction.data.value, 0, transaction.data.lock_time_block)
add_to_amount = latest_block_height + 1 !=
transaction.data.lock_time_block - Application.get_env(:aecore, :tx_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_amount)
end

reduce_map_list(block_state)
Expand Down Expand Up @@ -85,20 +88,17 @@ defmodule Aecore.Chain.ChainState do
|> Enum.all?()
end

@spec substract_locked_amounts_from_chain_state(map(), integer()) :: map()
def substract_locked_amounts_from_chain_state(chain_state, new_block_height) 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) ->
{locked_amount, updated_locked} =
{locked_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 ->
if(new_block_height == lock_time_block - 10) do
{amount_update_value - amount, updated_locked ++ [%{amount: amount, block: lock_time_block}]}
else
{amount_update_value, updated_locked ++ [%{amount: amount, block: lock_time_block}]}
end
{amount_update_value, updated_locked ++ [%{amount: amount, block: lock_time_block}]}
lock_time_block == new_block_height ->
{amount_update_value + amount, updated_locked}

true ->
{amount_update_value, updated_locked}
end
Expand All @@ -108,8 +108,8 @@ defmodule Aecore.Chain.ChainState do
end)
end

@spec update_block_state(map(), binary(), integer(), integer(), integer()) :: map()
defp update_block_state(block_state, account, value, nonce, lock_time_block) do
@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_amount) do
block_state_filled_empty =
cond do
!Map.has_key?(block_state, account) ->
Expand All @@ -119,6 +119,12 @@ defmodule Aecore.Chain.ChainState do
block_state
end

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

new_nonce = cond do
block_state_filled_empty[account].nonce < nonce ->
nonce
Expand All @@ -127,10 +133,16 @@ defmodule Aecore.Chain.ChainState do
block_state_filled_empty[account].nonce
end

new_account_state = %{balance: block_state_filled_empty[account].balance + value,
new_locked = if(value > 0) 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: block_state_filled_empty[account].locked ++
[%{amount: value, block: lock_time_block}]}
locked: new_locked}

Map.put(block_state_filled_empty, account, new_account_state)
end

Expand Down
12 changes: 7 additions & 5 deletions apps/aecore/lib/aecore/chain/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ defmodule Aecore.Chain.Worker 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)
latest_block_chain_state = %{genesis_block_hash => genesis_chain_state}
txs_index = calculate_block_acc_txs_info(Block.genesis_block())
{genesis_block_map, latest_block_chain_state, txs_index}
Expand Down Expand Up @@ -73,10 +74,10 @@ defmodule Aecore.Chain.Worker do
latest_block = latest_block()

prev_block_chain_state = chain_state()
new_block_state = ChainState.calculate_block_state(block.txs)
new_block_state = ChainState.calculate_block_state(block.txs, latest_block.header.height)
new_chain_state = ChainState.calculate_chain_state(new_block_state, prev_block_chain_state)
new_chain_state_locked_amounts =
ChainState.substract_locked_amounts_from_chain_state(new_chain_state, latest_block.header.height + 1)
ChainState.update_chain_state_locked(new_chain_state, latest_block.header.height + 1)

latest_header_hash = BlockValidation.block_header_hash(latest_block.header)

Expand Down Expand Up @@ -148,10 +149,11 @@ defmodule Aecore.Chain.Worker do
def handle_call({:add_validated_block, %Block{} = block}, _from, state) do
{block_map, latest_block_chain_state, txs_index} = state
prev_block_chain_state = latest_block_chain_state[block.header.prev_hash]
new_block_state = ChainState.calculate_block_state(block.txs)
latest_block = block_map[block.header.prev_hash]
new_block_state = ChainState.calculate_block_state(block.txs, latest_block.header.height)
new_chain_state = ChainState.calculate_chain_state(new_block_state, prev_block_chain_state)
new_chain_state_locked_amounts =
ChainState.substract_locked_amounts_from_chain_state(new_chain_state, block.header.height)
ChainState.update_chain_state_locked(new_chain_state, block.header.height)

new_block_txs_index = calculate_block_acc_txs_info(block)
new_txs_index = update_txs_index(txs_index, new_block_txs_index)
Expand Down
6 changes: 3 additions & 3 deletions apps/aecore/lib/aecore/miner/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -175,13 +175,13 @@ defmodule Aecore.Miner.Worker do
total_fees = calculate_total_fees(valid_txs)
valid_txs = [get_coinbase_transaction(pubkey, total_fees,
latest_block.header.height + 1 +
TxData.get_lock_time_block()) | valid_txs]
Application.get_env(:aecore, :tx_data)[:lock_time_block]) | valid_txs]
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, latest_block.header.height)
new_chain_state = ChainState.calculate_chain_state(new_block_state, chain_state)
new_chain_state_locked_amounts =
ChainState.substract_locked_amounts_from_chain_state(new_chain_state, latest_block.header.height + 1)
ChainState.update_chain_state_locked(new_chain_state, latest_block.header.height + 1)
chain_state_hash = ChainState.calculate_chain_state_hash(new_chain_state_locked_amounts)

latest_block_hash = BlockValidation.block_header_hash(latest_block.header)
Expand Down
6 changes: 1 addition & 5 deletions apps/aecore/lib/aecore/peers/sync.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ defmodule Aecore.Peers.Sync do

alias Aecore.Peers.Worker, as: Peers
alias Aehttpclient.Client, as: HttpClient
alias Aecore.Chain.Worker, as: Chain
alias Aecore.Utils.Blockchain.BlockValidation
alias Aecore.Utils.Serialization

use GenServer

Expand Down Expand Up @@ -101,7 +98,7 @@ defmodule Aecore.Peers.Sync do
|> Enum.shuffle
|> Enum.reduce(0, fn(peer, acc) ->
#if we have successfully added less then number_of_peers_to_add peers then try to add another one
if acc < number_of_peers_to_add do
if acc < number_of_peers_to_add do
case Peers.add_peer(peer) do
:ok -> acc+1
_ -> acc
Expand All @@ -112,4 +109,3 @@ defmodule Aecore.Peers.Sync do
end)
end
end

1 change: 0 additions & 1 deletion apps/aecore/lib/aecore/peers/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ defmodule Aecore.Peers.Worker do
alias Aecore.Utils.Blockchain.BlockValidation
alias Aehttpclient.Client, as: HttpClient
alias Aecore.Utils.Serialization
alias Aecore.Peers.Sync

require Logger

Expand Down
5 changes: 0 additions & 5 deletions apps/aecore/lib/aecore/structures/tx_data.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ defmodule Aecore.Structures.TxData do
alias Aecore.Structures.TxData
@type tx_data() :: %TxData{}

@lock_time_block 10

@doc """
Definition of Aecore TxData structure
Expand All @@ -20,9 +18,6 @@ defmodule Aecore.Structures.TxData do
defstruct [:nonce, :from_acc, :to_acc, :value, :fee, :lock_time_block]
use ExConstructor

@spec get_lock_time_block() :: integer()
def get_lock_time_block(), do: @lock_time_block

@spec create(binary(), binary(), integer(), integer(), integer(), integer()) :: {:ok, %TxData{}}
def create(from_acc, to_acc, value, nonce, fee, lock_time_block) do
{:ok, %TxData{from_acc: from_acc,
Expand Down
4 changes: 2 additions & 2 deletions apps/aecore/lib/aecore/utils/blockchain/block_validation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ defmodule Aecore.Utils.Blockchain.BlockValidation do

cond do
tx_has_valid_nonce && from_account_has_necessary_balance ->
from_acc_new_state = %{balance: -tx.data.value, nonce: 1}
to_acc_new_state = %{balance: tx.data.value, nonce: 0}
from_acc_new_state = %{balance: -tx.data.value, 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
69 changes: 52 additions & 17 deletions apps/aecore/test/aecore_chain_state_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,41 +10,76 @@ defmodule AecoreChainStateTest do
alias Aecore.Structures.TxData, as: TxData
alias Aecore.Structures.SignedTx, as: SignedTx
alias Aecore.Chain.ChainState, as: ChainState
alias Aecore.Chain.Worker, as: Chain

test "block state" do
block = get_block()
lock_time_block = Enum.at(block.txs, 0).data.lock_time_block
latest_block = Chain.latest_block()

assert %{"a" => %{balance: 1, nonce: 102},
"b" => %{balance: -2, nonce: 1},
"c" => %{balance: -3, nonce: 1}} ==
ChainState.calculate_block_state(block.txs)
assert %{"a" => %{balance: -9, nonce: 102,
locked: [%{amount: 10, block: lock_time_block}]},
"b" => %{nonce: 1, balance: -11,
locked: [%{amount: 4, block: lock_time_block},
%{amount: 5, block: lock_time_block}]},
"c" => %{nonce: 1, balance: -5,
locked: [%{amount: 2, block: lock_time_block}]}} ==
ChainState.calculate_block_state(block.txs, latest_block.header.height)
end

test "chain state" do
next_block_height = Chain.latest_block().header.height + 1
chain_state =
ChainState.calculate_chain_state(%{"a" => %{balance: 3, nonce: 100},
"b" => %{balance: 5, nonce: 1},
"c" => %{balance: 4, nonce: 1}},
%{"a" => %{balance: 3, nonce: 0},
"b" => %{balance: -1, nonce: 0},
"c" => %{balance: -2, nonce: 0}})
assert %{"a" => %{balance: 6, nonce: 100},
"b" => %{balance: 4, nonce: 1},
"c" => %{balance: 2, nonce: 1}} == chain_state
ChainState.calculate_chain_state(%{"a" => %{balance: 3, nonce: 100,
locked: [%{amount: 1, block: next_block_height}]},
"b" => %{balance: 5, nonce: 1,
locked: [%{amount: 1, block: next_block_height + 1}]},
"c" => %{balance: 4, nonce: 1,
locked: [%{amount: 1, block: next_block_height}]}},
%{"a" => %{balance: 3, nonce: 0, locked: []},
"b" => %{balance: -1, nonce: 0, locked: []},
"c" => %{balance: -2, nonce: 0, locked: []}})
assert %{"a" => %{balance: 6, nonce: 100,
locked: [%{amount: 1, block: next_block_height}]},
"b" => %{balance: 4, nonce: 1,
locked: [%{amount: 1, block: next_block_height + 1}]},
"c" => %{balance: 2, nonce: 1,
locked: [%{amount: 1, block: next_block_height}]}} == chain_state

new_chain_state_locked_amounts =
ChainState.update_chain_state_locked(chain_state, next_block_height)
assert %{"a" => %{balance: 7, nonce: 100, locked: []},
"b" => %{balance: 4, nonce: 1, locked: [%{amount: 1, block: next_block_height + 1}]},
"c" => %{balance: 3, nonce: 1, locked: []}} == new_chain_state_locked_amounts
end

defp get_block() do
%Block{header: %Header{height: 1, prev_hash: <<1, 24, 45>>,
txs_hash: <<12, 123, 12>>, difficulty_target: 0, nonce: 0,
timestamp: System.system_time(:milliseconds), version: 1}, txs: [
%SignedTx{data: %TxData{from_acc: "a", to_acc: "b",
value: 5, nonce: 101, fee: 1}, signature: <<0>>},
value: 5, nonce: 101, fee: 1,
lock_time_block: Chain.latest_block().header.height +
Application.get_env(:aecore, :tx_data)[:lock_time_block] + 1},
signature: <<0>>},

%SignedTx{data: %TxData{from_acc: "a", to_acc: "c",
value: 2, nonce: 102, fee: 1}, signature: <<0>>},
value: 2, nonce: 102, fee: 1,
lock_time_block: Chain.latest_block().header.height +
Application.get_env(:aecore, :tx_data)[:lock_time_block] + 1},
signature: <<0>>},

%SignedTx{data: %TxData{from_acc: "c", to_acc: "b",
value: 4, nonce: 1, fee: 1}, signature: <<0>>},
value: 4, nonce: 1, fee: 1,
lock_time_block: Chain.latest_block().header.height +
Application.get_env(:aecore, :tx_data)[:lock_time_block] + 1},
signature: <<0>>},

%SignedTx{data: %TxData{from_acc: "b", to_acc: "a",
value: 10, nonce: 1, fee: 1}, signature: <<0>>}]}
value: 10, nonce: 1, fee: 1,
lock_time_block: Chain.latest_block().header.height +
Application.get_env(:aecore, :tx_data)[:lock_time_block] + 1},
signature: <<0>>}]}
end

end
2 changes: 1 addition & 1 deletion apps/aecore/test/aecore_chain_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ defmodule AecoreChainTest do
latest_block_hash = BlockValidation.block_header_hash(latest_block.header)

chain_state = Chain.chain_state(latest_block_hash)
new_block_state = ChainState.calculate_block_state([])
new_block_state = ChainState.calculate_block_state([], latest_block.header.height)
new_chain_state = ChainState.calculate_chain_state(new_block_state, chain_state)
new_chain_state_hash = ChainState.calculate_chain_state_hash(new_chain_state)

Expand Down
6 changes: 5 additions & 1 deletion apps/aecore/test/aecore_keys_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ defmodule AecoreKeysTest do

test "sign transaction" do
{:ok, to_account} = Keys.pubkey()
assert {:ok, _} = Keys.sign_tx(to_account, 5, Map.get(Chain.chain_state, to_account, %{nonce: 0}).nonce + 1, 1)
assert {:ok, _} = Keys.sign_tx(to_account, 5,
Map.get(Chain.chain_state,
to_account, %{nonce: 0}).nonce + 1, 1,
Chain.latest_block().header.height +
Application.get_env(:aecore, :tx_data)[:lock_time_block] + 1)
end

test "check pubkey length" do
Expand Down
6 changes: 5 additions & 1 deletion apps/aecore/test/aecore_tx_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ defmodule AecoreTxTest do

test "create and verify a signed tx" do
{:ok, to_account} = Keys.pubkey()
{:ok, tx} = Keys.sign_tx(to_account, 5, Map.get(Chain.chain_state, to_account, %{nonce: 0}).nonce + 1, 1)
{:ok, tx} = Keys.sign_tx(to_account, 5,
Map.get(Chain.chain_state,
to_account, %{nonce: 0}).nonce + 1, 1,
Chain.latest_block().header.height +
Application.get_env(:aecore, :tx_data)[:lock_time_block] + 1)

assert :true = Keys.verify_tx(tx)
end
Expand Down
10 changes: 8 additions & 2 deletions apps/aecore/test/aecore_txs_pool_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@ defmodule AecoreTxsPoolTest do
test "add transaction, remove it and get pool" do
{:ok, to_account} = Keys.pubkey()
{:ok, tx1} = Keys.sign_tx(to_account, 5,
Map.get(Chain.chain_state, to_account, %{nonce: 0}).nonce + 1, 1)
Map.get(Chain.chain_state,
to_account, %{nonce: 0}).nonce + 1, 1,
Chain.latest_block().header.height +
Application.get_env(:aecore, :tx_data)[:lock_time_block] + 1)
{:ok, tx2} = Keys.sign_tx(to_account, 5,
Map.get(Chain.chain_state, to_account, %{nonce: 0}).nonce + 1, 1)
Map.get(Chain.chain_state,
to_account, %{nonce: 0}).nonce + 1, 1,
Chain.latest_block().header.height +
Application.get_env(:aecore, :tx_data)[:lock_time_block] + 1)
Miner.resume()
Miner.suspend()
assert :ok = Pool.add_transaction(tx1)
Expand Down
Loading

0 comments on commit 73d6393

Please sign in to comment.