// Copyright (c) 2012-2014, 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 "Currency.h" #include #include // epee #include "include_base_utils.h" #include "string_tools.h" #include "common/base58.h" #include "common/int-util.h" #include "cryptonote_core/account.h" #include "cryptonote_core/cryptonote_format_utils.h" #include "cryptonote_core/UpgradeDetector.h" namespace cryptonote { bool Currency::init() { bool r; r = generateGenesisBlock(); CHECK_AND_ASSERT_MES(r, false, "Failed to generate genesis block"); r = get_block_hash(m_genesisBlock, m_genesisBlockHash); CHECK_AND_ASSERT_MES(r, false, "Failed to get genesis block hash"); if (isTestnet()) { m_blocksFileName = "testnet_" + m_blocksFileName; m_blocksCacheFileName = "testnet_" + m_blocksCacheFileName; m_blockIndexesFileName = "testnet_" + m_blockIndexesFileName; m_txPoolFileName = "testnet_" + m_txPoolFileName; } return true; } bool Currency::generateGenesisBlock() { m_genesisBlock = boost::value_initialized(); //account_public_address ac = boost::value_initialized(); //std::vector sz; //constructMinerTx(0, 0, 0, 0, 0, ac, m_genesisBlock.minerTx); // zero fee in genesis //blobdata txb = tx_to_blob(m_genesisBlock.minerTx); //std::string hex_tx_represent = string_tools::buff_to_hex_nodelimer(txb); // Hard code coinbase tx in genesis block, because through generating tx use random, but genesis should be always the same std::string genesisCoinbaseTxHex = "010a01ff0001ffffffffffff0f029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121013c086a48c15fb637a96991bc6d53caf77068b5ba6eeb3c82357228c49790584a"; blobdata minerTxBlob; epee::string_tools::parse_hexstr_to_binbuff(genesisCoinbaseTxHex, minerTxBlob); bool r = parse_and_validate_tx_from_blob(minerTxBlob, m_genesisBlock.minerTx); CHECK_AND_ASSERT_MES(r, false, "failed to parse coinbase tx from hard coded blob"); m_genesisBlock.majorVersion = BLOCK_MAJOR_VERSION_1; m_genesisBlock.minorVersion = BLOCK_MINOR_VERSION_0; m_genesisBlock.timestamp = 0; m_genesisBlock.nonce = 70; if (m_testnet) { ++m_genesisBlock.nonce; } //miner::find_nonce_for_given_block(bl, 1, 0); return true; } bool Currency::getBlockReward(size_t medianSize, size_t currentBlockSize, uint64_t alreadyGeneratedCoins, uint64_t fee, bool penalizeFee, uint64_t& reward, int64_t& emissionChange) const { assert(alreadyGeneratedCoins <= m_moneySupply); assert(m_emissionSpeedFactor > 0 && m_emissionSpeedFactor <= 8 * sizeof(uint64_t)); uint64_t baseReward = (m_moneySupply - alreadyGeneratedCoins) >> m_emissionSpeedFactor; size_t blockGrantedFullRewardZone = penalizeFee ? m_blockGrantedFullRewardZone : cryptonote::parameters::CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1; medianSize = std::max(medianSize, blockGrantedFullRewardZone); if (currentBlockSize > UINT64_C(2) * medianSize) { LOG_PRINT_L4("Block cumulative size is too big: " << currentBlockSize << ", expected less than " << 2 * medianSize); return false; } uint64_t penalizedBaseReward = getPenalizedAmount(baseReward, medianSize, currentBlockSize); uint64_t penalizedFee = penalizeFee ? getPenalizedAmount(fee, medianSize, currentBlockSize) : fee; emissionChange = penalizedBaseReward - (fee - penalizedFee); reward = penalizedBaseReward + penalizedFee; return true; } size_t Currency::maxBlockCumulativeSize(uint64_t height) const { assert(height <= std::numeric_limits::max() / m_maxBlockSizeGrowthSpeedNumerator); size_t maxSize = static_cast(m_maxBlockSizeInitial + (height * m_maxBlockSizeGrowthSpeedNumerator) / m_maxBlockSizeGrowthSpeedDenominator); assert(maxSize >= m_maxBlockSizeInitial); return maxSize; } bool Currency::constructMinerTx(size_t height, size_t medianSize, uint64_t alreadyGeneratedCoins, size_t currentBlockSize, uint64_t fee, const AccountPublicAddress& minerAddress, Transaction& tx, const blobdata& extraNonce/* = blobdata()*/, size_t maxOuts/* = 1*/, bool penalizeFee/* = false*/) const { tx.vin.clear(); tx.vout.clear(); tx.extra.clear(); KeyPair txkey = KeyPair::generate(); add_tx_pub_key_to_extra(tx, txkey.pub); if (!extraNonce.empty()) { if (!add_extra_nonce_to_tx_extra(tx.extra, extraNonce)) { return false; } } TransactionInputGenerate in; in.height = height; uint64_t blockReward; int64_t emissionChange; if (!getBlockReward(medianSize, currentBlockSize, alreadyGeneratedCoins, fee, penalizeFee, blockReward, emissionChange)) { LOG_PRINT_L0("Block is too big"); return false; } #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) LOG_PRINT_L1("Creating block template: reward " << blockReward << ", fee " << fee); #endif std::vector outAmounts; decompose_amount_into_digits(blockReward, m_defaultDustThreshold, [&outAmounts](uint64_t a_chunk) { outAmounts.push_back(a_chunk); }, [&outAmounts](uint64_t a_dust) { outAmounts.push_back(a_dust); }); CHECK_AND_ASSERT_MES(1 <= maxOuts, false, "max_out must be non-zero"); while (maxOuts < outAmounts.size()) { outAmounts[outAmounts.size() - 2] += outAmounts.back(); outAmounts.resize(outAmounts.size() - 1); } uint64_t summaryAmounts = 0; for (size_t no = 0; no < outAmounts.size(); no++) { crypto::key_derivation derivation = boost::value_initialized(); crypto::public_key outEphemeralPubKey = boost::value_initialized(); bool r = crypto::generate_key_derivation(minerAddress.m_viewPublicKey, txkey.sec, derivation); CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to generate_key_derivation(" << minerAddress.m_viewPublicKey << ", " << txkey.sec << ")"); r = crypto::derive_public_key(derivation, no, minerAddress.m_spendPublicKey, outEphemeralPubKey); CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to derive_public_key(" << derivation << ", " << no << ", "<< minerAddress.m_spendPublicKey << ")"); TransactionOutputToKey tk; tk.key = outEphemeralPubKey; TransactionOutput out; summaryAmounts += out.amount = outAmounts[no]; out.target = tk; tx.vout.push_back(out); } CHECK_AND_ASSERT_MES(summaryAmounts == blockReward, false, "Failed to construct miner tx, summaryAmounts = " << summaryAmounts << " not equal blockReward = " << blockReward); tx.version = CURRENT_TRANSACTION_VERSION; //lock tx.unlockTime = height + m_minedMoneyUnlockWindow; tx.vin.push_back(in); return true; } std::string Currency::accountAddressAsString(const account_base& account) const { return getAccountAddressAsStr(m_publicAddressBase58Prefix, account.get_keys().m_account_address); } bool Currency::parseAccountAddressString(const std::string& str, AccountPublicAddress& addr) const { uint64_t prefix; if (!cryptonote::parseAccountAddressString(prefix, addr, str)) { return false; } if (prefix != m_publicAddressBase58Prefix) { LOG_PRINT_L1("Wrong address prefix: " << prefix << ", expected " << m_publicAddressBase58Prefix); return false; } return true; } std::string Currency::formatAmount(uint64_t amount) const { std::string s = std::to_string(amount); if (s.size() < m_numberOfDecimalPlaces + 1) { s.insert(0, m_numberOfDecimalPlaces + 1 - s.size(), '0'); } s.insert(s.size() - m_numberOfDecimalPlaces, "."); return s; } bool Currency::parseAmount(const std::string& str, uint64_t& amount) const { std::string strAmount = str; boost::algorithm::trim(strAmount); size_t pointIndex = strAmount.find_first_of('.'); size_t fractionSize; if (std::string::npos != pointIndex) { fractionSize = strAmount.size() - pointIndex - 1; while (m_numberOfDecimalPlaces < fractionSize && '0' == strAmount.back()) { strAmount.erase(strAmount.size() - 1, 1); --fractionSize; } if (m_numberOfDecimalPlaces < fractionSize) { return false; } strAmount.erase(pointIndex, 1); } else { fractionSize = 0; } if (strAmount.empty()) { return false; } if (fractionSize < m_numberOfDecimalPlaces) { strAmount.append(m_numberOfDecimalPlaces - fractionSize, '0'); } return epee::string_tools::get_xtype_from_string(amount, strAmount); } difficulty_type Currency::nextDifficulty(std::vector timestamps, std::vector cumulativeDifficulties) const { assert(m_difficultyWindow >= 2); if (timestamps.size() > m_difficultyWindow) { timestamps.resize(m_difficultyWindow); cumulativeDifficulties.resize(m_difficultyWindow); } size_t length = timestamps.size(); assert(length == cumulativeDifficulties.size()); assert(length <= m_difficultyWindow); if (length <= 1) { return 1; } sort(timestamps.begin(), timestamps.end()); size_t cutBegin, cutEnd; assert(2 * m_difficultyCut <= m_difficultyWindow - 2); if (length <= m_difficultyWindow - 2 * m_difficultyCut) { cutBegin = 0; cutEnd = length; } else { cutBegin = (length - (m_difficultyWindow - 2 * m_difficultyCut) + 1) / 2; cutEnd = cutBegin + (m_difficultyWindow - 2 * m_difficultyCut); } assert(/*cut_begin >= 0 &&*/ cutBegin + 2 <= cutEnd && cutEnd <= length); uint64_t timeSpan = timestamps[cutEnd - 1] - timestamps[cutBegin]; if (timeSpan == 0) { timeSpan = 1; } difficulty_type totalWork = cumulativeDifficulties[cutEnd - 1] - cumulativeDifficulties[cutBegin]; assert(totalWork > 0); uint64_t low, high; low = mul128(totalWork, m_difficultyTarget, &high); if (high != 0 || low + timeSpan - 1 < low) { return 0; } return (low + timeSpan - 1) / timeSpan; } bool Currency::checkProofOfWorkV1(crypto::cn_context& context, const Block& block, difficulty_type currentDiffic, crypto::hash& proofOfWork) const { if (BLOCK_MAJOR_VERSION_1 != block.majorVersion) { return false; } if (!get_block_longhash(context, block, proofOfWork)) { return false; } return check_hash(proofOfWork, currentDiffic); } bool Currency::checkProofOfWorkV2(crypto::cn_context& context, const Block& block, difficulty_type currentDiffic, crypto::hash& proofOfWork) const { if (BLOCK_MAJOR_VERSION_2 != block.majorVersion) { return false; } if (!get_block_longhash(context, block, proofOfWork)) { return false; } if (!check_hash(proofOfWork, currentDiffic)) { return false; } tx_extra_merge_mining_tag mmTag; if (!get_mm_tag_from_extra(block.parentBlock.minerTx.extra, mmTag)) { LOG_ERROR("merge mining tag wasn't found in extra of the parent block miner transaction"); return false; } if (8 * sizeof(m_genesisBlockHash) < block.parentBlock.blockchainBranch.size()) { return false; } crypto::hash auxBlockHeaderHash; if (!get_aux_block_header_hash(block, auxBlockHeaderHash)) { return false; } crypto::hash auxBlocksMerkleRoot; crypto::tree_hash_from_branch(block.parentBlock.blockchainBranch.data(), block.parentBlock.blockchainBranch.size(), auxBlockHeaderHash, &m_genesisBlockHash, auxBlocksMerkleRoot); CHECK_AND_NO_ASSERT_MES(auxBlocksMerkleRoot == mmTag.merkle_root, false, "Aux block hash wasn't found in merkle tree"); return true; } bool Currency::checkProofOfWork(crypto::cn_context& context, const Block& block, difficulty_type currentDiffic, crypto::hash& proofOfWork) const { switch (block.majorVersion) { case BLOCK_MAJOR_VERSION_1: return checkProofOfWorkV1(context, block, currentDiffic, proofOfWork); case BLOCK_MAJOR_VERSION_2: return checkProofOfWorkV2(context, block, currentDiffic, proofOfWork); } CHECK_AND_ASSERT_MES(false, false, "Unknown block major version: " << block.majorVersion << "." << block.minorVersion); } CurrencyBuilder::CurrencyBuilder() { maxBlockNumber(parameters::CRYPTONOTE_MAX_BLOCK_NUMBER); maxBlockBlobSize(parameters::CRYPTONOTE_MAX_BLOCK_BLOB_SIZE); maxTxSize(parameters::CRYPTONOTE_MAX_TX_SIZE); publicAddressBase58Prefix(parameters::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX); minedMoneyUnlockWindow(parameters::CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW); timestampCheckWindow(parameters::BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW); blockFutureTimeLimit(parameters::CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT); moneySupply(parameters::MONEY_SUPPLY); emissionSpeedFactor(parameters::EMISSION_SPEED_FACTOR); rewardBlocksWindow(parameters::CRYPTONOTE_REWARD_BLOCKS_WINDOW); blockGrantedFullRewardZone(parameters::CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE); minerTxBlobReservedSize(parameters::CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE); numberOfDecimalPlaces(parameters::CRYPTONOTE_DISPLAY_DECIMAL_POINT); mininumFee(parameters::MINIMUM_FEE); defaultDustThreshold(parameters::DEFAULT_DUST_THRESHOLD); difficultyTarget(parameters::DIFFICULTY_TARGET); difficultyWindow(parameters::DIFFICULTY_WINDOW); difficultyLag(parameters::DIFFICULTY_LAG); difficultyCut(parameters::DIFFICULTY_CUT); maxBlockSizeInitial(parameters::MAX_BLOCK_SIZE_INITIAL); maxBlockSizeGrowthSpeedNumerator(parameters::MAX_BLOCK_SIZE_GROWTH_SPEED_NUMERATOR); maxBlockSizeGrowthSpeedDenominator(parameters::MAX_BLOCK_SIZE_GROWTH_SPEED_DENOMINATOR); lockedTxAllowedDeltaSeconds(parameters::CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS); lockedTxAllowedDeltaBlocks(parameters::CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS); mempoolTxLiveTime(parameters::CRYPTONOTE_MEMPOOL_TX_LIVETIME); mempoolTxFromAltBlockLiveTime(parameters::CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME); upgradeHeight(UpgradeDetectorBase::UNDEF_HEIGHT); upgradeVotingThreshold(parameters::UPGRADE_VOTING_THRESHOLD); upgradeVotingWindow(parameters::UPGRADE_VOTING_WINDOW); upgradeWindow(parameters::UPGRADE_WINDOW); blocksFileName(parameters::CRYPTONOTE_BLOCKS_FILENAME); blocksCacheFileName(parameters::CRYPTONOTE_BLOCKSCACHE_FILENAME); blockIndexesFileName(parameters::CRYPTONOTE_BLOCKINDEXES_FILENAME); txPoolFileName(parameters::CRYPTONOTE_POOLDATA_FILENAME); testnet(false); } CurrencyBuilder& CurrencyBuilder::emissionSpeedFactor(unsigned int val) { if (val <= 0 || val > 8 * sizeof(uint64_t)) { throw std::invalid_argument("val at emissionSpeedFactor()"); } m_currency.m_emissionSpeedFactor = val; return *this; } CurrencyBuilder& CurrencyBuilder::numberOfDecimalPlaces(size_t val) { m_currency.m_numberOfDecimalPlaces = val; m_currency.m_coin = 1; for (size_t i = 0; i < m_currency.m_numberOfDecimalPlaces; ++i) { m_currency.m_coin *= 10; } return *this; } CurrencyBuilder& CurrencyBuilder::difficultyWindow(size_t val) { if (val < 2) { throw std::invalid_argument("val at difficultyWindow()"); } m_currency.m_difficultyWindow = val; return *this; } CurrencyBuilder& CurrencyBuilder::upgradeVotingThreshold(unsigned int val) { if (val <= 0 || val > 100) { throw std::invalid_argument("val at upgradeVotingThreshold()"); } m_currency.m_upgradeVotingThreshold = val; return *this; } CurrencyBuilder& CurrencyBuilder::upgradeWindow(size_t val) { if (val <= 0) { throw std::invalid_argument("val at upgradeWindow()"); } m_currency.m_upgradeWindow = val; return *this; } }