diff --git a/src/Rpc/CoreRpcServerCommandsDefinitions.h b/src/Rpc/CoreRpcServerCommandsDefinitions.h index 2c95c6481b..36221d8682 100755 --- a/src/Rpc/CoreRpcServerCommandsDefinitions.h +++ b/src/Rpc/CoreRpcServerCommandsDefinitions.h @@ -302,13 +302,13 @@ struct COMMAND_RPC_START_MINING { } }; }; - +//----------------------------------------------- struct COMMAND_HTTP { typedef EMPTY_STRUCT request; typedef std::string response; }; - +//----------------------------------------------- struct COMMAND_EXPLORER { struct request { uint32_t height = 0; @@ -333,6 +333,17 @@ struct COMMAND_EXPLORER_GET_BLOCK_DETAILS_BY_HASH { typedef std::string response; }; +struct COMMAND_EXPLORER_GET_TRANSACTION_DETAILS_BY_HASH { + struct request { + std::string hash; + + void serialize(ISerializer& s) { + KV_MEMBER(hash); + } + }; + + typedef std::string response; +}; //----------------------------------------------- struct COMMAND_RPC_GET_INFO { typedef EMPTY_STRUCT request; diff --git a/src/Rpc/RpcServer.cpp b/src/Rpc/RpcServer.cpp index 0758ea9bf0..75366f103a 100755 --- a/src/Rpc/RpcServer.cpp +++ b/src/Rpc/RpcServer.cpp @@ -493,14 +493,26 @@ void RpcServer::processRequest(const httplib::Request& request, httplib::Respons response.status = 500; response.set_content("Internal error", "text/html"); } - return; - return; } if (Common::starts_with(url, tx_method)) { + std::string hash_str = url.substr(tx_method.size()); + + COMMAND_EXPLORER_GET_TRANSACTION_DETAILS_BY_HASH::request req; + req.hash = hash_str; + COMMAND_EXPLORER_GET_TRANSACTION_DETAILS_BY_HASH::response rsp; + bool r = on_get_explorer_tx_by_hash(req, rsp); + if (r) { + response.status = 200; + response.set_content(rsp, "text/html"); + } + else { + response.status = 500; + response.set_content("Internal error", "text/html"); + } return; } @@ -1692,6 +1704,195 @@ bool RpcServer::on_get_explorer_block_by_hash(const COMMAND_EXPLORER_GET_BLOCK_D return true; } +bool RpcServer::on_get_explorer_tx_by_hash(const COMMAND_EXPLORER_GET_TRANSACTION_DETAILS_BY_HASH::request& req, COMMAND_EXPLORER_GET_TRANSACTION_DETAILS_BY_HASH::response& res) { + try { + std::list missed_txs; + std::list txs; + std::vector hashes; + Crypto::Hash tx_hash; + if (!parse_hash256(req.hash, tx_hash)) { + throw JsonRpc::JsonRpcError{ + CORE_RPC_ERROR_CODE_WRONG_PARAM, + "Failed to parse hex representation of transaction hash. Hex = " + req.hash + '.' }; + } + hashes.push_back(tx_hash); + m_core.getTransactions(hashes, txs, missed_txs, true); + + if (txs.empty() || !missed_txs.empty()) { + std::string hash_str = Common::podToHex(missed_txs.back()); + throw JsonRpc::JsonRpcError{ CORE_RPC_ERROR_CODE_WRONG_PARAM, + "transaction wasn't found. Hash = " + hash_str + '.' }; + } + + TransactionDetails transactionsDetails; + if (!blockchainExplorerDataBuilder.fillTransactionDetails(txs.back(), transactionsDetails)) { + throw JsonRpc::JsonRpcError{ CORE_RPC_ERROR_CODE_INTERNAL_ERROR, + "Internal error: can't fill transaction details." }; + } + + std::string body = index_start + (m_core.currency().isTestnet() ? "testnet" : "mainnet") + "\n

"; + + body += "

Transaction " + Common::podToHex(transactionsDetails.hash) + "

\n"; + + body += "
    \n"; + if (transactionsDetails.inBlockchain) { + body += "
  • \n"; + body += " In block: "; + body += ""; + body += std::to_string(transactionsDetails.blockHeight) + Common::podToHex(transactionsDetails.blockHash); + body += " \n"; + body += "
  • \n"; + body += "
  • \n"; + time_t rawtime = (const time_t)transactionsDetails.timestamp; + struct tm* timeinfo; + timeinfo = localtime(&rawtime); + body += " First confirmation time: "; + body += asctime(timeinfo); + body += "
  • \n"; + } + else { + body += "
  • \n"; + body += " Unconfirmed\n"; + body += "
  • \n"; + } + body += "
  • \n"; + body += " Sum of outputs: " + m_core.currency().formatAmount(transactionsDetails.totalOutputsAmount) + "\n"; + body += "
  • \n"; + body += "
  • \n"; + body += " Size: " + std::to_string(transactionsDetails.size) + "\n"; + body += "
  • \n"; + body += "
  • \n"; + body += " Unlock time: " + std::to_string(transactionsDetails.unlockTime) + "\n"; + body += "
  • \n"; + body += "
  • \n"; + body += " Version: " + std::to_string(transactionsDetails.version) + "\n"; + body += "
  • \n"; + body += "
  • \n"; + body += " Mixin count: " + std::to_string(transactionsDetails.mixin) + "\n"; + body += "
  • \n"; + body += "
  • \n"; + body += " Public Key: " + Common::podToHex(transactionsDetails.extra.publicKey) + "\n"; + body += "
  • \n"; + if (transactionsDetails.hasPaymentId) { + body += "
  • \n"; + body += " Payment ID: " + Common::podToHex(transactionsDetails.paymentId) + "\n"; + body += "
  • \n"; + } + body += "
\n"; + + body += "

Inputs

\n"; + + body += "\n"; + body += " \n"; + body += " \n"; + body += " \n"; + body += " \n"; + body += "\n"; + body += "\n"; + for (const auto& in : transactionsDetails.inputs) { + body += " \n"; + body += " \n \n"; + } + else if (in.type() == typeid(KeyInputDetails)) { + KeyInputDetails k = boost::get(in); + body += m_core.currency().formatAmount(k.input.amount); + body += "\n \n \n"; + } + else if (in.type() == typeid(MultisignatureInputDetails)) { + MultisignatureInputDetails m = boost::get(in); + body += m_core.currency().formatAmount(m.input.amount); + body += "\n \n "; + body += "output index: " + std::to_string(m.input.outputIndex) + ", "; + body += "signature count: " + std::to_string(m.input.signatureCount) + ", "; + body += "output number: " + std::to_string(m.output.number) + ", "; + body += "output tx hash: " + Common::podToHex(m.output.transactionHash); + body += " \n"; + } + body += " \n"; + } + body += "\n"; + body += "
AmountKey ImageOutput Indexes
"; + if (in.type() == typeid(BaseInputDetails)) { + BaseInputDetails c = boost::get(in); + body += m_core.currency().formatAmount(c.amount); + body += "coinbase"; + body += Common::podToHex(k.input.keyImage); + body += ""; + for (const auto& oi : k.input.outputIndexes) { + body += std::to_string(oi) + ", "; + } + body.pop_back(); + body.pop_back(); + body += " multisig
\n"; + + body += "

Outputs

\n"; + + body += "\n"; + body += " \n"; + body += " \n"; + body += " \n"; + body += " \n"; + body += "\n"; + body += "\n"; + for (const auto& o : transactionsDetails.outputs) { + body += " \n"; + body += " \n \n \n"; + body += " \n"; + } + body += "\n"; + body += "
AmountPublic Key (stealth address)Global Index
"; + body += m_core.currency().formatAmount(o.output.amount); + body += ""; + if (o.output.target.type() == typeid(KeyOutput)) { + KeyOutput ko = boost::get(o.output.target); + body += Common::podToHex(ko); + } + else if (o.output.target.type() == typeid(MultisignatureOutput)) { + body += "multisig\n"; + MultisignatureOutput mo = boost::get(o.output.target); + body += "keys: \n"; + for (const auto& k : mo.keys) { + body += Common::podToHex(k) + "\n"; + } + body += "required signature count: "; + body += std::to_string(mo.requiredSignatureCount); + } + body += ""; + body += std::to_string(o.globalIndex); + body += "
\n"; + + body += "

Signatures

\n"; + + body += "
    \n"; + for (const auto& s0 : transactionsDetails.signatures) { + body += "
  1. \n"; + body += "
      \n"; + for (const auto& s1 : s0) { + body += "
    1. \n"; + body += " " + Common::podToHex(s1) + "\n"; + body += "
    2. \n"; + } + body += "
    \n"; + body += "
  2. \n"; + } + body += "
\n"; + + body += index_finish; + + res = body; + } + catch (std::system_error& e) { + throw JsonRpc::JsonRpcError{ CORE_RPC_ERROR_CODE_INTERNAL_ERROR, e.what() }; + return false; + } + catch (std::exception& e) { + throw JsonRpc::JsonRpcError{ CORE_RPC_ERROR_CODE_INTERNAL_ERROR, "Error: " + std::string(e.what()) }; + return false; + } + + return true; +} + // // JSON handlers diff --git a/src/Rpc/RpcServer.h b/src/Rpc/RpcServer.h index 36a05c32e5..df384308fd 100755 --- a/src/Rpc/RpcServer.h +++ b/src/Rpc/RpcServer.h @@ -92,6 +92,7 @@ class RpcServer { // explorer bool on_get_explorer(const COMMAND_EXPLORER::request& req, COMMAND_EXPLORER::response& res); bool on_get_explorer_block_by_hash(const COMMAND_EXPLORER_GET_BLOCK_DETAILS_BY_HASH::request& req, COMMAND_EXPLORER_GET_BLOCK_DETAILS_BY_HASH::response& res); + bool on_get_explorer_tx_by_hash(const COMMAND_EXPLORER_GET_TRANSACTION_DETAILS_BY_HASH::request& req, COMMAND_EXPLORER_GET_TRANSACTION_DETAILS_BY_HASH::response& res); // json handlers bool on_get_info(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RPC_GET_INFO::response& res);