diff options
Diffstat (limited to 'src/cryptonote_core')
-rw-r--r-- | src/cryptonote_core/blockchain_storage.cpp | 63 | ||||
-rw-r--r-- | src/cryptonote_core/checkpoints_create.h | 3 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_format_utils.cpp | 139 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_format_utils.h | 21 | ||||
-rw-r--r-- | src/cryptonote_core/difficulty.cpp | 2 | ||||
-rw-r--r-- | src/cryptonote_core/tx_extra.h | 87 | ||||
-rw-r--r-- | src/cryptonote_core/tx_pool.h | 3 |
7 files changed, 245 insertions, 73 deletions
diff --git a/src/cryptonote_core/blockchain_storage.cpp b/src/cryptonote_core/blockchain_storage.cpp index c1b9619f2..0e20b454b 100644 --- a/src/cryptonote_core/blockchain_storage.cpp +++ b/src/cryptonote_core/blockchain_storage.cpp @@ -582,6 +582,42 @@ bool blockchain_storage::create_block_template(block& b, const account_public_ad if (!m_tx_pool.fill_block_template(b, median_size, already_generated_coins, txs_size, fee)) { return false; } +#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) + size_t real_txs_size = 0; + uint64_t real_fee = 0; + CRITICAL_REGION_BEGIN(m_tx_pool.m_transactions_lock); + BOOST_FOREACH(crypto::hash &cur_hash, b.tx_hashes) { + auto cur_res = m_tx_pool.m_transactions.find(cur_hash); + if (cur_res == m_tx_pool.m_transactions.end()) { + LOG_ERROR("Creating block template: error: transaction not found"); + continue; + } + tx_memory_pool::tx_details &cur_tx = cur_res->second; + real_txs_size += cur_tx.blob_size; + real_fee += cur_tx.fee; + if (cur_tx.blob_size != get_object_blobsize(cur_tx.tx)) { + LOG_ERROR("Creating block template: error: invalid transaction size"); + } + uint64_t inputs_amount; + if (!get_inputs_money_amount(cur_tx.tx, inputs_amount)) { + LOG_ERROR("Creating block template: error: cannot get inputs amount"); + } else if (cur_tx.fee != inputs_amount - get_outs_money_amount(cur_tx.tx)) { + LOG_ERROR("Creating block template: error: invalid fee"); + } + } + if (txs_size != real_txs_size) { + LOG_ERROR("Creating block template: error: wrongly calculated transaction size"); + } + if (fee != real_fee) { + LOG_ERROR("Creating block template: error: wrongly calculated fee"); + } + CRITICAL_REGION_END(); + LOG_PRINT_L1("Creating block template: height " << height << + ", median size " << median_size << + ", already generated coins " << already_generated_coins << + ", transaction size " << txs_size << + ", fee " << fee); +#endif /* two-phase miner transaction generation: we don't know exact block size until we prepare block, but we don't know reward until we know @@ -590,27 +626,32 @@ bool blockchain_storage::create_block_template(block& b, const account_public_ad //make blocks coin-base tx looks close to real coinbase tx to get truthful blob size bool r = construct_miner_tx(height, median_size, already_generated_coins, txs_size, fee, miner_address, b.miner_tx, ex_nonce, 11); CHECK_AND_ASSERT_MES(r, false, "Failed to construc miner tx, first chance"); -#ifdef _DEBUG - std::list<size_t> try_val; - try_val.push_back(get_object_blobsize(b.miner_tx)); -#endif - size_t cumulative_size = txs_size + get_object_blobsize(b.miner_tx); +#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) + LOG_PRINT_L1("Creating block template: miner tx size " << get_object_blobsize(b.miner_tx) << + ", cumulative size " << cumulative_size); +#endif for (size_t try_count = 0; try_count != 10; ++try_count) { r = construct_miner_tx(height, median_size, already_generated_coins, cumulative_size, fee, miner_address, b.miner_tx, ex_nonce, 11); -#ifdef _DEBUG - try_val.push_back(get_object_blobsize(b.miner_tx)); -#endif CHECK_AND_ASSERT_MES(r, false, "Failed to construc miner tx, second chance"); size_t coinbase_blob_size = get_object_blobsize(b.miner_tx); if (coinbase_blob_size > cumulative_size - txs_size) { cumulative_size = txs_size + coinbase_blob_size; +#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) + LOG_PRINT_L1("Creating block template: miner tx size " << coinbase_blob_size << + ", cumulative size " << cumulative_size << " is greater then before"); +#endif continue; } if (coinbase_blob_size < cumulative_size - txs_size) { size_t delta = cumulative_size - txs_size - coinbase_blob_size; +#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) + LOG_PRINT_L1("Creating block template: miner tx size " << coinbase_blob_size << + ", cumulative size " << txs_size + coinbase_blob_size << + " is less then before, adding " << delta << " zero bytes"); +#endif b.miner_tx.extra.insert(b.miner_tx.extra.end(), delta, 0); //here could be 1 byte difference, because of extra field counter is varint, and it can become from 1-byte len to 2-bytes len. if (cumulative_size != txs_size + get_object_blobsize(b.miner_tx)) { @@ -626,6 +667,10 @@ bool blockchain_storage::create_block_template(block& b, const account_public_ad } } CHECK_AND_ASSERT_MES(cumulative_size == txs_size + get_object_blobsize(b.miner_tx), false, "unexpected case: cumulative_size=" << cumulative_size << " is not equal txs_cumulative_size=" << txs_size << " + get_object_blobsize(b.miner_tx)=" << get_object_blobsize(b.miner_tx)); +#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) + LOG_PRINT_L1("Creating block template: miner tx size " << coinbase_blob_size << + ", cumulative size " << cumulative_size << " is now good"); +#endif return true; } LOG_ERROR("Failed to create_block_template with " << 10 << " tries"); @@ -1603,4 +1648,4 @@ bool blockchain_storage::add_new_block(const block& bl_, block_verification_cont } return handle_block_to_main_chain(bl, id, bvc); -} +}
\ No newline at end of file diff --git a/src/cryptonote_core/checkpoints_create.h b/src/cryptonote_core/checkpoints_create.h index 8dff85d84..304096ef2 100644 --- a/src/cryptonote_core/checkpoints_create.h +++ b/src/cryptonote_core/checkpoints_create.h @@ -11,7 +11,8 @@ namespace cryptonote { inline bool create_checkpoints(cryptonote::checkpoints& checkpoints) - { + { + ADD_CHECKPOINT(22231, "d69526de16c58b07058bd80a9a8c99389814d17b1935623223b0d6e4a311b80f"); return true; } } diff --git a/src/cryptonote_core/cryptonote_format_utils.cpp b/src/cryptonote_core/cryptonote_format_utils.cpp index a231f9c75..aa2c82b2a 100644 --- a/src/cryptonote_core/cryptonote_format_utils.cpp +++ b/src/cryptonote_core/cryptonote_format_utils.cpp @@ -61,8 +61,8 @@ namespace cryptonote keypair txkey = keypair::generate(); add_tx_pub_key_to_extra(tx, txkey.pub); - if(extra_nonce.size()) - if(!add_tx_extra_nonce(tx, extra_nonce)) + if(!extra_nonce.empty()) + if(!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce)) return false; txin_gen in; @@ -74,6 +74,10 @@ namespace cryptonote LOG_PRINT_L0("Block is too big"); return false; } +#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) + LOG_PRINT_L1("Creating block template: reward " << block_reward << + ", fee " << fee) +#endif block_reward += fee; std::vector<size_t> out_amounts; @@ -204,53 +208,52 @@ namespace cryptonote return r; } //--------------------------------------------------------------- - crypto::public_key get_tx_pub_key_from_extra(const transaction& tx) + bool parse_tx_extra(const std::vector<uint8_t>& tx_extra, std::vector<tx_extra_field>& tx_extra_fields) { - crypto::public_key pk = null_pkey; - parse_and_validate_tx_extra(tx, pk); - return pk; - } - //--------------------------------------------------------------- - bool parse_and_validate_tx_extra(const transaction& tx, crypto::public_key& tx_pub_key) - { - tx_pub_key = null_pkey; - bool padding_started = false; //let the padding goes only at the end - bool tx_extra_tag_pubkey_found = false; - bool tx_extra_extra_nonce_found = false; - for(size_t i = 0; i != tx.extra.size();) + tx_extra_fields.clear(); + + if(tx_extra.empty()) + return true; + + std::string extra_str(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size()); + std::istringstream iss(extra_str); + binary_archive<false> ar(iss); + + bool eof = false; + while (!eof) { - if(padding_started) - { - CHECK_AND_ASSERT_MES(!tx.extra[i], false, "Failed to parse transaction extra (not 0 after padding) in tx " << get_transaction_hash(tx)); - } - else if(tx.extra[i] == TX_EXTRA_TAG_PUBKEY) - { - CHECK_AND_ASSERT_MES(sizeof(crypto::public_key) <= tx.extra.size()-1-i, false, "Failed to parse transaction extra (TX_EXTRA_TAG_PUBKEY have not enough bytes) in tx " << get_transaction_hash(tx)); - CHECK_AND_ASSERT_MES(!tx_extra_tag_pubkey_found, false, "Failed to parse transaction extra (duplicate TX_EXTRA_TAG_PUBKEY entry) in tx " << get_transaction_hash(tx)); - tx_pub_key = *reinterpret_cast<const crypto::public_key*>(&tx.extra[i+1]); - i += 1 + sizeof(crypto::public_key); - tx_extra_tag_pubkey_found = true; - continue; - }else if(tx.extra[i] == TX_EXTRA_NONCE) - { - //CHECK_AND_ASSERT_MES(is_coinbase(tx), false, "Failed to parse transaction extra (TX_EXTRA_NONCE can be only in coinbase) in tx " << get_transaction_hash(tx)); - CHECK_AND_ASSERT_MES(!tx_extra_extra_nonce_found, false, "Failed to parse transaction extra (duplicate TX_EXTRA_NONCE entry) in tx " << get_transaction_hash(tx)); - CHECK_AND_ASSERT_MES(tx.extra.size()-1-i >= 1, false, "Failed to parse transaction extra (TX_EXTRA_NONCE have not enough bytes) in tx " << get_transaction_hash(tx)); - ++i; - CHECK_AND_ASSERT_MES(tx.extra.size()-1-i >= tx.extra[i], false, "Failed to parse transaction extra (TX_EXTRA_NONCE have wrong bytes counter) in tx " << get_transaction_hash(tx)); - tx_extra_extra_nonce_found = true; - i += tx.extra[i];//actually don't need to extract it now, just skip - } - else if(!tx.extra[i]) - { - padding_started = true; - continue; - } - ++i; + tx_extra_field field; + bool r = ::do_serialize(ar, field); + CHECK_AND_NO_ASSERT_MES(r, false, "failed to deserialize extra field. extra = " << string_tools::buff_to_hex_nodelimer(std::string(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size()))); + tx_extra_fields.push_back(field); + + std::ios_base::iostate state = iss.rdstate(); + eof = (EOF == iss.peek()); + iss.clear(state); } + CHECK_AND_NO_ASSERT_MES(::serialization::check_stream_state(ar), false, "failed to deserialize extra field. extra = " << string_tools::buff_to_hex_nodelimer(std::string(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size()))); + return true; } //--------------------------------------------------------------- + crypto::public_key get_tx_pub_key_from_extra(const std::vector<uint8_t>& tx_extra) + { + std::vector<tx_extra_field> tx_extra_fields; + if (!parse_tx_extra(tx_extra, tx_extra_fields)) + return null_pkey; + + tx_extra_pub_key pub_key_field; + if(!find_tx_extra_field_by_type(tx_extra_fields, pub_key_field)) + return null_pkey; + + return pub_key_field.pub_key; + } + //--------------------------------------------------------------- + crypto::public_key get_tx_pub_key_from_extra(const transaction& tx) + { + return get_tx_pub_key_from_extra(tx.extra); + } + //--------------------------------------------------------------- bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key) { tx.extra.resize(tx.extra.size() + 1 + sizeof(crypto::public_key)); @@ -259,32 +262,50 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - bool add_tx_extra_nonce(transaction& tx, const blobdata& extra_nonce) + bool add_extra_nonce_to_tx_extra(std::vector<uint8_t>& tx_extra, const blobdata& extra_nonce) { - CHECK_AND_ASSERT_MES(extra_nonce.size() <=255, false, "extra nonce could be 255 bytes max"); - size_t start_pos = tx.extra.size(); - tx.extra.resize(tx.extra.size() + 2 + extra_nonce.size()); + CHECK_AND_ASSERT_MES(extra_nonce.size() <= TX_EXTRA_NONCE_MAX_COUNT, false, "extra nonce could be 255 bytes max"); + size_t start_pos = tx_extra.size(); + tx_extra.resize(tx_extra.size() + 2 + extra_nonce.size()); //write tag - tx.extra[start_pos] = TX_EXTRA_NONCE; + tx_extra[start_pos] = TX_EXTRA_NONCE; //write len ++start_pos; - tx.extra[start_pos] = static_cast<uint8_t>(extra_nonce.size()); + tx_extra[start_pos] = static_cast<uint8_t>(extra_nonce.size()); //write data ++start_pos; - memcpy(&tx.extra[start_pos], extra_nonce.data(), extra_nonce.size()); + memcpy(&tx_extra[start_pos], extra_nonce.data(), extra_nonce.size()); + return true; + } + //--------------------------------------------------------------- + void set_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash& payment_id) + { + extra_nonce.clear(); + extra_nonce.push_back(TX_EXTRA_NONCE_PAYMENT_ID); + const uint8_t* payment_id_ptr = reinterpret_cast<const uint8_t*>(&payment_id); + std::copy(payment_id_ptr, payment_id_ptr + sizeof(payment_id), std::back_inserter(extra_nonce)); + } + //--------------------------------------------------------------- + bool get_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash& payment_id) + { + if(sizeof(crypto::hash) + 1 != extra_nonce.size()) + return false; + if(TX_EXTRA_NONCE_PAYMENT_ID != extra_nonce[0]) + return false; + payment_id = *reinterpret_cast<const crypto::hash*>(extra_nonce.data() + 1); return true; } //--------------------------------------------------------------- - bool construct_tx(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, transaction& tx, uint64_t unlock_time) + bool construct_tx(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time) { tx.vin.clear(); tx.vout.clear(); tx.signatures.clear(); - tx.extra.clear(); tx.version = CURRENT_TRANSACTION_VERSION; tx.unlock_time = unlock_time; + tx.extra = extra; keypair txkey = keypair::generate(); add_tx_pub_key_to_extra(tx, txkey.pub); @@ -506,7 +527,10 @@ namespace cryptonote //--------------------------------------------------------------- bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector<size_t>& outs, uint64_t& money_transfered) { - return lookup_acc_outs(acc, tx, get_tx_pub_key_from_extra(tx), outs, money_transfered); + crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); + if(null_pkey == tx_pub_key) + return false; + return lookup_acc_outs(acc, tx, tx_pub_key, outs, money_transfered); } //--------------------------------------------------------------- bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector<size_t>& outs, uint64_t& money_transfered) @@ -594,6 +618,7 @@ namespace cryptonote //genesis block bl = boost::value_initialized<block>(); + account_public_address ac = boost::value_initialized<account_public_address>(); std::vector<size_t> sz; construct_miner_tx(0, 0, 0, 0, 0, ac, bl.miner_tx); // zero fee in genesis @@ -601,16 +626,16 @@ namespace cryptonote std::string hex_tx_represent = string_tools::buff_to_hex_nodelimer(txb); //hard code coinbase tx in genesis block, because "tru" generating tx use random, but genesis should be always the same - std::string genesis_coinbase_tx_hex = "013c01ff0001ffffffffffff03029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121017767aafcde9be00dcfd098715ebcf7f410daebc582fda69d24a28e9d0bc890d1"; + std::string genesis_coinbase_tx_hex = "010a01ff0001ffffffffffff0f029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121013c086a48c15fb637a96991bc6d53caf77068b5ba6eeb3c82357228c49790584a"; blobdata tx_bl; string_tools::parse_hexstr_to_binbuff(genesis_coinbase_tx_hex, tx_bl); bool r = parse_and_validate_tx_from_blob(tx_bl, bl.miner_tx); CHECK_AND_ASSERT_MES(r, false, "failed to parse coinbase tx from hard coded blob"); - bl.major_version = 1; - bl.minor_version = 0; + bl.major_version = CURRENT_BLOCK_MAJOR_VERSION; + bl.minor_version = CURRENT_BLOCK_MINOR_VERSION; bl.timestamp = 0; - bl.nonce = 10000; + bl.nonce = 70; miner::find_nonce_for_given_block(bl, 1, 0); return true; } diff --git a/src/cryptonote_core/cryptonote_format_utils.h b/src/cryptonote_core/cryptonote_format_utils.h index 5873d2943..138fb5224 100644 --- a/src/cryptonote_core/cryptonote_format_utils.h +++ b/src/cryptonote_core/cryptonote_format_utils.h @@ -41,11 +41,26 @@ namespace cryptonote }; //--------------------------------------------------------------- - bool construct_tx(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, transaction& tx, uint64_t unlock_time); - bool parse_and_validate_tx_extra(const transaction& tx, crypto::public_key& tx_pub_key); + bool construct_tx(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time); + + template<typename T> + bool find_tx_extra_field_by_type(const std::vector<tx_extra_field>& tx_extra_fields, T& field) + { + auto it = std::find_if(tx_extra_fields.begin(), tx_extra_fields.end(), [](const tx_extra_field& f) { return typeid(T) == f.type(); }); + if(tx_extra_fields.end() == it) + return false; + + field = boost::get<T>(*it); + return true; + } + + bool parse_tx_extra(const std::vector<uint8_t>& tx_extra, std::vector<tx_extra_field>& tx_extra_fields); + crypto::public_key get_tx_pub_key_from_extra(const std::vector<uint8_t>& tx_extra); crypto::public_key get_tx_pub_key_from_extra(const transaction& tx); bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key); - bool add_tx_extra_nonce(transaction& tx, const blobdata& extra_nonce); + bool add_extra_nonce_to_tx_extra(std::vector<uint8_t>& tx_extra, const blobdata& extra_nonce); + void set_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash& payment_id); + bool get_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash& payment_id); bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, size_t output_index); bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector<size_t>& outs, uint64_t& money_transfered); bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector<size_t>& outs, uint64_t& money_transfered); diff --git a/src/cryptonote_core/difficulty.cpp b/src/cryptonote_core/difficulty.cpp index 052f46662..3dde6ad6c 100644 --- a/src/cryptonote_core/difficulty.cpp +++ b/src/cryptonote_core/difficulty.cpp @@ -24,7 +24,7 @@ namespace cryptonote { #include <winnt.h> static inline void mul(uint64_t a, uint64_t b, uint64_t &low, uint64_t &high) { - low = UnsignedMultiply128(a, b, &high); + low = mul128(a, b, &high); } #else diff --git a/src/cryptonote_core/tx_extra.h b/src/cryptonote_core/tx_extra.h index 7e27cbc7f..8cff085dc 100644 --- a/src/cryptonote_core/tx_extra.h +++ b/src/cryptonote_core/tx_extra.h @@ -2,10 +2,93 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#pragma once +#pragma once +#define TX_EXTRA_PADDING_MAX_COUNT 255 +#define TX_EXTRA_NONCE_MAX_COUNT 255 -#define TX_EXTRA_PADDING_MAX_COUNT 40 +#define TX_EXTRA_TAG_PADDING 0x00 #define TX_EXTRA_TAG_PUBKEY 0x01 #define TX_EXTRA_NONCE 0x02 + +#define TX_EXTRA_NONCE_PAYMENT_ID 0x00 + +namespace cryptonote +{ + struct tx_extra_padding + { + size_t size; + + // load + template <template <bool> class Archive> + bool do_serialize(Archive<false>& ar) + { + // size - 1 - because of variant tag + for (size = 1; size <= TX_EXTRA_PADDING_MAX_COUNT; ++size) + { + std::ios_base::iostate state = ar.stream().rdstate(); + bool eof = EOF == ar.stream().peek(); + ar.stream().clear(state); + + if (eof) + break; + + uint8_t zero; + if (!::do_serialize(ar, zero)) + return false; + + if (0 != zero) + return false; + } + + return size <= TX_EXTRA_PADDING_MAX_COUNT; + } + + // store + template <template <bool> class Archive> + bool do_serialize(Archive<true>& ar) + { + if(TX_EXTRA_PADDING_MAX_COUNT < size) + return false; + + // i = 1 - because of variant tag + for (size_t i = 1; i < size; ++i) + { + uint8_t zero = 0; + if (!::do_serialize(ar, zero)) + return false; + } + return true; + } + }; + + struct tx_extra_pub_key + { + crypto::public_key pub_key; + + BEGIN_SERIALIZE() + FIELD(pub_key) + END_SERIALIZE() + }; + + struct tx_extra_nonce + { + std::string nonce; + + BEGIN_SERIALIZE() + FIELD(nonce) + if(TX_EXTRA_NONCE_MAX_COUNT < nonce.size()) return false; + END_SERIALIZE() + }; + + // tx_extra_field format, except tx_extra_padding and tx_extra_pub_key: + // varint tag; + // varint size; + // varint data[]; + typedef boost::variant<tx_extra_padding, tx_extra_pub_key, tx_extra_nonce> tx_extra_field; +} + +VARIANT_TAG(binary_archive, cryptonote::tx_extra_padding, TX_EXTRA_TAG_PADDING); +VARIANT_TAG(binary_archive, cryptonote::tx_extra_pub_key, TX_EXTRA_TAG_PUBKEY); +VARIANT_TAG(binary_archive, cryptonote::tx_extra_nonce, TX_EXTRA_NONCE); diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 3ac1331bd..3978dfb96 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -141,6 +141,9 @@ namespace cryptonote uint64_t operator()(const txin_to_scripthash& tx) const {return 0;} }; +#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) + friend class blockchain_storage; +#endif }; } |