// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers
//
// This file is part of Bytecoin.
//
// Bytecoin is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Bytecoin is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Bytecoin. If not, see .
#include "TestGenerator.h"
#include
#include "cryptonote_core/account.h"
#include "cryptonote_core/miner.h"
using namespace std;
using namespace CryptoNote;
#ifndef CHECK_AND_ASSERT_MES
#define CHECK_AND_ASSERT_MES(expr, fail_ret_val, message) do{if(!(expr)) {std::cerr << message << std::endl; return fail_ret_val;};}while(0)
#endif
void test_generator::getBlockchain(std::vector& blockchain, const crypto::hash& head, size_t n) const {
crypto::hash curr = head;
while (null_hash != curr && blockchain.size() < n) {
auto it = m_blocksInfo.find(curr);
if (m_blocksInfo.end() == it) {
throw std::runtime_error("block hash wasn't found");
}
blockchain.push_back(it->second);
curr = it->second.prevId;
}
std::reverse(blockchain.begin(), blockchain.end());
}
void test_generator::getLastNBlockSizes(std::vector& blockSizes, const crypto::hash& head, size_t n) const {
std::vector blockchain;
getBlockchain(blockchain, head, n);
for (auto& bi : blockchain) {
blockSizes.push_back(bi.blockSize);
}
}
uint64_t test_generator::getAlreadyGeneratedCoins(const crypto::hash& blockId) const {
auto it = m_blocksInfo.find(blockId);
if (it == m_blocksInfo.end()) {
throw std::runtime_error("block hash wasn't found");
}
return it->second.alreadyGeneratedCoins;
}
uint64_t test_generator::getAlreadyGeneratedCoins(const CryptoNote::Block& blk) const {
crypto::hash blkHash;
get_block_hash(blk, blkHash);
return getAlreadyGeneratedCoins(blkHash);
}
void test_generator::addBlock(const CryptoNote::Block& blk, size_t tsxSize, uint64_t fee,
std::vector& blockSizes, uint64_t alreadyGeneratedCoins) {
const size_t blockSize = tsxSize + get_object_blobsize(blk.minerTx);
int64_t emissionChange;
uint64_t blockReward;
bool penalizeFee = blk.majorVersion > BLOCK_MAJOR_VERSION_1;
m_currency.getBlockReward(Common::medianValue(blockSizes), blockSize, alreadyGeneratedCoins, fee, penalizeFee,
blockReward, emissionChange);
m_blocksInfo[get_block_hash(blk)] = BlockInfo(blk.prevId, alreadyGeneratedCoins + emissionChange, blockSize);
}
bool test_generator::constructBlock(CryptoNote::Block& blk, uint32_t height, const crypto::hash& prevId,
const CryptoNote::account_base& minerAcc, uint64_t timestamp, uint64_t alreadyGeneratedCoins,
std::vector& blockSizes, const std::list& txList) {
blk.majorVersion = defaultMajorVersion;
blk.minorVersion = defaultMinorVersion;
blk.timestamp = timestamp;
blk.prevId = prevId;
blk.txHashes.reserve(txList.size());
for (const Transaction &tx : txList) {
crypto::hash tx_hash;
get_transaction_hash(tx, tx_hash);
blk.txHashes.push_back(tx_hash);
}
uint64_t totalFee = 0;
size_t txsSize = 0;
for (auto& tx : txList) {
uint64_t fee = 0;
bool r = get_tx_fee(tx, fee);
CHECK_AND_ASSERT_MES(r, false, "wrong transaction passed to construct_block");
totalFee += fee;
txsSize += get_object_blobsize(tx);
}
blk.minerTx = boost::value_initialized();
size_t targetBlockSize = txsSize + get_object_blobsize(blk.minerTx);
while (true) {
if (!m_currency.constructMinerTx(height, Common::medianValue(blockSizes), alreadyGeneratedCoins, targetBlockSize,
totalFee, minerAcc.get_keys().m_account_address, blk.minerTx, blobdata(), 10)) {
return false;
}
size_t actualBlockSize = txsSize + get_object_blobsize(blk.minerTx);
if (targetBlockSize < actualBlockSize) {
targetBlockSize = actualBlockSize;
} else if (actualBlockSize < targetBlockSize) {
size_t delta = targetBlockSize - actualBlockSize;
blk.minerTx.extra.resize(blk.minerTx.extra.size() + delta, 0);
actualBlockSize = txsSize + get_object_blobsize(blk.minerTx);
if (actualBlockSize == targetBlockSize) {
break;
} else {
CHECK_AND_ASSERT_MES(targetBlockSize < actualBlockSize, false, "Unexpected block size");
delta = actualBlockSize - targetBlockSize;
blk.minerTx.extra.resize(blk.minerTx.extra.size() - delta);
actualBlockSize = txsSize + get_object_blobsize(blk.minerTx);
if (actualBlockSize == targetBlockSize) {
break;
} else {
CHECK_AND_ASSERT_MES(actualBlockSize < targetBlockSize, false, "Unexpected block size");
blk.minerTx.extra.resize(blk.minerTx.extra.size() + delta, 0);
targetBlockSize = txsSize + get_object_blobsize(blk.minerTx);
}
}
} else {
break;
}
}
if (blk.majorVersion >= BLOCK_MAJOR_VERSION_2) {
blk.parentBlock.majorVersion = BLOCK_MAJOR_VERSION_1;
blk.parentBlock.minorVersion = BLOCK_MINOR_VERSION_0;
blk.parentBlock.numberOfTransactions = 1;
CryptoNote::tx_extra_merge_mining_tag mmTag;
mmTag.depth = 0;
if (!CryptoNote::get_aux_block_header_hash(blk, mmTag.merkle_root)) {
return false;
}
blk.parentBlock.minerTx.extra.clear();
if (!CryptoNote::append_mm_tag_to_extra(blk.parentBlock.minerTx.extra, mmTag)) {
return false;
}
}
// Nonce search...
blk.nonce = 0;
crypto::cn_context context;
while (!miner::find_nonce_for_given_block(context, blk, getTestDifficulty())) {
blk.timestamp++;
}
addBlock(blk, txsSize, totalFee, blockSizes, alreadyGeneratedCoins);
return true;
}
bool test_generator::constructBlock(CryptoNote::Block& blk, const CryptoNote::account_base& minerAcc, uint64_t timestamp) {
std::vector blockSizes;
std::list txList;
return constructBlock(blk, 0, null_hash, minerAcc, timestamp, 0, blockSizes, txList);
}
bool test_generator::constructBlock(CryptoNote::Block& blk, const CryptoNote::Block& blkPrev,
const CryptoNote::account_base& minerAcc,
const std::list& txList/* = std::list()*/) {
uint32_t height = boost::get(blkPrev.minerTx.vin.front()).height + 1;
crypto::hash prevId = get_block_hash(blkPrev);
// Keep difficulty unchanged
uint64_t timestamp = blkPrev.timestamp + m_currency.difficultyTarget();
uint64_t alreadyGeneratedCoins = getAlreadyGeneratedCoins(prevId);
std::vector blockSizes;
getLastNBlockSizes(blockSizes, prevId, m_currency.rewardBlocksWindow());
return constructBlock(blk, height, prevId, minerAcc, timestamp, alreadyGeneratedCoins, blockSizes, txList);
}
bool test_generator::constructBlockManually(Block& blk, const Block& prevBlock, const account_base& minerAcc,
int actualParams/* = bf_none*/, uint8_t majorVer/* = 0*/,
uint8_t minorVer/* = 0*/, uint64_t timestamp/* = 0*/,
const crypto::hash& prevId/* = crypto::hash()*/, const difficulty_type& diffic/* = 1*/,
const Transaction& minerTx/* = transaction()*/,
const std::vector& txHashes/* = std::vector()*/,
size_t txsSizes/* = 0*/, uint64_t fee/* = 0*/) {
blk.majorVersion = actualParams & bf_major_ver ? majorVer : defaultMajorVersion;
blk.minorVersion = actualParams & bf_minor_ver ? minorVer : defaultMinorVersion;
blk.timestamp = actualParams & bf_timestamp ? timestamp : prevBlock.timestamp + m_currency.difficultyTarget(); // Keep difficulty unchanged
blk.prevId = actualParams & bf_prev_id ? prevId : get_block_hash(prevBlock);
blk.txHashes = actualParams & bf_tx_hashes ? txHashes : std::vector();
uint32_t height = get_block_height(prevBlock) + 1;
uint64_t alreadyGeneratedCoins = getAlreadyGeneratedCoins(prevBlock);
std::vector blockSizes;
getLastNBlockSizes(blockSizes, get_block_hash(prevBlock), m_currency.rewardBlocksWindow());
if (actualParams & bf_miner_tx) {
blk.minerTx = minerTx;
} else {
size_t currentBlockSize = txsSizes + get_object_blobsize(blk.minerTx);
// TODO: This will work, until size of constructed block is less then m_currency.blockGrantedFullRewardZone()
if (!m_currency.constructMinerTx(height, Common::medianValue(blockSizes), alreadyGeneratedCoins, currentBlockSize, 0,
minerAcc.get_keys().m_account_address, blk.minerTx, blobdata(), 1, blk.majorVersion > BLOCK_MAJOR_VERSION_1)) {
return false;
}
}
if (blk.majorVersion >= BLOCK_MAJOR_VERSION_2) {
blk.parentBlock.majorVersion = BLOCK_MAJOR_VERSION_1;
blk.parentBlock.minorVersion = BLOCK_MINOR_VERSION_0;
blk.parentBlock.numberOfTransactions = 1;
CryptoNote::tx_extra_merge_mining_tag mmTag;
mmTag.depth = 0;
if (!CryptoNote::get_aux_block_header_hash(blk, mmTag.merkle_root)) {
return false;
}
blk.parentBlock.minerTx.extra.clear();
if (!CryptoNote::append_mm_tag_to_extra(blk.parentBlock.minerTx.extra, mmTag)) {
return false;
}
}
difficulty_type aDiffic = actualParams & bf_diffic ? diffic : getTestDifficulty();
if (1 < aDiffic) {
fillNonce(blk, aDiffic);
}
addBlock(blk, txsSizes, fee, blockSizes, alreadyGeneratedCoins);
return true;
}
bool test_generator::constructBlockManuallyTx(CryptoNote::Block& blk, const CryptoNote::Block& prevBlock,
const CryptoNote::account_base& minerAcc,
const std::vector& txHashes, size_t txsSize) {
return constructBlockManually(blk, prevBlock, minerAcc, bf_tx_hashes, 0, 0, 0, crypto::hash(), 0, Transaction(),
txHashes, txsSize);
}
bool test_generator::constructMaxSizeBlock(CryptoNote::Block& blk, const CryptoNote::Block& blkPrev,
const CryptoNote::account_base& minerAccount,
size_t medianBlockCount/* = 0*/,
const std::list& txList/* = std::list()*/) {
std::vector blockSizes;
medianBlockCount = medianBlockCount == 0 ? m_currency.rewardBlocksWindow() : medianBlockCount;
getLastNBlockSizes(blockSizes, get_block_hash(blkPrev), medianBlockCount);
size_t median = Common::medianValue(blockSizes);
size_t blockGrantedFullRewardZone = defaultMajorVersion <= BLOCK_MAJOR_VERSION_1 ?
CryptoNote::parameters::CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1 :
m_currency.blockGrantedFullRewardZone();
median = std::max(median, blockGrantedFullRewardZone);
uint64_t totalFee = 0;
size_t txsSize = 0;
std::vector txHashes;
for (auto& tx : txList) {
uint64_t fee = 0;
bool r = get_tx_fee(tx, fee);
CHECK_AND_ASSERT_MES(r, false, "wrong transaction passed to construct_max_size_block");
totalFee += fee;
txsSize += get_object_blobsize(tx);
txHashes.push_back(get_transaction_hash(tx));
}
Transaction minerTx;
bool r = constructMinerTxBySize(m_currency, minerTx, get_block_height(blkPrev) + 1,
getAlreadyGeneratedCoins(blkPrev), minerAccount.get_keys().m_account_address, blockSizes,
2 * median - txsSize, 2 * median, totalFee, defaultMajorVersion > BLOCK_MAJOR_VERSION_1);
if (!r) {
return false;
}
return constructBlockManually(blk, blkPrev, minerAccount, test_generator::bf_miner_tx | test_generator::bf_tx_hashes,
0, 0, 0, crypto::hash(), 0, minerTx, txHashes, txsSize, totalFee);
}
void fillNonce(CryptoNote::Block& blk, const difficulty_type& diffic) {
blk.nonce = 0;
crypto::cn_context context;
while (!miner::find_nonce_for_given_block(context, blk, diffic)) {
blk.timestamp++;
}
}
bool constructMinerTxManually(const CryptoNote::Currency& currency, uint32_t height, uint64_t alreadyGeneratedCoins,
const AccountPublicAddress& minerAddress, Transaction& tx, uint64_t fee,
KeyPair* pTxKey/* = 0*/) {
KeyPair txkey;
txkey = KeyPair::generate();
add_tx_pub_key_to_extra(tx, txkey.pub);
if (0 != pTxKey) {
*pTxKey = txkey;
}
TransactionInputGenerate in;
in.height = height;
tx.vin.push_back(in);
// This will work, until size of constructed block is less then currency.blockGrantedFullRewardZone()
int64_t emissionChange;
uint64_t blockReward;
if (!currency.getBlockReward(0, 0, alreadyGeneratedCoins, fee, false, blockReward, emissionChange)) {
std::cerr << "Block is too big" << std::endl;
return false;
}
crypto::key_derivation derivation;
crypto::public_key outEphPublicKey;
crypto::generate_key_derivation(minerAddress.m_viewPublicKey, txkey.sec, derivation);
crypto::derive_public_key(derivation, 0, minerAddress.m_spendPublicKey, outEphPublicKey);
TransactionOutput out;
out.amount = blockReward;
out.target = TransactionOutputToKey(outEphPublicKey);
tx.vout.push_back(out);
tx.version = CURRENT_TRANSACTION_VERSION;
tx.unlockTime = height + currency.minedMoneyUnlockWindow();
return true;
}
bool constructMinerTxBySize(const CryptoNote::Currency& currency, CryptoNote::Transaction& minerTx, uint32_t height,
uint64_t alreadyGeneratedCoins, const CryptoNote::AccountPublicAddress& minerAddress,
std::vector& blockSizes, size_t targetTxSize, size_t targetBlockSize,
uint64_t fee/* = 0*/, bool penalizeFee/* = false*/) {
if (!currency.constructMinerTx(height, Common::medianValue(blockSizes), alreadyGeneratedCoins, targetBlockSize,
fee, minerAddress, minerTx, CryptoNote::blobdata(), 1, penalizeFee)) {
return false;
}
size_t currentSize = get_object_blobsize(minerTx);
size_t tryCount = 0;
while (targetTxSize != currentSize) {
++tryCount;
if (10 < tryCount) {
return false;
}
if (targetTxSize < currentSize) {
size_t diff = currentSize - targetTxSize;
if (diff <= minerTx.extra.size()) {
minerTx.extra.resize(minerTx.extra.size() - diff);
} else {
return false;
}
} else {
size_t diff = targetTxSize - currentSize;
minerTx.extra.resize(minerTx.extra.size() + diff);
}
currentSize = get_object_blobsize(minerTx);
}
return true;
}