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