// 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<size_t> 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<size_t> 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());
  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());
  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 + 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())); // no cashback, just payment
  transaction tx_1 = AUTO_VAL_INIT(tx_1);
  r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_1, 0);
  CHECK_AND_ASSERT_MES(r, false, "construct_tx failed");
  events.push_back(tx_1);

  // second tx
  sources.clear();
  r = fill_tx_sources(sources, events, blk_1r, miner_acc.get_keys(), new_amount + 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
  r = construct_tx(miner_acc.get_keys(), sources, destinations, empty_attachment, tx_2, 0);
  CHECK_AND_ASSERT_MES(r, false, "construct_tx failed");
  events.push_back(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 many 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());
  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;
}

