// Copyright (c) 2011-2016 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "BlockchainExplorerDataBuilder.h" #include #include #include "Common/StringTools.h" #include "CryptoNoteCore/CryptoNoteFormatUtils.h" #include "CryptoNoteCore/CryptoNoteTools.h" #include "CryptoNoteCore/TransactionExtra.h" #include "CryptoNoteConfig.h" namespace CryptoNote { BlockchainExplorerDataBuilder::BlockchainExplorerDataBuilder(CryptoNote::ICore& core, CryptoNote::ICryptoNoteProtocolQuery& protocol) : core(core), protocol(protocol) { } bool BlockchainExplorerDataBuilder::getMixin(const Transaction& transaction, uint64_t& mixin) { mixin = 0; for (const TransactionInput& txin : transaction.inputs) { if (txin.type() != typeid(KeyInput)) { continue; } uint64_t currentMixin = boost::get(txin).outputIndexes.size(); if (currentMixin > mixin) { mixin = currentMixin; } } return true; } bool BlockchainExplorerDataBuilder::getPaymentId(const Transaction& transaction, Crypto::Hash& paymentId) { std::vector txExtraFields; parseTransactionExtra(transaction.extra, txExtraFields); TransactionExtraNonce extraNonce; if (!findTransactionExtraFieldByType(txExtraFields, extraNonce)) { return false; } return getPaymentIdFromTransactionExtraNonce(extraNonce.nonce, paymentId); } bool BlockchainExplorerDataBuilder::fillTxExtra(const std::vector& rawExtra, TransactionExtraDetails& extraDetails) { extraDetails.raw = rawExtra; std::vector txExtraFields; parseTransactionExtra(rawExtra, txExtraFields); for (const TransactionExtraField& field : txExtraFields) { if (typeid(TransactionExtraPadding) == field.type()) { extraDetails.padding.push_back(std::move(boost::get(field).size)); } else if (typeid(TransactionExtraPublicKey) == field.type()) { extraDetails.publicKey.push_back(std::move(boost::get(field).publicKey)); } else if (typeid(TransactionExtraNonce) == field.type()) { extraDetails.nonce.push_back(Common::toHex(boost::get(field).nonce.data(), boost::get(field).nonce.size())); } } return true; } size_t BlockchainExplorerDataBuilder::median(std::vector& v) { if (v.empty()) return boost::value_initialized(); if (v.size() == 1) return v[0]; size_t n = (v.size()) / 2; std::sort(v.begin(), v.end()); //nth_element(v.begin(), v.begin()+n-1, v.end()); if (v.size() % 2) {//1, 3, 5... return v[n]; } else {//2, 4, 6... return (v[n - 1] + v[n]) / 2; } } bool BlockchainExplorerDataBuilder::fillBlockDetails(const Block &block, BlockDetails& blockDetails) { Crypto::Hash hash = get_block_hash(block); blockDetails.majorVersion = block.majorVersion; blockDetails.minorVersion = block.minorVersion; blockDetails.timestamp = block.timestamp; blockDetails.prevBlockHash = block.previousBlockHash; blockDetails.nonce = block.nonce; blockDetails.hash = hash; blockDetails.reward = 0; for (const TransactionOutput& out : block.baseTransaction.outputs) { blockDetails.reward += out.amount; } if (block.baseTransaction.inputs.front().type() != typeid(BaseInput)) return false; blockDetails.height = boost::get(block.baseTransaction.inputs.front()).blockIndex; Crypto::Hash tmpHash = core.getBlockIdByHeight(blockDetails.height); blockDetails.isOrphaned = hash != tmpHash; if (!core.getBlockDifficulty(blockDetails.height, blockDetails.difficulty)) { return false; } std::vector blocksSizes; if (!core.getBackwardBlocksSizes(blockDetails.height, blocksSizes, parameters::CRYPTONOTE_REWARD_BLOCKS_WINDOW)) { return false; } blockDetails.sizeMedian = median(blocksSizes); size_t blockSize = 0; if (!core.getBlockSize(hash, blockSize)) { return false; } blockDetails.transactionsCumulativeSize = blockSize; size_t blokBlobSize = getObjectBinarySize(block); size_t minerTxBlobSize = getObjectBinarySize(block.baseTransaction); blockDetails.blockSize = blokBlobSize + blockDetails.transactionsCumulativeSize - minerTxBlobSize; if (!core.getAlreadyGeneratedCoins(hash, blockDetails.alreadyGeneratedCoins)) { return false; } if (!core.getGeneratedTransactionsNumber(blockDetails.height, blockDetails.alreadyGeneratedTransactions)) { return false; } uint64_t prevBlockGeneratedCoins = 0; if (blockDetails.height > 0) { if (!core.getAlreadyGeneratedCoins(block.previousBlockHash, prevBlockGeneratedCoins)) { return false; } } uint64_t maxReward = 0; uint64_t currentReward = 0; int64_t emissionChange = 0; if (!core.getBlockReward(blockDetails.sizeMedian, 0, prevBlockGeneratedCoins, 0, maxReward, emissionChange)) { return false; } if (!core.getBlockReward(blockDetails.sizeMedian, blockDetails.transactionsCumulativeSize, prevBlockGeneratedCoins, 0, currentReward, emissionChange)) { return false; } blockDetails.baseReward = maxReward; if (maxReward == 0 && currentReward == 0) { blockDetails.penalty = static_cast(0); } else { if (maxReward < currentReward) { return false; } blockDetails.penalty = static_cast(maxReward - currentReward) / static_cast(maxReward); } blockDetails.transactions.reserve(block.transactionHashes.size() + 1); TransactionDetails transactionDetails; if (!fillTransactionDetails(block.baseTransaction, transactionDetails, block.timestamp)) { return false; } blockDetails.transactions.push_back(std::move(transactionDetails)); std::list found; std::list missed; core.getTransactions(block.transactionHashes, found, missed, blockDetails.isOrphaned); if (found.size() != block.transactionHashes.size()) { return false; } blockDetails.totalFeeAmount = 0; for (const Transaction& tx : found) { TransactionDetails transactionDetails; if (!fillTransactionDetails(tx, transactionDetails, block.timestamp)) { return false; } blockDetails.transactions.push_back(std::move(transactionDetails)); blockDetails.totalFeeAmount += transactionDetails.fee; } return true; } bool BlockchainExplorerDataBuilder::fillTransactionDetails(const Transaction& transaction, TransactionDetails& transactionDetails, uint64_t timestamp) { Crypto::Hash hash = getObjectHash(transaction); transactionDetails.hash = hash; transactionDetails.timestamp = timestamp; Crypto::Hash blockHash; uint32_t blockHeight; if (!core.getBlockContainingTx(hash, blockHash, blockHeight)) { transactionDetails.inBlockchain = false; transactionDetails.blockHeight = boost::value_initialized(); transactionDetails.blockHash = boost::value_initialized(); } else { transactionDetails.inBlockchain = true; transactionDetails.blockHeight = blockHeight; transactionDetails.blockHash = blockHash; if (timestamp == 0) { Block block; if (!core.getBlockByHash(blockHash, block)) { return false; } transactionDetails.timestamp = block.timestamp; } } transactionDetails.size = getObjectBinarySize(transaction); transactionDetails.unlockTime = transaction.unlockTime; transactionDetails.totalOutputsAmount = get_outs_money_amount(transaction); uint64_t inputsAmount; if (!get_inputs_money_amount(transaction, inputsAmount)) { return false; } transactionDetails.totalInputsAmount = inputsAmount; if (transaction.inputs.size() > 0 && transaction.inputs.front().type() == typeid(BaseInput)) { //It's gen transaction transactionDetails.fee = 0; transactionDetails.mixin = 0; } else { uint64_t fee; if (!get_tx_fee(transaction, fee)) { return false; } transactionDetails.fee = fee; uint64_t mixin; if (!getMixin(transaction, mixin)) { return false; } transactionDetails.mixin = mixin; } Crypto::Hash paymentId; if (getPaymentId(transaction, paymentId)) { transactionDetails.paymentId = paymentId; } else { transactionDetails.paymentId = boost::value_initialized(); } fillTxExtra(transaction.extra, transactionDetails.extra); transactionDetails.signatures.reserve(transaction.signatures.size()); for (const std::vector& signatures : transaction.signatures) { std::vector signaturesDetails; signaturesDetails.reserve(signatures.size()); for (const Crypto::Signature& signature : signatures) { signaturesDetails.push_back(std::move(signature)); } transactionDetails.signatures.push_back(std::move(signaturesDetails)); } transactionDetails.inputs.reserve(transaction.inputs.size()); for (const TransactionInput& txIn : transaction.inputs) { TransactionInputDetails txInDetails; if (txIn.type() == typeid(BaseInput)) { TransactionInputGenerateDetails txInGenDetails; txInGenDetails.height = boost::get(txIn).blockIndex; txInDetails.amount = 0; for (const TransactionOutput& out : transaction.outputs) { txInDetails.amount += out.amount; } txInDetails.input = txInGenDetails; } else if (txIn.type() == typeid(KeyInput)) { TransactionInputToKeyDetails txInToKeyDetails; const KeyInput& txInToKey = boost::get(txIn); std::list> outputReferences; if (!core.scanOutputkeysForIndices(txInToKey, outputReferences)) { return false; } txInDetails.amount = txInToKey.amount; txInToKeyDetails.outputIndexes = txInToKey.outputIndexes; txInToKeyDetails.keyImage = txInToKey.keyImage; txInToKeyDetails.mixin = txInToKey.outputIndexes.size(); txInToKeyDetails.output.number = outputReferences.back().second; txInToKeyDetails.output.transactionHash = outputReferences.back().first; txInDetails.input = txInToKeyDetails; } else if (txIn.type() == typeid(MultisignatureInput)) { TransactionInputMultisignatureDetails txInMultisigDetails; const MultisignatureInput& txInMultisig = boost::get(txIn); txInDetails.amount = txInMultisig.amount; txInMultisigDetails.signatures = txInMultisig.signatureCount; std::pair outputReference; if (!core.getMultisigOutputReference(txInMultisig, outputReference)) { return false; } txInMultisigDetails.output.number = outputReference.second; txInMultisigDetails.output.transactionHash = outputReference.first; txInDetails.input = txInMultisigDetails; } else { return false; } transactionDetails.inputs.push_back(std::move(txInDetails)); } transactionDetails.outputs.reserve(transaction.outputs.size()); std::vector globalIndices; globalIndices.reserve(transaction.outputs.size()); if (!transactionDetails.inBlockchain || !core.get_tx_outputs_gindexs(hash, globalIndices)) { for (size_t i = 0; i < transaction.outputs.size(); ++i) { globalIndices.push_back(0); } } typedef boost::tuple outputWithIndex; auto range = boost::combine(transaction.outputs, globalIndices); for (const outputWithIndex& txOutput : range) { TransactionOutputDetails txOutDetails; txOutDetails.amount = txOutput.get<0>().amount; txOutDetails.globalIndex = txOutput.get<1>(); if (txOutput.get<0>().target.type() == typeid(KeyOutput)) { TransactionOutputToKeyDetails txOutToKeyDetails; txOutToKeyDetails.txOutKey = boost::get(txOutput.get<0>().target).key; txOutDetails.output = txOutToKeyDetails; } else if (txOutput.get<0>().target.type() == typeid(MultisignatureOutput)) { TransactionOutputMultisignatureDetails txOutMultisigDetails; MultisignatureOutput txOutMultisig = boost::get(txOutput.get<0>().target); txOutMultisigDetails.keys.reserve(txOutMultisig.keys.size()); for (const Crypto::PublicKey& key : txOutMultisig.keys) { txOutMultisigDetails.keys.push_back(std::move(key)); } txOutMultisigDetails.requiredSignatures = txOutMultisig.requiredSignatureCount; txOutDetails.output = txOutMultisigDetails; } else { return false; } transactionDetails.outputs.push_back(std::move(txOutDetails)); } return true; } }