Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
def pytest_runtest_call(item: pytest.Item):
# get not_supported marked
marker = item.get_closest_marker("not_supported")
not_implemented = False
if marker is None:
marker = item.get_closest_marker("not_implemented")
not_implemented = True

if marker is None:
# marked not found
return
Expand All @@ -17,6 +22,8 @@ def pytest_runtest_call(item: pytest.Item):
if "not supported" in e_str or "does not support" in e_str:
# Ok
pytest.xfail(str(e))
if not_implemented and "not implemented" in e_str:
pytest.xfail(str(e))
raise
else:
# fail test
Expand Down
1 change: 1 addition & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ markers =
unit: fast unit tests, no external dependencies
integration: slow integration tests, requires external services
not_supported: expects not supported error
not_implemented: expects not implemented error
7 changes: 7 additions & 0 deletions src/cpp/common/py_monero_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ boost::property_tree::ptree PyGenUtils::pyobject_to_ptree(const py::object& obj)
return tree;
}

boost::property_tree::ptree PyGenUtils::parse_json_string(const std::string &json) {
boost::property_tree::ptree pt;
std::istringstream iss(json);
boost::property_tree::read_json(iss, pt);
return pt;
}

std::string PyMoneroBinaryRequest::to_binary_val() const {
auto json_val = serialize();
std::string binary_val;
Expand Down
12 changes: 8 additions & 4 deletions src/cpp/common/py_monero_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,13 @@ class PyMoneroRpcError : public PyMoneroError {
public:
int code;

PyMoneroRpcError(int error_code, const std::string& msg)
: code(error_code) {
message = msg;
}
PyMoneroRpcError(int error_code, const std::string& msg) : code(error_code) {
message = msg;
}

PyMoneroRpcError(const std::string& msg) : code(-1) {
message = msg;
}
};

class PyMoneroSslOptions {
Expand Down Expand Up @@ -112,6 +115,7 @@ class PyGenUtils {
static py::object convert_value(const std::string& val);
static py::object ptree_to_pyobject(const boost::property_tree::ptree& tree);
static boost::property_tree::ptree pyobject_to_ptree(const py::object& obj);
static boost::property_tree::ptree parse_json_string(const std::string &json);
};

class PyMoneroRequest : public PySerializableStruct {
Expand Down
81 changes: 48 additions & 33 deletions src/cpp/daemon/py_monero_daemon_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,50 +81,40 @@ void PyMoneroBlock::from_property_tree(const boost::property_tree::ptree& node,
}

void PyMoneroBlock::from_property_tree(const boost::property_tree::ptree& node, const std::vector<uint64_t>& heights, std::vector<std::shared_ptr<monero::monero_block>>& blocks) {
// used by get_blocks_by_height
const auto& rpc_blocks = node.get_child("blocks");
const auto& rpc_txs = node.get_child("txs");
const auto& rpc_txs = node.get_child("txs");
if (rpc_blocks.size() != rpc_txs.size()) {
throw std::runtime_error("blocks and txs size mismatch");
}

auto it_block = rpc_blocks.begin();
auto it_txs = rpc_txs.begin();
auto it_txs = rpc_txs.begin();
size_t idx = 0;

for (; it_block != rpc_blocks.end(); ++it_block, ++it_txs, ++idx) {
// build block
auto block = std::make_shared<monero::monero_block>();
boost::property_tree::ptree block_n;
std::istringstream block_iis = std::istringstream(it_block->second.get_value<std::string>());
boost::property_tree::read_json(block_iis, block_n);
PyMoneroBlock::from_property_tree(block_n, block);
PyMoneroBlock::from_property_tree(it_block->second, block);
block->m_height = heights.at(idx);
blocks.push_back(block);
std::vector<std::string> tx_hashes;
if (auto hashes = it_block->second.get_child_optional("tx_hashes")) {
for (const auto& h : *hashes) tx_hashes.push_back(h.second.get_value<std::string>());
}

// build transactions
std::vector<std::shared_ptr<monero::monero_tx>> txs;
size_t tx_idx = 0;
for (const auto& tx_node : it_txs->second) {
auto tx = std::make_shared<monero::monero_tx>();
tx->m_hash = tx_hashes.at(tx_idx++);
tx->m_hash = block->m_tx_hashes.at(tx_idx++);
tx->m_is_confirmed = true;
tx->m_in_tx_pool = false;
tx->m_is_miner_tx = false;
tx->m_relay = true;
tx->m_is_relayed = true;
tx->m_is_failed = false;
tx->m_is_double_spend_seen = false;
boost::property_tree::ptree tx_n;
std::istringstream tx_iis = std::istringstream(tx_node.second.get_value<std::string>());
boost::property_tree::read_json(tx_iis, tx_n);
PyMoneroTx::from_property_tree(tx_n, tx);
PyMoneroTx::from_property_tree(tx_node.second, tx);
txs.push_back(tx);
}

// merge into one block
block->m_txs.clear();
for (auto& tx : txs) {
Expand All @@ -140,23 +130,21 @@ void PyMoneroBlock::from_property_tree(const boost::property_tree::ptree& node,
void PyMoneroOutput::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr<monero_output>& output) {
for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) {
std::string key = it->first;

if (key == std::string("gen")) throw std::runtime_error("Output with 'gen' from daemon rpc is miner tx which we ignore (i.e. each miner input is null)");
else if (key == std::string("key")) {
auto key_node = it->second;
for (auto it2 = key_node.begin(); it2 != key_node.end(); ++it2) {
std::string key_key = it2->first;

if (key_key == std::string("amount")) output->m_amount = it2->second.get_value<uint64_t>();
else if (key_key == std::string("k_image")) {
if (!output->m_key_image) output->m_key_image = std::make_shared<monero::monero_key_image>();
output->m_key_image.get()->m_hex = it2->second.data();
}
else if (key_key == std::string("key_offsets")) {
auto offsets_node = it->second;
auto offsets_node = it2->second;

for (auto it2 = offsets_node.begin(); it2 != offsets_node.end(); ++it2) {
output->m_ring_output_indices.push_back(it2->second.get_value<uint64_t>());
for (auto it3 = offsets_node.begin(); it3 != offsets_node.end(); ++it3) {
output->m_ring_output_indices.push_back(it3->second.get_value<uint64_t>());
}
}
}
Expand Down Expand Up @@ -188,8 +176,10 @@ void PyMoneroOutput::from_property_tree(const boost::property_tree::ptree& node,
}

void PyMoneroTx::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr<monero::monero_tx>& tx) {
std::shared_ptr<monero_block> block = nullptr;

std::shared_ptr<monero_block> block = tx->m_block == boost::none ? nullptr : tx->m_block.get();
std::string as_json;
std::string tx_json;

for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) {
std::string key = it->first;
if (key == std::string("tx_hash") || key == std::string("id_hash")) {
Expand Down Expand Up @@ -230,22 +220,37 @@ void PyMoneroTx::from_property_tree(const boost::property_tree::ptree& node, con
if (block == nullptr) block = std::make_shared<monero_block>();
tx->m_version = it->second.get_value<uint32_t>();
}
else if (key == std::string("vin") && it->second.size() != 1) {
auto node2 = it->second;
std::vector<std::shared_ptr<monero_output>> inputs;
for(auto it2 = node2.begin(); it2 != node2.end(); ++it2) {
auto output = std::make_shared<monero::monero_output>();
PyMoneroOutput::from_property_tree(it2->second, output);
inputs.push_back(output);
else if (key == std::string("vin")) {
auto &rpc_inputs = it->second;
bool is_miner_input = false;

if (rpc_inputs.size() == 1) {
auto first = rpc_inputs.begin()->second;
if (first.get_child_optional("gen")) {
is_miner_input = true;
}
}
// ignore miner input
// TODO why?
if (!is_miner_input) {
std::vector<std::shared_ptr<monero::monero_output>> inputs;
for (auto &vin_entry : rpc_inputs) {
auto output = std::make_shared<monero::monero_output>();
PyMoneroOutput::from_property_tree(vin_entry.second, output);
output->m_tx = tx;
inputs.push_back(output);
}

tx->m_inputs = inputs;
}
if (inputs.size() != 1) tx->m_inputs = inputs;
}
else if (key == std::string("vout")) {
auto node2 = it->second;

for(auto it2 = node2.begin(); it2 != node2.end(); ++it2) {
auto output = std::make_shared<monero::monero_output>();
PyMoneroOutput::from_property_tree(it2->second, output);
output->m_tx = tx;
tx->m_outputs.push_back(output);
}
}
Expand All @@ -267,6 +272,8 @@ void PyMoneroTx::from_property_tree(const boost::property_tree::ptree& node, con
if (block == nullptr) block = std::make_shared<monero_block>();
tx->m_unlock_time = it->second.get_value<uint64_t>();
}
else if (key == std::string("as_json")) as_json = it->second.data();
else if (key == std::string("tx_json")) tx_json = it->second.data();
else if (key == std::string("as_hex") || key == std::string("tx_blob")) tx->m_full_hex = it->second.data();
else if (key == std::string("blob_size")) tx->m_size = it->second.get_value<uint64_t>();
else if (key == std::string("weight")) tx->m_weight = it->second.get_value<uint64_t>();
Expand Down Expand Up @@ -341,8 +348,16 @@ void PyMoneroTx::from_property_tree(const boost::property_tree::ptree& node, con
output->m_index = tx->m_output_indices[i++];
}
}
//if (rpcTx.containsKey("as_json") && !"".equals(rpcTx.get("as_json"))) convertRpcTx(JsonUtils.deserialize(MoneroRpcConnection.MAPPER, (String) rpcTx.get("as_json"), new TypeReference<Map<String, Object>>(){}), tx);
//if (rpcTx.containsKey("tx_json") && !"".equals(rpcTx.get("tx_json"))) convertRpcTx(JsonUtils.deserialize(MoneroRpcConnection.MAPPER, (String) rpcTx.get("tx_json"), new TypeReference<Map<String, Object>>(){}), tx);

if (!as_json.empty()) {
auto n = PyGenUtils::parse_json_string(as_json);
PyMoneroTx::from_property_tree(n, tx);
}
if (!tx_json.empty()) {
auto n = PyGenUtils::parse_json_string(tx_json);
PyMoneroTx::from_property_tree(n, tx);
}

if (tx->m_is_relayed != true) tx->m_last_relayed_timestamp = boost::none;
}

Expand Down
8 changes: 4 additions & 4 deletions src/cpp/daemon/py_monero_daemon_rpc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ std::vector<std::shared_ptr<monero::monero_block>> PyMoneroDaemonRpc::get_blocks
from_zero = false;
}
auto max_blocks = get_max_blocks(height_to_get, end_height, max_chunk_size);
blocks.insert(blocks.end(), max_blocks.begin(), max_blocks.end());
if (!max_blocks.empty()) blocks.insert(blocks.end(), max_blocks.begin(), max_blocks.end());
last_height = blocks[blocks.size() - 1]->m_height.get();
}
return blocks;
Expand Down Expand Up @@ -802,20 +802,20 @@ void PyMoneroDaemonRpc::check_response_status(const boost::property_tree::ptree&
if (status == std::string("OK")) {
return;
}
else throw std::runtime_error(status);
else throw PyMoneroRpcError(status);
}
}

throw std::runtime_error("Could not get JSON RPC response status");
}

void PyMoneroDaemonRpc::check_response_status(std::shared_ptr<PyMoneroPathResponse> response) {
void PyMoneroDaemonRpc::check_response_status(const std::shared_ptr<PyMoneroPathResponse>& response) {
if (response->m_response == boost::none) throw std::runtime_error("Invalid Monero RPC response");
auto node = response->m_response.get();
check_response_status(node);
}

void PyMoneroDaemonRpc::check_response_status(std::shared_ptr<PyMoneroJsonResponse> response) {
void PyMoneroDaemonRpc::check_response_status(const std::shared_ptr<PyMoneroJsonResponse>& response) {
if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSON RPC response");
auto node = response->m_result.get();
check_response_status(node);
Expand Down
4 changes: 2 additions & 2 deletions src/cpp/daemon/py_monero_daemon_rpc.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ class PyMoneroDaemonRpc : public PyMoneroDaemonDefault {
std::shared_ptr<PyMoneroDaemonUpdateDownloadResult> download_update() override;
void stop() override;
std::shared_ptr<monero::monero_block_header> wait_for_next_block_header();
static void check_response_status(std::shared_ptr<PyMoneroPathResponse> response);
static void check_response_status(std::shared_ptr<PyMoneroJsonResponse> response);
static void check_response_status(const std::shared_ptr<PyMoneroPathResponse>& response);
static void check_response_status(const std::shared_ptr<PyMoneroJsonResponse>& response);

protected:
std::shared_ptr<PyMoneroRpcConnection> m_rpc;
Expand Down
22 changes: 21 additions & 1 deletion src/cpp/py_monero.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1109,6 +1109,15 @@ PYBIND11_MODULE(monero, m) {
.def_readwrite("below_amount", &monero::monero_tx_config::m_below_amount)
.def_readwrite("sweep_each_subaddress", &monero::monero_tx_config::m_sweep_each_subaddress)
.def_readwrite("key_image", &monero::monero_tx_config::m_key_image)
.def("set_address", [](monero::monero_tx_config& self, const std::string& address) {
if (self.m_destinations.size() > 1) throw PyMoneroError("Cannot set address because MoneroTxConfig already has multiple destinations");
if (self.m_destinations.empty()) {
auto dest = std::make_shared<monero::monero_destination>();
dest->m_address = address;
self.m_destinations.push_back(dest);
}
else self.m_destinations[0]->m_address = address;
})
.def("copy", [](monero::monero_tx_config& self) {
MONERO_CATCH_AND_RETHROW(self.copy());
})
Expand Down Expand Up @@ -1637,7 +1646,18 @@ PYBIND11_MODULE(monero, m) {
MONERO_CATCH_AND_RETHROW(self.get_txs());
})
.def("get_txs", [](PyMoneroWallet& self, const monero::monero_tx_query& query) {
MONERO_CATCH_AND_RETHROW(self.get_txs(query));
try {
auto txs = self.get_txs(query);
PyMoneroUtils::sort_txs_wallet(txs, query.m_hashes);
return txs;
} catch (const PyMoneroRpcError& e) {
throw;
} catch (const PyMoneroError& e) {
throw;
}
catch (const std::exception& e) {
throw PyMoneroError(e.what());
}
}, py::arg("query"))
.def("get_transfers", [](PyMoneroWallet& self, const monero::monero_transfer_query& query) {
MONERO_CATCH_AND_RETHROW(self.get_transfers(query));
Expand Down
63 changes: 62 additions & 1 deletion src/cpp/utils/py_monero_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,44 @@ void PyMoneroUtils::binary_blocks_to_json(const std::string &bin, std::string &j
void PyMoneroUtils::binary_blocks_to_property_tree(const std::string &bin, boost::property_tree::ptree &node) {
std::string response_json;
monero_utils::binary_blocks_to_json(bin, response_json);
std::istringstream iss = response_json.empty() ? std::istringstream() : std::istringstream(response_json);
std::istringstream iss(response_json);
boost::property_tree::read_json(iss, node);

auto blocks = node.get_child("blocks");
boost::property_tree::ptree parsed_blocks;

for (auto &entry : blocks) {
const std::string &block_str = entry.second.get_value<std::string>();
parsed_blocks.push_back(std::make_pair("", PyGenUtils::parse_json_string(block_str)));
}

node.put_child("blocks", parsed_blocks);

auto txs = node.get_child("txs");
boost::property_tree::ptree all_txs;

for (auto &rpc_txs_entry : txs) {
boost::property_tree::ptree txs_for_block;
const auto &rpc_txs = rpc_txs_entry.second;

if (!rpc_txs.empty() || !rpc_txs.data().empty()) {
for (auto &tx_entry : rpc_txs) {
std::string tx_str = tx_entry.second.get_value<std::string>();

auto pos = tx_str.find(',');
if (pos != std::string::npos) {
tx_str.replace(pos, 1, "{");
tx_str += "}";
}

txs_for_block.push_back(std::make_pair("", PyGenUtils::parse_json_string(tx_str)));
}
}

all_txs.push_back(std::make_pair("", txs_for_block));
}

node.put_child("txs", all_txs);
}

bool PyMoneroUtils::is_valid_language(const std::string& language) {
Expand Down Expand Up @@ -248,3 +284,28 @@ monero_integrated_address PyMoneroUtils::get_integrated_address(monero_network_t
return monero_utils::get_integrated_address(network_type, standard_address, payment_id);
}

void PyMoneroUtils::sort_txs_wallet(std::vector<std::shared_ptr<monero::monero_tx_wallet>>& txs, const std::vector<std::string>& hashes) {
bool empty = hashes.empty();
std::vector<std::string> tx_hashes;
std::unordered_map<std::string, std::shared_ptr<monero::monero_tx_wallet>> tx_map;

for (const auto& tx : txs) {
std::string tx_hash = tx->m_hash.get();
tx_map.emplace(tx_hash, tx);
if (empty) tx_hashes.push_back(tx_hash);
}

std::vector<std::shared_ptr<monero::monero_tx_wallet>> sorted_txs;
sorted_txs.reserve(hashes.size());

const auto& v_hashes = empty ? tx_hashes : hashes;

for (const auto& tx_hash : v_hashes) {
auto it = tx_map.find(tx_hash);
if (it != tx_map.end()) {
sorted_txs.push_back(it->second);
}
}

txs = std::move(sorted_txs);
}
Loading