1
0
Fork 0
forked from lthn/blockchain
blockchain/tests/core_tests/emission_test.cpp

573 lines
30 KiB
C++

// Copyright (c) 2014-2018 Zano Project
// Copyright (c) 2014-2018 The Louisdor Project
// 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 "emission_test.h"
#include "pos_block_builder.h"
#include "random_helper.h"
using namespace currency;
emission_test::emission_test()
{
REGISTER_CALLBACK_METHOD(emission_test, c1);
}
bool emission_test::generate(std::vector<test_event_entry> &events)
{
m_miner_acc.generate();
MAKE_GENESIS_BLOCK(events, blk_0, m_miner_acc, test_core_time::get_time());
DO_CALLBACK(events, "c1");
return true;
}
bool emission_test::c1(currency::core& c, size_t ev_index, const std::vector<test_event_entry> &events)
{
bool r = false;
size_t days_to_simulate = 365;
blockchain_storage& bcs = c.get_blockchain_storage();
const block& genesis = boost::get<block>(events[0]);
uint64_t already_generated_coins = get_outs_money_amount(genesis.miner_tx);
uint64_t pos_coins = 0;
uint64_t pow_coins = 0;
uint64_t timestamp = genesis.timestamp;
uint64_t blocks_interval = DIFFICULTY_TOTAL_TARGET;
wide_difficulty_type difficulty = 0;
size_t height = 0;
pos_block_builder pb;
std::list<std::pair<crypto::hash, size_t>> stake_tx_outs; // pairs of (tx, output_index) using for PoS (due to tx unlock time we can't use the same tx-out for all PoS blocks)
crypto::hash genesis_miner_tx_hash = get_transaction_hash(genesis.miner_tx);
for (size_t i = 0; i < genesis.miner_tx.vout.size(); ++i)
stake_tx_outs.push_back(std::make_pair(genesis_miner_tx_hash, i));
std::stringstream s;
s << ENDL << "time" << "\t" << "height" << "\ttype\t" << "gen_coins" << "\t" << "pow_coins" << "\t" << "pos_coins" << "\t" << "already_generated_coins" << ENDL;
epee::log_space::get_set_log_detalisation_level(true, LOG_LEVEL_0);
currency::block_verification_context bvc = AUTO_VAL_INIT(bvc);
for(size_t j = 0; j < days_to_simulate; ++j)
{
size_t start_height = height;
while(height < start_height + CURRENCY_BLOCKS_PER_DAY)
{
// pow
timestamp += blocks_interval;
test_core_time::adjust(timestamp);
currency::block b = AUTO_VAL_INIT(b);
blobdata extra = AUTO_VAL_INIT(extra);
uint64_t height_from_template = 0;
r = c.get_block_template(b, m_miner_acc.get_public_address(), m_miner_acc.get_public_address(), difficulty, height_from_template, extra);
CHECK_AND_ASSERT_MES(r || height_from_template != height, false, "get_block_template failed");
r = miner::find_nonce_for_given_block(b, difficulty, height);
CHECK_AND_ASSERT_MES(r, false, "find_nonce_for_given_block failed");
c.handle_incoming_block(t_serializable_object_to_blob(b), bvc);
CHECK_AND_NO_ASSERT_MES(!bvc.m_verification_failed && !bvc.m_marked_as_orphaned && !bvc.m_already_exists, false, "block verification context check failed");
//CHECK_AND_ASSERT_MES(b.timestamp == timestamp, false, "Invalid block ts: " << b.timestamp << ", expected: " << timestamp);
uint64_t gen_coins = get_outs_money_amount(b.miner_tx);
already_generated_coins += gen_coins;
CHECK_AND_ASSERT_MES(already_generated_coins == bcs.total_coins(), false, "total coins missmatch: BCS has: " << bcs.total_coins() << ", expected: " << already_generated_coins);
pow_coins += gen_coins;
s << epee::misc_utils::get_time_str_v3(boost::posix_time::from_time_t(timestamp)) << "\t" << height << "\tPoW\t" << gen_coins << "\t" << pow_coins << "\t" << pos_coins << "\t" << already_generated_coins << ENDL;
++height;
// pos
if (height < CURRENCY_MINED_MONEY_UNLOCK_WINDOW)
continue; // mint PoS blocks only when premine money is unlocked
timestamp += blocks_interval;
test_core_time::adjust(timestamp);
crypto::hash prev_id = get_block_hash(b);
crypto::hash stake_tx_hash = stake_tx_outs.front().first;
size_t stake_output_idx = stake_tx_outs.front().second;
auto tce_ptr = c.get_blockchain_storage().get_tx_chain_entry(stake_tx_hash);
CHECK_AND_ASSERT_MES(tce_ptr, false, "");
CHECK_AND_ASSERT_MES(stake_output_idx < tce_ptr->m_global_output_indexes.size(), false, "");
const transaction& stake = tce_ptr->tx;
crypto::public_key stake_tx_pub_key = get_tx_pub_key_from_extra(stake);
size_t stake_output_gidx = tce_ptr->m_global_output_indexes[stake_output_idx];
uint64_t stake_output_amount =boost::get<currency::tx_out_bare>( stake.vout[stake_output_idx]).amount;
crypto::key_image stake_output_key_image;
keypair kp;
generate_key_image_helper(m_miner_acc.get_keys(), stake_tx_pub_key, stake_output_idx, kp, stake_output_key_image);
crypto::public_key stake_output_pubkey = boost::get<txout_to_key>(boost::get<currency::tx_out_bare>(stake.vout[stake_output_idx]).target).key;
difficulty = c.get_blockchain_storage().get_next_diff_conditional(true);
//size_t median_size = 0; // little hack: we're using small blocks (only coinbase tx), so we're in CURRENCY_BLOCK_GRANTED_FULL_REWARD_ZONE - don't need to calc median size
pb.clear();
pb.step1_init_header(get_block_height(b) + 1, prev_id);
pb.step2_set_txs(std::vector<transaction>());
pb.step3_build_stake_kernel(stake_output_amount, stake_output_gidx, stake_output_key_image, difficulty, prev_id, null_hash, timestamp);
pb.step4_generate_coinbase_tx(0, already_generated_coins, m_miner_acc.get_public_address());
set_block_datetime(timestamp, pb.m_block);
pb.step5_sign(stake_tx_pub_key, stake_output_idx, stake_output_pubkey, m_miner_acc);
c.handle_incoming_block(t_serializable_object_to_blob(pb.m_block), bvc);
CHECK_AND_NO_ASSERT_MES(!bvc.m_verification_failed && !bvc.m_marked_as_orphaned && !bvc.m_already_exists, false, "PoS block verification context check failed");
//CHECK_AND_ASSERT_MES(get_actual_timestamp(pb.m_block) == timestamp, false, "Invalid PoS block ts: " << get_actual_timestamp(pb.m_block) << ", expected: " << timestamp);
gen_coins = get_outs_money_amount(pb.m_block.miner_tx) - boost::get<txin_to_key>(pb.m_block.miner_tx.vin[1]).amount;
already_generated_coins += gen_coins;
CHECK_AND_ASSERT_MES(already_generated_coins == bcs.total_coins(), false, "total coins missmatch: BCS has: " << bcs.total_coins() << ", expected: " << already_generated_coins);
pos_coins += gen_coins;
// update stakes queue: pop used one from the front and push output of this PoS block to the back
size_t biggest_output_idx = std::max_element(pb.m_block.miner_tx.vout.begin(), pb.m_block.miner_tx.vout.end(),
[](const currency::tx_out_v& l, const currency::tx_out_v& r)
{
return boost::get<tx_out_bare>(l).amount < boost::get<tx_out_bare>(r).amount;
}) - pb.m_block.miner_tx.vout.begin();
stake_tx_outs.pop_front();
stake_tx_outs.push_back(std::make_pair(get_transaction_hash(pb.m_block.miner_tx), biggest_output_idx));
s << epee::misc_utils::get_time_str_v3(boost::posix_time::from_time_t(timestamp)) << "\t" << height << "\tPoS\t" << gen_coins << "\t" << pow_coins << "\t" << pos_coins << "\t" << already_generated_coins << ENDL;
++height;
}
}
LOG_PRINT2("emission_test.log", s.str(), LOG_LEVEL_0);
return true;
}
//------------------------------------------------------------------------------
pos_emission_test::pos_emission_test()
{
REGISTER_CALLBACK_METHOD(pos_emission_test, c1);
REGISTER_CALLBACK_METHOD(pos_emission_test, c2);
REGISTER_CALLBACK_METHOD(pos_emission_test, c3);
m_pos_entries_to_generate = WALLET_DEFAULT_TX_SPENDABLE_AGE * 2;
m_total_money_in_minting = (50 * COIN) * m_pos_entries_to_generate;
}
bool pos_emission_test::generate(std::vector<test_event_entry> &events)
{
m_accounts.resize(TOTAL_ACCS_COUNT);
GENERATE_ACCOUNT(preminer_acc);
m_accounts[DAN_ACC_IDX] = preminer_acc; // make Dan a preminer
GENERATE_ACCOUNT(miner_acc);
m_accounts[MINER_ACC_IDX] = miner_acc;
GENERATE_ACCOUNT(alice_acc);
m_accounts[ALICE_ACC_IDX] = alice_acc;
GENERATE_ACCOUNT(bob_acc);
m_accounts[BOB_ACC_IDX] = bob_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);
uint64_t pos_entry_amount = m_total_money_in_minting / m_pos_entries_to_generate;
m_total_money_in_minting = m_pos_entries_to_generate * pos_entry_amount;
std::vector<tx_source_entry> sources;
bool r = fill_tx_sources(sources, events, blk_0r, preminer_acc.get_keys(), m_total_money_in_minting + TESTS_DEFAULT_FEE, 0);
CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources failed");
std::vector<tx_destination_entry> destinations;
for (size_t i = 0; i < m_pos_entries_to_generate; ++i)
destinations.push_back(tx_destination_entry(pos_entry_amount, alice_acc.get_public_address()));
transaction tx_1 = AUTO_VAL_INIT(tx_1);
r = construct_tx(preminer_acc.get_keys(), sources, destinations, empty_attachment, tx_1, get_tx_version_from_events(events), 0);
CHECK_AND_ASSERT_MES(r, false, "construct_tx failed");
events.push_back(tx_1);
MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_1);
REWIND_BLOCKS_N_WITH_TIME(events, blk_1r, blk_1, miner_acc, WALLET_DEFAULT_TX_SPENDABLE_AGE);
ADJUST_TEST_CORE_TIME(blk_1r.timestamp);
//DO_CALLBACK(events, "c1");
DO_CALLBACK(events, "c3");
return true;
}
bool pos_emission_test::c1(currency::core& c, size_t ev_index, const std::vector<test_event_entry> &events)
{
// Mining scenario:
// 0. Alice has m_total_money_in_minting coins in the wallet and this amount is being kept constant during the test (transferring the excess to Miner)
// 1. PoW blocks go with equal intervals of DIFFICULTY_POW_TARGET
// 2. After each PoW:
// 2.1 Alice tries to mint as many PoS blocks as possible
// 2.2 If no PoS blocks were minted at this timestamp - she waits for POS_SCAN_STEP secs and repeat 2.1 (within DIFFICULTY_POS_TARGET secs window)
// 3. Alice tries to consolidate wallet's pos entries by sending herself pos_entry_amount coins each time they become unlocked
//
// The tests checks merely nothing. The only usefull output is logs
std::shared_ptr<tools::wallet2> alice_wlt = init_playtime_test_wallet(events, c, ALICE_ACC_IDX);
std::shared_ptr<tools::wallet2> miner_wlt = init_playtime_test_wallet(events, c, MINER_ACC_IDX);
size_t blocks_fetched = 0;
bool received_money;
std::atomic<bool> atomic_false = ATOMIC_VAR_INIT(false);
alice_wlt->refresh(blocks_fetched, received_money, atomic_false);
CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1 + WALLET_DEFAULT_TX_SPENDABLE_AGE, false, "Incorrect numbers of blocks fetched");
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool");
if (!check_balance_via_wallet(*alice_wlt.get(), "alice_wlt", m_total_money_in_minting))
return false;
uint64_t ts = test_core_time::get_time();
uint64_t pos_entry_amount = m_total_money_in_minting / m_pos_entries_to_generate;
uint64_t pos_target = DIFFICULTY_POS_TARGET;
uint64_t pow_target = DIFFICULTY_POW_TARGET;
uint64_t last_pow_block_ts = ts - pow_target;
//uint64_t last_pos_block_ts = ts - pos_target;
size_t n_pos_trials = POS_SCAN_WINDOW * 2 / POS_SCAN_STEP; // number of PoS mining trials
bool r = false;
block b;
wide_difficulty_type diff = 0;
bool switched_to_higher_stake_amount = false;
size_t height_to_twice_stake_amount = 2000;
//std::stringstream s;
//s << ENDL << "time" << "\t" << "height" << "\ttype\t" << "gen_coins" << "\t" << "pow_coins" << "\t" << "pos_coins" << "\t" << "already_generated_coins" << ENDL;
LOG_PRINT2("pos_emission_test_brief.log", "\theight\tdifficulty\tstake amount\twallet balance\tpos entries count", LOG_LEVEL_0);
for(size_t n = 0; n < 30000; ++n)
{
// pow
uint64_t ideal_next_pow_block_ts = last_pow_block_ts + pow_target;
if (ts < ideal_next_pow_block_ts)
ts = ideal_next_pow_block_ts; // "wait" until ideal_next_pow_block_ts if it was not already happened (fast forward but don't wayback the time)
test_core_time::adjust(ts);
r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c);
CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed");
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "invalid number of txs in tx pool: " << c.get_pool_transactions_count());
//int64_t pow_block_timediff = ts - last_pow_block_ts;
last_pow_block_ts = ts;
c.get_blockchain_storage().get_top_block(b);
//s << epee::misc_utils::get_time_str_v3(boost::posix_time::from_time_t(ts)) << "(" << ts << ")\t" << get_block_height(b) << "\tPoW\t" << b.timestamp << " (+" << pow_block_timediff << ")\t" << ENDL;
if (get_block_height(b) > height_to_twice_stake_amount && !switched_to_higher_stake_amount)
{
miner_wlt->refresh();
miner_wlt->transfer(m_total_money_in_minting, m_accounts[ALICE_ACC_IDX].get_public_address()); // transfer to Alice some coins to twice the minting wallet amount
pos_entry_amount *= 2;
m_total_money_in_minting *= 2;
switched_to_higher_stake_amount = true;
}
// pos
uint64_t pos_ts_upper_bound = ts + pos_target;
for(uint64_t i = 0; i < n_pos_trials && ts < pos_ts_upper_bound; ++i)
{
alice_wlt->refresh();
uint64_t wallet_balance_mined = 0, wallet_balance_unlocked = 0, stub = 0;
uint64_t wallet_balance = alice_wlt->balance(wallet_balance_unlocked, stub, stub, wallet_balance_mined);
LOG_PRINT_BLUE(">>>>>>>>>>>>>> Alice wallet balance: " << print_money(wallet_balance) << ", mined: " << print_money(wallet_balance_mined), LOG_LEVEL_0);
if (wallet_balance > m_total_money_in_minting && wallet_balance_unlocked > 0)
{
uint64_t amount = std::min(wallet_balance_unlocked, wallet_balance - m_total_money_in_minting);
size_t tx_count_before = c.get_pool_transactions_count();
alice_wlt->transfer(amount - TESTS_DEFAULT_FEE, m_accounts[MINER_ACC_IDX].get_public_address()); // transfer out excess of money
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() > tx_count_before, false, "invalid number of txs in tx pool: " << c.get_pool_transactions_count());
}
diff = c.get_blockchain_storage().get_next_diff_conditional(true);
uint64_t bc_height_before = c.get_blockchain_storage().get_current_blockchain_size();
r = alice_wlt->try_mint_pos();
if (r && bc_height_before < c.get_blockchain_storage().get_current_blockchain_size())
{
LOG_PRINT_GREEN(">>>>>>>>>>>>>> Minted a PoS block at " << ts << " with difficuly: " << diff, LOG_LEVEL_0);
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "invalid number of txs in tx pool: " << c.get_pool_transactions_count());
c.get_blockchain_storage().get_top_block(b);
//int64_t pos_block_timediff = b.timestamp - last_pos_block_ts;
uint64_t stake_amount = boost::get<txin_to_key>(b.miner_tx.vin[1]).amount;
size_t pos_entries_count = alice_wlt->get_pos_entries_count();
//s << epee::misc_utils::get_time_str_v3(boost::posix_time::from_time_t(ts)) << "(" << ts << ")\t" << get_block_height(b) << "\tPoS\t" << b.timestamp << " (" << (pos_block_timediff > 0 ? "+" : "") << pos_block_timediff << ")\t" << diff << "\tstake: " << stake_amount << " balance: " << print_money(wallet_balance) << ", mined: " << print_money(wallet_balance_mined) << ENDL;
LOG_PRINT2("pos_emission_test_brief.log", "\t" << get_block_height(b) << "\t" << diff << "\t" << print_money(stake_amount) << "\t" << print_money(wallet_balance) << "\t" << pos_entries_count, LOG_LEVEL_0);
//last_pos_block_ts = b.timestamp;
continue;
}
LOG_PRINT_CYAN(">>>>>>>>>>>>>> Can't mint a PoS block at " << ts << ", wait a bit...", LOG_LEVEL_0);
ts += POS_SCAN_STEP;
test_core_time::adjust(ts);
}
alice_wlt->refresh();
uint64_t wallet_balance_unlocked = 0;
alice_wlt->balance(wallet_balance_unlocked);
if (wallet_balance_unlocked > pos_entry_amount)
{
size_t tx_count_before = c.get_pool_transactions_count();
alice_wlt->transfer(pos_entry_amount, alice_wlt->get_account().get_public_address()); // transfer money from Alice to Alice to consolidate PoS entries for more fast minting process
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() > tx_count_before, false, "invalid number of txs in tx pool: " << c.get_pool_transactions_count());
}
}
//LOG_PRINT2("pos_emission_test.log", s.str(), LOG_LEVEL_0);
return true;
}
bool pos_emission_test::c2(currency::core& c, size_t ev_index, const std::vector<test_event_entry> &events)
{
// Mining scenario:
std::shared_ptr<tools::wallet2> alice_wlt = init_playtime_test_wallet(events, c, ALICE_ACC_IDX);
std::shared_ptr<tools::wallet2> miner_wlt = init_playtime_test_wallet(events, c, MINER_ACC_IDX);
size_t blocks_fetched = 0;
bool received_money;
std::atomic<bool> atomic_false = ATOMIC_VAR_INIT(false);
alice_wlt->refresh(blocks_fetched, received_money, atomic_false);
CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1 + WALLET_DEFAULT_TX_SPENDABLE_AGE, false, "Incorrect numbers of blocks fetched");
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool");
if (!check_balance_via_wallet(*alice_wlt.get(), "alice_wlt", m_total_money_in_minting))
return false;
uint64_t ts = test_core_time::get_time();
uint64_t pos_entry_amount = m_total_money_in_minting / m_pos_entries_to_generate;
uint64_t pos_target = DIFFICULTY_POS_TARGET;
uint64_t pow_target = DIFFICULTY_POW_TARGET;
uint64_t last_pow_block_ts = ts - pow_target;
//uint64_t last_pos_block_ts = ts - pos_target;
size_t n_pos_trials = POS_SCAN_WINDOW * 2 / POS_SCAN_STEP; // number of PoS mining trials
bool r = false;
block b;
wide_difficulty_type diff = 0;
bool switched_to_higher_stake_amount = false;
size_t height_to_twice_stake_amount = 200000;
//std::stringstream s;
//s << ENDL << "time" << "\t" << "height" << "\ttype\t" << "gen_coins" << "\t" << "pow_coins" << "\t" << "pos_coins" << "\t" << "already_generated_coins" << ENDL;
LOG_PRINT2("pos_emission_test_brief_c2.log", "\theight\tdifficulty\tstake amount\twallet balance\tpos entries count", LOG_LEVEL_0);
for(size_t n = 0; n < 30000; ++n)
{
// pow
uint64_t ideal_next_pow_block_ts = last_pow_block_ts + pow_target;
if (ts < ideal_next_pow_block_ts)
ts = ideal_next_pow_block_ts; // "wait" until ideal_next_pow_block_ts if it was not already happened (fast forward but don't wayback the time)
test_core_time::adjust(ts);
r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c);
CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed");
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "invalid number of txs in tx pool: " << c.get_pool_transactions_count());
//int64_t pow_block_timediff = ts - last_pow_block_ts;
last_pow_block_ts = ts;
c.get_blockchain_storage().get_top_block(b);
//s << epee::misc_utils::get_time_str_v3(boost::posix_time::from_time_t(ts)) << "(" << ts << ")\t" << get_block_height(b) << "\tPoW\t" << b.timestamp << " (+" << pow_block_timediff << ")\t" << ENDL;
if (get_block_height(b) > height_to_twice_stake_amount && !switched_to_higher_stake_amount)
{
miner_wlt->refresh();
for(size_t i = 0; i < m_pos_entries_to_generate; ++i)
miner_wlt->transfer(pos_entry_amount, m_accounts[ALICE_ACC_IDX].get_public_address()); // transfer to Alice some coins to twice the minting wallet amount
m_pos_entries_to_generate *= 2;
m_total_money_in_minting *= 2;
switched_to_higher_stake_amount = true;
}
// pos
uint64_t pos_ts_upper_bound = ts + pos_target;
for(uint64_t i = 0; i < n_pos_trials && ts < pos_ts_upper_bound; ++i)
{
alice_wlt->refresh();
diff = c.get_blockchain_storage().get_next_diff_conditional(true);
uint64_t bc_height_before = c.get_blockchain_storage().get_current_blockchain_size();
size_t pos_entries_count = 0;
if (mine_next_pos_block_in_playtime_with_wallet(*alice_wlt.get(), m_accounts[MINER_ACC_IDX].get_public_address(), pos_entries_count))
{
CHECK_AND_ASSERT_MES(bc_height_before < c.get_blockchain_storage().get_current_blockchain_size(), false, "didn't mint a block!");
LOG_PRINT_GREEN(">>>>>>>>>>>>>> Minted a PoS block at " << ts << " with difficuly: " << diff, LOG_LEVEL_0);
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "invalid number of txs in tx pool: " << c.get_pool_transactions_count());
c.get_blockchain_storage().get_top_block(b);
//int64_t pos_block_timediff = b.timestamp - last_pos_block_ts;
uint64_t stake_amount = boost::get<txin_to_key>(b.miner_tx.vin[1]).amount;
//s << epee::misc_utils::get_time_str_v3(boost::posix_time::from_time_t(ts)) << "(" << ts << ")\t" << get_block_height(b) << "\tPoS\t" << b.timestamp << " (" << (pos_block_timediff > 0 ? "+" : "") << pos_block_timediff << ")\t" << diff << "\tstake: " << stake_amount << " balance: " << print_money(wallet_balance) << ", mined: " << print_money(wallet_balance_mined) << ENDL;
LOG_PRINT2("pos_emission_test_brief_c2.log", "\t" << get_block_height(b) << "\t" << diff << "\t" << print_money(stake_amount) << "\t" << print_money(alice_wlt->balance()) << "\t" << pos_entries_count, LOG_LEVEL_0);
//last_pos_block_ts = b.timestamp;
continue;
}
LOG_PRINT_CYAN(">>>>>>>>>>>>>> Can't mint a PoS block at " << ts << ", wait a bit...", LOG_LEVEL_0);
ts += POS_SCAN_STEP;
test_core_time::adjust(ts);
}
}
//LOG_PRINT2("pos_emission_test.log", s.str(), LOG_LEVEL_0);
return true;
}
bool pos_emission_test::populate_wallet_with_pos_entries(std::shared_ptr<tools::wallet2> w, std::shared_ptr<tools::wallet2> coin_source, size_t iter_idx, pos_entries_change_scheme change_scheme)
{
switch(change_scheme)
{
case change_pos_entries_count:
{
/*
size_t coin_exponent = log10(COIN);
size_t total_money_in_mining_exp = log10(m_total_money_in_minting);
pos_entry_amount = random_in_range(1, 9);
pos_entry_amount *= pow(10, random_in_range(coin_exponent, total_money_in_mining_exp - 1)); // 10 ^^ [1..100]*/
static const double pos_entry_amounts[] = {50, 0.5, 80, 10, 100, 120, 15, 0.1};
m_pos_entry_amount = pos_entry_amounts[iter_idx % (sizeof pos_entry_amounts / sizeof pos_entry_amounts[0])];
m_pos_entries_to_generate = m_total_money_in_minting / m_pos_entry_amount;
}
break;
case change_total_money_in_minting:
{
m_pos_entry_amount = random_in_range(1, 9) * 100 * COIN;
m_total_money_in_minting = m_pos_entry_amount * m_pos_entries_to_generate;
}
break;
default:
LOG_ERROR("invalid pos_entries_change_scheme value");
LOCAL_ASSERT(false);
}
LOG_PRINT_L0("changing pos entries: m_total_money_in_minting: " << m_total_money_in_minting << ", m_pos_entries_to_generate: " << m_pos_entries_to_generate << ", pos_entry_amount: " << print_money(m_pos_entry_amount));
coin_source->refresh();
// populate current wallet with pos entries from scratch
for(size_t i = 0; i < m_pos_entries_to_generate; ++i)
coin_source->transfer(m_pos_entry_amount, w->get_account().get_public_address()); // transfer to current wallet stake coins
uint64_t total_stake_transferred = m_pos_entries_to_generate * m_pos_entry_amount;
CHECK_AND_ASSERT_MES(total_stake_transferred <= m_total_money_in_minting, false, "total_stake_transferred: " << total_stake_transferred << ", m_total_money_in_minting: " << m_total_money_in_minting);
uint64_t remainder = m_total_money_in_minting - total_stake_transferred;
if (remainder > TESTS_DEFAULT_FEE)
coin_source->transfer(remainder, w->get_account().get_public_address());
return true;
}
bool pos_emission_test::c3(currency::core& c, size_t ev_index, const std::vector<test_event_entry> &events)
{
// Mining scenario:
#define LOG1_FILENAME "pos_emission_test_brief_c3.log"
#define LOG2_FILENAME "pos_emission_test_brief_c3_aux.log"
std::shared_ptr<tools::wallet2> alice_wlt = init_playtime_test_wallet(events, c, ALICE_ACC_IDX);
std::shared_ptr<tools::wallet2> bob_wlt = init_playtime_test_wallet(events, c, BOB_ACC_IDX);
std::shared_ptr<tools::wallet2> dan_wlt = init_playtime_test_wallet(events, c, DAN_ACC_IDX); // Dan is a preminer with buttloads of money
std::shared_ptr<tools::wallet2> miner_wlt = init_playtime_test_wallet(events, c, MINER_ACC_IDX);
size_t blocks_fetched = 0;
bool received_money;
std::atomic<bool> atomic_false = ATOMIC_VAR_INIT(false);
alice_wlt->refresh(blocks_fetched, received_money, atomic_false);
CHECK_AND_ASSERT_MES(blocks_fetched == CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1 + WALLET_DEFAULT_TX_SPENDABLE_AGE, false, "Incorrect numbers of blocks fetched");
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool");
if (!check_balance_via_wallet(*alice_wlt.get(), "alice_wlt", m_total_money_in_minting))
return false;
uint64_t ts = test_core_time::get_time();
//uint64_t pos_entry_amount = m_total_money_in_minting / m_pos_entries_to_generate;
uint64_t pos_target = DIFFICULTY_POS_TARGET;
uint64_t pow_target = DIFFICULTY_POW_TARGET;
uint64_t last_pow_block_ts = ts - pow_target;
//uint64_t last_pos_block_ts = ts - pos_target; // block_header::ts
//uint64_t last_pos_block_ts_real = ts - pos_target; // real time at the moment of successfull minting
size_t n_pos_trials = POS_SCAN_WINDOW * 2 / POS_SCAN_STEP; // number of PoS mining trials
bool r = false;
block b;
wide_difficulty_type diff = 0;
size_t block_n_to_change_pos_entries = 5000;
size_t next_block_height_to_change_pos_entries = 0; // start with pos entries population as soon as possible
size_t change_pos_entries_iter_index = 0;
std::shared_ptr<tools::wallet2> current_wallet = alice_wlt;
LOG_PRINT2(LOG1_FILENAME, "\theight\tdifficulty\tstake amount\twallet balance\tpos entries count\tstake to diff coeff\tpow seq factor\tpos seq factor", LOG_LEVEL_0);
LOG_PRINT2_JORNAL(LOG2_FILENAME, "\theight", LOG_LEVEL_0);
for(size_t n = 0; n < 30000; ++n)
{
// pow
uint64_t ideal_next_pow_block_ts = last_pow_block_ts + pow_target;
if (ts < ideal_next_pow_block_ts)
ts = ideal_next_pow_block_ts; // "wait" until ideal_next_pow_block_ts if it was not already happened (fast forward but don't wayback the time)
test_core_time::adjust(ts);
size_t tx_count_before = c.get_pool_transactions_count();
r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c);
CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed");
CHECK_AND_ASSERT_MES(tx_count_before == 0 || c.get_pool_transactions_count() < tx_count_before, false, "invalid number of txs in tx pool: " << c.get_pool_transactions_count() << ", was: " << tx_count_before);
//uint64_t pow_blocks_interval = ts - last_pow_block_ts;
last_pow_block_ts = ts;
c.get_blockchain_storage().get_top_block(b);
size_t height = get_block_height(b);
// pos entries changing mechanism
if (height >= next_block_height_to_change_pos_entries)
{
next_block_height_to_change_pos_entries += block_n_to_change_pos_entries;
// switch wallets (so new one is always has all outs unlocked -- to be able to transfers out all the money)
if (current_wallet == alice_wlt)
current_wallet = bob_wlt;
else
current_wallet = alice_wlt;
// clear wallet by transferring out all the coins
current_wallet->refresh();
uint64_t unlocked_balance = 0;
uint64_t total_balance = current_wallet->balance(unlocked_balance);
CHECK_AND_ASSERT_MES(unlocked_balance == total_balance, false, "Total and unlocked balances don't equal: " << unlocked_balance << " != " << total_balance);
if (unlocked_balance > TESTS_DEFAULT_FEE)
current_wallet->transfer(unlocked_balance - TESTS_DEFAULT_FEE, m_accounts[MINER_ACC_IDX].get_public_address()); // transfer out all the money to Miner -- clear pos entries
r = populate_wallet_with_pos_entries(current_wallet, dan_wlt, change_pos_entries_iter_index++, pos_entries_change_scheme::change_pos_entries_count);
CHECK_AND_ASSERT_MES(r, false, "populate_wallet_with_pos_entries failed");
}
// pos
uint64_t pos_ts_upper_bound = ts + pos_target / 2;
for(uint64_t i = 0; i < n_pos_trials && ts < pos_ts_upper_bound; ++i)
{
size_t pow_seq_factor = c.get_blockchain_storage().get_current_sequence_factor(false);
size_t pos_seq_factor = c.get_blockchain_storage().get_current_sequence_factor(true);
current_wallet->refresh();
diff = c.get_blockchain_storage().get_next_diff_conditional(true);
uint64_t bc_height_before = c.get_blockchain_storage().get_current_blockchain_size();
size_t pos_entries_count = 0;
if (mine_next_pos_block_in_playtime_with_wallet(*current_wallet.get(), m_accounts[MINER_ACC_IDX].get_public_address(), pos_entries_count))
{
CHECK_AND_ASSERT_MES(bc_height_before < c.get_blockchain_storage().get_current_blockchain_size(), false, "didn't mint a block!");
LOG_PRINT_GREEN(">>>>>>>>>>>>>> Minted a PoS block at " << ts << " with difficuly: " << diff, LOG_LEVEL_0);
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "invalid number of txs in tx pool: " << c.get_pool_transactions_count());
c.get_blockchain_storage().get_top_block(b);
//int64_t pos_block_timediff = b.timestamp - last_pos_block_ts;
uint64_t stake_amount = boost::get<txin_to_key>(b.miner_tx.vin[1]).amount;
double pos_total_stake_to_diff_coeff = static_cast<double>(diff / COIN) / static_cast<double>(m_total_money_in_minting / COIN);
LOG_PRINT2("pos_emission_test_brief_c3.log", "\t" << get_block_height(b) << "\t" << diff << "\t" << print_money(stake_amount) << "\t" << print_money(current_wallet->balance()) << "\t" << pos_entries_count <<
"\t" << pos_total_stake_to_diff_coeff << "\t" << pow_seq_factor << "\t" << pos_seq_factor, LOG_LEVEL_0);
//last_pos_block_ts = b.timestamp;
//last_pos_block_ts_real = ts;
continue;
}
LOG_PRINT_CYAN(">>>>>>>>>>>>>> Can't mint a PoS block at " << ts << ", wait a bit...", LOG_LEVEL_0);
ts += POS_SCAN_STEP;
test_core_time::adjust(ts);
}
}
return true;
#undef LOG1_FILENAME
#undef LOG2_FILENAME
}