mirror of
https://git.wownero.com/wownero/onion-wownero-blockchain-explorer.git
synced 2024-08-15 00:33:12 +00:00
api/outputsblocks added
https://github.com/moneroexamples/onion-monero-blockchain-explorer/issues/73
This commit is contained in:
parent
2cef6fb998
commit
9647945261
4 changed files with 401 additions and 1 deletions
|
@ -108,7 +108,6 @@ set(LIBRARIES
|
||||||
mnemonics
|
mnemonics
|
||||||
epee
|
epee
|
||||||
easylogging
|
easylogging
|
||||||
readline
|
|
||||||
${Boost_LIBRARIES}
|
${Boost_LIBRARIES}
|
||||||
pthread
|
pthread
|
||||||
unbound
|
unbound
|
||||||
|
|
55
README.md
55
README.md
|
@ -642,6 +642,61 @@ curl -w "\n" -X GET "http://139.162.32.245:8081/api/networkinfo"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### api/outputsblocks
|
||||||
|
|
||||||
|
Search for our outputs in last few blocks (up to 5 blocks), using provided address and viewkey.
|
||||||
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# testnet address
|
||||||
|
curl -w "\n" -X GET http://127.0.0.1:8081/api/outputsblocks?address=9sDyNU82ih1gdhDgrqHbEcfSDFASjFgxL9B9v5f1AytFUrYsVEj7bD9Pyx5Sw2qLk8HgGdFM8qj5DNecqGhm24Ce6QwEGDi&viewkey=807079280293998634d66e745562edaaca45c0a75c8290603578b54e9397e90a&limit=5&mempool=1
|
||||||
|
```
|
||||||
|
|
||||||
|
Example result:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"address": "0182d5be0f708cecf2b6f9889738bde5c930fad846d5b530e021afd1ae7e24a687ad50af3a5d38896655669079ad0163b4a369f6c852cc816dace5fc7792b72f",
|
||||||
|
"height": 960526,
|
||||||
|
"limit": "5",
|
||||||
|
"mempool": true,
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"amount": 33000000000000,
|
||||||
|
"block_no": 0,
|
||||||
|
"in_mempool": true,
|
||||||
|
"output_idx": 1,
|
||||||
|
"output_pubkey": "2417b24fc99b2cbd9459278b532b37f15eab6b09bbfc44f9d17e15cd25d5b44f",
|
||||||
|
"payment_id": "",
|
||||||
|
"tx_hash": "9233708004c51d15f44e86ac1a3b99582ed2bede4aaac6e2dd71424a9147b06f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amount": 2000000000000,
|
||||||
|
"block_no": 960525,
|
||||||
|
"in_mempool": false,
|
||||||
|
"output_idx": 0,
|
||||||
|
"output_pubkey": "9984101f5471dda461f091962f1f970b122d4469077aed6b978a910dc3ed4576",
|
||||||
|
"payment_id": "0000000000000055",
|
||||||
|
"tx_hash": "37825d0feb2e96cd10fa9ec0b990ac2e97d2648c0f23e4f7d68d2298996acefd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"amount": 96947454120000,
|
||||||
|
"block_no": 960525,
|
||||||
|
"in_mempool": false,
|
||||||
|
"output_idx": 1,
|
||||||
|
"output_pubkey": "e4bded8e2a9ec4d41682a34d0a37596ec62742b28e74b897fcc00a47fcaa8629",
|
||||||
|
"payment_id": "0000000000000000000000000000000000000000000000000000000000001234",
|
||||||
|
"tx_hash": "4fad5f2bdb6dbd7efc2ce7efa3dd20edbd2a91640ce35e54c6887f0ee5a1a679"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"viewkey": "807079280293998634d66e745562edaaca45c0a75c8290603578b54e9397e90a"
|
||||||
|
},
|
||||||
|
"status": "success"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### api/emission
|
#### api/emission
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
31
main.cpp
31
main.cpp
|
@ -561,6 +561,37 @@ main(int ac, const char* av[])
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
CROW_ROUTE(app, "/api/outputsblocks").methods("GET"_method)
|
||||||
|
([&](const crow::request &req) {
|
||||||
|
|
||||||
|
string limit = regex_search(req.raw_url, regex {"limit=\\d+"}) ?
|
||||||
|
req.url_params.get("limit") : "3";
|
||||||
|
|
||||||
|
string address = regex_search(req.raw_url, regex {"address=\\w+"}) ?
|
||||||
|
req.url_params.get("address") : "";
|
||||||
|
|
||||||
|
string viewkey = regex_search(req.raw_url, regex {"viewkey=\\w+"}) ?
|
||||||
|
req.url_params.get("viewkey") : "";
|
||||||
|
|
||||||
|
bool in_mempool_aswell {false};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
in_mempool_aswell = regex_search(req.raw_url, regex {"mempool=[01]"}) ?
|
||||||
|
boost::lexical_cast<bool>(req.url_params.get("mempool")) :
|
||||||
|
false;
|
||||||
|
}
|
||||||
|
catch (const boost::bad_lexical_cast &e)
|
||||||
|
{
|
||||||
|
cerr << "Cant parse tx_prove as bool. Using default value" << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
myxmr::jsonresponse r{xmrblocks.json_outputsblocks(limit, address, viewkey, in_mempool_aswell)};
|
||||||
|
|
||||||
|
return r;
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enable_autorefresh_option)
|
if (enable_autorefresh_option)
|
||||||
|
|
315
src/page.h
315
src/page.h
|
@ -4662,6 +4662,190 @@ namespace xmreg
|
||||||
return j_response;
|
return j_response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
json
|
||||||
|
json_outputsblocks(string _limit,
|
||||||
|
string address_str,
|
||||||
|
string viewkey_str,
|
||||||
|
bool in_mempool_aswell = false)
|
||||||
|
{
|
||||||
|
boost::trim(_limit);
|
||||||
|
boost::trim(address_str);
|
||||||
|
boost::trim(viewkey_str);
|
||||||
|
|
||||||
|
json j_response {
|
||||||
|
{"status", "fail"},
|
||||||
|
{"data", json {}}
|
||||||
|
};
|
||||||
|
|
||||||
|
json& j_data = j_response["data"];
|
||||||
|
|
||||||
|
uint64_t no_of_last_blocks {3};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
no_of_last_blocks = boost::lexical_cast<uint64_t>(_limit);
|
||||||
|
}
|
||||||
|
catch (const boost::bad_lexical_cast& e)
|
||||||
|
{
|
||||||
|
j_data["title"] = fmt::format(
|
||||||
|
"Cant parse page and/or limit numbers: {:s}", _limit);
|
||||||
|
return j_response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maxium five last blocks
|
||||||
|
no_of_last_blocks = std::min(no_of_last_blocks, 5ul);
|
||||||
|
|
||||||
|
if (address_str.empty())
|
||||||
|
{
|
||||||
|
j_response["status"] = "error";
|
||||||
|
j_response["message"] = "Monero address not provided";
|
||||||
|
return j_response;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viewkey_str.empty())
|
||||||
|
{
|
||||||
|
j_response["status"] = "error";
|
||||||
|
j_response["message"] = "Viewkey not provided";
|
||||||
|
return j_response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse string representing given monero address
|
||||||
|
cryptonote::account_public_address address;
|
||||||
|
|
||||||
|
if (!xmreg::parse_str_address(address_str, address, testnet))
|
||||||
|
{
|
||||||
|
j_response["status"] = "error";
|
||||||
|
j_response["message"] = "Cant parse monero address: " + address_str;
|
||||||
|
return j_response;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse string representing given private key
|
||||||
|
crypto::secret_key prv_view_key;
|
||||||
|
|
||||||
|
if (!xmreg::parse_str_secret_key(viewkey_str, prv_view_key))
|
||||||
|
{
|
||||||
|
j_response["status"] = "error";
|
||||||
|
j_response["message"] = "Cant parse view key: "
|
||||||
|
+ viewkey_str;
|
||||||
|
return j_response;
|
||||||
|
}
|
||||||
|
|
||||||
|
string error_msg;
|
||||||
|
|
||||||
|
j_data["outputs"] = json::array();
|
||||||
|
json& j_outptus = j_data["outputs"];
|
||||||
|
|
||||||
|
|
||||||
|
if (in_mempool_aswell)
|
||||||
|
{
|
||||||
|
// first check if there is something for us in the mempool
|
||||||
|
// get mempool tx from mempoolstatus thread
|
||||||
|
vector<MempoolStatus::mempool_tx> mempool_txs
|
||||||
|
= MempoolStatus::get_mempool_txs();
|
||||||
|
|
||||||
|
uint64_t no_mempool_txs = mempool_txs.size();
|
||||||
|
|
||||||
|
// need to use vector<transactions>,
|
||||||
|
// not vector<MempoolStatus::mempool_tx>
|
||||||
|
vector<transaction> tmp_vector;
|
||||||
|
tmp_vector.reserve(no_mempool_txs);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < no_mempool_txs; ++i)
|
||||||
|
{
|
||||||
|
// get transaction info of the tx in the mempool
|
||||||
|
tmp_vector.push_back(std::move(mempool_txs.at(i).tx));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!find_our_outputs(
|
||||||
|
address, prv_view_key,
|
||||||
|
0 /* block_no */, true /*is mempool*/,
|
||||||
|
tmp_vector.cbegin(), tmp_vector.cend(),
|
||||||
|
j_outptus /* found outputs are pushed to this*/,
|
||||||
|
error_msg))
|
||||||
|
{
|
||||||
|
j_response["status"] = "error";
|
||||||
|
j_response["message"] = error_msg;
|
||||||
|
return j_response;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // if (in_mempool_aswell)
|
||||||
|
|
||||||
|
|
||||||
|
// and now serach for outputs in last few blocks in the blockchain
|
||||||
|
|
||||||
|
uint64_t height = core_storage->get_current_blockchain_height();
|
||||||
|
|
||||||
|
// calculate starting and ending block numbers to show
|
||||||
|
int64_t start_height = height - no_of_last_blocks;
|
||||||
|
|
||||||
|
// check if start height is not below range
|
||||||
|
start_height = start_height < 0 ? 0 : start_height;
|
||||||
|
|
||||||
|
int64_t end_height = start_height + no_of_last_blocks - 1;
|
||||||
|
|
||||||
|
// loop index
|
||||||
|
int64_t block_no = end_height;
|
||||||
|
|
||||||
|
|
||||||
|
// iterate over last no_of_last_blocks of blocks
|
||||||
|
while (block_no >= start_height)
|
||||||
|
{
|
||||||
|
// get block at the given height block_no
|
||||||
|
block blk;
|
||||||
|
|
||||||
|
if (!mcore->get_block_by_height(block_no, blk))
|
||||||
|
{
|
||||||
|
j_response["status"] = "error";
|
||||||
|
j_response["message"] = fmt::format("Cant get block: {:d}", block_no);
|
||||||
|
return j_response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get transactions in the given block
|
||||||
|
list <cryptonote::transaction> blk_txs{blk.miner_tx};
|
||||||
|
list <crypto::hash> missed_txs;
|
||||||
|
|
||||||
|
if (!core_storage->get_transactions(blk.tx_hashes, blk_txs, missed_txs))
|
||||||
|
{
|
||||||
|
j_response["status"] = "error";
|
||||||
|
j_response["message"] = fmt::format("Cant get transactions in block: {:d}", block_no);
|
||||||
|
return j_response;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void) missed_txs;
|
||||||
|
|
||||||
|
if (!find_our_outputs(
|
||||||
|
address, prv_view_key,
|
||||||
|
block_no, false /*is mempool*/,
|
||||||
|
blk_txs.cbegin(), blk_txs.cend(),
|
||||||
|
j_outptus /* found outputs are pushed to this*/,
|
||||||
|
error_msg))
|
||||||
|
{
|
||||||
|
j_response["status"] = "error";
|
||||||
|
j_response["message"] = error_msg;
|
||||||
|
return j_response;
|
||||||
|
}
|
||||||
|
|
||||||
|
--block_no;
|
||||||
|
|
||||||
|
} // while (block_no >= start_height)
|
||||||
|
|
||||||
|
// return parsed values. can be use to double
|
||||||
|
// check if submited data in the request
|
||||||
|
// matches to what was used to produce response.
|
||||||
|
j_data["address"] = pod_to_hex(address);
|
||||||
|
j_data["viewkey"] = pod_to_hex(prv_view_key);
|
||||||
|
j_data["limit"] = _limit;
|
||||||
|
j_data["height"] = height;
|
||||||
|
j_data["mempool"] = in_mempool_aswell;
|
||||||
|
|
||||||
|
j_response["status"] = "success";
|
||||||
|
|
||||||
|
return j_response;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Lets use this json api convention for success and error
|
* Lets use this json api convention for success and error
|
||||||
* https://labs.omniti.com/labs/jsend
|
* https://labs.omniti.com/labs/jsend
|
||||||
|
@ -4755,6 +4939,137 @@ namespace xmreg
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
template <typename Iterator>
|
||||||
|
bool
|
||||||
|
find_our_outputs(
|
||||||
|
account_public_address const& address,
|
||||||
|
secret_key const& prv_view_key,
|
||||||
|
uint64_t const& block_no,
|
||||||
|
bool const& is_mempool,
|
||||||
|
Iterator const& txs_begin,
|
||||||
|
Iterator const& txs_end,
|
||||||
|
json& j_outptus,
|
||||||
|
string& error_msg)
|
||||||
|
{
|
||||||
|
|
||||||
|
// for each tx, perform output search using provided
|
||||||
|
// address and viewkey
|
||||||
|
for (auto it = txs_begin; it != txs_end; ++it)
|
||||||
|
{
|
||||||
|
cryptonote::transaction const& tx = *it;
|
||||||
|
|
||||||
|
tx_details txd = get_tx_details(tx);
|
||||||
|
|
||||||
|
// public transaction key is combined with our viewkey
|
||||||
|
// to create, so called, derived key.
|
||||||
|
key_derivation derivation;
|
||||||
|
|
||||||
|
if (!generate_key_derivation(txd.pk, prv_view_key, derivation))
|
||||||
|
{
|
||||||
|
error_msg = "Cant calculate key_derivation";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t output_idx{0};
|
||||||
|
|
||||||
|
std::vector<uint64_t> money_transfered(tx.vout.size(), 0);
|
||||||
|
|
||||||
|
//j_data["outputs"] = json::array();
|
||||||
|
//json& j_outptus = j_data["outputs"];
|
||||||
|
|
||||||
|
for (pair<txout_to_key, uint64_t> &outp: txd.output_pub_keys)
|
||||||
|
{
|
||||||
|
|
||||||
|
// get the tx output public key
|
||||||
|
// that normally would be generated for us,
|
||||||
|
// if someone had sent us some xmr.
|
||||||
|
public_key tx_pubkey;
|
||||||
|
|
||||||
|
derive_public_key(derivation,
|
||||||
|
output_idx,
|
||||||
|
address.m_spend_public_key,
|
||||||
|
tx_pubkey);
|
||||||
|
|
||||||
|
// check if generated public key matches the current output's key
|
||||||
|
bool mine_output = (outp.first.key == tx_pubkey);
|
||||||
|
|
||||||
|
// if mine output has RingCT, i.e., tx version is 2
|
||||||
|
if (mine_output && tx.version == 2)
|
||||||
|
{
|
||||||
|
// cointbase txs have amounts in plain sight.
|
||||||
|
// so use amount from ringct, only for non-coinbase txs
|
||||||
|
if (!is_coinbase(tx))
|
||||||
|
{
|
||||||
|
|
||||||
|
// initialize with regular amount
|
||||||
|
uint64_t rct_amount = money_transfered[output_idx];
|
||||||
|
|
||||||
|
bool r {false};
|
||||||
|
|
||||||
|
rct::key mask = tx.rct_signatures.ecdhInfo[output_idx].mask;
|
||||||
|
|
||||||
|
r = decode_ringct(tx.rct_signatures,
|
||||||
|
txd.pk,
|
||||||
|
prv_view_key,
|
||||||
|
output_idx,
|
||||||
|
mask,
|
||||||
|
rct_amount);
|
||||||
|
|
||||||
|
if (!r)
|
||||||
|
{
|
||||||
|
error_msg = "Cant decode ringct for tx: "
|
||||||
|
+ pod_to_hex(txd.hash);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outp.second = rct_amount;
|
||||||
|
money_transfered[output_idx] = rct_amount;
|
||||||
|
|
||||||
|
} // if (!is_coinbase(tx))
|
||||||
|
|
||||||
|
} // if (mine_output && tx.version == 2)
|
||||||
|
|
||||||
|
if (mine_output)
|
||||||
|
{
|
||||||
|
|
||||||
|
string payment_id;
|
||||||
|
|
||||||
|
// decrypt encrypted payment id, as used in integreated addresses
|
||||||
|
crypto::hash8 decrypted_payment_id8 = txd.payment_id8;
|
||||||
|
|
||||||
|
if (decrypted_payment_id8 != null_hash8)
|
||||||
|
{
|
||||||
|
if (decrypt_payment_id(decrypted_payment_id8, txd.pk, prv_view_key))
|
||||||
|
{
|
||||||
|
payment_id = pod_to_hex(decrypted_payment_id8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(txd.payment_id != null_hash)
|
||||||
|
{
|
||||||
|
payment_id = pod_to_hex(txd.payment_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
j_outptus.push_back(json {
|
||||||
|
{"output_pubkey" , pod_to_hex(outp.first.key)},
|
||||||
|
{"amount" , outp.second},
|
||||||
|
{"block_no" , block_no},
|
||||||
|
{"in_mempool" , is_mempool},
|
||||||
|
{"output_idx" , output_idx},
|
||||||
|
{"tx_hash" , pod_to_hex(txd.hash)},
|
||||||
|
{"payment_id" , payment_id}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
++output_idx;
|
||||||
|
|
||||||
|
} // for (pair<txout_to_key, uint64_t>& outp: txd.output_pub_keys)
|
||||||
|
|
||||||
|
} // for (auto it = blk_txs.begin(); it != blk_txs.end(); ++it)
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
json
|
json
|
||||||
get_tx_json(const transaction& tx, const tx_details& txd)
|
get_tx_json(const transaction& tx, const tx_details& txd)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue