// Copyright (c) 2014-2018 Zano Project
// Copyright (c) 2014-2018 The Louisdor Project
// Copyright (c) 2012-2013 The Cryptonote developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "chaingen.h"
#include "chain_switch_1.h"
#include "random_helper.h"

using namespace epee;
using namespace currency;

#include "currency_core/currency_format_utils.h"

gen_chain_switch_1::gen_chain_switch_1()
  : m_miner_initial_amount(0)
{
  REGISTER_CALLBACK("check_split_not_switched", gen_chain_switch_1::check_split_not_switched);
  REGISTER_CALLBACK("check_split_switched", gen_chain_switch_1::check_split_switched);
}


//-----------------------------------------------------------------------------------------------------
bool gen_chain_switch_1::generate(std::vector<test_event_entry>& events) const
{
  uint64_t ts_start = 1450000000;
  /*
   0    10...11   12...22    23   24   25              <- main blockchain height (assuming unlock window is 10 blocks)
  (0 )-(0r)-(1 )-(2 )-(2r) -(3 )-(4 )                  <- main chain, until 7 isn't connected
                        \ |-(5 )-(6 )|                 <- alt chain, until 7 isn't connected

  (0 )-(0r)-(1 )-(2 )-(2r) -(5 )-(6 )-(7 )             <- main chain, after reorganize
                        \ |-(3 )-(4 )|                 <- alt chain, after reorganize

  MAIN CHAIN                (3 )-(4 )
  miner      -5             -14  -16       = -35
  acc_1      +5              +3            = +8
  acc_2                      +8  +2        = +10
  acc_3                      +1  +13       = +14
  acc_4                      +2  +1        = +3

  ALT CHAIN                (5 )-(6 )-(7 )
  miner      -5             -20  -16       = -41
  acc_1      +5             +1   +2        = +8
  acc_2                     +3             = +3
  acc_3                     +2   +12       = +14
  acc_4                     +14  +2        = +16

  transactions ([n] - tx amount, (m) - block):
  (1)     : miner -[ 5]-> account_1 ( +5 in main chain,  +5 in alt chain)
  (3)     : miner -[ 7]-> account_2 ( +7 in main chain,  +0 in alt chain), tx will be in tx pool after switch
  (4), (6): miner -[11]-> account_3 (+11 in main chain, +11 in alt chain)
  (5)     : miner -[13]-> account_4 ( +0 in main chain, +13 in alt chain), tx will be in tx pool before switch

  transactions orders ([n] - tx amount, (m) - block):
  miner -[1], [2]-> account_1: in main chain (3), (3), in alt chain (5), (6)
  miner -[1], [2]-> account_2: in main chain (3), (4), in alt chain (5), (5)
  miner -[1], [2]-> account_3: in main chain (3), (4), in alt chain (6), (5)
  miner -[1], [2]-> account_4: in main chain (4), (3), in alt chain (5), (6)
  */

  GENERATE_ACCOUNT(miner_account);
  m_miner_account = miner_account;

  //                                                                                              events
  MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start);                                     //  0
  m_miner_initial_amount = get_outs_money_amount(blk_0.miner_tx);
  MAKE_ACCOUNT(events, recipient_account_1);                                                      //  1
  MAKE_ACCOUNT(events, recipient_account_2);                                                      //  2
  MAKE_ACCOUNT(events, recipient_account_3);                                                      //  3
  MAKE_ACCOUNT(events, recipient_account_4);                                                      //  4
  REWIND_BLOCKS(events, blk_0r, blk_0, miner_account)                                             // <N blocks>
  MAKE_TX(events, tx_00, miner_account, recipient_account_1, MK_TEST_COINS(5), blk_0r);           //  5 + N
  MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_account, tx_00);                               //  6 + N
  MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_account);                                           //  7 + N
  REWIND_BLOCKS(events, blk_2r, blk_2, miner_account)                                             // <N blocks>

  // Transactions to test account balances after switch
  MAKE_TX_LIST_START(events, txs_blk_3, miner_account, recipient_account_2, MK_TEST_COINS(7), blk_2r);  //  8 + 2N
  MAKE_TX_LIST_START(events, txs_blk_4, miner_account, recipient_account_3, MK_TEST_COINS(11), blk_2r); //  9 + 2N
  MAKE_TX_LIST_START(events, txs_blk_5, miner_account, recipient_account_4, MK_TEST_COINS(13), blk_2r); // 10 + 2N
  std::list<transaction> txs_blk_6;
  txs_blk_6.push_back(txs_blk_4.front());

  // Transactions, that has different order in alt block chains
  MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_1, MK_TEST_COINS(1), blk_2r);        // 11 + 2N
  txs_blk_5.push_back(txs_blk_3.back());
  MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_1, MK_TEST_COINS(2), blk_2r);        // 12 + 2N
  txs_blk_6.push_back(txs_blk_3.back());

  MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_2, MK_TEST_COINS(1), blk_2r);        // 13 + 2N
  txs_blk_5.push_back(txs_blk_3.back());
  MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_2, MK_TEST_COINS(2), blk_2r);        // 14 + 2N
  txs_blk_5.push_back(txs_blk_4.back());

  MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_3, MK_TEST_COINS(1), blk_2r);        // 15 + 2N
  txs_blk_6.push_back(txs_blk_3.back());
  MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_3, MK_TEST_COINS(2), blk_2r);        // 16 + 2N
  txs_blk_5.push_back(txs_blk_4.back());

  MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_4, MK_TEST_COINS(1), blk_2r);        // 17 + 2N
  txs_blk_5.push_back(txs_blk_4.back());
  MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_4, MK_TEST_COINS(2), blk_2r);        // 18 + 2N
  txs_blk_6.push_back(txs_blk_3.back());

  MAKE_NEXT_BLOCK_TX_LIST(events, blk_3, blk_2r, miner_account, txs_blk_3);                       // 19 + 2N
  MAKE_NEXT_BLOCK_TX_LIST(events, blk_4, blk_3, miner_account, txs_blk_4);                        // 20 + 2N
  //split
  MAKE_NEXT_BLOCK_TX_LIST(events, blk_5, blk_2r, miner_account, txs_blk_5);                       // 22 + 2N
  MAKE_NEXT_BLOCK_TX_LIST(events, blk_6, blk_5, miner_account, txs_blk_6);                        // 23 + 2N
  DO_CALLBACK(events, "check_split_not_switched");                                                // 21 + 2N
  MAKE_NEXT_BLOCK(events, blk_7, blk_6, miner_account);                                           // 24 + 2N
  DO_CALLBACK(events, "check_split_switched");                                                    // 25 + 2N

  return true;
}


//-----------------------------------------------------------------------------------------------------
bool gen_chain_switch_1::check_split_not_switched(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{

  m_recipient_account_1 = boost::get<account_base>(events[1]);
  m_recipient_account_2 = boost::get<account_base>(events[2]);
  m_recipient_account_3 = boost::get<account_base>(events[3]);
  m_recipient_account_4 = boost::get<account_base>(events[4]);

  std::list<block> blocks;
  bool r = c.get_blocks(0, 10000, blocks);
  CHECK_TEST_CONDITION(r);
  CHECK_EQ(5 + 2 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW, blocks.size());
  CHECK_TEST_CONDITION(blocks.back() == boost::get<block>(events[20 + 2 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW]));  // blk_4

  CHECK_EQ(2, c.get_alternative_blocks_count());

  std::vector<currency::block> chain;
  map_hash2tx_t mtx;
  r = find_block_chain(events, chain, mtx, get_block_hash(blocks.back()));
  CHECK_TEST_CONDITION(r);
  CHECK_EQ(MK_TEST_COINS(8),  get_balance(m_recipient_account_1, chain, mtx));
  CHECK_EQ(MK_TEST_COINS(10), get_balance(m_recipient_account_2, chain, mtx));
  CHECK_EQ(MK_TEST_COINS(14), get_balance(m_recipient_account_3, chain, mtx));
  CHECK_EQ(MK_TEST_COINS(3),  get_balance(m_recipient_account_4, chain, mtx));

  crypto::hash genesis_block_hash = currency::get_block_hash(boost::get<currency::block>(events[0]));
  uint64_t mined_after_genesis = 0;
  BOOST_FOREACH(currency::block b, blocks)
  {
    if (currency::get_block_hash(b) != genesis_block_hash)
      mined_after_genesis += get_outs_money_amount(b.miner_tx);
  }
  const size_t tx_main_chain_count = 11;
  uint64_t amount = m_miner_initial_amount + mined_after_genesis - MK_TEST_COINS(35) - TESTS_DEFAULT_FEE * tx_main_chain_count;
  uint64_t balance = get_balance(m_miner_account, chain, mtx);
  CHECK_TEST_CONDITION(amount > balance ? amount - balance : balance - amount <= DEFAULT_DUST_THRESHOLD);

  std::list<transaction> tx_pool;
  r = c.get_pool_transactions(tx_pool);
  CHECK_TEST_CONDITION(r);
  CHECK_EQ(1, tx_pool.size());

  std::vector<wallet_out_info> tx_outs;
  uint64_t transfered;
  crypto::key_derivation derivation = AUTO_VAL_INIT(derivation);
  lookup_acc_outs(m_recipient_account_4.get_keys(), tx_pool.front(), get_tx_pub_key_from_extra(tx_pool.front()), tx_outs, transfered, derivation);
  CHECK_EQ(MK_TEST_COINS(13), transfered);

  m_chain_1.swap(blocks);
  m_tx_pool.swap(tx_pool);

  return true;
}

//-----------------------------------------------------------------------------------------------------
bool gen_chain_switch_1::check_split_switched(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{

  std::list<block> blocks;
  bool r = c.get_blocks(0, 10000, blocks);
  CHECK_TEST_CONDITION(r);
  CHECK_EQ(6 + 2 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW, blocks.size());
  auto it = blocks.end();
  --it; --it; --it;
  CHECK_TEST_CONDITION(std::equal(blocks.begin(), it, m_chain_1.begin()));
  CHECK_TEST_CONDITION(blocks.back() == boost::get<block>(events[24 + 2 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW]));  // blk_7

  std::list<block> alt_blocks;
  r = c.get_alternative_blocks(alt_blocks);
  CHECK_TEST_CONDITION(r);
  CHECK_EQ(2, c.get_alternative_blocks_count());

  // Some blocks that were in main chain are in alt chain now
  BOOST_FOREACH(block b, alt_blocks)
  {
    CHECK_TEST_CONDITION(m_chain_1.end() != std::find(m_chain_1.begin(), m_chain_1.end(), b));
  }

  std::vector<currency::block> chain;
  map_hash2tx_t mtx;
  r = find_block_chain(events, chain, mtx, get_block_hash(blocks.back()));
  CHECK_TEST_CONDITION(r);
  CHECK_EQ(MK_TEST_COINS(8),  get_balance(m_recipient_account_1, chain, mtx));
  CHECK_EQ(MK_TEST_COINS(3),  get_balance(m_recipient_account_2, chain, mtx));
  CHECK_EQ(MK_TEST_COINS(14), get_balance(m_recipient_account_3, chain, mtx));
  CHECK_EQ(MK_TEST_COINS(16), get_balance(m_recipient_account_4, chain, mtx));

  crypto::hash genesis_block_hash = currency::get_block_hash(boost::get<currency::block>(events[0]));
  uint64_t mined_after_genesis = 0;
  BOOST_FOREACH(currency::block b, blocks)
  {
    if (currency::get_block_hash(b) != genesis_block_hash)
      mined_after_genesis += get_outs_money_amount(b.miner_tx);
  }
  const size_t tx_alt_chain_count = 11;
  uint64_t amount = m_miner_initial_amount + mined_after_genesis - MK_TEST_COINS(41) - TESTS_DEFAULT_FEE * tx_alt_chain_count;
  uint64_t balance = get_balance(m_miner_account, chain, mtx);
  CHECK_TEST_CONDITION(amount > balance ? amount - balance : balance - amount <= DEFAULT_DUST_THRESHOLD);

  std::list<transaction> tx_pool;
  r = c.get_pool_transactions(tx_pool);
  CHECK_TEST_CONDITION(r);
  CHECK_EQ(1, tx_pool.size());
  CHECK_TEST_CONDITION(!(tx_pool.front() == m_tx_pool.front()));

  std::vector<wallet_out_info> tx_outs;
  uint64_t transfered;
  crypto::key_derivation derivation = AUTO_VAL_INIT(derivation);
  lookup_acc_outs(m_recipient_account_2.get_keys(), tx_pool.front(), tx_outs, transfered, derivation);
  CHECK_EQ(MK_TEST_COINS(7), transfered);

  return true;
}

//-----------------------------------------------------------------------------------------------------

bad_chain_switching_with_rollback::bad_chain_switching_with_rollback()
{
  REGISTER_CALLBACK_METHOD(bad_chain_switching_with_rollback, c1);
}

bool bad_chain_switching_with_rollback::generate(std::vector<test_event_entry>& events) const
{
  GENERATE_ACCOUNT(preminer_acc);
  GENERATE_ACCOUNT(miner_acc);
  GENERATE_ACCOUNT(alice_acc);
  GENERATE_ACCOUNT(bob_acc);

  MAKE_GENESIS_BLOCK(events, blk_0, preminer_acc, test_core_time::get_time());
  REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 4);

  MAKE_TX(events, tx_1, miner_acc, alice_acc, TESTS_DEFAULT_FEE * 2, blk_0r);
  MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_1);

  MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_acc);

  // make tx_2 referring to tx_1 output
  MAKE_TX(events, tx_2, alice_acc, bob_acc, TESTS_DEFAULT_FEE, blk_2);
  MAKE_NEXT_BLOCK_TX1(events, blk_3, blk_2, miner_acc, tx_2);

  // check balance at gentime

  // Alice should have nothing
  std::shared_ptr<tools::wallet2> alice_wlt;
  generator.init_test_wallet(alice_acc, get_block_hash(blk_0), alice_wlt);
  bool r = generator.refresh_test_wallet(events, alice_wlt.get(), get_block_hash(blk_3), CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 4 + 3);
  CHECK_AND_ASSERT_MES(r, false, "refresh_test_wallet failed");
  CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt.get(), "Alice", 0, 0, 0, 0, 0), false, "");

  // Bob should have TESTS_DEFAULT_FEE
  std::shared_ptr<tools::wallet2> bob_wlt;
  generator.init_test_wallet(bob_acc, get_block_hash(blk_0), bob_wlt);
  r = generator.refresh_test_wallet(events, bob_wlt.get(), get_block_hash(blk_3), CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 4 + 3);
  CHECK_AND_ASSERT_MES(r, false, "refresh_test_wallet failed");
  CHECK_AND_ASSERT_MES(check_balance_via_wallet(*bob_wlt.get(), "Bob", TESTS_DEFAULT_FEE, 0, 0, 0, 0), false, "");

  // start altchain from blk_0r, include tx_2 but NOT include tx_1
  DO_CALLBACK(events, "mark_invalid_block");
  MAKE_NEXT_BLOCK_TX1(events, blk_1a, blk_0r, miner_acc, tx_2);
  DO_CALLBACK(events, "mark_orphan_block");
  MAKE_NEXT_BLOCK(events, blk_2a, blk_1a, miner_acc);
  
  //  0 ... 14    15    16    17    18    19     <- height
  // (0 )- (0r)- (1 )- (2 )- (3 )-               <- main chain
  //         |   tx_1        tx_2               <- txs (tx_2 uses output of tx_1)
  //         |     |           |
  //         |     +--<--<--<--+
  //         |
  //          \- !1a!- !2a!-                     <- alt chain
  //             tx_2                            <- txs (tx_2 uses output of tx_1)

  // top block should be the 8last block in the main chain -- blk_3
  DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_3), get_block_hash(blk_3)));

  m_invalid_block_hash_to_check = get_block_hash(blk_1a);
  DO_CALLBACK(events, "c1");

  return true;
}

bool bad_chain_switching_with_rollback::c1(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
  // altblocks now are marked as invalid only if rollback was unsuccessfull, so don't check it here
  // invalid block can be checked by have_block() but can't be retreived by get_block_by_hash
  //bool r = c.get_blockchain_storage().have_block(m_invalid_block_hash_to_check);
  //CHECK_AND_ASSERT_MES(r, false, "have_block failed when called with id = " << m_invalid_block_hash_to_check);

  block b = AUTO_VAL_INIT(b);
  bool r = c.get_blockchain_storage().get_block_by_hash(m_invalid_block_hash_to_check, b);
  CHECK_AND_ASSERT_MES(!r, false, "get_block_extended_info_by_hash suceeded, but expected to fail");

  return true;
}

//-----------------------------------------------------------------------------------------------------
struct tx_in_pool_info
{
  crypto::hash hash;
  size_t blobsize;
};

struct params_tx_pool
{
  params_tx_pool() {}
  params_tx_pool(crypto::hash hash, size_t blobsize) : txs{ tx_in_pool_info{ hash, blobsize } }
  {}
  params_tx_pool(crypto::hash hash1, size_t blobsize1, crypto::hash hash2, size_t blobsize2)
  {
    txs.push_back(tx_in_pool_info{ hash1, blobsize1 });
    txs.push_back(tx_in_pool_info{ hash2, blobsize2 });
  }

  std::vector<tx_in_pool_info> txs;
  BEGIN_KV_SERIALIZE_MAP()
    KV_SERIALIZE_CONTAINER_POD_AS_BLOB(txs)
  END_KV_SERIALIZE_MAP()
};


chain_switching_and_tx_with_attachment_blobsize::chain_switching_and_tx_with_attachment_blobsize()
{
  REGISTER_CALLBACK_METHOD(chain_switching_and_tx_with_attachment_blobsize, check_tx_pool_txs);
}

bool chain_switching_and_tx_with_attachment_blobsize::generate(std::vector<test_event_entry>& events) const
{
  GENERATE_ACCOUNT(preminer_acc);
  GENERATE_ACCOUNT(miner_acc);
  
  MAKE_GENESIS_BLOCK(events, blk_0, preminer_acc, test_core_time::get_time());
  REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW+4);

  tx_comment comment_att = AUTO_VAL_INIT(comment_att);
  comment_att.comment = get_random_text(CURRENCY_MAX_TRANSACTION_BLOB_SIZE * 9 / 10); // this is necessary to push cummulative block size out of full reward zone, so block reward will be non-default
  std::vector<attachment_v> attachment({comment_att});

  // create txs with attachment (and put it in the pool)
  MAKE_TX_ATTACH(events, tx_1, miner_acc, miner_acc, TESTS_DEFAULT_FEE, blk_0r, attachment);
  MAKE_TX_ATTACH(events, tx_2, miner_acc, miner_acc, TESTS_DEFAULT_FEE, blk_0r, attachment);

  // make sure the pool has correct txs
  DO_CALLBACK_PARAMS_STR(events, "check_tx_pool_txs", epee::serialization::store_t_to_json(params_tx_pool(get_transaction_hash(tx_1), get_object_blobsize(tx_1), get_transaction_hash(tx_2), get_object_blobsize(tx_2))));

  MAKE_NEXT_BLOCK(events, blk_1, blk_0r, miner_acc);
  MAKE_NEXT_BLOCK_TX_LIST(events, blk_2, blk_1, miner_acc, std::list<transaction>({ tx_1, tx_2 }));

  // make sure tx pool is empty now
  DO_CALLBACK(events, "check_tx_pool_empty");

  // make altchain and grow it till reorganization happens
  MAKE_NEXT_BLOCK(events, blk_1a, blk_0r, miner_acc);
  MAKE_NEXT_BLOCK(events, blk_2a, blk_1a, miner_acc);
  MAKE_NEXT_BLOCK(events, blk_3a, blk_2a, miner_acc);

  // make sure chain switching happened indeed
  DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_3a), get_block_hash(blk_3a)));

  // txs must have been added back to tx_pool, make sure they are
  DO_CALLBACK_PARAMS_STR(events, "check_tx_pool_txs", epee::serialization::store_t_to_json(params_tx_pool(get_transaction_hash(tx_1), get_object_blobsize(tx_1), get_transaction_hash(tx_2), get_object_blobsize(tx_2))));

  // put a block with txs
  // (this step triggered invalid block reward calculation a.k.a. "coinbase transaction doesn't use full amount of block reward"
  // in past due to invalid tx blob size calculations)
  MAKE_NEXT_BLOCK_TX_LIST(events, blk_4a, blk_3a, miner_acc, std::list<transaction>({ tx_1, tx_2 }));

  DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_4a), get_block_hash(blk_4a)));

  // make sure tx pool is empty now
  DO_CALLBACK(events, "check_tx_pool_empty");

  return true;
}

bool chain_switching_and_tx_with_attachment_blobsize::check_tx_pool_txs(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
  params_tx_pool p = AUTO_VAL_INIT(p);
  bool r = epee::serialization::load_t_from_json(p, boost::get<callback_entry>(events[ev_index]).callback_params);
  CHECK_AND_ASSERT_MES(r, false, "Can't obtain event params. Forgot to pass them?");

  CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == p.txs.size(), false, "Tx pool has " << c.get_pool_transactions_count() << " transactions, expected: " << p.txs.size());

  for (auto &te : p.txs)
  {
    transaction tx = AUTO_VAL_INIT(tx);
    bool r = c.get_transaction(te.hash, tx);
    CHECK_AND_ASSERT_MES(r, false, "get_transaction failed for " << te.hash);

    crypto::hash hash = get_transaction_hash(tx);
    CHECK_AND_ASSERT_MES(te.hash == hash, false, "Tx in the pool has incorrect hash: " << hash << ", while expected is " << te.hash);

    size_t blobsize = get_object_blobsize(tx);
    CHECK_AND_ASSERT_MES(te.blobsize == blobsize, false, "Tx in the pool has incorrect blobsize == " << blobsize << ", while excepted value is " << te.blobsize);
  }

  return true;
}

//-----------------------------------------------------------------------------------------------------

bool chain_switching_when_gindex_spent_in_both_chains::generate(std::vector<test_event_entry>& events) const
{
  GENERATE_ACCOUNT(miner_acc);
  GENERATE_ACCOUNT(alice_acc);
  MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time());
  DO_CALLBACK(events, "configure_core"); // necessary to set m_hardforks
  if (m_hardforks.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, 2))
  {
    // HF4 requires tests_random_split_strategy (for 2 outputs minimum)
    test_gentime_settings tgts = generator.get_test_gentime_settings();
    tgts.split_strategy = tests_random_split_strategy;
    generator.set_test_gentime_settings(tgts);
  }

  MAKE_NEXT_BLOCK(events, blk_1, blk_0, alice_acc);
  REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);

  uint64_t amount = get_outs_money_amount(blk_1.miner_tx) - TESTS_DEFAULT_FEE;

  MAKE_NEXT_BLOCK(events, blk_2, blk_1r, miner_acc);

  MAKE_TX_FEE(events, tx_1, alice_acc, alice_acc, amount, TESTS_DEFAULT_FEE, blk_2);
  MAKE_NEXT_BLOCK_TX1(events, blk_3, blk_2, miner_acc, tx_1);

  //  0      1      11     12     13     14             < height
  // (0 )-  (1 )...(1r)-  (2 )-  (3 )-                  < main chain
  //         ^        \          tx_1                   < txs
  //         |         \ 
  //         |          \-(2a)-  (3a)-  (4a)-           < alt chain
  //         +                   tx_1                   < txs

  MAKE_NEXT_BLOCK(events, blk_2a, blk_1r, miner_acc);
  MAKE_NEXT_BLOCK_TX1(events, blk_3a, blk_2a, miner_acc, tx_1);
  
  MAKE_NEXT_BLOCK(events, blk_4a, blk_3a, miner_acc);

  DO_CALLBACK(events, "check_tx_pool_empty");
  DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_4a), get_block_hash(blk_4a)));

  CREATE_TEST_WALLET(alice_wlt, alice_acc, blk_0);
  REFRESH_TEST_WALLET_AT_GEN_TIME(events, alice_wlt, blk_4a, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 4);
  CHECK_TEST_WALLET_BALANCE_AT_GEN_TIME(alice_wlt, amount);

  return true;
}

//-----------------------------------------------------------------------------------------------------

bool alt_chain_coins_pow_mined_then_spent::generate(std::vector<test_event_entry>& events) const
{
  // test idea: in alt chain mine and then spent some coins, then trigger chain switching

  GENERATE_ACCOUNT(miner_acc);
  GENERATE_ACCOUNT(alice_acc);
  MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time());
  MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_acc);
  REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1);

  MAKE_NEXT_BLOCK(events, blk_2a, blk_1, alice_acc);
  REWIND_BLOCKS_N(events, blk_2ra, blk_2a, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);

  //  0      1      2      12      13
  // (0 )-  (1 )-  . . .  (1r)-  
  //          \ 
  //           \-  (2a)...(2ra)-  (3a)-
  //                |
  //                +-------------tx_1      tx_1 spents 2a.miner_tx output
  
  events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true)); // simulate alt-block tx behaviour
  MAKE_TX(events, tx_1, alice_acc, alice_acc, TESTS_DEFAULT_FEE, blk_2ra);
  events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false));
  
  MAKE_NEXT_BLOCK_TX1(events, blk_3a, blk_2ra, miner_acc, tx_1);

  DO_CALLBACK(events, "check_tx_pool_empty");
  DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_3a), get_block_hash(blk_3a)));

  return true;
};


//-----------------------------------------------------------------------------------------------------

bool alt_blocks_validation_and_same_new_amount_in_two_txs::generate(std::vector<test_event_entry>& events) const
{
  // test idea: make an alt block with two txs, containing the same, but never seen before output amount
  // Should be processed ok with no problems

  GENERATE_ACCOUNT(miner_acc);
  GENERATE_ACCOUNT(alice_acc);
  MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time());
  DO_CALLBACK(events, "configure_core"); // necessary to set m_hardforks
  
  MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_acc);
  REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1);

  // find out an amount that is never seen 
  uint64_t stub, new_amount = 0;
  bool r = calculate_amounts_many_outs_have_and_no_outs_have(get_outs_money_amount(blk_1.miner_tx), stub, new_amount);
  CHECK_AND_ASSERT_MES(r, false, "calculate_amounts_many_outs_have_and_no_outs_have failed");

  // main chain
  MAKE_NEXT_BLOCK(events, blk_2, blk_1r, miner_acc);
  MAKE_NEXT_BLOCK(events, blk_3, blk_2, miner_acc);
  
  // alt chain
  MAKE_NEXT_BLOCK(events, blk_2a, blk_1r, miner_acc);

  // make two txs with one output (huge fee, probably - doesn't matter) with amount that is never seen before
  std::vector<tx_source_entry> sources;
  r = fill_tx_sources(sources, events, blk_1r, miner_acc.get_keys(), new_amount + 2*TESTS_DEFAULT_FEE, 0);
  CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources failed");
  std::vector<tx_destination_entry> destinations;
  destinations.push_back(tx_destination_entry(new_amount, miner_acc.get_public_address()));
  destinations.push_back(tx_destination_entry(TESTS_DEFAULT_FEE, miner_acc.get_public_address())); //just to make two outputs (to please HF4 rules)
  transaction tx_1{};
  size_t tx_hardfork_id{};
  uint64_t tx_version = get_tx_version_and_hardfork_id(get_block_height(blk_3), m_hardforks, tx_hardfork_id);
  r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_1, tx_version, tx_hardfork_id, 0);
  CHECK_AND_ASSERT_MES(r, false, "construct_tx failed");
  ADD_CUSTOM_EVENT(events, tx_1);

  // second tx
  sources.clear();
  r = fill_tx_sources(sources, events, blk_1r, miner_acc.get_keys(), new_amount + 2*TESTS_DEFAULT_FEE, 0, sources);
  CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources failed");
  transaction tx_2 = AUTO_VAL_INIT(tx_2);
  // use the same destinations
  tx_version = get_tx_version_and_hardfork_id(get_block_height(blk_3), m_hardforks, tx_hardfork_id);
  r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_2, tx_version, tx_hardfork_id, 0);
  CHECK_AND_ASSERT_MES(r, false, "construct_tx failed");
  ADD_CUSTOM_EVENT(events, tx_2);
  
  // make an alt block with these txs
  MAKE_NEXT_BLOCK_TX_LIST(events, blk_3a, blk_2a, miner_acc, std::list<transaction>({ tx_1, tx_2 }));

  //  0      1       11      12      13
  // (0 )-  (1 )-...(1r)-   (2 )-   (3 )-
  //                  \ 
  //                   \-   (2a)-   (3a)-
  //                                tx_1
  //                                tx_2

  MAKE_NEXT_BLOCK(events, blk_4a, blk_3a, miner_acc); // this should switch the chains

  DO_CALLBACK(events, "check_tx_pool_empty");
  DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_4a), get_block_hash(blk_4a)));

  return true;
}

//-----------------------------------------------------------------------------------------------------

alt_blocks_with_the_same_txs::alt_blocks_with_the_same_txs()
{
  REGISTER_CALLBACK_METHOD(alt_blocks_with_the_same_txs, check_tx_related_to_altblock);
  REGISTER_CALLBACK_METHOD(alt_blocks_with_the_same_txs, check_tx_not_related_to_altblock);
}

bool alt_blocks_with_the_same_txs::generate(std::vector<test_event_entry>& events) const
{
  // Test idea: check that two alt blocks having the same tx are correctly handled with respect to is_tx_related_to_altblock()

  GENERATE_ACCOUNT(miner_acc);
  MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time());
  DO_CALLBACK(events, "configure_core"); // necessary to set m_hardforks
  if (m_hardforks.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, 2))
  {
    // HF4 requires tests_random_split_strategy (for 2 outputs minimum)
    test_gentime_settings tgts = generator.get_test_gentime_settings();
    tgts.split_strategy = tests_random_split_strategy;
    generator.set_test_gentime_settings(tgts);
  }

  MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_acc);
  REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1);

  //  0      1       11      12      13      14
  // (0 )-  (1 )-...(1r)-   (2 )-   (3 )-
  //                  \     tx_0
  //                   \ 
  //                    \-  (2a)-   (3a)-   (4 )-
  //                        tx_0

  MAKE_TX(events, tx_0, miner_acc, miner_acc, TESTS_DEFAULT_FEE, blk_1r);

  MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1r, miner_acc, tx_0);
  MAKE_NEXT_BLOCK(events, blk_3, blk_2, miner_acc);

  crypto::hash tx_0_hash = get_transaction_hash(tx_0);
  DO_CALLBACK_PARAMS(events, "check_tx_not_related_to_altblock", tx_0_hash);

  MAKE_NEXT_BLOCK_TX1(events, blk_2a, blk_1r, miner_acc, tx_0);
  MAKE_NEXT_BLOCK(events, blk_3a, blk_2a, miner_acc);
  
  DO_CALLBACK_PARAMS(events, "check_tx_related_to_altblock", tx_0_hash);

  // this should trigger reorganize
  MAKE_NEXT_BLOCK(events, blk_4a, blk_3a, miner_acc);

  DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_4a), get_block_hash(blk_4a)));

  DO_CALLBACK_PARAMS(events, "check_tx_related_to_altblock", tx_0_hash);

  return true;
}

bool alt_blocks_with_the_same_txs::check_tx_related_to_altblock(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
  crypto::hash tx_0_hash = null_hash;
  epee::string_tools::hex_to_pod(boost::get<callback_entry>(events[ev_index]).callback_params, tx_0_hash);

  bool r = c.get_blockchain_storage().is_tx_related_to_altblock(tx_0_hash);
  CHECK_AND_ASSERT_MES(r, false, "tx " << tx_0_hash << " is expected to be related to an altblock, but BCS returned false");

  return true;
}

bool alt_blocks_with_the_same_txs::check_tx_not_related_to_altblock(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
  crypto::hash tx_0_hash = null_hash;
  epee::string_tools::hex_to_pod(boost::get<callback_entry>(events[ev_index]).callback_params, tx_0_hash);

  bool r = !c.get_blockchain_storage().is_tx_related_to_altblock(tx_0_hash);
  CHECK_AND_ASSERT_MES(r, false, "tx " << tx_0_hash << " is expected to NOT be related to an altblock, but BCS returned true");

  return true;
}

//-----------------------------------------------------------------------------------------------------

bool chain_switching_when_out_spent_in_alt_chain_mixin::generate(std::vector<test_event_entry>& events) const
{
  // Test idea: make sure a tx can spend an output with mixins from another tx when both txs are in an altchain.
  bool r = false;
  GENERATE_ACCOUNT(miner_acc);
  GENERATE_ACCOUNT(alice_acc);
  GENERATE_ACCOUNT(bob_acc);
  MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time());
  DO_CALLBACK(events, "configure_core"); // necessary to set m_hardforks
  if (m_hardforks.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, 2))
  {
    // HF4 requires tests_random_split_strategy (for 2 outputs minimum)
    test_gentime_settings tgts = generator.get_test_gentime_settings();
    tgts.split_strategy = tests_random_split_strategy;
    generator.set_test_gentime_settings(tgts);
  }

  MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_acc);
  REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);

  //  0      1       11      12      22      23      24   
  // (0 )-  (1 )-...(1r)-   (2 )-...(2r)-   (3 )-            <-- main chain
  //                  \     
  //                   \ 
  //                    \-  (2a)-...(2ar)-  (3a)-   (4a)-
  //                        tx_0  <--------  tx_1            // tx_1 spends output from tx_0

  // send batch of 10 x 5 test coins to Alice for easier tx_1 construction (and to generate free decoys)
  transaction tx_0;
  r = construct_tx_with_many_outputs(m_hardforks, events, blk_1r, miner_acc.get_keys(), alice_acc.get_public_address(), MK_TEST_COINS(50), 10,
    TESTS_DEFAULT_FEE, tx_0);
  CHECK_AND_ASSERT_MES(r, false, "construct_tx_with_many_outputs failed");
  events.push_back(tx_0);

  MAKE_NEXT_BLOCK(events, blk_2, blk_1r, miner_acc);
  REWIND_BLOCKS_N(events, blk_2r, blk_2, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); // this was added to adapt test to HF4 rule of min 10 blocks age to be spent, condiser rewriting the test in future to simplify this -- sowle
  MAKE_NEXT_BLOCK(events, blk_3, blk_2r, miner_acc);

  // altchain

  MAKE_NEXT_BLOCK_TX1(events, blk_2a, blk_1r, miner_acc, tx_0);
  REWIND_BLOCKS_N(events, blk_2ar, blk_2a, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); // this was added to adapt test to HF4 rule of min 10 blocks age to be spent, condiser rewriting the test in future to simplify this -- sowle

  // make sure Alice received exactly 50 test coins
  CREATE_TEST_WALLET(alice_wlt, alice_acc, blk_0);
  REFRESH_TEST_WALLET_AT_GEN_TIME(events, alice_wlt, blk_2ar, 2 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 2);
  CHECK_TEST_WALLET_BALANCE_AT_GEN_TIME(alice_wlt, MK_TEST_COINS(50));

  // Alice spends her 5 test coins received by tx_0
  MAKE_TX_FEE_MIX(events, tx_1, alice_acc, bob_acc, MK_TEST_COINS(4), TESTS_DEFAULT_FEE, 3 /* nmix */, blk_2ar);
  events.pop_back(); // pop back tx_1 as it won't go into the tx pool normally because of alt chain

  // simulate handling a block with that tx: handle tx like going with the block...
  events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true));
  events.push_back(tx_1);
  events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false));

  MAKE_NEXT_BLOCK_TX1(events, blk_3a, blk_2ar, miner_acc, tx_1);
  MAKE_NEXT_BLOCK(events, blk_4a, blk_3a, miner_acc);

  // make sure Alice has correct balance
  REFRESH_TEST_WALLET_AT_GEN_TIME(events, alice_wlt, blk_4a, 2);
  CHECK_TEST_WALLET_BALANCE_AT_GEN_TIME(alice_wlt, MK_TEST_COINS(45));

  // make sure chain successfully switched
  DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_4a), get_block_hash(blk_4a)));

  return true;
}

//-----------------------------------------------------------------------------------------------------

bool chain_switching_when_out_spent_in_alt_chain_ref_id::generate(std::vector<test_event_entry>& events) const
{
  //random_state_test_restorer::reset_random(0); // to make the test deterministic
  uint64_t ts = 1450000000;
  test_core_time::adjust(ts);

  // Test idea: make sure tx can spend (using ref_by_id) an output from another tx when both txs are in an altchain.
  bool r = false;
  GENERATE_ACCOUNT(miner_acc);  
  GENERATE_ACCOUNT(alice_acc);
  GENERATE_ACCOUNT(bob_acc);
  miner_acc.set_createtime(ts);
  alice_acc.set_createtime(ts);
  bob_acc.set_createtime(ts);

  MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time());
  DO_CALLBACK(events, "configure_core"); // necessary to set m_hardforks
  if (m_hardforks.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, 2))
  {
    // HF4 requires tests_random_split_strategy (for 2 outputs minimum)
    test_gentime_settings tgts = generator.get_test_gentime_settings();
    tgts.split_strategy = tests_random_split_strategy;
    generator.set_test_gentime_settings(tgts);
  }

  MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_acc);
  REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);

  //  0      1       11      12      22      23      24   
  // (0 )-  (1 )-...(1r)-   (2 )-...(2r)-   (3 )-            <-- main chain
  //                  \     
  //                   \ 
  //                    \-  (2a)-...(2ar)-  (3a)-   (4a)-
  //                        tx_0  <--------  tx_1            // tx_1 spends an output from tx_0 using ref_by_id and mixins

  // send batch of 10 x 5 test coins to Alice for easier tx_0 construction
  transaction tx_0;
  r = construct_tx_with_many_outputs(m_hardforks, events, blk_1r, miner_acc.get_keys(), alice_acc.get_public_address(), MK_TEST_COINS(50), 10,
    TESTS_DEFAULT_FEE, tx_0, true);
  CHECK_AND_ASSERT_MES(r, false, "construct_tx_with_many_outputs failed");

  // make sure tx_0 really use ref_by_id
  size_t refs_count = 0, gindex_count = 0;
  count_ref_by_id_and_gindex_refs_for_tx_inputs(tx_0, refs_count, gindex_count);
  CHECK_AND_ASSERT_MES(refs_count == 1 && gindex_count == 0, false, "incorrect input references: " << refs_count << ", " << gindex_count);

  ADD_CUSTOM_EVENT(events, tx_0);

  MAKE_NEXT_BLOCK(events, blk_2, blk_1r, miner_acc);
  REWIND_BLOCKS_N(events, blk_2r, blk_2, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); // this was added to adapt test to HF4 rule of min 10 blocks age to be spent, condiser rewriting the test in future to simplify this -- sowle
  MAKE_NEXT_BLOCK(events, blk_3, blk_2r, miner_acc);

  // altchain

  MAKE_NEXT_BLOCK_TX1(events, blk_2a, blk_1r, miner_acc, tx_0);
  REWIND_BLOCKS_N(events, blk_2ar, blk_2a, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); // this was added to adapt test to HF4 rule of min 10 blocks age to be spent, condiser rewriting the test in future to simplify this -- sowle

  // make sure Alice received exactly 50 test coins
  CREATE_TEST_WALLET(alice_wlt, alice_acc, blk_0);
  REFRESH_TEST_WALLET_AT_GEN_TIME(events, alice_wlt, blk_2ar, 2*CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 2);
  CHECK_TEST_WALLET_BALANCE_AT_GEN_TIME(alice_wlt, MK_TEST_COINS(50));

  // Alice spends her 5 test coins received by tx_0
  transaction tx_1;
  std::vector<tx_destination_entry> destinations;
  destinations.push_back(tx_destination_entry(MK_TEST_COINS(3), bob_acc.get_public_address()));
  destinations.push_back(tx_destination_entry(MK_TEST_COINS(1), bob_acc.get_public_address()));
  size_t nmix = 3;
  r = construct_tx_to_key(m_hardforks, events, tx_1, blk_2ar, alice_acc, destinations, TESTS_DEFAULT_FEE, nmix, 0, empty_extra, empty_attachment, true, true, true /* use_ref_by_id */);
  CHECK_AND_ASSERT_MES(r, false, "construct_tx_to_key failed");

  // make sure tx_1 really use ref_by_id
  refs_count = 0, gindex_count = 0;
  count_ref_by_id_and_gindex_refs_for_tx_inputs(tx_1, refs_count, gindex_count);
  CHECK_AND_ASSERT_MES(refs_count == nmix + 1 && gindex_count == 0, false, "incorrect input references: " << refs_count << ", " << gindex_count);

  // simulate handling a block with that tx: handle tx like going with the block...
  ADD_CUSTOM_EVENT(events, event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true));
  ADD_CUSTOM_EVENT(events, tx_1);
  ADD_CUSTOM_EVENT(events, event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false));

  MAKE_NEXT_BLOCK_TX1(events, blk_3a, blk_2ar, miner_acc, tx_1);
  MAKE_NEXT_BLOCK(events, blk_4a, blk_3a, miner_acc);

  // make sure Alice has correct balance
  REFRESH_TEST_WALLET_AT_GEN_TIME(events, alice_wlt, blk_4a, 2);
  CHECK_TEST_WALLET_BALANCE_AT_GEN_TIME(alice_wlt, MK_TEST_COINS(45));

  // make sure Bob has correct balance
  CREATE_TEST_WALLET(bob_wlt, bob_acc, blk_0);
  REFRESH_TEST_WALLET_AT_GEN_TIME(events, bob_wlt, blk_4a, 2*CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 4);
  CHECK_TEST_WALLET_BALANCE_AT_GEN_TIME(bob_wlt, MK_TEST_COINS(4));

  // make sure chain successfully switched
  DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_4a), get_block_hash(blk_4a)));

  return true;
}

alt_chain_and_block_tx_fee_median::alt_chain_and_block_tx_fee_median()
{
  REGISTER_CALLBACK_METHOD(alt_chain_and_block_tx_fee_median, check_after_hf4);
  REGISTER_CALLBACK_METHOD(alt_chain_and_block_tx_fee_median, check_before_hf4);
}

bool alt_chain_and_block_tx_fee_median::generate(
  std::vector<test_event_entry>& events) const
{
  /* Test idea: check chain switching rules.
  Rules before and after HF4 for PoW blocks are different. There're only PoW
  blocks in the test situation. If the last blocks contain transactions (non
  empty blocks), then the chain with the largest this_block_tx_fee_median on its
  head becomes the main.
   0            10     11           21     22
  (0 ) - ... - (0r) - (1 ) - ... - (1r) - (2 )
                  |   main            |    \
                  |                   |     [tx_0]
                  |                   |
                  |                   |    main
                  \ - (1a)            \ - (2a)
                                           \
                                            [tx_1]
  Chain with head blk_1 versus chain with head blk_1a: chain with head blk_1
  is the main, because blocks 1, 1a are empty.
  Chain with head blk_2 versus chain with head blk_2a: chain with head blk_2a
  is the main, because blocks 2, 2a aren't empty and the fee of tx_1 is larger
  than the fee of tx_0.
  */

  bool success{};
  bool hf4_active{};
  std::vector<tx_source_entry> sources{};
  std::vector<tx_destination_entry> destinations{};
  transaction tx_0{}, tx_1{};
  uint64_t tx_version{};
  crypto::hash top_block{};

  GENERATE_ACCOUNT(miner);

  MAKE_GENESIS_BLOCK(events,
                     blk_0,
                     miner,
                     test_core_time::get_time());

  DO_CALLBACK(events, "configure_core");

  REWIND_BLOCKS_N(events, blk_0r, blk_0, miner,
                  CURRENCY_MINED_MONEY_UNLOCK_WINDOW);

  MAKE_NEXT_BLOCK(events, blk_1, blk_0r, miner);
  MAKE_NEXT_BLOCK(events, blk_1a, blk_0r, miner);

  /* It is decided which chain will be the main: with the head blk_1 or with the
  head blk_1a.
   0            10     11
                  / - (1 )
                  |
  (0 ) - ... - (0r)
                  |
                  \ - (1a)
  */

  CHECK_AND_ASSERT_EQ(is_pos_block(blk_1), false);
  CHECK_AND_ASSERT_EQ(is_pos_block(blk_1a), false);
  CHECK_AND_ASSERT_EQ(get_block_height(blk_1), 11);
  CHECK_AND_ASSERT_EQ(get_block_height(blk_1), get_block_height(blk_1a));

  /* Blocks blk_1, blk_1a do not contain transactions (they are empty blocks).
  Switching to the alternative chain with head blk_1a will not occur. The main
  chain is the chain with the head blk_1. */

  DO_CALLBACK_PARAMS(events,
                     "check_top_block",
                     params_top_block(get_block_height(blk_1),
                                      get_block_hash(blk_1)));

  REWIND_BLOCKS_N(events, blk_1r, blk_1, miner,
                  CURRENCY_MINED_MONEY_UNLOCK_WINDOW);

  // Transaction tx_0 is constructed and placed in block blk_2.

  success = fill_tx_sources_and_destinations(
    events,
    /* head = */ blk_1r,
    /* from = */ miner.get_keys(),
    /* to = */ miner.get_public_address(),
    /* amount = */ MK_TEST_COINS(10),
    /* fee = */ m_fee_tx_0_blk_2,
    /* nmix = */ 0,
    sources,
    destinations);

  CHECK_AND_ASSERT_MES(success, false, "fail to fill sources, destinations");

  size_t tx_hardfork_id{};
  tx_version = get_tx_version_and_hardfork_id(get_block_height(blk_1r), m_hardforks, tx_hardfork_id);

  success = construct_tx(miner.get_keys(), sources, destinations, empty_attachment, tx_0, tx_version, tx_hardfork_id, 0);

  CHECK_AND_ASSERT_MES(success, false, "fail to construct tx_0");

  ADD_CUSTOM_EVENT(events, tx_0);
  MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1r, miner, tx_0);

  sources.clear();
  destinations.clear();

  // Transaction tx_1 is constructed and placed in block blk_2a.

  tx_version = get_tx_version_and_hardfork_id(get_block_height(blk_1r), m_hardforks, tx_hardfork_id);

  success = fill_tx_sources_and_destinations(
    events,
    /* head = */ blk_1r,
    /* from = */ miner.get_keys(),
    /* to = */ miner.get_public_address(),
    /* amount = */ MK_TEST_COINS(10),
    /* fee = */ m_fee_tx_1_blk_2a,
    /* nmix = */ 0,
    sources,
    destinations);

  CHECK_AND_ASSERT_MES(success, false, "fail to fill sources, destinations");

  success = construct_tx(miner.get_keys(), sources, destinations, empty_attachment, tx_1, tx_version, tx_hardfork_id, 0);

  CHECK_AND_ASSERT_MES(success, false, "fail to construct tx_1");

  ADD_CUSTOM_EVENT(events, tx_1);
  MAKE_NEXT_BLOCK_TX1(events, blk_2a, blk_1r, miner, tx_1);

  /* It is decided which chain will be the main: with the head blk_2 or with the
  head blk_2a.
   0            21     22
                  / - (2 )
                  |    \
                  |     [tx_0]
                  |
  (0 ) - ... - (1r)
                  |
                  |
                  |
                  \ - (2a)
                       \
                        [tx_1]
  */

  CHECK_AND_ASSERT_EQ(is_pos_block(blk_2), false);
  CHECK_AND_ASSERT_EQ(is_pos_block(blk_2a), false);
  CHECK_AND_ASSERT_GREATER(m_fee_tx_1_blk_2a, m_fee_tx_0_blk_2);
  CHECK_AND_ASSERT_EQ(get_block_height(blk_2), 22);
  CHECK_AND_ASSERT_EQ(get_block_height(blk_2), get_block_height(blk_2a));

  hf4_active =
    m_hardforks.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM,
                                              get_block_height(blk_2) + 1);

  if (hf4_active)
  {
    /* With HF4 active, the chain with head blk_2a wins because transaction tx_1
    has a greater fee than transaction tx_0. The main chain is the chain with
    the head blk_2a. */

    DO_CALLBACK(events, "check_after_hf4");
    top_block = get_block_hash(blk_2a);
  }

  else
  {
    /* The chains have the same commulative difficulty. Therefore, with HF4
    inactive, switching to the chain with the blk_2a head will not occur. The
    main chain is the chain with the head blk_2. */

    DO_CALLBACK(events, "check_before_hf4");
    top_block = get_block_hash(blk_2);
  }

  DO_CALLBACK_PARAMS(events,
                     "check_top_block",
                     params_top_block(/* height = */ 22, top_block));

  return true;
}

bool alt_chain_and_block_tx_fee_median::check_after_hf4(
  currency::core& c,
  size_t ev_index,
  const std::vector<test_event_entry>& events)
{
  block_extended_info bei{};
  const uint64_t height_block{22};

  CHECK_AND_ASSERT_EQ(
    m_hardforks.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM,
                                              height_block),
    true);

  c.get_blockchain_storage().get_block_extended_info_by_height(height_block,
                                                               bei);
  CHECK_AND_ASSERT_EQ(bei.this_block_tx_fee_median, m_fee_tx_1_blk_2a);

  return true;
}

bool alt_chain_and_block_tx_fee_median::check_before_hf4(
  currency::core& c,
  size_t ev_index,
  const std::vector<test_event_entry>& events)
{
  block_extended_info bei{};
  const uint64_t height_block{22};

  CHECK_AND_ASSERT_EQ(
    m_hardforks.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM,
                                              height_block),
    false);

  c.get_blockchain_storage().get_block_extended_info_by_height(height_block,
                                                               bei);
  CHECK_AND_ASSERT_EQ(bei.this_block_tx_fee_median, m_fee_tx_0_blk_2);

  return true;
}
