<commit_before>%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%-------------------------------------------------------------------
%%% @copyright (C) 2017, <NAME>
%%%-------------------------------------------------------------------

-module(aec_tx_pool_tests).

-include_lib("eunit/include/eunit.hrl").

-define(TAB, aec_tx_pool_test_keys).

-define(BENEFICIARY, <<"<KEY>">>).

tx_pool_test_() ->
    {foreach,
     fun() ->
             ok = application:set_env(aecore, beneficiary, ?BENEFICIARY),
             application:ensure_started(gproc),
             ok = application:ensure_started(crypto),
             TmpKeysDir = aec_test_utils:aec_keys_setup(),
             aec_test_utils:start_chain_db(),
             aec_test_utils:mock_genesis(),
             GB = aec_test_utils:genesis_block(),
             aec_chain_state:insert_block(GB),
             aec_test_utils:mock_block_target_validation(), %% Mocks aec_governance.
             {ok, _} = aec_tx_pool_gc:start_link(),
             {ok, _} = aec_tx_pool:start_link(),
             %% Start `aec_keys` merely for generating realistic test
             %% signed txs - as a node would do.
             ets:new(?TAB, [public, ordered_set, named_table]),
             meck:new(aeu_time, [passthrough]),
             meck:new(aec_jobs_queues),
             meck:expect(aec_jobs_queues, run, fun(_, F) -> F() end),
             TmpKeysDir
     end,
     fun(TmpKeysDir) ->
             ok = application:unset_env(aecore, beneficiary),
             ok = aec_test_utils:aec_keys_cleanup(TmpKeysDir),
             ok = application:stop(gproc),
             ets:delete(?TAB),
             aec_test_utils:stop_chain_db(),
             aec_test_utils:unmock_genesis(),
             aec_test_utils:unmock_block_target_validation(), %% Unloads aec_governance mock.
             ok = aec_tx_pool:stop(),
             ok = aec_tx_pool_gc:stop(),
             meck:unload(aeu_time),
             meck:unload(aec_jobs_queues),
             ok
     end,
     [{"No txs in mempool",
       fun() ->
               ?assertEqual({ok, []}, aec_tx_pool:peek(1)),
               ?assertEqual({ok, []}, aec_tx_pool:peek(3)),
               ?assertEqual(0, aec_tx_pool:size())
       end},
      {"As a healthy network peer, the node stores in mempool txs received from"
       " peers and serves txs in mempool to peers",
       fun() ->
               %% No txs to serve to peers.
               ?assertEqual({ok, []}, aec_tx_pool:peek(1)),

               %% Tx received from a peer.
               STx1 = a_signed_tx(new_pubkey(), me, 1, 20000),
               ?assertEqual(ok, aec_tx_pool:push(STx1, tx_received)),

               %% One tx to serve to peers.
               ?assertEqual({ok, [STx1]}, aec_tx_pool:peek(1)),

               %% Add it again and see that it is not added twice
               ?assertEqual(ok, aec_tx_pool:push(STx1, tx_received)),
               ?assertEqual({ok, [STx1]}, aec_tx_pool:peek(1)),

               %% Other tx received from a peer.
               STx2 = a_signed_tx(new_pubkey(), me, 1, 20000),
               ?assertEqual(ok, aec_tx_pool:push(STx2, tx_received)),

               %% Two tx2 to serve to peers.
               {ok, PoolTxs} = aec_tx_pool:peek(infinity),
               ?assertEqual(lists:sort([STx1, STx2]), lists:sort(PoolTxs))
       end},
      {"fill micro block with transactions",
       fun() ->
               %% No txs to serve to peers.
               ?assertEqual({ok, []}, aec_tx_pool:peek(1)),

               %% Tx received from a peer.
               PubKey = new_pubkey(),
               STxs = [ a_signed_tx(PubKey, me, Nonce, 1, 10) || Nonce <- lists:seq(1,400) ],
               [ aec_tx_pool:push(STx, tx_created) || STx <- STxs ],

               {ok, Hash} = aec_headers:hash_header(aec_block_genesis:genesis_header()),
               {ok, STxs2} = aec_tx_pool:get_candidate(aec_governance:block_gas_limit(), Hash),
               TotalGas = lists:sum([ aetx:gas(aetx_sign:tx(T)) || T <- STxs2 ]),
               MinGas = aetx:gas(aetx_sign:tx(hd(STxs))),

               %% No single tx would have fitted on top of this
               ?assert(MinGas > aec_governance:block_gas_limit() - TotalGas)
       end},
      {"Mempool follows chain insertions and forks",
       fun() ->
               aec_test_utils:stop_chain_db(),
               %% Prepare a chain with specific genesis block with some funds
               PubKey1 = new_pubkey(),
               PubKey2 = new_pubkey(),
               meck:expect(aec_genesis_block_settings, preset_accounts, 0,
                  [{PubKey1, 100000}, {PubKey2, 100000}]),
               {GenesisBlock, _} = aec_block_genesis:genesis_block_with_state(),
               aec_test_utils:start_chain_db(),
               ok = aec_chain_state:insert_block(GenesisBlock),

               %% The first block needs to be a key-block
               {ok, KeyBlock1} = aec_block_key_candidate:create(aec_chain:top_block(), PubKey1),
               {ok, KeyHash1} = aec_blocks:hash_internal_representation(KeyBlock1),
               ok = aec_chain_state:insert_block(KeyBlock1),
               ?assertEqual(KeyHash1, aec_chain:top_block_hash()),
               ok = aec_keys:promote_candidate(aec_blocks:miner(KeyBlock1)),

               TopBlock = aec_chain:top_block(),
               TopBlockHash = aec_chain:top_block_hash(),

               %% Prepare a few txs.
               STx1 = a_signed_tx(PubKey1, new_pubkey(), 1, 20000),
               STx2 = a_signed_tx(PubKey1, new_pubkey(), 2, 20000),
               ?assertEqual(ok, aec_tx_pool:push(STx1)),
               ?assertEqual(ok, aec_tx_pool:push(STx2)),
               {ok, PoolTxs} = aec_tx_pool:peek(infinity),
               ?assertEqual(lists:sort([STx1, STx2]), lists:sort(PoolTxs)),

               %% Insert a block in chain.
               {ok, USCandidate1, _} = aec_block_micro_candidate:create(TopBlock),
               {ok, Candidate1} = aec_keys:sign_micro_block(USCandidate1),

               {ok, CHash1} = aec_blocks:hash_internal_representation(Candidate1),
               ok = aec_chain_state:insert_block(Candidate1),
               ?assertEqual(CHash1, aec_chain:top_block_hash()),

               %% Check that we uses all the txs in mempool
               Included = aec_blocks:txs(Candidate1),
               ?assertEqual(lists:sort(Included), lists:sort([STx1, STx2])),

               %% Ping tx_pool for top change
               aec_tx_pool:top_change(micro, TopBlockHash, CHash1),

               %% The mempool should now be empty
               ?assertEqual({ok, []}, aec_tx_pool:peek(infinity)),

               %% Create a fork
               %% First add a chain of two micro blocks with key blocks
               %% on top of each of them
               %% Ensure micro_block_cycle time
               meck:expect(aeu_time, now_in_msecs, fun() -> meck:passthrough([]) + 3000 end),
               STx3 = a_signed_tx(PubKey2, new_pubkey(), 1, 20000),
               ?assertEqual(ok, aec_tx_pool:push(STx3)),
               {ok, USCandidate3, _} = aec_block_micro_candidate:create(aec_chain:top_block()),
               {ok, Candidate3} = aec_keys:sign_micro_block(USCandidate3),

               ok = aec_chain_state:insert_block(Candidate3),
               TopBlockFork1 = aec_chain:top_block(),
               {ok, KeyBlock2} = aec_block_key_candidate:create(TopBlockFork1, PubKey1),
               {ok, CHashFork1} = aec_blocks:hash_internal_representation(KeyBlock2),

               meck:expect(aeu_time, now_in_msecs, fun() -> meck:passthrough([]) + 6000 end),
               STx4 = a_signed_tx(PubKey2, new_pubkey(), 2, 40000),
               ?assertEqual(ok, aec_tx_pool:push(STx4)),
               {ok, USCandidate4, _} = aec_block_micro_candidate:create(aec_chain:top_block()),
               {ok, Candidate4} = aec_keys:sign_micro_block(USCandidate4),

               ok = aec_chain_state:insert_block(Candidate4),
               TopBlockFork2 = aec_chain:top_block(),
               {ok, KeyBlock3} = aec_block_key_candidate:create(TopBlockFork2, PubKey1),
               {ok, CHashFork2} = aec_blocks:hash_internal_representation(KeyBlock3),

               %% Push the keyblock with the longest chain of micro blocks
               ok = aec_chain_state:insert_block(KeyBlock3),
               ?assertEqual(CHashFork2, aec_chain:top_block_hash()),
               aec_tx_pool:top_change(key, CHash1, CHashFork2),
               %% The mempool should now be empty
               ?assertEqual({ok, []}, aec_tx_pool:peek(infinity)),

               %% Ensure that the shorter fork takes over by
               %% increasing the difficulty
               meck:new(aec_blocks, [passthrough]),
               meck:expect(aec_headers, difficulty,
                           fun(B) -> meck:passthrough([B]) * 2 end),

               %% Push the keyblock with the shorter chain of micro blocks
               %% and check that it takes over.
               ok = aec_chain_state:insert_block(KeyBlock2),
               ?assertEqual(CHashFork1, aec_chain:top_block_hash()),

               %% Ping tx_pool for top change
               aec_tx_pool:top_change(key, CHashFork2, CHashFork1),

               %% The not included transaction should now be back in the pool
               ?assertEqual({ok, [STx4]}, aec_tx_pool:peek(infinity)),

               meck:unload(aec_headers),
               ok
       end},
      {"Ensure ordering",
       fun() ->
                 %% We should sort by fee, but preserve the order of nonces for each sender
                 PK1 = new_pubkey(),
                 PK2 = new_pubkey(),
                 PK3 = new_pubkey(),
                 PK4 = new_pubkey(),
                 STxs =
                   [ a_signed_tx        (_Sender=PK1, me,_Nonce=1,_Fee=300000)
                   , a_signed_tx        (        PK1, me,       2,     400000)
                   , a_signed_tx        (        PK1, me,       3,     500000)
                   , a_signed_tx        (        PK2, me,       2,     700000)
                   , a_signed_tx        (        PK2, me,       1,     800000)
                   , signed_ct_create_tx(        PK4,           1,     300000,_GasPrice=1100000000)
                   , signed_ct_call_tx  (        PK4,           2,     600000,          9000000000)
                   , signed_ct_call_tx  (        PK4,           3,     900000,          1)
                   ],

                 [?assertEqual(ok, aec_tx_pool:push(Tx)) || Tx <- STxs],
                 {ok, CurrentMempoolSigned} = aec_tx_pool:peek(10),
                 %% extract transactions without verification
                 CurrentMempool = [ aetx_sign:tx(STx) || STx <- CurrentMempoolSigned ],

                 MempoolOrder = [{aetx:origin(Tx), aetx:nonce(Tx)} || Tx <- CurrentMempool],
                 %% this is not-optimal order: transactions for PK1 and PK4 are invalid in that order
                 CorrectOrder = [{PK4,3},{PK2,1},{PK2,2},{PK4,2},{PK1,3},{PK1,2},{PK4,1},{PK1,1}],

                 ?assertEqual(CorrectOrder, MempoolOrder),

                 %% check if we track nonces correctly
                 MaxNonce = aec_tx_pool:get_max_nonce(PK1),
                 ?assertEqual({ok,3}, MaxNonce),

                 NotExistingSender = aec_tx_pool:get_max_nonce(PK3),
                 ?assertEqual(undefined, NotExistingSender)
             end},
      {"Mempool consistency",
       fun() ->
               PK = new_pubkey(),
               MaxGas = aec_governance:block_gas_limit(),
               TopBlockHash = aec_chain:top_block_hash(),
               STx1 = a_signed_tx(PK, me, Nonce1=1, _Fee1=20000),
               ?assertEqual(ok, aec_tx_pool:push(STx1)),
               ?assertEqual([], aec_tx_pool:peek_visited()),
               [{PK, Nonce1, _}] = aec_tx_pool:peek_nonces(),
               Size = aec_tx_pool:size(),
               ?assertEqual({ok, [STx1]},
                            aec_tx_pool:get_candidate(MaxGas, TopBlockHash)),
               ?assertEqual([STx1], aec_tx_pool:peek_visited()),
               ?assertEqual([], aec_tx_pool:peek_db()),
               ?assertEqual(Size, aec_tx_pool:size()),
               %% a 'key' top_change should restore visited to the mempool
               aec_tx_pool:top_change(key, TopBlockHash, TopBlockHash),
               ?assertEqual([], aec_tx_pool:peek_visited()),
               ?assertEqual([STx1], aec_tx_pool:peek_db()),
               ?assertEqual(Size, aec_tx_pool:size())
       end},
      {"Ensure candidate ordering",
       fun() ->
               PK = new_pubkey(),
               MaxGas = aec_governance:block_gas_limit(),

               %% Only one tx in pool
               STx1 = a_signed_tx(PK, me, Nonce1=1,_Fee1=20000),
               ?assertEqual(ok, aec_tx_pool:push(STx1)),
               ?assertEqual({ok, [STx1]}, aec_tx_pool:get_candidate(MaxGas, aec_chain:top_block_hash())),

               aec_tx_pool:restore_mempool(),
               %% Order by nonce even if fee is higher
               STx2 = a_signed_tx(PK, me, Nonce2=2, Fee2=200000),
               ?assertEqual(ok, aec_tx_pool:push(STx2)),

               ?assertEqual({ok, [STx1, STx2]}, aec_tx_pool:get_candidate(MaxGas, aec_chain:top_block_hash())),

               aec_tx_pool:restore_mempool(),
               %% Replace same nonce with the higher fee
               STx3 = a_signed_tx(PK, me, Nonce1=1, 20000),
               ?assertEqual(ok, aec_tx_pool:push(STx3)),
               ?assertEqual({ok, [STx3, STx2]}, aec_tx_pool:get_candidate(MaxGas, aec_chain:top_block_hash())),

               aec_tx_pool:restore_mempool(),
               %% Replace same nonce with same fee but positive gas price (gas price of transaction without gas price is considered zero)
               STx4 = signed_ct_create_tx(PK, Nonce2=2, Fee2=200000,_GasPrice4=1100000000),
               ?assertEqual(ok, aec_tx_pool:push(STx4)),
               ?assertEqual({ok, [STx3, STx4]}, aec_tx_pool:get_candidate(MaxGas, aec_chain:top_block_hash())),

               aec_tx_pool:restore_mempool(),
               %% Replace same nonce with same fee but higher gas price
               STx5 = signed_ct_create_tx(PK, Nonce2=2, Fee2=200000, 2000000000),
               ?assertEqual(ok, aec_tx_pool:push(STx5)),
               ?assertEqual({ok, [STx3, STx5]}, aec_tx_pool:get_candidate(MaxGas, aec_chain:top_block_hash())),

               aec_tx_pool:restore_mempool(),
               %% Order by nonce even if fee and gas price are higher
               STx6 = signed_ct_call_tx(PK, _Nonce6=3,_Fee6=1000000,_GasPrice6=9000000000),
               ?assertEqual(ok, aec_tx_pool:push(STx6)),
               ?assertEqual({ok, [STx3, STx5, STx6]}, aec_tx_pool:get_candidate(MaxGas, aec_chain:top_block_hash())),

               ok
       end},
      {"Ensure block gas limit",
       fun() ->
               PK1 = new_pubkey(),
               PK2 = new_pubkey(),
               PK3 = new_pubkey(),

               %% Prepare 3 txs:
               %% 1st tx has the lowest gas
               %% Depends on aec_geovernance settings whether 2nd or 3rd is largest
               STx1 = a_signed_tx(        me,   PK1,      1, 20000),
               STx2 = signed_ct_create_tx(PK2,    1, 800000,  1000),
               STx3 = signed_ct_call_tx(  PK3,    1, 800000,  1000),

               GasTx1 = aetx:gas(aetx_sign:tx(STx1)),
               GasTx2 = aetx:gas(aetx_sign:tx(STx2)),
               GasTx3 = aetx:gas(aetx_sign:tx(STx3)),

               ?assert(GasTx2 > GasTx1),
               ?assert(GasTx3 > GasTx1),
               {MinGasTx, MaxGasTx} =
                   case GasTx2 > GasTx3 of
                       true -> { {STx3, GasTx3}, {STx2, GasTx2} };
                       false ->  { {STx2, GasTx2}, {STx3, GasTx3} }
                     end,

               %% Push all txs to the pool.
               ?assertEqual(ok, aec_tx_pool:push(STx1)),
               ?assertEqual(ok, aec_tx_pool:push(STx2)),
               ?assertEqual(ok, aec_tx_pool:push(STx3)),

               %% Do not get any tx - not enough gas.
               ?assertEqual({ok, []},
                            aec_tx_pool:get_candidate(GasTx1 - 1, aec_chain:top_block_hash())),

               %% Get 1st tx only.
               ?assertEqual({ok, [STx1]},
                            aec_tx_pool:get_candidate(GasTx1, aec_chain:top_block_hash())),

               aec_tx_pool:restore_mempool(),

               %% Get only 2 txs, the 1st + 2nd or 1st + 3rd.
               {_, Max} = MaxGasTx,
               {ok, STxs1} = aec_tx_pool:get_candidate(GasTx1 + Max, aec_chain:top_block_hash()),
               ?assert(lists:member(STx1, STxs1) and (lists:member(STx2, STxs1) or lists:member(STx3, STxs1))),

               aec_tx_pool:restore_mempool(),
               %% Get all 3 txs by providing exactly the gas the txs need.
               {ok, STxs2} = aec_tx_pool:get_candidate(GasTx1 + GasTx2 + GasTx3, aec_chain:top_block_hash()),
               ?assert(lists:member(STx1, STxs2)),
               ?assert(lists:member(STx2, STxs2)),
               ?assert(lists:member(STx3, STxs2)),

               aec_tx_pool:restore_mempool(),

               %% Get 1st and 3rd tx, skip 2nd tx.
               {MinSTx, Min} = MinGasTx,
               {ok, STxs3} = aec_tx_pool:get_candidate(GasTx1 + Min, aec_chain:top_block_hash()),
               ?assert(lists:member(STx1, STxs3)),
               ?assert(lists:member(MinSTx, STxs3)),

               ok
       end},
      {"Ensure persistence",
       fun() ->
               %% Prepare a few txs.
               STx1 = a_signed_tx(me, new_pubkey(), 1, 20000),
               STx2 = a_signed_tx(me, new_pubkey(), 2, 20000),
               ?assertEqual(ok, aec_tx_pool:push(STx1)),
               ?assertEqual(ok, aec_tx_pool:push(STx2)),
               {ok, PoolTxs} = aec_tx_pool:peek(infinity),
               ?assertEqual(lists:sort([STx1, STx2]), lists:sort(PoolTxs)),

               %% Stop the mempool and start it again to see that it reinits
               ok        = aec_tx_pool:stop(),
               {ok, Pid} = aec_tx_pool:start_link(),
               {ok, PoolTxs2} = aec_tx_pool:peek(infinity),
               ?assertEqual(lists:sort([STx1, STx2]), lists:sort(PoolTxs2)),
               unlink(Pid), %% Leave it for the cleanup
               ok
       end},
      {"Test rejection of transactions",
       fun() ->
               aec_test_utils:stop_chain_db(),
               %% Prepare a chain with specific genesis block with some funds
               PubKey1 = new_pubkey(),
               PubKey2 = new_pubkey(),
               meck:expect(aec_genesis_block_settings, preset_accounts, 0,
                  [{PubKey1, 100000}, {PubKey2, 100000}]),
               {GenesisBlock, _} = aec_block_genesis:genesis_block_with_state(),
               aec_test_utils:start_chain_db(),
               ok = aec_chain_state:insert_block(GenesisBlock),

               %% The first block needs to be a key-block
               {ok, KeyBlock1} = aec_block_key_candidate:create(aec_chain:top_block(), PubKey1),
               {ok, KeyHash1} = aec_blocks:hash_internal_representation(KeyBlock1),
               ok = aec_chain_state:insert_block(KeyBlock1),
               ?assertEqual(KeyHash1, aec_chain:top_block_hash()),
               ok = aec_keys:promote_candidate(aec_blocks:miner(KeyBlock1)),

               TopBlock = aec_chain:top_block(),

               %% Add a transaction to the chain
               STx1 = a_signed_tx(PubKey1, new_pubkey(), 1, 20000),
               ?assertEqual(ok, aec_tx_pool:push(STx1)),
               {ok, USCandidate1, _} = aec_block_micro_candidate:create(TopBlock),
               {ok, Candidate1} = aec_keys:sign_micro_block(USCandidate1),
               {ok, Top} = aec_blocks:hash_internal_representation(Candidate1),
               ok = aec_chain_state:insert_block(Candidate1),
               ?assertEqual(Top, aec_chain:top_block_hash()),

               %% Now we should reject the same transaction since it
               %% is already in the chain
               ?assertEqual({error, already_accepted},
                            aec_tx_pool:push(STx1)),

               %% A transaction with too low nonce should be rejected
               STx2 = a_signed_tx(PubKey1, new_pubkey(), 1, 20000),
               ?assertEqual({error, account_nonce_too_high},
                            aec_tx_pool:push(STx2)),

               %% A transaction with too high nonce should _NOT_ be rejected
               STx3 = a_signed_tx(PubKey1, new_pubkey(), 5, 20000),
               ?assertEqual(ok, aec_tx_pool:push(STx3)),

               %% A transaction with too low fee should be rejected
               STx4 = a_signed_tx(PubKey1, new_pubkey(), 6,
                                  aec_governance:minimum_tx_fee() - 1),
               ?assertEqual({error, too_low_fee}, aec_tx_pool:push(STx4)),

               %% A transaction with too low gas price should be rejected
               meck:expect(aec_governance, minimum_gas_price, 0, 1),
               ?assertEqual(ok, aec_tx_pool:push(signed_ct_create_tx(PubKey1, 10, 1000000, 1))),
               ?assertEqual(ok, aec_tx_pool:push(signed_ct_call_tx  (PubKey1, 20, 1000000, 1))),
               ?assertEqual(ok, aec_tx_pool:push(signed_ct_create_tx(PubKey1, 11, 2000000, 2))),
               ?assertEqual(ok, aec_tx_pool:push(signed_ct_call_tx  (PubKey1, 21, 2000000, 2))),
               meck:expect(aec_governance, minimum_gas_price, 0, 2),
               ?assertEqual({error, too_low_gas_price}, aec_tx_pool:push(signed_ct_create_tx(PubKey1, 12, 2000000, 0))),
               ?assertEqual({error, too_low_gas_price}, aec_tx_pool:push(signed_ct_call_tx  (PubKey1, 22, 2000000, 0))),
               ?assertEqual({error, too_low_gas_price}, aec_tx_pool:push(signed_ct_create_tx(PubKey1, 13, 2000000, 1))),
               ?assertEqual({error, too_low_gas_price}, aec_tx_pool:push(signed_ct_call_tx  (PubKey1, 23, 2000000, 1))),
               ?assertEqual(ok, aec_tx_pool:push(signed_ct_create_tx(PubKey1, 14, 2000000, 2))),
               ?assertEqual(ok, aec_tx_pool:push(signed_ct_call_tx  (PubKey1, 24, 2000000, 2))),
               ?assertEqual(ok, aec_tx_pool:push(signed_ct_create_tx(PubKey1, 15, 4000000, 3))),
               ?assertEqual(ok, aec_tx_pool:push(signed_ct_call_tx  (PubKey1, 25, 4000000, 3))),

               %% A transaction with too low ttl should be rejected
               %% First add another block to make the chain high enough to
               %% fail on TTL
               {ok, Candidate2} = aec_block_key_candidate:create(aec_chain:top_block(), PubKey1),
               {ok, Top2} = aec_blocks:hash_internal_representation(Candidate2),
               ok = aec_chain_state:insert_block(Candidate2),
               ?assertEqual(Top2, aec_chain:top_block_hash()),

               STx5 = a_signed_tx(PubKey1, new_pubkey(), 6, 40000, 1),
               ?assertEqual({error, ttl_expired}, aec_tx_pool:push(STx5)),

               ok
       end},
       {"Test GC",
        fun() ->
            %% initialize chain
            {GenesisBlock, _} = aec_block_genesis:genesis_block_with_state(),
            ok = aec_chain_state:insert_block(GenesisBlock),

            %% Prepare three transactions
            PubKey = new_pubkey(),
            STx1 = a_signed_tx(PubKey, PubKey, 1, 20000),
            STx2 = a_signed_tx(PubKey, PubKey, 2, 20000),
            STx3 = a_signed_tx(PubKey, PubKey, 3, 20000),

            %% Post them
            ?assertEqual(ok, aec_tx_pool:push(STx1)),
            ?assertEqual(ok, aec_tx_pool:push(STx2)),
            ?assertEqual(ok, aec_tx_pool:push(STx3)),

            ?assertMatch({ok, [_, _, _]}, aec_tx_pool:peek(infinity)),

            %% Txs will be scheduled for
            %% removal at Height + ?TX_TTL
            %% For test ?TX_TTL = 8

            %% Doing a garbage collect at height 0 shouldn't affect
            tx_pool_gc(0),
            _ = sys:get_status(aec_tx_pool_gc),
            ?assertMatch({ok, [_, _, _]}, aec_tx_pool:peek(infinity)),

            %% At 4 still GC should not kick in.
            tx_pool_gc(4),
            ?assertMatch({ok, [_, _, _]}, aec_tx_pool:peek(infinity)),

            %% At 8, now TXs should be dropped.
            tx_pool_gc(8),
            ?assertMatch({ok, []}, aec_tx_pool:peek(infinity))

        end}
     ]}.

tx_pool_gc(Height) ->
    aec_tx_pool_gc:gc(Height),
    sys:get_status(aec_tx_pool_gc), %% sync point (gc is asynchronous)
    ok.

a_signed_tx(Sender, Recipient, Nonce, Fee) ->
    a_signed_tx(Sender, Recipient, Nonce, Fee,0).

a_signed_tx(Sender, Recipient, Nonce, Fee, TTL) ->
    {ok, Tx} = aec_spend_tx:new(#{sender_id => acct(Sender),
                                  recipient_id => acct(Recipient),
                                  amount => 1,
                                  nonce => Nonce,
                                  fee => Fee,
                                  ttl => TTL,
                                  payload => <<"">>}),
    {ok, STx} = sign(Sender, Tx),
    STx.

signed_ct_create_tx(Sender, Nonce, Fee, GasPrice) ->
    Spec =
        #{ fee        => Fee
         , owner_id   => aec_id:create(account, Sender)
         , nonce      => Nonce
         , code       => <<"NOT PROPER BYTE CODE">>
         , vm_version => 1
         , deposit    => 10
         , amount     => 200
         , gas        => 100000
         , gas_price  => GasPrice
         , call_data  => <<"NOT ENCODED ACCORDING TO ABI">>
         , ttl        => 0
         },
    {ok, Tx} = aect_create_tx:new(Spec),
    {ok, STx} = sign(Sender, Tx),
    STx.

signed_ct_call_tx(Sender, Nonce, Fee, GasPrice) ->
    ContractId = aec_id:create(contract, <<"contract_address......(32 bytes)">>),
    Spec =
        #{ fee         => Fee
         , contract_id => ContractId
         , caller_id   => aec_id:create(account, Sender)
         , nonce       => Nonce
         , vm_version  => 1
         , amount      => 100
         , gas         => 50000
         , gas_price   => GasPrice
         , call_data   => <<"CALL DATA">>
         , ttl         => 0
         },
    {ok, Tx} = aect_call_tx:new(Spec),
    {ok, STx} = sign(Sender, Tx),
    STx.

sign(me, Tx) ->
    {ok, PrivKey} = aec_keys:sign_privkey(),
    {ok, aec_test_utils:sign_tx(Tx, PrivKey)};
sign(PubKey, Tx) ->
    try
        [{_, PrivKey}] = ets:lookup(?TAB, PubKey),
        {ok, Trees} = aec_chain:get_top_state(),
        {ok, Signers} = aetx:signers(Tx, Trees),
        true = lists:member(PubKey, Signers),
        {ok, aec_test_utils:sign_tx(Tx, PrivKey)}
    catch
        error:Err ->
            erlang:error({Err, erlang:get_stacktrace()})
    end.

acct(me) ->
    {ok, Key} = aec_keys:pubkey(),
    aec_id:create(account, Key);
acct(A) when is_binary(A) ->
    aec_id:create(account, A).

new_pubkey() ->
    {Pub, Priv} = keypair(),
    ets:insert(?TAB, {Pub, Priv}),
    Pub.

keypair() ->
    #{ public := Pub, secret := Priv } = enacl:sign_keypair(),
    {Pub, Priv}.
<commit_msg>Fee must be high enough<commit_after>%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%-------------------------------------------------------------------
%%% @copyright (C) 2017, <NAME>
%%%-------------------------------------------------------------------

-module(aec_tx_pool_tests).

-include_lib("eunit/include/eunit.hrl").

-define(TAB, aec_tx_pool_test_keys).

-define(BENEFICIARY, <<"<KEY>">>).

tx_pool_test_() ->
    {foreach,
     fun() ->
             ok = application:set_env(aecore, beneficiary, ?BENEFICIARY),
             application:ensure_started(gproc),
             ok = application:ensure_started(crypto),
             TmpKeysDir = aec_test_utils:aec_keys_setup(),
             aec_test_utils:start_chain_db(),
             aec_test_utils:mock_genesis(),
             GB = aec_test_utils:genesis_block(),
             aec_chain_state:insert_block(GB),
             aec_test_utils:mock_block_target_validation(), %% Mocks aec_governance.
             {ok, _} = aec_tx_pool_gc:start_link(),
             {ok, _} = aec_tx_pool:start_link(),
             %% Start `aec_keys` merely for generating realistic test
             %% signed txs - as a node would do.
             ets:new(?TAB, [public, ordered_set, named_table]),
             meck:new(aeu_time, [passthrough]),
             meck:new(aec_jobs_queues),
             meck:expect(aec_jobs_queues, run, fun(_, F) -> F() end),
             TmpKeysDir
     end,
     fun(TmpKeysDir) ->
             ok = application:unset_env(aecore, beneficiary),
             ok = aec_test_utils:aec_keys_cleanup(TmpKeysDir),
             ok = application:stop(gproc),
             ets:delete(?TAB),
             aec_test_utils:stop_chain_db(),
             aec_test_utils:unmock_genesis(),
             aec_test_utils:unmock_block_target_validation(), %% Unloads aec_governance mock.
             ok = aec_tx_pool:stop(),
             ok = aec_tx_pool_gc:stop(),
             meck:unload(aeu_time),
             meck:unload(aec_jobs_queues),
             ok
     end,
     [{"No txs in mempool",
       fun() ->
               ?assertEqual({ok, []}, aec_tx_pool:peek(1)),
               ?assertEqual({ok, []}, aec_tx_pool:peek(3)),
               ?assertEqual(0, aec_tx_pool:size())
       end},
      {"As a healthy network peer, the node stores in mempool txs received from"
       " peers and serves txs in mempool to peers",
       fun() ->
               %% No txs to serve to peers.
               ?assertEqual({ok, []}, aec_tx_pool:peek(1)),

               %% Tx received from a peer.
               STx1 = a_signed_tx(new_pubkey(), me, 1, 20000),
               ?assertEqual(ok, aec_tx_pool:push(STx1, tx_received)),

               %% One tx to serve to peers.
               ?assertEqual({ok, [STx1]}, aec_tx_pool:peek(1)),

               %% Add it again and see that it is not added twice
               ?assertEqual(ok, aec_tx_pool:push(STx1, tx_received)),
               ?assertEqual({ok, [STx1]}, aec_tx_pool:peek(1)),

               %% Other tx received from a peer.
               STx2 = a_signed_tx(new_pubkey(), me, 1, 20000),
               ?assertEqual(ok, aec_tx_pool:push(STx2, tx_received)),

               %% Two tx2 to serve to peers.
               {ok, PoolTxs} = aec_tx_pool:peek(infinity),
               ?assertEqual(lists:sort([STx1, STx2]), lists:sort(PoolTxs))
       end},
      {"fill micro block with transactions",
       fun() ->
               %% No txs to serve to peers.
               ?assertEqual({ok, []}, aec_tx_pool:peek(1)),

               %% Tx received from a peer.
               PubKey = new_pubkey(),
               STxs = [ a_signed_tx(PubKey, me, Nonce, 20000, 10) || Nonce <- lists:seq(1,400) ],
               [ aec_tx_pool:push(STx, tx_created) || STx <- STxs ],

               {ok, Hash} = aec_headers:hash_header(aec_block_genesis:genesis_header()),
               {ok, STxs2} = aec_tx_pool:get_candidate(aec_governance:block_gas_limit(), Hash),
               TotalGas = lists:sum([ aetx:gas(aetx_sign:tx(T)) || T <- STxs2 ]),
               MinGas = aetx:gas(aetx_sign:tx(hd(STxs))),

               %% No single tx would have fitted on top of this
               ?assert(MinGas > aec_governance:block_gas_limit() - TotalGas)
       end},
      {"Mempool follows chain insertions and forks",
       fun() ->
               aec_test_utils:stop_chain_db(),
               %% Prepare a chain with specific genesis block with some funds
               PubKey1 = new_pubkey(),
               PubKey2 = new_pubkey(),
               meck:expect(aec_genesis_block_settings, preset_accounts, 0,
                  [{PubKey1, 100000}, {PubKey2, 100000}]),
               {GenesisBlock, _} = aec_block_genesis:genesis_block_with_state(),
               aec_test_utils:start_chain_db(),
               ok = aec_chain_state:insert_block(GenesisBlock),

               %% The first block needs to be a key-block
               {ok, KeyBlock1} = aec_block_key_candidate:create(aec_chain:top_block(), PubKey1),
               {ok, KeyHash1} = aec_blocks:hash_internal_representation(KeyBlock1),
               ok = aec_chain_state:insert_block(KeyBlock1),
               ?assertEqual(KeyHash1, aec_chain:top_block_hash()),
               ok = aec_keys:promote_candidate(aec_blocks:miner(KeyBlock1)),

               TopBlock = aec_chain:top_block(),
               TopBlockHash = aec_chain:top_block_hash(),

               %% Prepare a few txs.
               STx1 = a_signed_tx(PubKey1, new_pubkey(), 1, 20000),
               STx2 = a_signed_tx(PubKey1, new_pubkey(), 2, 20000),
               ?assertEqual(ok, aec_tx_pool:push(STx1)),
               ?assertEqual(ok, aec_tx_pool:push(STx2)),
               {ok, PoolTxs} = aec_tx_pool:peek(infinity),
               ?assertEqual(lists:sort([STx1, STx2]), lists:sort(PoolTxs)),

               %% Insert a block in chain.
               {ok, USCandidate1, _} = aec_block_micro_candidate:create(TopBlock),
               {ok, Candidate1} = aec_keys:sign_micro_block(USCandidate1),

               {ok, CHash1} = aec_blocks:hash_internal_representation(Candidate1),
               ok = aec_chain_state:insert_block(Candidate1),
               ?assertEqual(CHash1, aec_chain:top_block_hash()),

               %% Check that we uses all the txs in mempool
               Included = aec_blocks:txs(Candidate1),
               ?assertEqual(lists:sort(Included), lists:sort([STx1, STx2])),

               %% Ping tx_pool for top change
               aec_tx_pool:top_change(micro, TopBlockHash, CHash1),

               %% The mempool should now be empty
               ?assertEqual({ok, []}, aec_tx_pool:peek(infinity)),

               %% Create a fork
               %% First add a chain of two micro blocks with key blocks
               %% on top of each of them
               %% Ensure micro_block_cycle time
               meck:expect(aeu_time, now_in_msecs, fun() -> meck:passthrough([]) + 3000 end),
               STx3 = a_signed_tx(PubKey2, new_pubkey(), 1, 20000),
               ?assertEqual(ok, aec_tx_pool:push(STx3)),
               {ok, USCandidate3, _} = aec_block_micro_candidate:create(aec_chain:top_block()),
               {ok, Candidate3} = aec_keys:sign_micro_block(USCandidate3),

               ok = aec_chain_state:insert_block(Candidate3),
               TopBlockFork1 = aec_chain:top_block(),
               {ok, KeyBlock2} = aec_block_key_candidate:create(TopBlockFork1, PubKey1),
               {ok, CHashFork1} = aec_blocks:hash_internal_representation(KeyBlock2),

               meck:expect(aeu_time, now_in_msecs, fun() -> meck:passthrough([]) + 6000 end),
               STx4 = a_signed_tx(PubKey2, new_pubkey(), 2, 40000),
               ?assertEqual(ok, aec_tx_pool:push(STx4)),
               {ok, USCandidate4, _} = aec_block_micro_candidate:create(aec_chain:top_block()),
               {ok, Candidate4} = aec_keys:sign_micro_block(USCandidate4),

               ok = aec_chain_state:insert_block(Candidate4),
               TopBlockFork2 = aec_chain:top_block(),
               {ok, KeyBlock3} = aec_block_key_candidate:create(TopBlockFork2, PubKey1),
               {ok, CHashFork2} = aec_blocks:hash_internal_representation(KeyBlock3),

               %% Push the keyblock with the longest chain of micro blocks
               ok = aec_chain_state:insert_block(KeyBlock3),
               ?assertEqual(CHashFork2, aec_chain:top_block_hash()),
               aec_tx_pool:top_change(key, CHash1, CHashFork2),
               %% The mempool should now be empty
               ?assertEqual({ok, []}, aec_tx_pool:peek(infinity)),

               %% Ensure that the shorter fork takes over by
               %% increasing the difficulty
               meck:new(aec_blocks, [passthrough]),
               meck:expect(aec_headers, difficulty,
                           fun(B) -> meck:passthrough([B]) * 2 end),

               %% Push the keyblock with the shorter chain of micro blocks
               %% and check that it takes over.
               ok = aec_chain_state:insert_block(KeyBlock2),
               ?assertEqual(CHashFork1, aec_chain:top_block_hash()),

               %% Ping tx_pool for top change
               aec_tx_pool:top_change(key, CHashFork2, CHashFork1),

               %% The not included transaction should now be back in the pool
               ?assertEqual({ok, [STx4]}, aec_tx_pool:peek(infinity)),

               meck:unload(aec_headers),
               ok
       end},
      {"Ensure ordering",
       fun() ->
                 %% We should sort by fee, but preserve the order of nonces for each sender
                 PK1 = new_pubkey(),
                 PK2 = new_pubkey(),
                 PK3 = new_pubkey(),
                 PK4 = new_pubkey(),
                 STxs =
                   [ a_signed_tx        (_Sender=PK1, me,_Nonce=1,_Fee=300000)
                   , a_signed_tx        (        PK1, me,       2,     400000)
                   , a_signed_tx        (        PK1, me,       3,     500000)
                   , a_signed_tx        (        PK2, me,       2,     700000)
                   , a_signed_tx        (        PK2, me,       1,     800000)
                   , signed_ct_create_tx(        PK4,           1,     300000,_GasPrice=1100000000)
                   , signed_ct_call_tx  (        PK4,           2,     600000,          9000000000)
                   , signed_ct_call_tx  (        PK4,           3,     900000,          1)
                   ],

                 [?assertEqual(ok, aec_tx_pool:push(Tx)) || Tx <- STxs],
                 {ok, CurrentMempoolSigned} = aec_tx_pool:peek(10),
                 %% extract transactions without verification
                 CurrentMempool = [ aetx_sign:tx(STx) || STx <- CurrentMempoolSigned ],

                 MempoolOrder = [{aetx:origin(Tx), aetx:nonce(Tx)} || Tx <- CurrentMempool],
                 %% this is not-optimal order: transactions for PK1 and PK4 are invalid in that order
                 CorrectOrder = [{PK4,3},{PK2,1},{PK2,2},{PK4,2},{PK1,3},{PK1,2},{PK4,1},{PK1,1}],

                 ?assertEqual(CorrectOrder, MempoolOrder),

                 %% check if we track nonces correctly
                 MaxNonce = aec_tx_pool:get_max_nonce(PK1),
                 ?assertEqual({ok,3}, MaxNonce),

                 NotExistingSender = aec_tx_pool:get_max_nonce(PK3),
                 ?assertEqual(undefined, NotExistingSender)
             end},
      {"Mempool consistency",
       fun() ->
               PK = new_pubkey(),
               MaxGas = aec_governance:block_gas_limit(),
               TopBlockHash = aec_chain:top_block_hash(),
               STx1 = a_signed_tx(PK, me, Nonce1=1, _Fee1=20000),
               ?assertEqual(ok, aec_tx_pool:push(STx1)),
               ?assertEqual([], aec_tx_pool:peek_visited()),
               [{PK, Nonce1, _}] = aec_tx_pool:peek_nonces(),
               Size = aec_tx_pool:size(),
               ?assertEqual({ok, [STx1]},
                            aec_tx_pool:get_candidate(MaxGas, TopBlockHash)),
               ?assertEqual([STx1], aec_tx_pool:peek_visited()),
               ?assertEqual([], aec_tx_pool:peek_db()),
               ?assertEqual(Size, aec_tx_pool:size()),
               %% a 'key' top_change should restore visited to the mempool
               aec_tx_pool:top_change(key, TopBlockHash, TopBlockHash),
               ?assertEqual([], aec_tx_pool:peek_visited()),
               ?assertEqual([STx1], aec_tx_pool:peek_db()),
               ?assertEqual(Size, aec_tx_pool:size())
       end},
      {"Ensure candidate ordering",
       fun() ->
               PK = new_pubkey(),
               MaxGas = aec_governance:block_gas_limit(),

               %% Only one tx in pool
               STx1 = a_signed_tx(PK, me, Nonce1=1,_Fee1=20000),
               ?assertEqual(ok, aec_tx_pool:push(STx1)),
               ?assertEqual({ok, [STx1]}, aec_tx_pool:get_candidate(MaxGas, aec_chain:top_block_hash())),

               aec_tx_pool:restore_mempool(),
               %% Order by nonce even if fee is higher
               STx2 = a_signed_tx(PK, me, Nonce2=2, Fee2=200000),
               ?assertEqual(ok, aec_tx_pool:push(STx2)),

               ?assertEqual({ok, [STx1, STx2]}, aec_tx_pool:get_candidate(MaxGas, aec_chain:top_block_hash())),

               aec_tx_pool:restore_mempool(),
               %% Replace same nonce with the higher fee
               STx3 = a_signed_tx(PK, me, Nonce1=1, 20000),
               ?assertEqual(ok, aec_tx_pool:push(STx3)),
               ?assertEqual({ok, [STx3, STx2]}, aec_tx_pool:get_candidate(MaxGas, aec_chain:top_block_hash())),

               aec_tx_pool:restore_mempool(),
               %% Replace same nonce with same fee but positive gas price (gas price of transaction without gas price is considered zero)
               STx4 = signed_ct_create_tx(PK, Nonce2=2, Fee2=200000,_GasPrice4=1100000000),
               ?assertEqual(ok, aec_tx_pool:push(STx4)),
               ?assertEqual({ok, [STx3, STx4]}, aec_tx_pool:get_candidate(MaxGas, aec_chain:top_block_hash())),

               aec_tx_pool:restore_mempool(),
               %% Replace same nonce with same fee but higher gas price
               STx5 = signed_ct_create_tx(PK, Nonce2=2, Fee2=200000, 2000000000),
               ?assertEqual(ok, aec_tx_pool:push(STx5)),
               ?assertEqual({ok, [STx3, STx5]}, aec_tx_pool:get_candidate(MaxGas, aec_chain:top_block_hash())),

               aec_tx_pool:restore_mempool(),
               %% Order by nonce even if fee and gas price are higher
               STx6 = signed_ct_call_tx(PK, _Nonce6=3,_Fee6=1000000,_GasPrice6=9000000000),
               ?assertEqual(ok, aec_tx_pool:push(STx6)),
               ?assertEqual({ok, [STx3, STx5, STx6]}, aec_tx_pool:get_candidate(MaxGas, aec_chain:top_block_hash())),

               ok
       end},
      {"Ensure block gas limit",
       fun() ->
               PK1 = new_pubkey(),
               PK2 = new_pubkey(),
               PK3 = new_pubkey(),

               %% Prepare 3 txs:
               %% 1st tx has the lowest gas
               %% Depends on aec_geovernance settings whether 2nd or 3rd is largest
               STx1 = a_signed_tx(        me,   PK1,      1, 20000),
               STx2 = signed_ct_create_tx(PK2,    1, 800000,  1000),
               STx3 = signed_ct_call_tx(  PK3,    1, 800000,  1000),

               GasTx1 = aetx:gas(aetx_sign:tx(STx1)),
               GasTx2 = aetx:gas(aetx_sign:tx(STx2)),
               GasTx3 = aetx:gas(aetx_sign:tx(STx3)),

               ?assert(GasTx2 > GasTx1),
               ?assert(GasTx3 > GasTx1),
               {MinGasTx, MaxGasTx} =
                   case GasTx2 > GasTx3 of
                       true -> { {STx3, GasTx3}, {STx2, GasTx2} };
                       false ->  { {STx2, GasTx2}, {STx3, GasTx3} }
                     end,

               %% Push all txs to the pool.
               ?assertEqual(ok, aec_tx_pool:push(STx1)),
               ?assertEqual(ok, aec_tx_pool:push(STx2)),
               ?assertEqual(ok, aec_tx_pool:push(STx3)),

               %% Do not get any tx - not enough gas.
               ?assertEqual({ok, []},
                            aec_tx_pool:get_candidate(GasTx1 - 1, aec_chain:top_block_hash())),

               %% Get 1st tx only.
               ?assertEqual({ok, [STx1]},
                            aec_tx_pool:get_candidate(GasTx1, aec_chain:top_block_hash())),

               aec_tx_pool:restore_mempool(),

               %% Get only 2 txs, the 1st + 2nd or 1st + 3rd.
               {_, Max} = MaxGasTx,
               {ok, STxs1} = aec_tx_pool:get_candidate(GasTx1 + Max, aec_chain:top_block_hash()),
               ?assert(lists:member(STx1, STxs1) and (lists:member(STx2, STxs1) or lists:member(STx3, STxs1))),

               aec_tx_pool:restore_mempool(),
               %% Get all 3 txs by providing exactly the gas the txs need.
               {ok, STxs2} = aec_tx_pool:get_candidate(GasTx1 + GasTx2 + GasTx3, aec_chain:top_block_hash()),
               ?assert(lists:member(STx1, STxs2)),
               ?assert(lists:member(STx2, STxs2)),
               ?assert(lists:member(STx3, STxs2)),

               aec_tx_pool:restore_mempool(),

               %% Get 1st and 3rd tx, skip 2nd tx.
               {MinSTx, Min} = MinGasTx,
               {ok, STxs3} = aec_tx_pool:get_candidate(GasTx1 + Min, aec_chain:top_block_hash()),
               ?assert(lists:member(STx1, STxs3)),
               ?assert(lists:member(MinSTx, STxs3)),

               ok
       end},
      {"Ensure persistence",
       fun() ->
               %% Prepare a few txs.
               STx1 = a_signed_tx(me, new_pubkey(), 1, 20000),
               STx2 = a_signed_tx(me, new_pubkey(), 2, 20000),
               ?assertEqual(ok, aec_tx_pool:push(STx1)),
               ?assertEqual(ok, aec_tx_pool:push(STx2)),
               {ok, PoolTxs} = aec_tx_pool:peek(infinity),
               ?assertEqual(lists:sort([STx1, STx2]), lists:sort(PoolTxs)),

               %% Stop the mempool and start it again to see that it reinits
               ok        = aec_tx_pool:stop(),
               {ok, Pid} = aec_tx_pool:start_link(),
               {ok, PoolTxs2} = aec_tx_pool:peek(infinity),
               ?assertEqual(lists:sort([STx1, STx2]), lists:sort(PoolTxs2)),
               unlink(Pid), %% Leave it for the cleanup
               ok
       end},
      {"Test rejection of transactions",
       fun() ->
               aec_test_utils:stop_chain_db(),
               %% Prepare a chain with specific genesis block with some funds
               PubKey1 = new_pubkey(),
               PubKey2 = new_pubkey(),
               meck:expect(aec_genesis_block_settings, preset_accounts, 0,
                  [{PubKey1, 100000}, {PubKey2, 100000}]),
               {GenesisBlock, _} = aec_block_genesis:genesis_block_with_state(),
               aec_test_utils:start_chain_db(),
               ok = aec_chain_state:insert_block(GenesisBlock),

               %% The first block needs to be a key-block
               {ok, KeyBlock1} = aec_block_key_candidate:create(aec_chain:top_block(), PubKey1),
               {ok, KeyHash1} = aec_blocks:hash_internal_representation(KeyBlock1),
               ok = aec_chain_state:insert_block(KeyBlock1),
               ?assertEqual(KeyHash1, aec_chain:top_block_hash()),
               ok = aec_keys:promote_candidate(aec_blocks:miner(KeyBlock1)),

               TopBlock = aec_chain:top_block(),

               %% Add a transaction to the chain
               STx1 = a_signed_tx(PubKey1, new_pubkey(), 1, 20000),
               ?assertEqual(ok, aec_tx_pool:push(STx1)),
               {ok, USCandidate1, _} = aec_block_micro_candidate:create(TopBlock),
               {ok, Candidate1} = aec_keys:sign_micro_block(USCandidate1),
               {ok, Top} = aec_blocks:hash_internal_representation(Candidate1),
               ok = aec_chain_state:insert_block(Candidate1),
               ?assertEqual(Top, aec_chain:top_block_hash()),

               %% Now we should reject the same transaction since it
               %% is already in the chain
               ?assertEqual({error, already_accepted},
                            aec_tx_pool:push(STx1)),

               %% A transaction with too low nonce should be rejected
               STx2 = a_signed_tx(PubKey1, new_pubkey(), 1, 20000),
               ?assertEqual({error, account_nonce_too_high},
                            aec_tx_pool:push(STx2)),

               %% A transaction with too high nonce should _NOT_ be rejected
               STx3 = a_signed_tx(PubKey1, new_pubkey(), 5, 20000),
               ?assertEqual(ok, aec_tx_pool:push(STx3)),

               %% A transaction with too low fee should be rejected
               STx4 = a_signed_tx(PubKey1, new_pubkey(), 6,
                                  aec_governance:minimum_tx_fee() - 1),
               ?assertEqual({error, too_low_fee}, aec_tx_pool:push(STx4)),

               %% A transaction with too low gas price should be rejected
               meck:expect(aec_governance, minimum_gas_price, 0, 1),
               ?assertEqual(ok, aec_tx_pool:push(signed_ct_create_tx(PubKey1, 10, 1000000, 1))),
               ?assertEqual(ok, aec_tx_pool:push(signed_ct_call_tx  (PubKey1, 20, 1000000, 1))),
               ?assertEqual(ok, aec_tx_pool:push(signed_ct_create_tx(PubKey1, 11, 2000000, 2))),
               ?assertEqual(ok, aec_tx_pool:push(signed_ct_call_tx  (PubKey1, 21, 2000000, 2))),
               meck:expect(aec_governance, minimum_gas_price, 0, 2),
               ?assertEqual({error, too_low_gas_price}, aec_tx_pool:push(signed_ct_create_tx(PubKey1, 12, 2000000, 0))),
               ?assertEqual({error, too_low_gas_price}, aec_tx_pool:push(signed_ct_call_tx  (PubKey1, 22, 2000000, 0))),
               ?assertEqual({error, too_low_gas_price}, aec_tx_pool:push(signed_ct_create_tx(PubKey1, 13, 2000000, 1))),
               ?assertEqual({error, too_low_gas_price}, aec_tx_pool:push(signed_ct_call_tx  (PubKey1, 23, 2000000, 1))),
               ?assertEqual(ok, aec_tx_pool:push(signed_ct_create_tx(PubKey1, 14, 2000000, 2))),
               ?assertEqual(ok, aec_tx_pool:push(signed_ct_call_tx  (PubKey1, 24, 2000000, 2))),
               ?assertEqual(ok, aec_tx_pool:push(signed_ct_create_tx(PubKey1, 15, 4000000, 3))),
               ?assertEqual(ok, aec_tx_pool:push(signed_ct_call_tx  (PubKey1, 25, 4000000, 3))),

               %% A transaction with too low ttl should be rejected
               %% First add another block to make the chain high enough to
               %% fail on TTL
               {ok, Candidate2} = aec_block_key_candidate:create(aec_chain:top_block(), PubKey1),
               {ok, Top2} = aec_blocks:hash_internal_representation(Candidate2),
               ok = aec_chain_state:insert_block(Candidate2),
               ?assertEqual(Top2, aec_chain:top_block_hash()),

               STx5 = a_signed_tx(PubKey1, new_pubkey(), 6, 40000, 1),
               ?assertEqual({error, ttl_expired}, aec_tx_pool:push(STx5)),

               ok
       end},
       {"Test GC",
        fun() ->
            %% initialize chain
            {GenesisBlock, _} = aec_block_genesis:genesis_block_with_state(),
            ok = aec_chain_state:insert_block(GenesisBlock),

            %% Prepare three transactions
            PubKey = new_pubkey(),
            STx1 = a_signed_tx(PubKey, PubKey, 1, 20000),
            STx2 = a_signed_tx(PubKey, PubKey, 2, 20000),
            STx3 = a_signed_tx(PubKey, PubKey, 3, 20000),

            %% Post them
            ?assertEqual(ok, aec_tx_pool:push(STx1)),
            ?assertEqual(ok, aec_tx_pool:push(STx2)),
            ?assertEqual(ok, aec_tx_pool:push(STx3)),

            ?assertMatch({ok, [_, _, _]}, aec_tx_pool:peek(infinity)),

            %% Txs will be scheduled for
            %% removal at Height + ?TX_TTL
            %% For test ?TX_TTL = 8

            %% Doing a garbage collect at height 0 shouldn't affect
            tx_pool_gc(0),
            _ = sys:get_status(aec_tx_pool_gc),
            ?assertMatch({ok, [_, _, _]}, aec_tx_pool:peek(infinity)),

            %% At 4 still GC should not kick in.
            tx_pool_gc(4),
            ?assertMatch({ok, [_, _, _]}, aec_tx_pool:peek(infinity)),

            %% At 8, now TXs should be dropped.
            tx_pool_gc(8),
            ?assertMatch({ok, []}, aec_tx_pool:peek(infinity))

        end}
     ]}.

tx_pool_gc(Height) ->
    aec_tx_pool_gc:gc(Height),
    sys:get_status(aec_tx_pool_gc), %% sync point (gc is asynchronous)
    ok.

a_signed_tx(Sender, Recipient, Nonce, Fee) ->
    a_signed_tx(Sender, Recipient, Nonce, Fee,0).

a_signed_tx(Sender, Recipient, Nonce, Fee, TTL) ->
    {ok, Tx} = aec_spend_tx:new(#{sender_id => acct(Sender),
                                  recipient_id => acct(Recipient),
                                  amount => 1,
                                  nonce => Nonce,
                                  fee => Fee,
                                  ttl => TTL,
                                  payload => <<"">>}),
    {ok, STx} = sign(Sender, Tx),
    STx.

signed_ct_create_tx(Sender, Nonce, Fee, GasPrice) ->
    Spec =
        #{ fee        => Fee
         , owner_id   => aec_id:create(account, Sender)
         , nonce      => Nonce
         , code       => <<"NOT PROPER BYTE CODE">>
         , vm_version => 1
         , deposit    => 10
         , amount     => 200
         , gas        => 100000
         , gas_price  => GasPrice
         , call_data  => <<"NOT ENCODED ACCORDING TO ABI">>
         , ttl        => 0
         },
    {ok, Tx} = aect_create_tx:new(Spec),
    {ok, STx} = sign(Sender, Tx),
    STx.

signed_ct_call_tx(Sender, Nonce, Fee, GasPrice) ->
    ContractId = aec_id:create(contract, <<"contract_address......(32 bytes)">>),
    Spec =
        #{ fee         => Fee
         , contract_id => ContractId
         , caller_id   => aec_id:create(account, Sender)
         , nonce       => Nonce
         , vm_version  => 1
         , amount      => 100
         , gas         => 50000
         , gas_price   => GasPrice
         , call_data   => <<"CALL DATA">>
         , ttl         => 0
         },
    {ok, Tx} = aect_call_tx:new(Spec),
    {ok, STx} = sign(Sender, Tx),
    STx.

sign(me, Tx) ->
    {ok, PrivKey} = aec_keys:sign_privkey(),
    {ok, aec_test_utils:sign_tx(Tx, PrivKey)};
sign(PubKey, Tx) ->
    try
        [{_, PrivKey}] = ets:lookup(?TAB, PubKey),
        {ok, Trees} = aec_chain:get_top_state(),
        {ok, Signers} = aetx:signers(Tx, Trees),
        true = lists:member(PubKey, Signers),
        {ok, aec_test_utils:sign_tx(Tx, PrivKey)}
    catch
        error:Err ->
            erlang:error({Err, erlang:get_stacktrace()})
    end.

acct(me) ->
    {ok, Key} = aec_keys:pubkey(),
    aec_id:create(account, Key);
acct(A) when is_binary(A) ->
    aec_id:create(account, A).

new_pubkey() ->
    {Pub, Priv} = keypair(),
    ets:insert(?TAB, {Pub, Priv}),
    Pub.

keypair() ->
    #{ public := Pub, secret := Priv } = enacl:sign_keypair(),
    {Pub, Priv}.

{'fraction_non_alphanumeric': 0.12175832260437741, 'fraction_numerical': 0.028876218502850836, 'mean_word_length': 1.9660684087065627, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 159, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 10, 'word_list_counts': {'cursed_substrings.json': 16, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '916018', 'n_tokens_mistral': 18724, 'n_tokens_neox': 17342, 'n_words': 3334}