functional_tests: add more blockchain related tests

Related to emission, reorgs, getting tx data back, output
distribution and histogram
This commit is contained in:
moneromooo-monero 2019-03-23 16:20:08 +00:00
parent 21b1ac1dd2
commit 064ab12340
No known key found for this signature in database
GPG key ID: 686F07454D6CEFC3
11 changed files with 399 additions and 77 deletions

View file

@ -142,6 +142,16 @@ namespace cryptonote
std::string print_money(uint64_t amount, unsigned int decimal_point = -1); std::string print_money(uint64_t amount, unsigned int decimal_point = -1);
//--------------------------------------------------------------- //---------------------------------------------------------------
template<class t_object> template<class t_object>
bool t_serializable_object_from_blob(t_object& to, const blobdata& b_blob)
{
std::stringstream ss;
ss << b_blob;
binary_archive<false> ba(ss);
bool r = ::serialization::serialize(ba, to);
return r;
}
//---------------------------------------------------------------
template<class t_object>
bool t_serializable_object_to_blob(const t_object& to, blobdata& b_blob) bool t_serializable_object_to_blob(const t_object& to, blobdata& b_blob)
{ {
std::stringstream ss; std::stringstream ss;

View file

@ -576,7 +576,8 @@ namespace cryptonote
//we lucky! //we lucky!
++m_config.current_extra_message_index; ++m_config.current_extra_message_index;
MGINFO_GREEN("Found block " << get_block_hash(b) << " at height " << height << " for difficulty: " << local_diff); MGINFO_GREEN("Found block " << get_block_hash(b) << " at height " << height << " for difficulty: " << local_diff);
if(!m_phandler->handle_block_found(b)) cryptonote::block_verification_context bvc;
if(!m_phandler->handle_block_found(b, bvc) || !bvc.m_added_to_main_chain)
{ {
--m_config.current_extra_message_index; --m_config.current_extra_message_index;
}else }else

View file

@ -34,6 +34,7 @@
#include <boost/logic/tribool_fwd.hpp> #include <boost/logic/tribool_fwd.hpp>
#include <atomic> #include <atomic>
#include "cryptonote_basic.h" #include "cryptonote_basic.h"
#include "verification_context.h"
#include "difficulty.h" #include "difficulty.h"
#include "math_helper.h" #include "math_helper.h"
#ifdef _WIN32 #ifdef _WIN32
@ -45,7 +46,7 @@ namespace cryptonote
struct i_miner_handler struct i_miner_handler
{ {
virtual bool handle_block_found(block& b) = 0; virtual bool handle_block_found(block& b, block_verification_context &bvc) = 0;
virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) = 0; virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) = 0;
protected: protected:
~i_miner_handler(){}; ~i_miner_handler(){};

View file

@ -1008,7 +1008,7 @@ bool Blockchain::rollback_blockchain_switching(std::list<block>& original_chain,
//------------------------------------------------------------------ //------------------------------------------------------------------
// This function attempts to switch to an alternate chain, returning // This function attempts to switch to an alternate chain, returning
// boolean based on success therein. // boolean based on success therein.
bool Blockchain::switch_to_alternative_blockchain(std::list<blocks_ext_by_hash::iterator>& alt_chain, bool discard_disconnected_chain) bool Blockchain::switch_to_alternative_blockchain(std::list<blocks_ext_by_hash::const_iterator>& alt_chain, bool discard_disconnected_chain)
{ {
LOG_PRINT_L3("Blockchain::" << __func__); LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock); CRITICAL_REGION_LOCAL(m_blockchain_lock);
@ -1109,7 +1109,7 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<blocks_ext_by_hash::
//------------------------------------------------------------------ //------------------------------------------------------------------
// This function calculates the difficulty target for the block being added to // This function calculates the difficulty target for the block being added to
// an alternate chain. // an alternate chain.
difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std::list<blocks_ext_by_hash::iterator>& alt_chain, block_extended_info& bei) const difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std::list<blocks_ext_by_hash::const_iterator>& alt_chain, block_extended_info& bei) const
{ {
if (m_fixed_difficulty) if (m_fixed_difficulty)
{ {
@ -1351,7 +1351,7 @@ uint64_t Blockchain::get_current_cumulative_block_weight_median() const
// in a lot of places. That flag is not referenced in any of the code // in a lot of places. That flag is not referenced in any of the code
// nor any of the makefiles, howeve. Need to look into whether or not it's // nor any of the makefiles, howeve. Need to look into whether or not it's
// necessary at all. // necessary at all.
bool Blockchain::create_block_template(block& b, const account_public_address& miner_address, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, const account_public_address& miner_address, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce)
{ {
LOG_PRINT_L3("Blockchain::" << __func__); LOG_PRINT_L3("Blockchain::" << __func__);
size_t median_weight; size_t median_weight;
@ -1361,8 +1361,7 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m
m_tx_pool.lock(); m_tx_pool.lock();
const auto unlock_guard = epee::misc_utils::create_scope_leave_handler([&]() { m_tx_pool.unlock(); }); const auto unlock_guard = epee::misc_utils::create_scope_leave_handler([&]() { m_tx_pool.unlock(); });
CRITICAL_REGION_LOCAL(m_blockchain_lock); CRITICAL_REGION_LOCAL(m_blockchain_lock);
height = m_db->height(); if (m_btc_valid && !from_block) {
if (m_btc_valid) {
// The pool cookie is atomic. The lack of locking is OK, as if it changes // The pool cookie is atomic. The lack of locking is OK, as if it changes
// just as we compare it, we'll just use a slightly old template, but // just as we compare it, we'll just use a slightly old template, but
// this would be the case anyway if we'd lock, and the change happened // this would be the case anyway if we'd lock, and the change happened
@ -1376,13 +1375,75 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m
expected_reward = m_btc_expected_reward; expected_reward = m_btc_expected_reward;
return true; return true;
} }
MDEBUG("Not using cached template: address " << (!memcmp(&miner_address, &m_btc_address, sizeof(cryptonote::account_public_address))) << ", nonce " << (m_btc_nonce == ex_nonce) << ", cookie " << (m_btc_pool_cookie == m_tx_pool.cookie())); MDEBUG("Not using cached template: address " << (!memcmp(&miner_address, &m_btc_address, sizeof(cryptonote::account_public_address))) << ", nonce " << (m_btc_nonce == ex_nonce) << ", cookie " << (m_btc_pool_cookie == m_tx_pool.cookie()) << ", from_block " << (!!from_block));
invalidate_block_template_cache(); invalidate_block_template_cache();
} }
if (from_block)
{
//build alternative subchain, front -> mainchain, back -> alternative head
//block is not related with head of main chain
//first of all - look in alternative chains container
auto it_prev = m_alternative_chains.find(*from_block);
bool parent_in_main = m_db->block_exists(*from_block);
if(it_prev == m_alternative_chains.end() && !parent_in_main)
{
MERROR("Unknown from block");
return false;
}
//we have new block in alternative chain
std::list<blocks_ext_by_hash::const_iterator> alt_chain;
block_verification_context bvc = boost::value_initialized<block_verification_context>();
std::vector<uint64_t> timestamps;
if (!build_alt_chain(*from_block, alt_chain, timestamps, bvc))
return false;
if (parent_in_main)
{
cryptonote::block prev_block;
CHECK_AND_ASSERT_MES(get_block_by_hash(*from_block, prev_block), false, "From block not found"); // TODO
uint64_t from_block_height = cryptonote::get_block_height(prev_block);
height = from_block_height + 1;
}
else
{
height = alt_chain.back()->second.height + 1;
}
b.major_version = m_hardfork->get_ideal_version(height);
b.minor_version = m_hardfork->get_ideal_version();
b.prev_id = *from_block;
// cheat and use the weight of the block we start from, virtually certain to be acceptable
// and use 1.9 times rather than 2 times so we're even more sure
if (parent_in_main)
{
median_weight = m_db->get_block_weight(height - 1);
already_generated_coins = m_db->get_block_already_generated_coins(height - 1);
}
else
{
median_weight = it_prev->second.block_cumulative_weight - it_prev->second.block_cumulative_weight / 20;
already_generated_coins = alt_chain.back()->second.already_generated_coins;
}
// FIXME: consider moving away from block_extended_info at some point
block_extended_info bei = boost::value_initialized<block_extended_info>();
bei.bl = b;
bei.height = alt_chain.size() ? it_prev->second.height + 1 : m_db->get_block_height(*from_block) + 1;
diffic = get_next_difficulty_for_alternative_chain(alt_chain, bei);
}
else
{
height = m_db->height();
b.major_version = m_hardfork->get_current_version(); b.major_version = m_hardfork->get_current_version();
b.minor_version = m_hardfork->get_ideal_version(); b.minor_version = m_hardfork->get_ideal_version();
b.prev_id = get_tail_id(); b.prev_id = get_tail_id();
median_weight = m_current_block_cumul_weight_limit / 2;
diffic = get_difficulty_for_next_block();
already_generated_coins = m_db->get_block_already_generated_coins(height - 1);
}
b.timestamp = time(NULL); b.timestamp = time(NULL);
uint64_t median_ts; uint64_t median_ts;
@ -1391,15 +1452,11 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m
b.timestamp = median_ts; b.timestamp = median_ts;
} }
diffic = get_difficulty_for_next_block();
CHECK_AND_ASSERT_MES(diffic, false, "difficulty overhead."); CHECK_AND_ASSERT_MES(diffic, false, "difficulty overhead.");
median_weight = m_current_block_cumul_weight_limit / 2;
already_generated_coins = m_db->get_block_already_generated_coins(height - 1);
size_t txs_weight; size_t txs_weight;
uint64_t fee; uint64_t fee;
if (!m_tx_pool.fill_block_template(b, median_weight, already_generated_coins, txs_weight, fee, expected_reward, m_hardfork->get_current_version())) if (!m_tx_pool.fill_block_template(b, median_weight, already_generated_coins, txs_weight, fee, expected_reward, b.major_version))
{ {
return false; return false;
} }
@ -1462,7 +1519,7 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m
block weight, so first miner transaction generated with fake amount of money, and with phase we know think we know expected block weight block weight, so first miner transaction generated with fake amount of money, and with phase we know think we know expected block weight
*/ */
//make blocks coin-base tx looks close to real coinbase tx to get truthful blob weight //make blocks coin-base tx looks close to real coinbase tx to get truthful blob weight
uint8_t hf_version = m_hardfork->get_current_version(); uint8_t hf_version = b.major_version;
size_t max_outs = hf_version >= 4 ? 1 : 11; size_t max_outs = hf_version >= 4 ? 1 : 11;
bool r = construct_miner_tx(height, median_weight, already_generated_coins, txs_weight, fee, miner_address, b.miner_tx, ex_nonce, max_outs, hf_version); bool r = construct_miner_tx(height, median_weight, already_generated_coins, txs_weight, fee, miner_address, b.miner_tx, ex_nonce, max_outs, hf_version);
CHECK_AND_ASSERT_MES(r, false, "Failed to construct miner tx, first chance"); CHECK_AND_ASSERT_MES(r, false, "Failed to construct miner tx, first chance");
@ -1517,6 +1574,7 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m
", cumulative weight " << cumulative_weight << " is now good"); ", cumulative weight " << cumulative_weight << " is now good");
#endif #endif
if (!from_block)
cache_block_template(b, miner_address, ex_nonce, diffic, height, expected_reward, pool_cookie); cache_block_template(b, miner_address, ex_nonce, diffic, height, expected_reward, pool_cookie);
return true; return true;
} }
@ -1524,9 +1582,14 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m
return false; return false;
} }
//------------------------------------------------------------------ //------------------------------------------------------------------
bool Blockchain::create_block_template(block& b, const account_public_address& miner_address, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce)
{
return create_block_template(b, NULL, miner_address, diffic, height, expected_reward, ex_nonce);
}
//------------------------------------------------------------------
// for an alternate chain, get the timestamps from the main chain to complete // for an alternate chain, get the timestamps from the main chain to complete
// the needed number of timestamps for the BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW. // the needed number of timestamps for the BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW.
bool Blockchain::complete_timestamps_vector(uint64_t start_top_height, std::vector<uint64_t>& timestamps) bool Blockchain::complete_timestamps_vector(uint64_t start_top_height, std::vector<uint64_t>& timestamps) const
{ {
LOG_PRINT_L3("Blockchain::" << __func__); LOG_PRINT_L3("Blockchain::" << __func__);
@ -1546,6 +1609,52 @@ bool Blockchain::complete_timestamps_vector(uint64_t start_top_height, std::vect
return true; return true;
} }
//------------------------------------------------------------------ //------------------------------------------------------------------
bool Blockchain::build_alt_chain(const crypto::hash &prev_id, std::list<blocks_ext_by_hash::const_iterator>& alt_chain, std::vector<uint64_t> &timestamps, block_verification_context& bvc) const
{
//build alternative subchain, front -> mainchain, back -> alternative head
blocks_ext_by_hash::const_iterator alt_it = m_alternative_chains.find(prev_id);
timestamps.clear();
while(alt_it != m_alternative_chains.end())
{
alt_chain.push_front(alt_it);
timestamps.push_back(alt_it->second.bl.timestamp);
alt_it = m_alternative_chains.find(alt_it->second.bl.prev_id);
}
// if block to be added connects to known blocks that aren't part of the
// main chain -- that is, if we're adding on to an alternate chain
if(!alt_chain.empty())
{
// make sure alt chain doesn't somehow start past the end of the main chain
CHECK_AND_ASSERT_MES(m_db->height() > alt_chain.front()->second.height, false, "main blockchain wrong height");
// make sure that the blockchain contains the block that should connect
// this alternate chain with it.
if (!m_db->block_exists(alt_chain.front()->second.bl.prev_id))
{
MERROR("alternate chain does not appear to connect to main chain...");
return false;
}
// make sure block connects correctly to the main chain
auto h = m_db->get_block_hash_from_height(alt_chain.front()->second.height - 1);
CHECK_AND_ASSERT_MES(h == alt_chain.front()->second.bl.prev_id, false, "alternative chain has wrong connection to main chain");
complete_timestamps_vector(m_db->get_block_height(alt_chain.front()->second.bl.prev_id), timestamps);
}
// if block not associated with known alternate chain
else
{
// if block parent is not part of main chain or an alternate chain,
// we ignore it
bool parent_in_main = m_db->block_exists(prev_id);
CHECK_AND_ASSERT_MES(parent_in_main, false, "internal error: broken imperative condition: parent_in_main");
complete_timestamps_vector(m_db->get_block_height(prev_id), timestamps);
}
return true;
}
//------------------------------------------------------------------
// If a block is to be added and its parent block is not the current // If a block is to be added and its parent block is not the current
// main chain top block, then we need to see if we know about its parent block. // main chain top block, then we need to see if we know about its parent block.
// If its parent block is part of a known forked chain, then we need to see // If its parent block is part of a known forked chain, then we need to see
@ -1590,47 +1699,18 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id
if(it_prev != m_alternative_chains.end() || parent_in_main) if(it_prev != m_alternative_chains.end() || parent_in_main)
{ {
//we have new block in alternative chain //we have new block in alternative chain
std::list<blocks_ext_by_hash::const_iterator> alt_chain;
//build alternative subchain, front -> mainchain, back -> alternative head
blocks_ext_by_hash::iterator alt_it = it_prev; //m_alternative_chains.find()
std::list<blocks_ext_by_hash::iterator> alt_chain;
std::vector<uint64_t> timestamps; std::vector<uint64_t> timestamps;
while(alt_it != m_alternative_chains.end()) if (!build_alt_chain(b.prev_id, alt_chain, timestamps, bvc))
{
alt_chain.push_front(alt_it);
timestamps.push_back(alt_it->second.bl.timestamp);
alt_it = m_alternative_chains.find(alt_it->second.bl.prev_id);
}
// if block to be added connects to known blocks that aren't part of the
// main chain -- that is, if we're adding on to an alternate chain
if(!alt_chain.empty())
{
// make sure alt chain doesn't somehow start past the end of the main chain
CHECK_AND_ASSERT_MES(m_db->height() > alt_chain.front()->second.height, false, "main blockchain wrong height");
// make sure that the blockchain contains the block that should connect
// this alternate chain with it.
if (!m_db->block_exists(alt_chain.front()->second.bl.prev_id))
{
MERROR("alternate chain does not appear to connect to main chain...");
return false; return false;
}
// make sure block connects correctly to the main chain // FIXME: consider moving away from block_extended_info at some point
auto h = m_db->get_block_hash_from_height(alt_chain.front()->second.height - 1); block_extended_info bei = boost::value_initialized<block_extended_info>();
CHECK_AND_ASSERT_MES(h == alt_chain.front()->second.bl.prev_id, false, "alternative chain has wrong connection to main chain"); bei.bl = b;
complete_timestamps_vector(m_db->get_block_height(alt_chain.front()->second.bl.prev_id), timestamps); const uint64_t prev_height = alt_chain.size() ? it_prev->second.height : m_db->get_block_height(b.prev_id);
} bei.height = prev_height + 1;
// if block not associated with known alternate chain uint64_t block_reward = get_outs_money_amount(b.miner_tx);
else bei.already_generated_coins = block_reward + (alt_chain.size() ? it_prev->second.already_generated_coins : m_db->get_block_already_generated_coins(prev_height));
{
// if block parent is not part of main chain or an alternate chain,
// we ignore it
CHECK_AND_ASSERT_MES(parent_in_main, false, "internal error: broken imperative condition: parent_in_main");
complete_timestamps_vector(m_db->get_block_height(b.prev_id), timestamps);
}
// verify that the block's timestamp is within the acceptable range // verify that the block's timestamp is within the acceptable range
// (not earlier than the median of the last X blocks) // (not earlier than the median of the last X blocks)
@ -1641,11 +1721,6 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id
return false; return false;
} }
// FIXME: consider moving away from block_extended_info at some point
block_extended_info bei = boost::value_initialized<block_extended_info>();
bei.bl = b;
bei.height = alt_chain.size() ? it_prev->second.height + 1 : m_db->get_block_height(b.prev_id) + 1;
bool is_a_checkpoint; bool is_a_checkpoint;
if(!m_checkpoints.check_block(bei.height, id, is_a_checkpoint)) if(!m_checkpoints.check_block(bei.height, id, is_a_checkpoint))
{ {

View file

@ -336,6 +336,7 @@ namespace cryptonote
* @brief creates a new block to mine against * @brief creates a new block to mine against
* *
* @param b return-by-reference block to be filled in * @param b return-by-reference block to be filled in
* @param from_block optional block hash to start mining from (main chain tip if NULL)
* @param miner_address address new coins for the block will go to * @param miner_address address new coins for the block will go to
* @param di return-by-reference tells the miner what the difficulty target is * @param di return-by-reference tells the miner what the difficulty target is
* @param height return-by-reference tells the miner what height it's mining against * @param height return-by-reference tells the miner what height it's mining against
@ -345,6 +346,7 @@ namespace cryptonote
* @return true if block template filled in successfully, else false * @return true if block template filled in successfully, else false
*/ */
bool create_block_template(block& b, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce); bool create_block_template(block& b, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce);
bool create_block_template(block& b, const crypto::hash *from_block, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce);
/** /**
* @brief checks if a block is known about with a given hash * @brief checks if a block is known about with a given hash
@ -1180,7 +1182,7 @@ namespace cryptonote
* *
* @return false if the reorganization fails, otherwise true * @return false if the reorganization fails, otherwise true
*/ */
bool switch_to_alternative_blockchain(std::list<blocks_ext_by_hash::iterator>& alt_chain, bool discard_disconnected_chain); bool switch_to_alternative_blockchain(std::list<blocks_ext_by_hash::const_iterator>& alt_chain, bool discard_disconnected_chain);
/** /**
* @brief removes the most recent block from the blockchain * @brief removes the most recent block from the blockchain
@ -1233,6 +1235,18 @@ namespace cryptonote
*/ */
bool handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc); bool handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc);
/**
* @brief builds a list of blocks connecting a block to the main chain
*
* @param prev_id the block hash of the tip of the alt chain
* @param alt_chain the chain to be added to
* @param timestamps returns the timestamps of previous blocks
* @param bvc the block verification context for error return
*
* @return true on success, false otherwise
*/
bool build_alt_chain(const crypto::hash &prev_id, std::list<blocks_ext_by_hash::const_iterator>& alt_chain, std::vector<uint64_t> &timestamps, block_verification_context& bvc) const;
/** /**
* @brief gets the difficulty requirement for a new block on an alternate chain * @brief gets the difficulty requirement for a new block on an alternate chain
* *
@ -1241,7 +1255,7 @@ namespace cryptonote
* *
* @return the difficulty requirement * @return the difficulty requirement
*/ */
difficulty_type get_next_difficulty_for_alternative_chain(const std::list<blocks_ext_by_hash::iterator>& alt_chain, block_extended_info& bei) const; difficulty_type get_next_difficulty_for_alternative_chain(const std::list<blocks_ext_by_hash::const_iterator>& alt_chain, block_extended_info& bei) const;
/** /**
* @brief sanity checks a miner transaction before validating an entire block * @brief sanity checks a miner transaction before validating an entire block
@ -1401,7 +1415,7 @@ namespace cryptonote
* *
* @return true unless start_height is greater than the current blockchain height * @return true unless start_height is greater than the current blockchain height
*/ */
bool complete_timestamps_vector(uint64_t start_height, std::vector<uint64_t>& timestamps); bool complete_timestamps_vector(uint64_t start_height, std::vector<uint64_t>& timestamps) const;
/** /**
* @brief calculate the block weight limit for the next block to be added * @brief calculate the block weight limit for the next block to be added

View file

@ -1268,6 +1268,11 @@ namespace cryptonote
return m_blockchain_storage.create_block_template(b, adr, diffic, height, expected_reward, ex_nonce); return m_blockchain_storage.create_block_template(b, adr, diffic, height, expected_reward, ex_nonce);
} }
//----------------------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------------------
bool core::get_block_template(block& b, const crypto::hash *prev_block, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce)
{
return m_blockchain_storage.create_block_template(b, prev_block, adr, diffic, height, expected_reward, ex_nonce);
}
//-----------------------------------------------------------------------------------------------
bool core::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const bool core::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const
{ {
return m_blockchain_storage.find_blockchain_supplement(qblock_ids, resp); return m_blockchain_storage.find_blockchain_supplement(qblock_ids, resp);
@ -1321,9 +1326,9 @@ namespace cryptonote
return bce; return bce;
} }
//----------------------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------------------
bool core::handle_block_found(block& b) bool core::handle_block_found(block& b, block_verification_context &bvc)
{ {
block_verification_context bvc = boost::value_initialized<block_verification_context>(); bvc = boost::value_initialized<block_verification_context>();
m_miner.pause(); m_miner.pause();
std::vector<block_complete_entry> blocks; std::vector<block_complete_entry> blocks;
try try
@ -1373,7 +1378,7 @@ namespace cryptonote
m_pprotocol->relay_block(arg, exclude_context); m_pprotocol->relay_block(arg, exclude_context);
} }
return bvc.m_added_to_main_chain; return true;
} }
//----------------------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------------------
void core::on_synchronized() void core::on_synchronized()

View file

@ -195,10 +195,11 @@ namespace cryptonote
* the network. * the network.
* *
* @param b the block found * @param b the block found
* @param bvc returns the block verification flags
* *
* @return true if the block was added to the main chain, otherwise false * @return true if the block was added to the main chain, otherwise false
*/ */
virtual bool handle_block_found( block& b); virtual bool handle_block_found(block& b, block_verification_context &bvc);
/** /**
* @copydoc Blockchain::create_block_template * @copydoc Blockchain::create_block_template
@ -206,6 +207,7 @@ namespace cryptonote
* @note see Blockchain::create_block_template * @note see Blockchain::create_block_template
*/ */
virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce); virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce);
virtual bool get_block_template(block& b, const crypto::hash *prev_block, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce);
/** /**
* @brief called when a transaction is relayed * @brief called when a transaction is relayed

View file

@ -496,6 +496,7 @@ namespace cryptonote
cryptonote::COMMAND_RPC_GET_OUTPUTS_BIN::request req_bin; cryptonote::COMMAND_RPC_GET_OUTPUTS_BIN::request req_bin;
req_bin.outputs = req.outputs; req_bin.outputs = req.outputs;
req_bin.get_txid = req.get_txid;
cryptonote::COMMAND_RPC_GET_OUTPUTS_BIN::response res_bin; cryptonote::COMMAND_RPC_GET_OUTPUTS_BIN::response res_bin;
if(!m_core.get_outs(req_bin, res_bin)) if(!m_core.get_outs(req_bin, res_bin))
{ {
@ -1259,7 +1260,17 @@ namespace cryptonote
cryptonote::blobdata blob_reserve; cryptonote::blobdata blob_reserve;
blob_reserve.resize(req.reserve_size, 0); blob_reserve.resize(req.reserve_size, 0);
cryptonote::difficulty_type wdiff; cryptonote::difficulty_type wdiff;
if(!m_core.get_block_template(b, info.address, wdiff, res.height, res.expected_reward, blob_reserve)) crypto::hash prev_block;
if (!req.prev_block.empty())
{
if (!epee::string_tools::hex_to_pod(req.prev_block, prev_block))
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Invalid prev_block";
return false;
}
}
if(!m_core.get_block_template(b, req.prev_block.empty() ? NULL : &prev_block, info.address, wdiff, res.height, res.expected_reward, blob_reserve))
{ {
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Internal error: failed to create block template"; error_resp.message = "Internal error: failed to create block template";
@ -1345,7 +1356,8 @@ namespace cryptonote
return false; return false;
} }
if(!m_core.handle_block_found(b)) block_verification_context bvc;
if(!m_core.handle_block_found(b, bvc))
{ {
error_resp.code = CORE_RPC_ERROR_CODE_BLOCK_NOT_ACCEPTED; error_resp.code = CORE_RPC_ERROR_CODE_BLOCK_NOT_ACCEPTED;
error_resp.message = "Block not accepted"; error_resp.message = "Block not accepted";
@ -1377,15 +1389,17 @@ namespace cryptonote
template_req.reserve_size = 1; template_req.reserve_size = 1;
template_req.wallet_address = req.wallet_address; template_req.wallet_address = req.wallet_address;
template_req.prev_block = req.prev_block;
submit_req.push_back(boost::value_initialized<std::string>()); submit_req.push_back(boost::value_initialized<std::string>());
res.height = m_core.get_blockchain_storage().get_current_blockchain_height(); res.height = m_core.get_blockchain_storage().get_current_blockchain_height();
bool r; bool r = CORE_RPC_STATUS_OK;
for(size_t i = 0; i < req.amount_of_blocks; i++) for(size_t i = 0; i < req.amount_of_blocks; i++)
{ {
r = on_getblocktemplate(template_req, template_res, error_resp, ctx); r = on_getblocktemplate(template_req, template_res, error_resp, ctx);
res.status = template_res.status; res.status = template_res.status;
template_req.prev_block.clear();
if (!r) return false; if (!r) return false;
@ -1403,6 +1417,7 @@ namespace cryptonote
error_resp.message = "Wrong block blob"; error_resp.message = "Wrong block blob";
return false; return false;
} }
b.nonce = req.starting_nonce;
miner::find_nonce_for_given_block(b, template_res.difficulty, template_res.height); miner::find_nonce_for_given_block(b, template_res.difficulty, template_res.height);
submit_req.front() = string_tools::buff_to_hex_nodelimer(block_to_blob(b)); submit_req.front() = string_tools::buff_to_hex_nodelimer(block_to_blob(b));
@ -1411,6 +1426,8 @@ namespace cryptonote
if (!r) return false; if (!r) return false;
res.blocks.push_back(epee::string_tools::pod_to_hex(get_block_hash(b)));
template_req.prev_block = res.blocks.back();
res.height = template_res.height; res.height = template_res.height;
} }

View file

@ -530,9 +530,11 @@ namespace cryptonote
struct request_t struct request_t
{ {
std::vector<get_outputs_out> outputs; std::vector<get_outputs_out> outputs;
bool get_txid;
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(outputs) KV_SERIALIZE(outputs)
KV_SERIALIZE(get_txid)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };
typedef epee::misc_utils::struct_init<request_t> request; typedef epee::misc_utils::struct_init<request_t> request;
@ -904,10 +906,12 @@ namespace cryptonote
{ {
uint64_t reserve_size; //max 255 bytes uint64_t reserve_size; //max 255 bytes
std::string wallet_address; std::string wallet_address;
std::string prev_block;
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(reserve_size) KV_SERIALIZE(reserve_size)
KV_SERIALIZE(wallet_address) KV_SERIALIZE(wallet_address)
KV_SERIALIZE(prev_block)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };
typedef epee::misc_utils::struct_init<request_t> request; typedef epee::misc_utils::struct_init<request_t> request;
@ -964,10 +968,14 @@ namespace cryptonote
{ {
uint64_t amount_of_blocks; uint64_t amount_of_blocks;
std::string wallet_address; std::string wallet_address;
std::string prev_block;
uint32_t starting_nonce;
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(amount_of_blocks) KV_SERIALIZE(amount_of_blocks)
KV_SERIALIZE(wallet_address) KV_SERIALIZE(wallet_address)
KV_SERIALIZE(prev_block)
KV_SERIALIZE_OPT(starting_nonce, (uint32_t)0)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };
typedef epee::misc_utils::struct_init<request_t> request; typedef epee::misc_utils::struct_init<request_t> request;
@ -975,10 +983,12 @@ namespace cryptonote
struct response_t struct response_t
{ {
uint64_t height; uint64_t height;
std::vector<std::string> blocks;
std::string status; std::string status;
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(height) KV_SERIALIZE(height)
KV_SERIALIZE(blocks)
KV_SERIALIZE(status) KV_SERIALIZE(status)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };

View file

@ -46,6 +46,7 @@ from framework.daemon import Daemon
class BlockchainTest(): class BlockchainTest():
def run_test(self): def run_test(self):
self._test_generateblocks(5) self._test_generateblocks(5)
self._test_alt_chains()
def _test_generateblocks(self, blocks): def _test_generateblocks(self, blocks):
assert blocks >= 2 assert blocks >= 2
@ -152,6 +153,130 @@ class BlockchainTest():
except: ok = True except: ok = True
assert ok assert ok
# get transactions
res = daemon.get_info()
assert res.height == height + blocks - 1
nblocks = height + blocks - 1
res = daemon.getblockheadersrange(0, nblocks - 1)
assert len(res.headers) == nblocks
assert res.headers[-1] == block_header
txids = [x.miner_tx_hash for x in res.headers]
res = daemon.get_transactions(txs_hashes = txids)
assert len(res.txs) == nblocks
assert not 'missed_txs' in res or len(res.missed_txs) == 0
running_output_index = 0
for i in range(len(txids)):
tx = res.txs[i]
assert tx.tx_hash == txids[i]
assert not tx.double_spend_seen
assert not tx.in_pool
assert tx.block_height == i
if i > 0:
for idx in tx.output_indices:
assert idx == running_output_index
running_output_index += 1
res_out = daemon.get_outs([{'amount': 0, 'index': i} for i in tx.output_indices], get_txid = True)
assert len(res_out.outs) == len(tx.output_indices)
for out in res_out.outs:
assert len(out.key) == 64
assert len(out.mask) == 64
assert not out.unlocked
assert out.height == i + 1
assert out.txid == txids[i + 1]
for i in range(height + nblocks - 1):
res_sum = daemon.get_coinbase_tx_sum(i, 1)
res_header = daemon.getblockheaderbyheight(i)
assert res_sum.emission_amount == res_header.block_header.reward
res = daemon.get_coinbase_tx_sum(0, 1)
assert res.emission_amount == 17592186044415
assert res.fee_amount == 0
sum_blocks = height + nblocks - 1
res = daemon.get_coinbase_tx_sum(0, sum_blocks)
extrapolated = 17592186044415 + 17592186044415 * 2 * (sum_blocks - 1)
assert res.emission_amount < extrapolated and res.emission_amount > extrapolated - 1e12
assert res.fee_amount == 0
sum_blocks_emission = res.emission_amount
res = daemon.get_coinbase_tx_sum(1, sum_blocks)
assert res.emission_amount == sum_blocks_emission - 17592186044415
assert res.fee_amount == 0
res = daemon.get_output_distribution([0, 1, 17592186044415], 0, 0)
assert len(res.distributions) == 3
for a in range(3):
assert res.distributions[a].amount == [0, 1, 17592186044415][a]
assert res.distributions[a].start_height == 0
assert res.distributions[a].base == 0
assert len(res.distributions[a].distribution) == height + nblocks - 1
assert res.distributions[a].binary == False
for i in range(height + nblocks - 1):
assert res.distributions[a].distribution[i] == (1 if i > 0 and a == 0 else 1 if a == 2 and i == 0 else 0)
res = daemon.get_output_histogram([], min_count = 0, max_count = 0)
assert len(res.histogram) == 2
for i in range(2):
assert res.histogram[i].amount in [0, 17592186044415]
assert res.histogram[i].total_instances in [height + nblocks - 2, 1]
assert res.histogram[i].unlocked_instances == 0
assert res.histogram[i].recent_instances == 0
def _test_alt_chains(self):
print('Testing alt chains')
daemon = Daemon()
res = daemon.get_info()
height = res.height
prev_hash = res.top_block_hash
res_template = daemon.getblocktemplate('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm')
nonce = 0
# 5 siblings
alt_blocks = [None] * 5
for i in range(len(alt_blocks)):
res = daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1, prev_block = prev_hash, starting_nonce = nonce)
assert res.height == height
assert len(res.blocks) == 1
txid = res.blocks[0]
res = daemon.getblockheaderbyhash(txid)
nonce = res.block_header.nonce
print('mined ' + ('alt' if res.block_header.orphan_status else 'tip') + ' block ' + str(height) + ', nonce ' + str(nonce))
assert res.block_header.prev_hash == prev_hash
assert res.block_header.orphan_status == (i > 0)
alt_blocks[i] = txid
nonce += 1
print 'mining 3 on 1'
# three more on [1]
res = daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 3, prev_block = alt_blocks[1], starting_nonce = nonce)
assert res.height == height + 3
assert len(res.blocks) == 3
blk_hash = res.blocks[2]
res = daemon.getblockheaderbyhash(blk_hash)
nonce = res.block_header.nonce
print('mined 3 blocks to height ' + str(height + 3))
assert not res.block_header.orphan_status
nonce += 1
print 'mining 4 on 3'
# 4 more on [3], the chain will reorg when we mine the 4th
top_block_hash = blk_hash
prev_block = alt_blocks[3]
for i in range(4):
res = daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1, prev_block = prev_block)
assert res.height == height + 1 + i
assert len(res.blocks) == 1
prev_block = res.blocks[-1]
res = daemon.getblockheaderbyhash(res.blocks[-1])
assert res.block_header.orphan_status == (i < 3)
res = daemon.get_info()
assert res.height == ((height + 4) if i < 3 else height + 5)
assert res.top_block_hash == (top_block_hash if i < 3 else prev_block)
res = daemon.get_info()
assert res.height == height + 5
assert res.top_block_hash == prev_block
if __name__ == '__main__': if __name__ == '__main__':
BlockchainTest().run_test() BlockchainTest().run_test()

View file

@ -35,12 +35,13 @@ class Daemon(object):
def __init__(self, protocol='http', host='127.0.0.1', port=0, idx=0): def __init__(self, protocol='http', host='127.0.0.1', port=0, idx=0):
self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port if port else 18180+idx)) self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port if port else 18180+idx))
def getblocktemplate(self, address): def getblocktemplate(self, address, prev_block = ""):
getblocktemplate = { getblocktemplate = {
'method': 'getblocktemplate', 'method': 'getblocktemplate',
'params': { 'params': {
'wallet_address': address, 'wallet_address': address,
'reserve_size' : 1 'reserve_size' : 1,
'prev_block' : prev_block,
}, },
'jsonrpc': '2.0', 'jsonrpc': '2.0',
'id': '0' 'id': '0'
@ -143,13 +144,15 @@ class Daemon(object):
} }
return self.rpc.send_json_rpc_request(hard_fork_info) return self.rpc.send_json_rpc_request(hard_fork_info)
def generateblocks(self, address, blocks=1): def generateblocks(self, address, blocks=1, prev_block = "", starting_nonce = 0):
generateblocks = { generateblocks = {
'method': 'generateblocks', 'method': 'generateblocks',
'params': { 'params': {
'amount_of_blocks' : blocks, 'amount_of_blocks' : blocks,
'reserve_size' : 20, 'reserve_size' : 20,
'wallet_address': address 'wallet_address': address,
'prev_block': prev_block,
'starting_nonce': starting_nonce,
}, },
'jsonrpc': '2.0', 'jsonrpc': '2.0',
'id': '0' 'id': '0'
@ -238,3 +241,62 @@ class Daemon(object):
'id': '0' 'id': '0'
} }
return self.rpc.send_json_rpc_request(set_bans) return self.rpc.send_json_rpc_request(set_bans)
def get_transactions(self, txs_hashes = [], decode_as_json = False, prune = False, split = False):
get_transactions = {
'txs_hashes': txs_hashes,
'decode_as_json': decode_as_json,
'prune': prune,
'split': split,
}
return self.rpc.send_request('/get_transactions', get_transactions)
def get_outs(self, outputs = [], get_txid = False):
get_outs = {
'outputs': outputs,
'get_txid': get_txid,
}
return self.rpc.send_request('/get_outs', get_outs)
def get_coinbase_tx_sum(self, height, count):
get_coinbase_tx_sum = {
'method': 'get_coinbase_tx_sum',
'params': {
'height': height,
'count': count,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(get_coinbase_tx_sum)
def get_output_distribution(self, amounts = [], from_height = 0, to_height = 0, cumulative = False, binary = False, compress = False):
get_output_distribution = {
'method': 'get_output_distribution',
'params': {
'amounts': amounts,
'from_height': from_height,
'to_height': to_height,
'cumulative': cumulative,
'binary': binary,
'compress': compress,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(get_output_distribution)
def get_output_histogram(self, amounts = [], min_count = 0, max_count = 0, unlocked = False, recent_cutoff = 0):
get_output_histogram = {
'method': 'get_output_histogram',
'params': {
'amounts': amounts,
'min_count': min_count,
'max_count': max_count,
'unlocked': unlocked,
'recent_cutoff': recent_cutoff,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(get_output_histogram)