From 2ea3a47d857180f982bf0ebf2ee923ace54cda62 Mon Sep 17 00:00:00 2001 From: Paul Shapiro Date: Thu, 16 Aug 2018 16:28:46 -0400 Subject: [PATCH] added serial_bridge_index with JSON-bridged create tx, with new convenience create_transaction fn for extra construction etc, plus test --- CMakeLists.txt | 2 + src/monero_transfer_utils.cpp | 126 +++++++++++++++++++++++- src/monero_transfer_utils.hpp | 70 ++++++++++++- src/serial_bridge_index.cpp | 180 ++++++++++++++++++++++++++++++++++ src/serial_bridge_index.hpp | 55 +++++++++++ test/test_all.cpp | 153 +++++++++++++++++++++++++++-- 6 files changed, 571 insertions(+), 15 deletions(-) create mode 100644 src/serial_bridge_index.cpp create mode 100644 src/serial_bridge_index.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1063195..8cb49af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,6 +79,8 @@ set( src/monero_fork_rules.cpp src/monero_wallet_utils.hpp src/monero_wallet_utils.cpp + src/serial_bridge_index.hpp + src/serial_bridge_index.cpp src/tools__ret_vals.hpp src/tools__ret_vals.cpp # diff --git a/src/monero_transfer_utils.cpp b/src/monero_transfer_utils.cpp index bc7db74..346f5a0 100644 --- a/src/monero_transfer_utils.cpp +++ b/src/monero_transfer_utils.cpp @@ -50,7 +50,7 @@ using namespace monero_fork_rules; // Protocol / Defaults uint32_t monero_transfer_utils::fixed_ringsize() { - return 7; // best practice is to conform to fixed ring size + return 7; // best practice is to conform to fixed default ring size } uint32_t monero_transfer_utils::fixed_mixinsize() { @@ -314,10 +314,9 @@ void monero_transfer_utils::create_transaction( bool rct, cryptonote::network_type nettype ) { - retVals.errCode = noError; // (does this need to be initialized?) + retVals.errCode = noError; // - // TODO: sort destinations by amount, here, according to 'decompose_destinations'? - + // TODO: do we need to sort destinations by amount, here, according to 'decompose_destinations'? // uint32_t fake_outputs_count = fixed_mixinsize(); bool bulletproof = use_fork_rules_fn(get_bulletproof_fork(), 0); @@ -545,3 +544,122 @@ void monero_transfer_utils::create_transaction( // retVals.tx = tx; } +// +// +void monero_transfer_utils::convenience__create_transaction( + Convenience_TransactionConstruction_RetVals &retVals, + const string &from_address_string, + const string &sec_viewKey_string, + const string &sec_spendKey_string, + const string &to_address_string, + optional payment_id_string, + uint64_t amount, // to send + uint64_t fee_amount, + const std::vector &dsts, // this must include change or dummy address + vector &outputs, + vector &mix_outs, + use_fork_rules_fn_type use_fork_rules_fn, + uint64_t unlock_time, + network_type nettype +) { + retVals.errCode = noError; + // + cryptonote::address_parse_info from_addr_info; + THROW_WALLET_EXCEPTION_IF(!cryptonote::get_account_address_from_str(from_addr_info, nettype, from_address_string), error::wallet_internal_error, "Couldn't parse from-address"); + cryptonote::account_keys account_keys; + { + account_keys.m_account_address = from_addr_info.address; + // + crypto::secret_key sec_viewKey; + THROW_WALLET_EXCEPTION_IF(!string_tools::hex_to_pod(sec_viewKey_string, sec_viewKey), error::wallet_internal_error, "Couldn't parse view key"); + account_keys.m_view_secret_key = sec_viewKey; + // + crypto::secret_key sec_spendKey; + THROW_WALLET_EXCEPTION_IF(!string_tools::hex_to_pod(sec_spendKey_string, sec_spendKey), error::wallet_internal_error, "Couldn't parse spend key"); + account_keys.m_spend_secret_key = sec_spendKey; + } + // + cryptonote::address_parse_info to_addr_info; + THROW_WALLET_EXCEPTION_IF(!cryptonote::get_account_address_from_str(to_addr_info, nettype, to_address_string), error::wallet_internal_error, "Couldn't parse to-address"); + // + std::vector extra; + bool payment_id_seen = false; + { // Detect hash8 or hash32 char hex string as pid and configure 'extra' accordingly + bool r = false; + if (payment_id_string != none) { + crypto::hash payment_id; + r = monero_paymentID_utils::parse_long_payment_id(*payment_id_string, payment_id); + if (r) { + std::string extra_nonce; + cryptonote::set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); + r = cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce); + } else { + crypto::hash8 payment_id8; + r = monero_paymentID_utils::parse_short_payment_id(*payment_id_string, payment_id8); + if (r) { + std::string extra_nonce; + cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id8); + r = cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce); + } + } + payment_id_seen = true; + } else { + if (to_addr_info.is_subaddress && payment_id_seen) { + retVals.errCode = cantUsePIDWithSubAddress; // Never use a subaddress with a payment ID + return; + } + if (to_addr_info.has_payment_id) { + if (payment_id_seen) { + retVals.errCode = nonZeroPIDWithIntAddress; // can't use int addr at same time as supplying manual pid + return; + } + if (to_addr_info.is_subaddress) { + THROW_WALLET_EXCEPTION_IF(false, error::wallet_internal_error, "Unexpected is_subaddress && has_payment_id"); // should never happen + return; + } + std::string extra_nonce; + cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, to_addr_info.payment_id); + bool r = cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce); + if (!r) { + retVals.errCode = couldntSetPIDToTXExtra; + return; + } + payment_id_seen = true; + } + } + } + // + uint32_t subaddr_account_idx = 0; + std::unordered_map subaddresses; + subaddresses[account_keys.m_account_address.m_spend_public_key] = {0,0}; + // + TransactionConstruction_RetVals actualCall_retVals; + create_transaction( + actualCall_retVals, + account_keys, + subaddr_account_idx, + subaddresses, + dsts, + outputs, + mix_outs, + fee_amount, + extra, + use_fork_rules_fn, + unlock_time, + true, // rct + nettype + ); + if (actualCall_retVals.errCode != noError) { + retVals.errCode = actualCall_retVals.errCode; // pass-through + return; // already set the error + } + auto txBlob = t_serializable_object_to_blob(actualCall_retVals.tx); + size_t txBlob_byteLength = txBlob.size(); + // cout << "txBlob: " << txBlob << endl; + cout << "txBlob_byteLength: " << txBlob_byteLength << endl; + THROW_WALLET_EXCEPTION_IF(txBlob_byteLength <= 0, error::wallet_internal_error, "Expected tx blob byte length > 0"); + // + // tx hash + retVals.tx_hash_string = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(actualCall_retVals.tx)); + retVals.signed_serialized_tx_string = epee::string_tools::buff_to_hex_nodelimer(cryptonote::tx_to_blob(actualCall_retVals.tx)); +} diff --git a/src/monero_transfer_utils.hpp b/src/monero_transfer_utils.hpp index 8fdb154..9a82b10 100644 --- a/src/monero_transfer_utils.hpp +++ b/src/monero_transfer_utils.hpp @@ -126,8 +126,52 @@ namespace monero_transfer_utils givenAnInvalidPubKey = 14, invalidCommitOrMaskOnOutputRCT = 15, transactionNotConstructed = 16, - transactionTooBig = 17 + transactionTooBig = 17, + notYetImplemented = 18 }; + static inline string err_msg_from_err_code__create_transaction(CreateTransactionErrorCode code) + { + switch (code) { + case noError: + return "No error"; + case noDestinations: + return "No destinations provided"; + case wrongNumberOfMixOutsProvided: + return "Wrong number of mix outputs provided"; + case notEnoughOutputsForMixing: + return "Not enough outputs for mixing"; + case invalidSecretKeys: + return "Invalid secret keys"; + case outputAmountOverflow: + return "Output amount overflow"; + case inputAmountOverflow: + return "Input amount overflow"; + case mixRCTOutsMissingCommit: + return "Mix RCT outs missing commit"; + case resultFeeNotEqualToGiven: + return "Result fee not equal to given fee"; + case needMoreMoneyThanFound: + return "Need more money than found"; + case invalidDestinationAddress: + return "Invalid destination address"; + case nonZeroPIDWithIntAddress: + return "Can't supply a PID with an integrated address"; + case cantUsePIDWithSubAddress: + return "Can't use PID with subaddress"; + case couldntSetPIDToTXExtra: + return "Couldn't set PID to tx extra"; + case givenAnInvalidPubKey: + return "Invalid pub key"; + case invalidCommitOrMaskOnOutputRCT: + return "Invalid commit or mask on output rct"; + case transactionNotConstructed: + return "Transaction not constructed"; + case transactionTooBig: + return "Transaction too big"; + case notYetImplemented: + return "Not yet implemented"; + } + } struct TransactionConstruction_RetVals { CreateTransactionErrorCode errCode; @@ -150,6 +194,30 @@ namespace monero_transfer_utils bool rct = true, network_type nettype = MAINNET ); + // + struct Convenience_TransactionConstruction_RetVals + { + CreateTransactionErrorCode errCode; + // + optional signed_serialized_tx_string; + optional tx_hash_string; + }; + void convenience__create_transaction( + Convenience_TransactionConstruction_RetVals &retVals, + const string &from_address_string, + const string &sec_viewKey_string, + const string &sec_spendKey_string, + const string &to_address_string, + optional payment_id_string, + uint64_t amount, // to send + uint64_t fee_amount, + const std::vector &dsts, // this must include change or else dummy address + vector &outputs, + vector &mix_outs, + use_fork_rules_fn_type use_fork_rules_fn, + uint64_t unlock_time = 0, // or 0 + network_type nettype = MAINNET + ); } #endif /* monero_transfer_utils_hpp */ diff --git a/src/serial_bridge_index.cpp b/src/serial_bridge_index.cpp new file mode 100644 index 0000000..b1467f7 --- /dev/null +++ b/src/serial_bridge_index.cpp @@ -0,0 +1,180 @@ +// +// serial_bridge_index.cpp +// Copyright (c) 2014-2018, MyMonero.com +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// +// +#include "serial_bridge_index.hpp" +// +#include +#include +#include +// +#include "monero_fork_rules.hpp" +#include "monero_transfer_utils.hpp" +#include "wallet_errors.h" +// +// +using namespace std; +using namespace boost; +using namespace cryptonote; +using namespace monero_transfer_utils; +using namespace monero_fork_rules; +// +using namespace serial_bridge; +// +network_type nettype_from_string(string nettype_string) +{ // TODO: possibly move this to network_type declaration + if (nettype_string == "MAINNET") { + return MAINNET; + } else if (nettype_string == "TESTNET") { + return TESTNET; + } else if (nettype_string == "STAGENET") { + return STAGENET; + } else if (nettype_string == "FAKECHAIN") { + return FAKECHAIN; + } else if (nettype_string == "UNDEFINED") { + return UNDEFINED; + } + THROW_WALLET_EXCEPTION_IF(false, error::wallet_internal_error, "Unrecognized nettype_string") + return UNDEFINED; +} +string error_ret_json_from_message(string err_msg) +{ + boost::property_tree::ptree root; + root.put(ret_json_key__any__err_msg(), err_msg); + stringstream ss; + boost::property_tree::write_json(ss, root); + // + return ss.str(); +} +string error_ret_json_from_code(int code) +{ + boost::property_tree::ptree root; + root.put(ret_json_key__any__err_code(), code); + stringstream ss; + boost::property_tree::write_json(ss, root); + // + return ss.str(); +} +// +string serial_bridge::create_transaction(std::string args_string) +{ + std::stringstream ss; + ss << args_string; + boost::property_tree::ptree json_root; + try { + boost::property_tree::read_json(ss, json_root); + } catch (std::exception const& e) { + THROW_WALLET_EXCEPTION_IF(false, error::wallet_internal_error, "Invalid JSON"); + return error_ret_json_from_message("Invalid JSON"); // TODO: centralize + } + network_type nettype = nettype_from_string(json_root.get("nettype_string")); + // + std::vector dsts; + BOOST_FOREACH(boost::property_tree::ptree::value_type &dst_desc, json_root.get_child("dsts")) + { + assert(dst_desc.first.empty()); // array elements have no names + cryptonote::tx_destination_entry de; + cryptonote::address_parse_info de_addr_info; + THROW_WALLET_EXCEPTION_IF(!cryptonote::get_account_address_from_str(de_addr_info, nettype, dst_desc.second.get("addr")), error::wallet_internal_error, "Invalid dsts.addr"); + de.addr = de_addr_info.address; + de.is_subaddress = dst_desc.second.get("is_subaddress"); + THROW_WALLET_EXCEPTION_IF(de.is_subaddress != de_addr_info.is_subaddress, error::wallet_internal_error, "Expected dsts.is_subaddress = parsed is_subaddress"); + de.amount = stoull(dst_desc.second.get("amount")); + // + dsts.push_back(de); + } + // + vector outputs; + BOOST_FOREACH(boost::property_tree::ptree::value_type &output_desc, json_root.get_child("outputs")) + { + assert(output_desc.first.empty()); // array elements have no names + SpendableOutput out{}; + out.amount = stoull(output_desc.second.get("amount")); + out.public_key = output_desc.second.get("public_key"); + out.rct = output_desc.second.get("rct"); + out.global_index = stoull(output_desc.second.get("global_index")); + out.index = stoull(output_desc.second.get("index")); + out.tx_pub_key = output_desc.second.get("tx_pub_key"); + // + outputs.push_back(out); + } + // + vector mix_outs; + BOOST_FOREACH(boost::property_tree::ptree::value_type &mix_out_desc, json_root.get_child("mix_outs")) + { + assert(mix_out_desc.first.empty()); // array elements have no names + auto amountAndOuts = RandomAmountOutputs{}; + amountAndOuts.amount = stoull(mix_out_desc.second.get("amount")); + BOOST_FOREACH(boost::property_tree::ptree::value_type &mix_out_output_desc, mix_out_desc.second.get_child("outputs")) + { + assert(mix_out_output_desc.first.empty()); // array elements have no names + auto amountOutput = RandomAmountOutput{}; + amountOutput.global_index = stoull(mix_out_output_desc.second.get("global_index")); // this is, I believe, presently supplied as a string by the API, probably to avoid overflow + amountOutput.public_key = mix_out_output_desc.second.get("public_key"); + amountOutput.rct = mix_out_output_desc.second.get("rct"); + amountAndOuts.outputs.push_back(amountOutput); + } + mix_outs.push_back(amountAndOuts); + } + // + Convenience_TransactionConstruction_RetVals retVals; + monero_transfer_utils::convenience__create_transaction( + retVals, + json_root.get("from_address_string"), + json_root.get("sec_viewKey_string"), + json_root.get("sec_spendKey_string"), + json_root.get("to_address_string"), + json_root.get_optional("payment_id_string"), + stoull(json_root.get("amount")), // to send + stoull(json_root.get("fee_amount")), + dsts, + outputs, + mix_outs, + [] (uint8_t version, int64_t early_blocks) -> bool + { + return lightwallet_hardcoded__use_fork_rules(version, early_blocks); + }, + 0, // unlock_time + nettype + ); + if (retVals.errCode != noError) { + return error_ret_json_from_code(retVals.errCode); + } + THROW_WALLET_EXCEPTION_IF(retVals.signed_serialized_tx_string == boost::none, error::wallet_internal_error, "Not expecting no signed_serialized_tx_string given no error"); + // + boost::property_tree::ptree root; + root.put(ret_json_key__create_transaction__serialized_signed_tx(), std::move(*(retVals.signed_serialized_tx_string))); + root.put(ret_json_key__create_transaction__tx_hash(), std::move(*(retVals.tx_hash_string))); + stringstream ret_ss; + boost::property_tree::write_json(ret_ss, root); + // + return ret_ss.str(); +} diff --git a/src/serial_bridge_index.hpp b/src/serial_bridge_index.hpp new file mode 100644 index 0000000..981b30a --- /dev/null +++ b/src/serial_bridge_index.hpp @@ -0,0 +1,55 @@ +// +// serial_bridge_index.hpp +// Copyright (c) 2014-2018, MyMonero.com +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// + +#ifndef serial_bridge_index_hpp +#define serial_bridge_index_hpp +// +#include +// +namespace serial_bridge +{ + using namespace std; + // + // Bridging Functions - these take and return JSON strings + string create_transaction(string args_string); + // + // JSON keys - Ret vals + static inline string ret_json_key__any__err_msg() { return "err_msg"; } + static inline string ret_json_key__any__err_code() { return "err_code"; } + static inline string ret_json_key__create_transaction__serialized_signed_tx() { return "serialized_signed_tx"; } + static inline string ret_json_key__create_transaction__tx_hash() { return "tx_hash"; } + // JSON keys - Args + // TODO: + // static inline string args_json_key__ +} + +#endif /* serial_bridge_index_hpp */ diff --git a/test/test_all.cpp b/test/test_all.cpp index 6aa8b5b..3aa118c 100644 --- a/test/test_all.cpp +++ b/test/test_all.cpp @@ -112,19 +112,19 @@ BOOST_AUTO_TEST_CASE(transfers__create) { using namespace monero_transfer_utils; using namespace monero_fork_rules; - + TransactionConstruction_RetVals retVals; monero_fork_rules::use_fork_rules_fn_type use_fork_rules_fn = [] (uint8_t version, int64_t early_blocks) -> bool { return monero_fork_rules::lightwallet_hardcoded__use_fork_rules(version, early_blocks); }; - + cryptonote::network_type nettype = cryptonote::MAINNET; - + string from_addressString = "43zxvpcj5Xv9SEkNXbMCG7LPQStHMpFCQCmkmR4u5nzjWwq5Xkv5VmGgYEsHXg4ja2FGRD5wMWbBVMijDTqmmVqm93wHGkg"; - string sec_viewKey_string = "......"; - string sec_spendKey_string = "......"; - + string sec_viewKey_string = "..."; + string sec_spendKey_string = "..."; + cryptonote::address_parse_info from_addr_info; BOOST_REQUIRE(cryptonote::get_account_address_from_str(from_addr_info, nettype, from_addressString)); cryptonote::account_keys account_keys; @@ -143,7 +143,6 @@ BOOST_AUTO_TEST_CASE(transfers__create) cout << "spend pub key: " << string_tools::pod_to_hex(from_addr_info.address.m_spend_public_key) << endl; // optional payment_id_string = string("b79f8efc81f58f67"); - bool pid_encrypt = true; uint64_t amount = 10000000000; uint64_t fee_amount = 2167750000; string to_address_string("43zxvpcj5Xv9SEkNXbMCG7LPQStHMpFCQCmkmR4u5nzjWwq5Xkv5VmGgYEsHXg4ja2FGRD5wMWbBVMijDTqmmVqm93wHGkg"); @@ -192,7 +191,7 @@ BOOST_AUTO_TEST_CASE(transfers__create) if (to_addr_info.is_subaddress) { BOOST_REQUIRE(false); // should never happen .. logic err? return; - } + } std::string extra_nonce; cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, to_addr_info.payment_id); bool r = cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce); @@ -309,12 +308,146 @@ BOOST_AUTO_TEST_CASE(transfers__create) // cout << "txBlob: " << txBlob << endl; cout << "txBlob_byteLength: " << txBlob_byteLength << endl; BOOST_REQUIRE(txBlob_byteLength > 0); - + // tx hash auto tx_hash_string = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(retVals.tx)); auto signed_serialized_tx_string = epee::string_tools::buff_to_hex_nodelimer(cryptonote::tx_to_blob(retVals.tx)); cout << "tx_hash_string: " << tx_hash_string << endl; cout << "signed_serialized_tx_string: " << signed_serialized_tx_string << endl; - +} +// +// +// Serialization bridge +#include "../src/serial_bridge_index.hpp" +#include +#include +BOOST_AUTO_TEST_CASE(bridged__transfers__create) +{ + using namespace serial_bridge; + using namespace monero_transfer_utils; + // + string from_address_string = "43zxvpcj5Xv9SEkNXbMCG7LPQStHMpFCQCmkmR4u5nzjWwq5Xkv5VmGgYEsHXg4ja2FGRD5wMWbBVMijDTqmmVqm93wHGkg"; + string to_address_string = "43zxvpcj5Xv9SEkNXbMCG7LPQStHMpFCQCmkmR4u5nzjWwq5Xkv5VmGgYEsHXg4ja2FGRD5wMWbBVMijDTqmmVqm93wHGkg"; + string amount_string = "10000000000"; + // + boost::property_tree::ptree root; + root.put("nettype_string", "MAINNET"); // TODO: specify this by constant and transform fn + root.put("from_address_string", from_address_string); + root.put("sec_viewKey_string", "..."); + root.put("sec_spendKey_string", "..."); + root.put("to_address_string", to_address_string); + root.put("payment_id_string", "b79f8efc81f58f67"); + root.put("amount", amount_string); + root.put("fee_amount", "2167750000"); + // + boost::property_tree::ptree dsts; + { // 0. actual destination address + boost::property_tree::ptree dst; + dst.put("addr", to_address_string); + dst.put("amount", amount_string); + dst.put("is_subaddress", false); + dsts.push_back(std::make_pair("", dst)); + } + { // 1. change (otherwise we'd have to supply a dummy addr) + boost::property_tree::ptree dst; + dst.put("addr", from_address_string); + dst.put("amount", "112832250000"); + dst.put("is_subaddress", false); + dsts.push_back(std::make_pair("", dst)); + } + root.add_child("dsts", dsts); + // + boost::property_tree::ptree outputs; + { + boost::property_tree::ptree out; + out.put("amount", "125000000000"); + out.put("public_key", "596fa47b6b3905269503435099a05e3ede54564026c93cbe5285e2df074c7118"); + out.put("rct", "920ee8d99299f304d17fdb104720d1f62be0b03383c7bb466ff39c6a264d80d616ce1eccd6c4de1cc0fba87e463f2e0c373146c475e8a1517f36e7a37351d50034688cc8cb528c14188cae45d89b313d444e583c9d68a32cb80938a5e2aa200b"); + out.put("global_index", "6451664"); + out.put("index", "0"); + out.put("tx_pub_key", "0a86e588dc67ca11993737e003a9e60c57174a663a47495e3b1d764f486fc88f"); + outputs.push_back(std::make_pair("", out)); + } + root.add_child("outputs", outputs); + // + boost::property_tree::ptree mix_outs; + { + boost::property_tree::ptree mix_out; + mix_out.put("amount", "0"); + boost::property_tree::ptree mix_out_outputs; + { + boost::property_tree::ptree mix_out_output; + mix_out_output.put("global_index", "5260585"); + mix_out_output.put("public_key", "da77082624fce921891c4fb80a1e7076a6714ca8c9fc547311737926a0b85a46"); + mix_out_output.put("rct", "bb227b27e36b7f3e695dffb641c29bb60bfd991accdb5ef4b580c9acd48c16b6"); + mix_out_outputs.push_back(std::make_pair("", mix_out_output)); + } + { + boost::property_tree::ptree mix_out_output; + mix_out_output.put("global_index", "1929918"); + mix_out_output.put("public_key", "8c983e7053d7a1dc9de8ac00468bcf11836a787d712dc0c02bd54a3ee00a55e8"); + mix_out_output.put("rct", "8dec45867644d1a76aafe4487292d7cf401302e6bbbb99a61c2f3b6cef4f4f34"); + mix_out_outputs.push_back(std::make_pair("", mix_out_output)); + } + { + boost::property_tree::ptree mix_out_output; + mix_out_output.put("global_index", "3921094"); + mix_out_output.put("public_key", "0133219bd5e247eef51003921ec792784c41fc34289c703e9326d46f78d9b10a"); + mix_out_output.put("rct", "75082f4ce31904acba4af37699c28d8d4f0f74fdf63b1e4a8069ebed50df3220"); + mix_out_outputs.push_back(std::make_pair("", mix_out_output)); + } + { + boost::property_tree::ptree mix_out_output; + mix_out_output.put("global_index", "6627106"); + mix_out_output.put("public_key", "daef1663dd1084bd7fe585c3d493480ee1c4cefb93254eac5855afdf38f662b1"); + mix_out_output.put("rct", "1d96763c5bc3300090c286705b7d544f02c185d9be8c32baac6bbfb8e0d0d283"); + mix_out_outputs.push_back(std::make_pair("", mix_out_output)); + } + { + boost::property_tree::ptree mix_out_output; + mix_out_output.put("global_index", "3308654"); + mix_out_output.put("public_key", "ae135f58762b1133667002538f8c353a1869db815aa686e2544b5243c2d2212f"); + mix_out_output.put("rct", "15046b93bb181189f2917eed38173202fbbb9cdbfcf3d1bc3e432df999ae1b1c"); + mix_out_outputs.push_back(std::make_pair("", mix_out_output)); + } + { + boost::property_tree::ptree mix_out_output; + mix_out_output.put("global_index", "1972531"); + mix_out_output.put("public_key", "39e44fa88d684d71762c40eb64ac80ddc694b74a99ac445667bf433536c09c8f"); + mix_out_output.put("rct", "66a42d0e8123768b392ad4a230759258d9156fab1aea00a19b041832326aca0a"); + mix_out_outputs.push_back(std::make_pair("", mix_out_output)); + } + { + boost::property_tree::ptree mix_out_output; + mix_out_output.put("global_index", "3274424"); + mix_out_output.put("public_key", "a89b91648645ba6f32e214ba5720f5387376e5a144e698d5d5d1ebac971de349"); + mix_out_output.put("rct", "815a6b1da6fc6a3bd791c4342782381cf948ee822ac9da7149f1b3717e0266d2"); + mix_out_outputs.push_back(std::make_pair("", mix_out_output)); + } + mix_out.add_child("outputs", mix_out_outputs); + mix_outs.push_back(std::make_pair("", mix_out)); + } + root.add_child("mix_outs", mix_outs); + // + stringstream args_ss; + boost::property_tree::write_json(args_ss, root); + auto ret_string = serial_bridge::create_transaction(args_ss.str()); + stringstream ret_stream; + ret_stream << ret_string; + boost::property_tree::ptree ret_tree; + boost::property_tree::read_json(ret_stream, ret_tree); + optional err_code = ret_tree.get_optional(ret_json_key__any__err_code()); + if (err_code != none && (CreateTransactionErrorCode)*err_code != monero_transfer_utils::noError) { + auto err_msg = err_msg_from_err_code__create_transaction((CreateTransactionErrorCode)*err_code); + BOOST_REQUIRE_MESSAGE(false, err_msg); + } + optional tx_hash = ret_tree.get_optional(ret_json_key__create_transaction__tx_hash()); + optional serialized_signed_tx = ret_tree.get_optional(ret_json_key__create_transaction__serialized_signed_tx()); + BOOST_REQUIRE(serialized_signed_tx != none); + BOOST_REQUIRE((*serialized_signed_tx).size() > 0); + cout << "bridged: serialized_signed_tx: " << *serialized_signed_tx << endl; + BOOST_REQUIRE(tx_hash != none); + BOOST_REQUIRE((*tx_hash).size() > 0); + cout << "bridged: tx_hash: " << *tx_hash << endl; }