Skip to content
Open
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 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
1 change: 1 addition & 0 deletions src/cpp/common/py_monero_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,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
2 changes: 1 addition & 1 deletion 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
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
60 changes: 59 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,25 @@ 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) {
if (hashes.empty()) {
return;
}

std::unordered_map<std::string, std::shared_ptr<monero::monero_tx_wallet>> tx_map;

for (const auto& tx : txs) {
tx_map.emplace(tx->m_hash.get(), tx);
}

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

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

txs = std::move(sorted_txs);
}
2 changes: 2 additions & 0 deletions src/cpp/utils/py_monero_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class PyMoneroUtils {
static uint64_t xmr_to_atomic_units(double amount_xmr);
static double atomic_units_to_xmr(uint64_t amount_atomic_units);

static void sort_txs_wallet(std::vector<std::shared_ptr<monero::monero_tx_wallet>>& txs, const std::vector<std::string>& hashes);

private:

static bool is_hex_64(const std::string& value);
Expand Down
7 changes: 7 additions & 0 deletions src/python/monero_tx_config.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,10 @@ class MoneroTxConfig(SerializableStruct):
...
def get_normalized_destinations(self) -> list[MoneroDestination]:
...
def set_address(self, address: str) -> None:
"""
Set the address of a single-destination configuration

:param str address: the address to set for the single destination
"""
...
7 changes: 3 additions & 4 deletions tests/test_monero_daemon_rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,7 @@ def test_get_block_by_height(self, daemon: MoneroDaemonRpc):
AssertUtils.assert_equals(last_header.height - 1, block.height)

# Can get blocks by height which includes transactions (binary)
#@pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled")
@pytest.mark.skip(reason="TODO fund wallet")
@pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled")
def test_get_blocks_by_height_binary(self, daemon: MoneroDaemonRpc):
# set number of blocks to test
num_blocks = 100
Expand Down Expand Up @@ -375,7 +374,7 @@ def test_get_txs_by_hashes(self, daemon: MoneroDaemonRpc, wallet: MoneroWalletRp
@pytest.mark.skip("TODO implement monero_wallet_rpc.get_txs()")
def test_get_tx_pool_statistics(self, daemon: MoneroDaemonRpc, wallet: MoneroWalletRpc):
wallet = wallet
Utils.WALLET_TX_TRACKER.wait_for_wallet_txs_to_clear_pool(daemon, Utils.SYNC_PERIOD_IN_MS, [wallet])
Utils.WALLET_TX_TRACKER.wait_for_txs_to_clear_pool(daemon, Utils.SYNC_PERIOD_IN_MS, [wallet])
tx_ids: list[str] = []
try:
# submit txs to the pool but don't relay
Expand Down Expand Up @@ -403,7 +402,7 @@ def test_get_tx_pool_statistics(self, daemon: MoneroDaemonRpc, wallet: MoneroWal
@pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled")
def test_get_miner_tx_sum(self, daemon: MoneroDaemonRpc) -> None:
tx_sum = daemon.get_miner_tx_sum(0, min(5000, daemon.get_height()))
DaemonUtils.test_miner_tx_sum(tx_sum)
DaemonUtils.test_miner_tx_sum(tx_sum, Utils.REGTEST)

# Can get fee estimate
@pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled")
Expand Down
Loading
Loading