// // Created by marcin on 5/11/15. // #include "tools.h" namespace xmreg { /** * Parse key string, e.g., a viewkey in a string * into crypto::secret_key or crypto::public_key * depending on the template argument. */ template bool parse_str_secret_key(const string& key_str, T& secret_key) { // hash and keys have same structure, so to parse string of // a key, e.g., a view key, we can first parse it into the hash // object using parse_hash256 function, and then copy the reslting // hash data into secret key. crypto::hash hash_; if(!parse_hash256(key_str, hash_)) { cerr << "Cant parse a key (e.g. viewkey): " << key_str << endl; return false; } // crypto::hash and crypto::secret_key have basicly same // structure. They both keep they key/hash as c-style char array // of fixed size. Thus we can just copy data from hash // to key copy(begin(hash_.data), end(hash_.data), secret_key.data); return true; } // explicit instantiations of get template function template bool parse_str_secret_key(const string& key_str, crypto::secret_key& secret_key); template bool parse_str_secret_key(const string& key_str, crypto::public_key& secret_key); template bool parse_str_secret_key(const string& key_str, crypto::hash& secret_key); /** * Get transaction tx using given tx hash. Hash is represent as string here, * so before we can tap into the blockchain, we need to pare it into * crypto::hash object. */ bool get_tx_pub_key_from_str_hash(Blockchain& core_storage, const string& hash_str, transaction& tx) { crypto::hash tx_hash; parse_hash256(hash_str, tx_hash); try { // get transaction with given hash tx = core_storage.get_db().get_tx(tx_hash); } catch (const TX_DNE& e) { cerr << e.what() << endl; return false; } return true; } /** * Parse monero address in a string form into * cryptonote::account_public_address object */ bool parse_str_address(const string& address_str, account_public_address& address, bool testnet) { if (!get_account_address_from_str(address, testnet, address_str)) { cerr << "Error getting address: " << address_str << endl; return false; } return true; } /** * Return string representation of monero address */ string print_address(const account_public_address& address, bool testnet) { return "<" + get_account_address_as_str(testnet, address) + ">"; } string print_sig (const signature& sig) { stringstream ss; ss << "c: <" << epee::string_tools::pod_to_hex(sig.c) << "> " << "r: <" << epee::string_tools::pod_to_hex(sig.r) << ">"; return ss.str(); } /** * Check if a character is a path seprator */ inline bool is_separator(char c) { // default linux path separator const char separator = PATH_SEPARARTOR; return c == separator; } /** * Remove trailinig path separator. */ string remove_trailing_path_separator(const string& in_path) { string new_string = in_path; if (!new_string.empty() && is_separator(new_string[new_string.size() - 1])) new_string.erase(new_string.size() - 1); return new_string; } bf::path remove_trailing_path_separator(const bf::path& in_path) { string path_str = in_path.native(); return bf::path(remove_trailing_path_separator(path_str)); } string timestamp_to_str(time_t timestamp, const char* format) { auto a_time_point = chrono::system_clock::from_time_t(timestamp); try { auto utc = date::to_utc_time(chrono::system_clock::from_time_t(timestamp)); auto sys_time = date::to_sys_time(utc); return date::format(format, date::floor(sys_time)); } catch (std::runtime_error& e) { cerr << "xmreg::timestamp_to_str: " << e.what() << endl; cerr << "Seems cant convert to UTC timezone using date libary. " "So just use local timezone." </.bitmonero string default_monero_dir = tools::get_default_data_dir(); if (testnet) default_monero_dir += "/testnet"; // the default folder of the lmdb blockchain database // is therefore as follows return default_monero_dir + string("/lmdb"); } /* * Ge blockchain exception from command line option * * If not given, provide default path */ bool get_blockchain_path(const boost::optional& bc_path, bf::path& blockchain_path, bool testnet) { // the default folder of the lmdb blockchain database string default_lmdb_dir = xmreg::get_default_lmdb_folder(testnet); blockchain_path = bc_path ? bf::path(*bc_path) : bf::path(default_lmdb_dir); if (!bf::is_directory(blockchain_path)) { cerr << "Given path \"" << blockchain_path << "\" " << "is not a folder or does not exist" << " " << endl; return false; } blockchain_path = xmreg::remove_trailing_path_separator(blockchain_path); return true; } uint64_t sum_money_in_outputs(const transaction& tx) { uint64_t sum_xmr {0}; for (const tx_out& txout: tx.vout) { sum_xmr += txout.amount; } return sum_xmr; } pair sum_money_in_outputs(const string& json_str) { pair sum_xmr {0, 0}; cout << json_str << endl; json j; try { j = json::parse( json_str); } catch (std::invalid_argument& e) { cerr << "sum_money_in_outputs: " << e.what() << endl; return sum_xmr; } for (json& vout: j["vout"]) { sum_xmr.first += vout["amount"].get(); ++sum_xmr.second; } return sum_xmr; }; uint64_t sum_money_in_inputs(const transaction& tx) { uint64_t sum_xmr {0}; size_t input_no = tx.vin.size(); for (size_t i = 0; i < input_no; ++i) { if(tx.vin[i].type() != typeid(cryptonote::txin_to_key)) { continue; } // get tx input key const cryptonote::txin_to_key& tx_in_to_key = boost::get(tx.vin[i]); sum_xmr += tx_in_to_key.amount; } return sum_xmr; } pair sum_money_in_inputs(const string& json_str) { pair sum_xmr {0, 0}; cout << json_str << endl; json j; try { j = json::parse( json_str); } catch (std::invalid_argument& e) { cerr << "sum_money_in_outputs: " << e.what() << endl; return sum_xmr; } for (json& vin: j["vin"]) { sum_xmr.first += vin["key"]["amount"].get(); ++sum_xmr.second; } return sum_xmr; }; array sum_money_in_tx(const transaction& tx) { array sum_xmr; sum_xmr[0] = sum_money_in_inputs(tx); sum_xmr[1] = sum_money_in_outputs(tx); return sum_xmr; }; array sum_money_in_txs(const vector& txs) { array sum_xmr {0,0}; for (const transaction& tx: txs) { sum_xmr[0] += sum_money_in_inputs(tx); sum_xmr[1] += sum_money_in_outputs(tx); } return sum_xmr; }; uint64_t sum_fees_in_txs(const vector& txs) { uint64_t fees_sum {0}; for (const transaction& tx: txs) { fees_sum += get_tx_fee(tx); } return fees_sum; } vector> get_ouputs(const transaction& tx) { vector> outputs; for (const tx_out& txout: tx.vout) { if (txout.target.type() != typeid(txout_to_key)) { continue; } // get tx input key const txout_to_key& txout_key = boost::get(txout.target); outputs.push_back(make_pair(txout_key, txout.amount)); } return outputs; }; vector> get_ouputs_tuple(const transaction& tx) { vector> outputs; for (uint64_t n = 0; n < tx.vout.size(); ++n) { if (tx.vout[n].target.type() != typeid(txout_to_key)) { continue; } // get tx input key const txout_to_key& txout_key = boost::get(tx.vout[n].target); outputs.push_back(make_tuple(txout_key, tx.vout[n].amount, n)); } return outputs; }; uint64_t get_mixin_no(const transaction& tx) { uint64_t mixin_no {0}; size_t input_no = tx.vin.size(); for (size_t i = 0; i < input_no; ++i) { if(tx.vin[i].type() != typeid(cryptonote::txin_to_key)) { continue; } // get tx input key const txin_to_key& tx_in_to_key = boost::get(tx.vin[i]); mixin_no = tx_in_to_key.key_offsets.size(); // look for first mixin number. // all inputs in a single transaction have same number if (mixin_no > 0) { break; } } return mixin_no; } vector get_mixin_no(const string& json_str) { vector mixin_no; return mixin_no; } vector get_mixin_no_in_txs(const vector& txs) { vector mixin_no; for (const transaction& tx: txs) { mixin_no.push_back(get_mixin_no(tx)); } return mixin_no; } vector get_key_images(const transaction& tx) { vector key_images; size_t input_no = tx.vin.size(); for (size_t i = 0; i < input_no; ++i) { if(tx.vin[i].type() != typeid(txin_to_key)) { continue; } // get tx input key const txin_to_key& tx_in_to_key = boost::get(tx.vin[i]); key_images.push_back(tx_in_to_key); } return key_images; } bool get_payment_id(const vector& extra, crypto::hash& payment_id, crypto::hash8& payment_id8) { payment_id = null_hash; payment_id8 = null_hash8; std::vector tx_extra_fields; if(!parse_tx_extra(extra, tx_extra_fields)) { return false; } tx_extra_nonce extra_nonce; if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) { // first check for encrypted id and then for normal one if(get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) { return true; } else if (get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) { return true; } } return false; } bool get_payment_id(const transaction& tx, crypto::hash& payment_id, crypto::hash8& payment_id8) { return get_payment_id(tx.extra, payment_id, payment_id8); } array timestamp_difference(uint64_t t1, uint64_t t2) { uint64_t timestamp_diff = t1 - t2; // calculate difference of timestamps from current block to the mixin one if (t2 > t1) { timestamp_diff = t2 - t1; } uint64_t time_diff_years = timestamp_diff / 31536000; timestamp_diff -= time_diff_years * 31536000; uint64_t time_diff_days = timestamp_diff / 86400; timestamp_diff -= time_diff_days * 86400; uint64_t time_diff_hours = timestamp_diff / 3600; timestamp_diff -= time_diff_hours * 3600; uint64_t time_diff_minutes = timestamp_diff / 60; timestamp_diff -= time_diff_minutes * 60; uint64_t time_diff_seconds = timestamp_diff ; return array {time_diff_years, time_diff_days, time_diff_hours, time_diff_minutes, time_diff_seconds}; }; string read(string filename) { if (!bf::exists(bf::path(filename))) { cerr << "File does not exist: " << filename << endl; return string(); } std::ifstream t(filename); return string(std::istreambuf_iterator(t), std::istreambuf_iterator()); } pair timestamps_time_scale(const vector& timestamps, uint64_t timeN, uint64_t resolution, uint64_t time0) { string empty_time = string(resolution, '_'); size_t time_axis_length = empty_time.size(); uint64_t interval_length = timeN-time0; double scale = double(interval_length) / double(time_axis_length); for (const auto& timestamp: timestamps) { if (timestamp < time0 || timestamp > timeN) { cout << "Out of range" << endl; continue; } uint64_t timestamp_place = double(timestamp-time0) / double(interval_length)*(time_axis_length - 1); empty_time[timestamp_place + 1] = '*'; } return make_pair(empty_time, scale); } // useful reference to get epoch time in correct timezon // http://www.boost.org/doc/libs/1_41_0/doc/html/date_time/examples.html#date_time.examples.seconds_since_epoch time_t ptime_to_time_t(const pt::ptime& in_ptime) { static pt::ptime epoch(gt::date(1970, 1, 1)); pt::time_duration::sec_type no_seconds = (in_ptime - epoch).total_seconds(); return time_t(no_seconds); } bool decode_ringct(const rct::rctSig& rv, const crypto::public_key pub, const crypto::secret_key &sec, unsigned int i, rct::key & mask, uint64_t & amount) { crypto::key_derivation derivation; bool r = crypto::generate_key_derivation(pub, sec, derivation); if (!r) { cerr <<"Failed to generate key derivation to decode rct output " << i << endl; return false; } crypto::secret_key scalar1; crypto::derivation_to_scalar(derivation, i, scalar1); try { switch (rv.type) { case rct::RCTTypeSimple: amount = rct::decodeRctSimple(rv, rct::sk2rct(scalar1), i, mask); break; case rct::RCTTypeFull: amount = rct::decodeRct(rv, rct::sk2rct(scalar1), i, mask); break; default: cerr << "Unsupported rct type: " << rv.type << endl; return false; } } catch (const std::exception &e) { cerr << "Failed to decode input " << i << endl; return false; } return true; } bool url_decode(const std::string& in, std::string& out) { out.clear(); out.reserve(in.size()); for (std::size_t i = 0; i < in.size(); ++i) { if (in[i] == '%') { if (i + 3 <= in.size()) { int value = 0; std::istringstream is(in.substr(i + 1, 2)); if (is >> std::hex >> value) { out += static_cast(value); i += 2; } else { return false; } } else { return false; } } else if (in[i] == '+') { out += ' '; } else { out += in[i]; } } return true; } map parse_crow_post_data(const string& req_body) { map body; vector vec; string tmp; bool result = url_decode(req_body, tmp); if (result) { boost::algorithm::split(vec, tmp, [](char x) {return x == '&'; }); for(auto &it : vec) { auto pos = it.find("="); if (pos != string::npos) { body[it.substr(0, pos)] = it.substr(pos + 1); } else { break; } } } return body; } bool get_dummy_account_keys(account_keys& dummy_keys, bool testnet) { secret_key adress_prv_viewkey; secret_key adress_prv_spendkey; account_public_address dummy_address; if (!get_account_address_from_str(dummy_address, testnet, "4BAyX63gVQgDqKS1wmqNVHdcCNjq1jooLYCXsKEY9w7VdGh45oZbPLvN7y8oVg2zmnhECkRBXpREWb97KtfAcT6p1UNXm9K")) { return false; } if (!epee::string_tools::hex_to_pod("f238be69411631f35b76c5a9148b3b7e8327eb41bfd0b396e090aeba40235d01", adress_prv_viewkey)) { return false; } if (!epee::string_tools::hex_to_pod("5db8e1d2c505f888e54aca15b1a365c8814d7deebc1a246690db3bf71324950d", adress_prv_spendkey)) { return false; } dummy_keys = account_keys { dummy_address, adress_prv_spendkey, adress_prv_viewkey }; return true; } // from wallet2::decrypt string decrypt(const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated) { const size_t prefix_size = sizeof(chacha8_iv) + (authenticated ? sizeof(crypto::signature) : 0); if (ciphertext.size() < prefix_size) { cerr << "Unexpected ciphertext size" << endl; return {}; } crypto::chacha8_key key; crypto::generate_chacha8_key(&skey, sizeof(skey), key); const crypto::chacha8_iv &iv = *(const crypto::chacha8_iv*)&ciphertext[0]; std::string plaintext; plaintext.resize(ciphertext.size() - prefix_size); if (authenticated) { crypto::hash hash; crypto::cn_fast_hash(ciphertext.data(), ciphertext.size() - sizeof(signature), hash); crypto::public_key pkey; crypto::secret_key_to_public_key(skey, pkey); const crypto::signature &signature = *(const crypto::signature*)&ciphertext[ciphertext.size() - sizeof(crypto::signature)]; if (!crypto::check_signature(hash, pkey, signature)) { cerr << "Failed to authenticate criphertext" << endl; return {}; } } crypto::chacha8(ciphertext.data() + sizeof(iv), ciphertext.size() - prefix_size, key, iv, &plaintext[0]); return plaintext; } // based on // crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const public_key get_tx_pub_key_from_received_outs(const transaction &tx) { std::vector tx_extra_fields; if(!parse_tx_extra(tx.extra, tx_extra_fields)) { // Extra may only be partially parsed, it's OK if tx_extra_fields contains public key } // Due to a previous bug, there might be more than one tx pubkey in extra, one being // the result of a previously discarded signature. // For speed, since scanning for outputs is a slow process, we check whether extra // contains more than one pubkey. If not, the first one is returned. If yes, they're // checked for whether they yield at least one output tx_extra_pub_key pub_key_field; if (!find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, 0)) { return null_pkey; } public_key tx_pub_key = pub_key_field.pub_key; bool two_found = find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, 1); if (!two_found) { // easy case, just one found return tx_pub_key; } else { // just return second one if there are two. // this does not require private view key, as // its not needed for my use case. return pub_key_field.pub_key; } return null_pkey; } date::sys_seconds parse(const std::string& str, string format) { std::istringstream in(str); date::sys_seconds tp; in >> date::parse(format, tp); if (in.fail()) { in.clear(); in.str(str); in >> date::parse(format, tp); } return tp; } /** * Check if given output (specified by output_index) * belongs is ours based * on our private view key and public spend key */ bool is_output_ours(const size_t& output_index, const transaction& tx, const public_key& pub_tx_key, const secret_key& private_view_key, const public_key& public_spend_key) { // public transaction key is combined with our viewkey // to create, so called, derived key. key_derivation derivation; if (!generate_key_derivation(pub_tx_key, private_view_key, derivation)) { cerr << "Cant get dervied key for: " << "\n" << "pub_tx_key: " << pub_tx_key << " and " << "prv_view_key" << private_view_key << endl; return false; } // get the tx output public key // that normally would be generated for us, // if someone had sent us some xmr. public_key pubkey; derive_public_key(derivation, output_index, public_spend_key, pubkey); //cout << "\n" << tx.vout.size() << " " << output_index << endl; // get tx output public key const txout_to_key tx_out_to_key = boost::get(tx.vout[output_index].target); if (tx_out_to_key.key == pubkey) { return true; } return false; } bool get_real_output_for_key_image(const key_image& ki, const transaction& tx, const secret_key& private_view_key, const public_key& public_spend_key, uint64_t output_idx, public_key output_pub_key) { return false; } bool make_tx_from_json(const string& json_str, transaction& tx) { json j; cout << json_str << endl; const char * raw = R"V0G0N( { "version": 1, "unlock_time": 0, "vin": [ { "key": { "amount": 500000000000, "key_offsets": [ 970946, 74679, 42349 ], "k_image": "a3569b74ac6072a09ff7aa87d6a71f4960b736166fe991ffd9ae6e023b8fccaa" } }, { "key": { "amount": 6000000000000, "key_offsets": [ 76729, 115575, 6350 ], "k_image": "15e6094d135fe034eacff7d905da915fabbeddb055cd4f2f56628ab74c149ba7" } } ], "vout": [ { "amount": 90000000000, "target": { "key": "017ec135fd5a83ef71e1733c6b8514b90b6af3a39bf048298a4cadcb527cb44d" } }, { "amount": 400000000000, "target": { "key": "f54dd59afbff69d0f963dba339c036c61149431ea4767b8f48bd687cb95ce863" } }, { "amount": 1000000000000, "target": { "key": "983eb1aef254a90cf1c053ca67f355c7d232b792219d4ab4e25c2e8c839e0daf" } }, { "amount": 5000000000000, "target": { "key": "b5d6f08ae20031ad2e904795b16c5336325a437293d9c4ec8d282d2f856245f3" } } ], "extra": [ 2, 33, 0, 16, 251, 80, 43, 162, 245, 74, 116, 191, 231, 90, 127, 157, 163, 123, 50, 55, 169, 21, 169, 213, 159, 79, 30, 148, 88, 38, 122, 80, 254, 174, 155, 1, 3, 187, 28, 103, 133, 53, 170, 134, 103, 235, 120, 47, 87, 205, 202, 40, 30, 129, 27, 27, 192, 122, 192, 209, 190, 161, 249, 75, 147, 29, 42, 15 ], "signatures": [ "023714291f36e2ec9985417f0792425ce192434bcfcb4c600806f94ff2215007299d16a105f5741e2ab75e4592b8b8f3f604a114ea8e9a6edc8360fbe07da80c3a108fa6a5861c684f20aecbe1c6bde30eae22e2f69aeff2ed605c8ac9e3a004e181eec999af9e9ec7ba4379dbe78f0c7ee41d77276c0472b071b33df83419065c5cd2ffb7ed4bfacbd67ec3aa304e3f59a00d310a455db1718d14c789adc8062c97c75bb421e1579e9df1ddadfd59a06fccd01c054cf39b1e93f8b43e4f5405", "4984136e387023b499fbff72a1b88bac64e3dcb54a0213ce2deb412de2781d0f41a0f0160dd6c07b98d6d63adc91b672cc3f2ff6f447d48bea09d54b87ab440310459fbb9a7cc59a2e4ac80d4bb41c9ea2500515a7ceaf6b1e23849965395501af1761413dc4d79eaba190ca72c00e0bdc3737502fb46bf705f714741e6434070a82684d5f441de2991989aff2c7f806ad4adaf93fb649a3d2715760d1ea870fd2a402b1678dafa172d2830e713ce60e776cede47367185fa00d78910d21f00d"] } )V0G0N"; try { j = json::parse(json_str); } catch (std::invalid_argument& e) { cerr << "make_tx_from_json: cant parse json string: " << e.what() << endl; return false; } // get version and unlock time from json tx.version = j["version"].get(); tx.unlock_time = j["unlock_time"].get(); // next get extra data for (json& extra_val: j["extra"]) tx.extra.push_back(extra_val.get()); // now populate output data from json vector& tx_outputs = tx.vout; for (json& vo: j["vout"]) { uint64_t amount = vo["amount"].get(); public_key out_pub_key; if (!epee::string_tools::hex_to_pod(vo["target"]["key"], out_pub_key)) { cerr << "Faild to parse public_key of an output from json" << endl; return false; } txout_target_v target {txout_to_key {out_pub_key}}; tx_outputs.push_back(tx_out {amount, target}); } // now populate input data from json vector& tx_inputs = tx.vin; for (json& vi: j["vin"]) { uint64_t amount = vi["key"]["amount"].get(); key_image in_key_image; if (!epee::string_tools::hex_to_pod(vi["key"]["k_image"], in_key_image)) { cerr << "Faild to parse key_image of an input from json" << endl; return false; } vector key_offsets; for (json& ko: vi["key"]["key_offsets"]) { key_offsets.push_back(ko.get()); } txin_v _txin_v {txin_to_key {amount, key_offsets, in_key_image}}; tx_inputs.push_back(_txin_v); } // add ring signatures field if (j.find("signatures") != j.end()) { vector>& signatures = tx.signatures; for (json& sigs: j["signatures"]) { string concatanted_sig = sigs; vector sig_split; auto split_sig = [&](string::iterator &b, string::iterator &e) { signature a_sig; if (!epee::string_tools::hex_to_pod(string(b, e), a_sig)) { cerr << "Faild to parse signature from json" << endl; return false; } sig_split.push_back(a_sig); return true; }; chunks(concatanted_sig.begin(), concatanted_sig.end(), 128, split_sig); signatures.push_back(sig_split); } } // now add rct_signatures from json to tx if exist if (j.find("rct_signatures") != j.end()) { rct::rctSig& rct_signatures = tx.rct_signatures; vector& ecdhInfo = rct_signatures.ecdhInfo; for (json& ecdhI: j["rct_signatures"]["ecdhInfo"]) { rct::ecdhTuple a_tuple; cout << "ecdhI[\"amount\"]: " << ecdhI["amount"] << endl; if (!epee::string_tools::hex_to_pod(ecdhI["amount"], a_tuple.amount)) { cerr << "Faild to parse ecdhInfo of an amount from json" << endl; return false; } //cout << epee::string_tools::pod_to_hex(a_tuple.amount) << endl; if (!epee::string_tools::hex_to_pod(ecdhI["mask"], a_tuple.mask)) { cerr << "Faild to parse ecdhInfo of an mask from json" << endl; return false; } ecdhInfo.push_back(a_tuple); } vector& outPk = rct_signatures.outPk; for (json& pk: j["rct_signatures"]["outPk"]) { rct::key a_key; if (!epee::string_tools::hex_to_pod(pk, a_key)) { cerr << "Faild to parse rct::key of an outPk from json" << endl; return false; } // dont have second value, i.e. mask, in the json // so I just put whatever, same key for exmaple outPk.push_back(rct::ctkey{a_key, a_key}); } rct_signatures.txnFee = j["rct_signatures"]["txnFee"].get(); rct_signatures.type = j["rct_signatures"]["type"].get(); } // if (j.find("rct_signatures") != j.end()) if (j.find("rctsig_prunable") != j.end()) { rct::rctSigPrunable &rctsig_prunable = tx.rct_signatures.p; vector& range_sigs = rctsig_prunable.rangeSigs; // for (json& range_s: j["rctsig_prunable"]["rangeSigs"]) // { // rct::asnlSig asig; // // if (!epee::string_tools::hex_to_pod(range_s["asig"], asig)) // { // cerr << "Faild to parse asig of an asnlSig from json" << endl; // return false; // } // // rct::key64 Ci; // // if (!epee::string_tools::hex_to_pod(range_s["Ci"], Ci)) // { // cerr << "Faild to parse Ci of an asnlSig from json" << endl; // return false; // } // // range_sigs.emplace_back(asig, Ci); // } } // j.find("rctsig_prunable") != j.end() cout << j.dump(4) << endl; cout << "From reconstructed tx: " << obj_to_json_str(tx) << endl; return true; } }