1
0
Fork 0
forked from lthn/blockchain

wallet: watch-only & cold-signing

This commit is contained in:
sowle 2019-04-08 14:16:11 +03:00
parent b08905c1cc
commit 25e9cfc537
10 changed files with 152 additions and 401 deletions

View file

@ -122,11 +122,16 @@ namespace currency
return get_account_address_as_str(m_keys.m_account_address);
}
//-----------------------------------------------------------------
void account_base::make_account_watch_only()
{
m_keys.m_spend_secret_key = currency::null_skey;
}
//-----------------------------------------------------------------
std::string transform_addr_to_str(const account_public_address& addr)
{
return get_account_address_as_str(addr);
}
//-----------------------------------------------------------------
account_public_address transform_str_to_addr(const std::string& str)
{
account_public_address ad = AUTO_VAL_INIT(ad);

View file

@ -63,6 +63,8 @@ namespace currency
bool load(const std::string& file_path);
bool store(const std::string& file_path);
void make_account_watch_only();
template <class t_archive>
inline void serialize(t_archive &a, const unsigned int /*ver*/)
{

View file

@ -206,6 +206,7 @@ simple_wallet::simple_wallet()
m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::integrated_address, this, _1), "integrated_address [<payment_id>|<integrated_address] - encodes given payment_id along with wallet's address into an integrated address (random payment_id will be used if none is provided). Decodes given integrated_address into standard address");
m_cmd_binder.set_handler("get_tx_key", boost::bind(&simple_wallet::get_tx_key, this, _1), "Get transaction one-time secret key (r) for a given <txid>");
m_cmd_binder.set_handler("save_watch_only", boost::bind(&simple_wallet::save_watch_only, this, _1), "Save a watch-only keys file <filename> <password>.");
}
//----------------------------------------------------------------------------------------------------
@ -1280,6 +1281,31 @@ void simple_wallet::set_offline_mode(bool offline_mode)
m_offline_mode = offline_mode;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::save_watch_only(const std::vector<std::string> &args)
{
if (args.size() < 2)
{
fail_msg_writer() << "wrong parameters, expected filename and password";
return true;
}
try
{
m_wallet->store(epee::string_encoding::convert_to_unicode(args[0]), args[1], true);
success_msg_writer() << "Watch-only wallet has been stored to " << args[0];
}
catch (const std::exception& e)
{
LOG_ERROR("unexpected error: " << e.what());
fail_msg_writer() << "unexpected error: " << e.what();
}
catch (...)
{
LOG_ERROR("Unknown error");
fail_msg_writer() << "unknown error";
}
return true;
}
//----------------------------------------------------------------------------------------------------
int main(int argc, char* argv[])
{
#ifdef WIN32

View file

@ -74,6 +74,7 @@ namespace currency
bool enable_concole_logger(const std::vector<std::string> &args);
bool integrated_address(const std::vector<std::string> &args);
bool get_tx_key(const std::vector<std::string> &args_);
bool save_watch_only(const std::vector<std::string> &args);
bool get_alias_from_daemon(const std::string& alias_name, currency::extra_alias_entry_base& ai);
bool get_transfer_address(const std::string& adr_str, currency::account_public_address& addr);

View file

@ -272,29 +272,52 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t
for (size_t i_in_outs = 0; i_in_outs != outs.size(); i_in_outs++)
{
size_t o = outs[i_in_outs];
THROW_IF_TRUE_WALLET_EX(tx.vout.size() <= o, error::wallet_internal_error, "wrong out in transaction: internal index=" +
std::to_string(o) + ", total_outs=" + std::to_string(tx.vout.size()));
WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(o < tx.vout.size(), "wrong out in transaction: internal index=" << o << ", total_outs=" << tx.vout.size());
if (tx.vout[o].target.type() == typeid(txout_to_key))
{
currency::keypair in_ephemeral;
const currency::txout_to_key& otk = boost::get<currency::txout_to_key>(tx.vout[o].target);
// obtain key image for this output
crypto::key_image ki = currency::null_ki;
currency::generate_key_image_helper(m_account.get_keys(), tx_pub_key, o, in_ephemeral, ki);
THROW_IF_TRUE_WALLET_EX(in_ephemeral.pub != boost::get<currency::txout_to_key>(tx.vout[o].target).key,
error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key");
auto it = m_key_images.find(ki);
if (it != m_key_images.end())
if (m_watch_only)
{
THROW_IF_TRUE_WALLET_EX(it->second >= m_transfers.size(), error::wallet_internal_error, "m_key_images entry has wrong m_transfers index, it->second: " + epee::string_tools::num_to_string_fast(it->second) + ", m_transfers.size(): " + epee::string_tools::num_to_string_fast(m_transfers.size()));
const transfer_details& local_td = m_transfers[it->second];
WLT_LOG_YELLOW("tx " << get_transaction_hash(tx) << " @ block " << height << " has output #" << o << " with key image " << ki << " that has already been seen in output #" <<
local_td.m_internal_output_index << " in tx " << get_transaction_hash(local_td.m_ptx_wallet_info->m_tx) << " @ block " << local_td.m_spent_height <<
". This output can't ever be spent and will be skipped.", LOG_LEVEL_0);
THROW_IF_TRUE_WALLET_EX(tx_money_got_in_outs < tx.vout[o].amount, error::wallet_internal_error, "tx_money_got_in_outs: " + epee::string_tools::num_to_string_fast(tx_money_got_in_outs) + ", tx.vout[o].amount:" + print_money(tx.vout[o].amount));
tx_money_got_in_outs -= tx.vout[o].amount;
continue; // skip the output
// don't have spend secret key, so we unable to calculate key image for an output
// look it up in special container instead
auto it = m_pending_key_images.find(otk.key);
if (it != m_pending_key_images.end())
{
ki = it->second;
WLT_LOG_L1("pending key image " << ki << " was found by out pub key " << otk.key);
}
else
{
ki = currency::null_ki;
WLT_LOG_L1("can't find pending key image by out pub key: " << otk.key << ", key image temporarily set to null");
}
}
else
{
// normal wallet, calculate and store key images for own outs
currency::keypair in_ephemeral;
currency::generate_key_image_helper(m_account.get_keys(), tx_pub_key, o, in_ephemeral, ki);
WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(in_ephemeral.pub == otk.key, "key_image generated ephemeral public key that does not match with output_key");
}
if (ki != currency::null_ki)
{
auto it = m_key_images.find(ki);
if (it != m_key_images.end())
{
WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it->second < m_transfers.size(), "m_key_images entry has wrong m_transfers index, it->second: " << it->second << ", m_transfers.size(): " << m_transfers.size());
const transfer_details& local_td = m_transfers[it->second];
WLT_LOG_YELLOW("tx " << get_transaction_hash(tx) << " @ block " << height << " has output #" << o << " with key image " << ki << " that has already been seen in output #" <<
local_td.m_internal_output_index << " in tx " << get_transaction_hash(local_td.m_ptx_wallet_info->m_tx) << " @ block " << local_td.m_spent_height <<
". This output can't ever be spent and will be skipped.", LOG_LEVEL_0);
WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(tx_money_got_in_outs >= tx.vout[o].amount, "tx_money_got_in_outs: " << tx_money_got_in_outs << ", tx.vout[o].amount:" << tx.vout[o].amount);
tx_money_got_in_outs -= tx.vout[o].amount;
continue; // skip the output
}
}
mtd.receive_indices.push_back(o);
@ -314,7 +337,8 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t
}
}
size_t transfer_index = m_transfers.size()-1;
m_key_images[td.m_key_image] = transfer_index;
if (td.m_key_image != currency::null_ki)
m_key_images[td.m_key_image] = transfer_index;
add_transfer_to_transfers_cache(tx.vout[o].amount, transfer_index);
WLT_LOG_L0("Received money, transfer #" << transfer_index << ", amount: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx) << ", at height " << height);
}
@ -1686,6 +1710,7 @@ bool wallet2::reset_all()
m_blockchain.clear();
m_transfers.clear();
m_key_images.clear();
// m_pending_key_images is not cleared intentionally
m_unconfirmed_in_transfers.clear();
m_unconfirmed_txs.clear();
m_unconfirmed_multisig_transfers.clear();
@ -1702,11 +1727,16 @@ bool wallet2::reset_all()
return true;
}
//----------------------------------------------------------------------------------------------------
bool wallet2::store_keys(std::string& buff, const std::string& password)
bool wallet2::store_keys(std::string& buff, const std::string& password, bool store_as_watch_only /* = false */)
{
currency::account_base acc = m_account;
if (store_as_watch_only)
acc.make_account_watch_only();
std::string account_data;
bool r = epee::serialization::store_t_to_binary(m_account, account_data);
bool r = epee::serialization::store_t_to_binary(acc, account_data);
WLT_CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet keys");
wallet2::keys_file_data keys_file_data = boost::value_initialized<wallet2::keys_file_data>();
crypto::chacha8_key key;
@ -1857,8 +1887,11 @@ void wallet2::generate(const std::wstring& path, const std::string& pass)
init_log_prefix();
boost::system::error_code ignored_ec;
THROW_IF_TRUE_WALLET_EX(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, epee::string_encoding::convert_to_ansii(m_wallet_file));
bool stub;
load_keys2ki(true, stub);
if (m_watch_only)
{
bool stub;
load_keys2ki(true, stub);
}
store();
}
//----------------------------------------------------------------------------------------------------
@ -1930,25 +1963,22 @@ void wallet2::load(const std::wstring& wallet_, const std::string& password)
//----------------------------------------------------------------------------------------------------
void wallet2::store()
{
store(m_wallet_file);
store(m_wallet_file, m_password, false);
}
//----------------------------------------------------------------------------------------------------
void wallet2::store(const std::wstring& path_to_save)
void wallet2::store(const std::wstring& path_to_save, const std::string& password, bool store_as_watch_only)
{
LOG_PRINT_L0("(before storing: pending_key_images: " << m_pending_key_images.size() << ", pki file elements: " << m_pending_key_images_file_container.size() << ", tx_keys: " << m_tx_keys.size() << ")");
WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!(store_as_watch_only && path_to_save == m_wallet_file), "ttrying to save watch-only wallet to the same wallet file!");
//prepare data
std::string keys_buff;
bool r = store_keys(keys_buff, m_password);
bool r = store_keys(keys_buff, password);
CHECK_AND_ASSERT_THROW_MES(r, "failed to store_keys for wallet " << epee::string_encoding::convert_to_ansii(m_wallet_file));
wallet_file_binary_header wbh = AUTO_VAL_INIT(wbh);
// std::stringstream ss;
// r = tools::portble_serialize_obj_to_stream(*this, ss);
// CHECK_AND_ASSERT_THROW_MES(r, "failed to portble_serialize_obj_to_stream for wallet " << epee::string_encoding::convert_to_ansii(m_wallet_file));
// std::string body_buff = ss.str();
//store data
wbh.m_signature = WALLET_FILE_SIGNATURE;
@ -1965,8 +1995,18 @@ void wallet2::store(const std::wstring& path_to_save)
CHECK_AND_ASSERT_THROW_MES(!data_file.fail(), "failed to open binary wallet file for saving: " << epee::string_encoding::convert_to_ansii(m_wallet_file));
data_file << header_buff << keys_buff;
WLT_LOG_L0("Storing to file...");
r = tools::portble_serialize_obj_to_stream(*this, data_file);
CHECK_AND_ASSERT_THROW_MES(r, "failed to portble_serialize_obj_to_stream for wallet " << epee::string_encoding::convert_to_ansii(m_wallet_file));
if (store_as_watch_only)
{
// TODO
r = tools::portble_serialize_obj_to_stream(*this, data_file);
CHECK_AND_ASSERT_THROW_MES(r, "failed to portble_serialize_obj_to_stream for wallet " << epee::string_encoding::convert_to_ansii(m_wallet_file));
}
else
{
r = tools::portble_serialize_obj_to_stream(*this, data_file);
CHECK_AND_ASSERT_THROW_MES(r, "failed to portble_serialize_obj_to_stream for wallet " << epee::string_encoding::convert_to_ansii(m_wallet_file));
}
data_file.flush();
data_file.close();
@ -2173,8 +2213,6 @@ void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::trans
crypto::hash tx_hash = get_transaction_hash(ft.tx);
// foolproof check to make sure create_tx_param and create_tx_result DO match each other
try
{
send_transaction_to_network(ft.tx);
@ -2182,12 +2220,12 @@ void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::trans
catch (...)
{
// clear spent transfers if smth went wrong
clear_transfers_from_flag(ft.ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, "broadcasting tx " + epee::string_tools::pod_to_hex(get_transaction_hash(ft.tx)) + " was unsuccessful");
clear_transfers_from_flag(ft.ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, "broadcasting tx " + epee::string_tools::pod_to_hex(tx_hash) + " was unsuccessful");
throw;
}
add_sent_tx_detailed_info(ft.tx, ft.ftp.prepared_destinations, ft.ftp.selected_transfers);
m_tx_keys.insert(std::make_pair(get_transaction_hash(ft.tx), ft.one_time_key));
m_tx_keys.insert(std::make_pair(tx_hash, ft.one_time_key));
if (m_watch_only)
{
@ -2237,9 +2275,9 @@ void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::trans
{
THROW_IF_FALSE_WALLET_INT_ERR_EX(p.first < m_transfers.size(), "incorrect transfer index: " << p.first);
auto& tr = m_transfers[p.first];
if (tr.m_key_image != currency::null_ki)
if (tr.m_key_image != currency::null_ki && tr.m_key_image != p.second)
{
LOG_PRINT_YELLOW("transfer #" << p.first << " has not null key image: " << tr.m_key_image << " will be replaced with ki " << p.second, LOG_LEVEL_0);
LOG_PRINT_YELLOW("transfer #" << p.first << " already has not null key image " << tr.m_key_image << " and it will be replaced with ki " << p.second, LOG_LEVEL_0);
}
tr.m_key_image = p.second;
m_key_images[p.second] = p.first;
@ -2247,8 +2285,8 @@ void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::trans
}
}
// print inputs' key images
// print tx was sent
// TODO: print inputs' key images
print_tx_sent_message(ft.tx, "(from submit_transfer)");
}
//----------------------------------------------------------------------------------------------------
void wallet2::submit_transfer_files(const std::string& signed_tx_file, currency::transaction& tx)
@ -3332,6 +3370,7 @@ void wallet2::exception_handler()
//----------------------------------------------------------------------------------------------------
void wallet2::mark_transfers_as_spent(const std::vector<uint64_t>& selected_transfers, const std::string& reason /* = empty_string */)
{
// TODO: design a safe undo for this operation
mark_transfers_with_flag(selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, reason);
}
//----------------------------------------------------------------------------------------------------
@ -3667,13 +3706,17 @@ void wallet2::set_genesis(const crypto::hash& genesis_hash)
m_blockchain[0] = genesis_hash;
}
//----------------------------------------------------------------------------------------------------
void wallet2::print_tx_sent_message(const currency::transaction& tx, const std::string& description, uint64_t fee)
void wallet2::print_tx_sent_message(const currency::transaction& tx, const std::string& description, uint64_t fee /* = UINT64_MAX */)
{
//uint64_t balance_unlocked = 0;
//uint64_t balance_total = balance(balance_unlocked);
std::stringstream ss;
if (fee != UINT64_MAX)
ss << "Commission: " << std::setw(21) << std::right << print_money(fee) << ENDL;
WLT_LOG_CYAN("Transaction " << get_transaction_hash(tx) << " was successfully sent " << description << ENDL
<< "Commission: " << std::setw(21) << std::right << print_money(fee) << ENDL
<< ss.str()
// << "Balance: " << std::setw(21) << print_money(balance_total) << ENDL
// << "Unlocked: " << std::setw(21) << print_money(balance_unlocked) << ENDL
<< "Please, wait for confirmation for your balance to be unlocked.",
@ -3746,28 +3789,6 @@ void wallet2::prepare_tx_destinations(uint64_t needed_money,
}
}
//----------------------------------------------------------------------------------------------------
/*void wallet2::prepare_transaction(const std::vector<currency::tx_destination_entry>& dsts,
size_t fake_outputs_count,
uint64_t unlock_time,
uint64_t fee,
const std::vector<currency::extra_v>& extra,
const std::vector<currency::attachment_v>& attachments,
tools::detail::split_strategy_id_t destination_split_strategy_id,
const tx_dust_policy& dust_policy,
const currency::account_public_address& crypt_address,
currency::transaction &tx,
uint8_t tx_outs_attr,
bool shuffle,
bool mark_tx_as_complete,
uint8_t flags,
std::vector<uint64_t>& selected_transfers,
currency::keypair& one_time_key,
std::vector<currency::tx_destination_entry>& prepared_destinations,
crypto::hash multisig_id)
{
// TODO
}*/
//----------------------------------------------------------------------------------------------------
void wallet2::prepare_transaction(const construct_tx_param& ctp, finalize_tx_param& ftp, const currency::transaction& tx_for_mode_separate /* = currency::transaction() */)
{
TIME_MEASURE_START_MS(get_needed_money_time);
@ -3905,12 +3926,10 @@ void wallet2::transfer(const std::vector<currency::tx_destination_entry>& dsts,
uint8_t tx_outs_attr,
bool shuffle,
uint8_t flags,
bool send_to_network)
bool send_to_network,
std::string* p_signed_tx_blob_str)
{
TIME_MEASURE_START(precalculation_time);
//using namespace currency;
currency::keypair onetime_keys = AUTO_VAL_INIT(onetime_keys);
construct_tx_param tx_param = AUTO_VAL_INIT(tx_param);
tx_param.attachments = attachments;
tx_param.crypt_address = currency::get_crypt_address_from_destinations(m_account.get_keys(), dsts);
@ -3933,7 +3952,6 @@ void wallet2::transfer(const std::vector<currency::tx_destination_entry>& dsts,
prepare_transaction(tx_param, ftp);
TIME_MEASURE_FINISH(prepare_transaction_time);
if (m_watch_only)
{
tx_param.spend_pub_key = m_account.get_public_address().m_spend_public_key;
@ -3941,7 +3959,9 @@ void wallet2::transfer(const std::vector<currency::tx_destination_entry>& dsts,
crypto::chacha_crypt(bl, m_account.get_keys().m_view_secret_key);
epee::file_io_utils::save_string_to_file("unsigned_zano_tx", bl);
LOG_PRINT_L0("Transaction stored to unsigned_zano_tx. You need to sign this tx using a full-access wallet.");
//relay_blob = bl; // TODO: save encrypted tx param blob to relay_blob
if (p_signed_tx_blob_str != nullptr)
*p_signed_tx_blob_str = bl;
// unlock transfers at the very end
TIME_MEASURE_START(mark_transfers_as_spent_time);

View file

@ -297,6 +297,8 @@ namespace tools
std::vector<uint64_t> selected_transfers;
std::vector<currency::tx_destination_entry> prepared_destinations;
crypto::public_key spend_pub_key; // only for validations
BEGIN_SERIALIZE_OBJECT()
FIELD(unlock_time)
FIELD(extra)
@ -309,6 +311,7 @@ namespace tools
FIELD(sources)
FIELD(selected_transfers)
FIELD(prepared_destinations)
FIELD(spend_pub_key)
END_SERIALIZE()
};
@ -468,7 +471,8 @@ namespace tools
void restore(const std::wstring& path, const std::string& pass, const std::string& restore_key);
void load(const std::wstring& wallet, const std::string& password);
void store();
void store(const std::wstring& path);
void store(const std::wstring& path, const std::string& password, bool store_as_watch_only = false);
bool store_keys(std::string& buff, const std::string& password, bool store_as_watch_only = false);
std::wstring get_wallet_path(){ return m_wallet_file; }
currency::account_base& get_account() { return m_account; }
const currency::account_base& get_account() const { return m_account; }
@ -534,7 +538,8 @@ namespace tools
uint8_t tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED,
bool shuffle = true,
uint8_t flags = 0,
bool send_to_network = true);
bool send_to_network = true,
std::string* p_signed_tx_blob_str = nullptr);
void transfer(const std::vector<currency::tx_destination_entry>& dsts,
size_t fake_outputs_count,
@ -698,25 +703,6 @@ namespace tools
const std::list<expiration_entry_info>& get_expiration_entries() const { return m_money_expirations; };
bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key) const;
/*void prepare_transaction(const std::vector<currency::tx_destination_entry>& dsts,
size_t fake_outputs_count,
uint64_t unlock_time,
uint64_t fee,
const std::vector<currency::extra_v>& extra,
const std::vector<currency::attachment_v>& attachments,
detail::split_strategy_id_t destination_split_strategy_id,
const tx_dust_policy& dust_policy,
const currency::account_public_address& crypt_address,
OUT currency::transaction &tx,
uint8_t tx_outs_attr,
bool shuffle,
bool mark_tx_as_complete,
uint8_t flags,
OUT std::vector<uint64_t>& selected_transfers,
OUT currency::keypair& one_time_key,
OUT std::vector<currency::tx_destination_entry>& prepared_destinations,
crypto::hash multisig_id = currency::null_hash);*/
void prepare_transaction(const construct_tx_param& ctp, finalize_tx_param& ftp, const currency::transaction& tx_for_mode_separate = currency::transaction());
void finalize_transaction(const finalize_tx_param& ftp, currency::transaction& tx, crypto::secret_key& tx_key, bool broadcast_tx);
@ -725,7 +711,6 @@ namespace tools
private:
void add_transfers_to_expiration_list(const std::vector<uint64_t>& selected_transfers, uint64_t expiration, uint64_t change_amount, const crypto::hash& related_tx_id);
void remove_transfer_from_expiration_list(uint64_t transfer_index);
bool store_keys(std::string& buff, const std::string& password);
void load_keys(const std::string& keys_file_name, const std::string& password);
void process_new_transaction(const currency::transaction& tx, uint64_t height, const currency::block& b);
void detach_blockchain(uint64_t height);
@ -797,7 +782,7 @@ private:
uint64_t get_tx_expiration_median() const;
void print_tx_sent_message(const currency::transaction& tx, const std::string& description, uint64_t fee);
void print_tx_sent_message(const currency::transaction& tx, const std::string& description, uint64_t fee = UINT64_MAX);
// Validates escrow template tx in assumption it's related to wallet's account (wallet's account is either A or B party in escrow process)
bool validate_escrow_proposal(const wallet_rpc::wallet_transfer_info& wti, const bc_services::proposal_body& prop,

View file

@ -676,6 +676,7 @@ if (cond)
tools::error::throw_wallet_ex<tools::error::wallet_internal_error>(std::string(__FILE__ ":" STRINGIZE(__LINE__)), ss.str()); \
}
#define THROW_IF_FALSE_WALLET_INT_ERR_EX_NO_HANDLER(cond, mess) THROW_IF_TRUE_WALLET_INT_ERR_EX_NO_HANDLER((!(cond)), mess)
#define THROW_IF_FALSE_WALLET_INT_ERR_EX(cond, mess) THROW_IF_TRUE_WALLET_INT_ERR_EX((!(cond)), mess)

View file

@ -158,8 +158,17 @@ namespace tools
currency::transaction tx;
std::vector<currency::extra_v> extra;
m_wallet.transfer(dsts, req.mixin, req.unlock_time, req.fee, extra, attachments, tx);
res.tx_hash = epee::string_tools::pod_to_hex(currency::get_transaction_hash(tx));
std::string signed_tx_blob_str;
m_wallet.transfer(dsts, req.mixin, req.unlock_time, req.fee, extra, attachments, detail::ssi_digit, tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, CURRENCY_TO_KEY_OUT_RELAXED, true, 0, true, &signed_tx_blob_str);
if (m_wallet.is_watch_only())
{
res.tx_unsigned_hex = epee::string_tools::buff_to_hex_nodelimer(signed_tx_blob_str); // watch-only wallets can't sign and relay transactions
// leave res.tx_hash empty, because tx has will change after signing
}
else
{
res.tx_hash = epee::string_tools::pod_to_hex(currency::get_transaction_hash(tx));
}
return true;
}
catch (const tools::error::daemon_busy& e)
@ -337,14 +346,6 @@ namespace tools
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_submit_transfer(const wallet_rpc::COMMAND_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx)
{
//std::string tx_unsigned_blob;
//if (!string_tools::parse_hexstr_to_binbuff(req.tx_unsigned_hex, tx_unsigned_blob))
//{
// er.code = WALLET_RPC_ERROR_CODE_WRONG_ARGUMENT;
// er.message = "tx_unsigned_hex is invalid";
// return false;
//}
std::string tx_signed_blob;
if (!string_tools::parse_hexstr_to_binbuff(req.tx_signed_hex, tx_signed_blob))
{
@ -356,7 +357,7 @@ namespace tools
try
{
currency::transaction tx = AUTO_VAL_INIT(tx);
// TODO m_wallet.submit_transfer(tx_unsigned_blob, tx_signed_blob, tx);
m_wallet.submit_transfer(tx_signed_blob, tx);
res.tx_hash = epee::string_tools::pod_to_hex(currency::get_transaction_hash(tx));
}
catch (const std::exception& e)
@ -371,290 +372,9 @@ namespace tools
er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR";
return false;
}
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_maketelepod(const wallet_rpc::COMMAND_RPC_MAKETELEPOD::request& req, wallet_rpc::COMMAND_RPC_MAKETELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx)
{
//check available balance
if (m_wallet.unlocked_balance() <= req.amount)
{
res.status = "INSUFFICIENT_COINS";
return true;
}
currency::account_base acc;
acc.generate();
std::vector<currency::tx_destination_entry> dsts(1);
dsts.back().amount = req.amount;
dsts.back().addr.resize(1);
dsts.back().addr.back() = acc.get_keys().m_account_address;
currency::transaction tx = AUTO_VAL_INIT(tx);
try
{
std::vector<currency::extra_v> extra;
std::vector<currency::attachment_v> attachments;
m_wallet.transfer(dsts, 0, 0, m_wallet.get_core_runtime_config().tx_default_fee, extra, attachments, tx);
}
catch (const std::runtime_error& er)
{
LOG_ERROR("Failed to send transaction: " << er.what());
res.status = "INTERNAL_ERROR";
return true;
}
res.tpd.basement_tx_id_hex = string_tools::pod_to_hex(currency::get_transaction_hash(tx));
std::string buff = epee::serialization::store_t_to_binary(acc);
res.tpd.account_keys_hex = string_tools::buff_to_hex_nodelimer(buff);
res.status = "OK";
LOG_PRINT_GREEN("TELEPOD ISSUED [" << currency::print_money(req.amount) << "BBR, base_tx_id: ]" << currency::get_transaction_hash(tx), LOG_LEVEL_0);
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::build_transaction_from_telepod(const wallet_rpc::telepod& tlp, const currency::account_public_address& acc2, currency::transaction& tx2, std::string& status)
{
//check if base transaction confirmed
currency::COMMAND_RPC_GET_TRANSACTIONS::request get_tx_req = AUTO_VAL_INIT(get_tx_req);
currency::COMMAND_RPC_GET_TRANSACTIONS::response get_tx_rsp = AUTO_VAL_INIT(get_tx_rsp);
get_tx_req.txs_hashes.push_back(tlp.basement_tx_id_hex);
if (!m_wallet.get_core_proxy()->call_COMMAND_RPC_GET_TRANSACTIONS(get_tx_req, get_tx_rsp)
|| get_tx_rsp.status != CORE_RPC_STATUS_OK
|| !get_tx_rsp.txs_as_hex.size())
{
status = "UNCONFIRMED";
return false;
}
//extract account keys
std::string acc_buff;
currency::account_base acc = AUTO_VAL_INIT(acc);
if (!string_tools::parse_hexstr_to_binbuff(tlp.account_keys_hex, acc_buff))
{
LOG_ERROR("Failed to parse_hexstr_to_binbuff(tlp.account_keys_hex, acc_buff)");
status = "BAD";
return false;
}
if (!epee::serialization::load_t_from_binary(acc, acc_buff))
{
LOG_ERROR("Failed to load_t_from_binary(acc, acc_buff)");
status = "BAD";
return false;
}
//extract transaction
currency::transaction tx = AUTO_VAL_INIT(tx);
std::string buff;
if (!string_tools::parse_hexstr_to_binbuff(get_tx_rsp.txs_as_hex.back(), buff))
{
LOG_ERROR("Failed to parse_hexstr_to_binbuff(get_tx_rsp.txs_as_hex.back(), buff)");
status = "INTERNAL_ERROR";
return false;
}
if (!currency::parse_and_validate_tx_from_blob(buff, tx))
{
LOG_ERROR("Failed to currency::parse_and_validate_tx_from_blob(buff, tx)");
status = "INTERNAL_ERROR";
return false;
}
crypto::public_key tx_pub_key = currency::get_tx_pub_key_from_extra(tx);
if (tx_pub_key == currency::null_pkey)
{
LOG_ERROR("Failed to currency::get_tx_pub_key_from_extra(tx)");
status = "BAD";
return false;
}
//get transaction global output indices
currency::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request get_ind_req = AUTO_VAL_INIT(get_ind_req);
currency::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response get_ind_rsp = AUTO_VAL_INIT(get_ind_rsp);
get_ind_req.txid = currency::get_transaction_hash(tx);
if (!m_wallet.get_core_proxy()->call_COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES(get_ind_req, get_ind_rsp)
|| get_ind_rsp.status != CORE_RPC_STATUS_OK
|| get_ind_rsp.o_indexes.size() != tx.vout.size())
{
LOG_ERROR("Problem with call_COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES(....) ");
status = "INTERNAL_ERROR";
return false;
}
//prepare inputs
std::vector<currency::tx_source_entry> sources;
size_t i = 0;
uint64_t amount = 0;
for (auto& o : get_ind_rsp.o_indexes)
{
//check if input is for telepod's address
if (currency::is_out_to_acc(acc.get_keys(), boost::get<currency::txout_to_key>(tx.vout[i].target), tx_pub_key, i))
{
//income output
amount += tx.vout[i].amount;
sources.resize(sources.size() + 1);
currency::tx_source_entry& tse = sources.back();
tse.amount = tx.vout[i].amount;
tse.outputs.push_back(currency::tx_source_entry::output_entry(o, boost::get<currency::txout_to_key>(tx.vout[i].target).key));
tse.real_out_tx_key = tx_pub_key;
tse.real_output = 0;
tse.real_output_in_tx_index = i;
}
++i;
}
//prepare outputs
std::vector<currency::tx_destination_entry> dsts(1);
currency::tx_destination_entry& dst = dsts.back();
dst.addr.push_back(acc2);
dst.amount = amount - m_wallet.get_core_runtime_config().tx_default_fee;
//generate transaction
const std::vector<currency::extra_v> extra;
const std::vector<currency::attachment_v> attachments;
crypto::secret_key sk;
bool r = currency::construct_tx(acc.get_keys(), sources, dsts, extra, attachments, tx2, sk, 0);
if (!r)
{
LOG_ERROR("Problem with construct_tx(....) ");
status = "INTERNAL_ERROR";
return false;
}
if (CURRENCY_MAX_TRANSACTION_BLOB_SIZE <= get_object_blobsize(tx2))
{
LOG_ERROR("Problem with construct_tx(....), blobl size os too big: " << get_object_blobsize(tx2));
status = "INTERNAL_ERROR";
return false;
}
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_clonetelepod(const wallet_rpc::COMMAND_RPC_CLONETELEPOD::request& req, wallet_rpc::COMMAND_RPC_CLONETELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx)
{
currency::transaction tx2 = AUTO_VAL_INIT(tx2);
//new destination account
currency::account_base acc2 = AUTO_VAL_INIT(acc2);
acc2.generate();
if (!build_transaction_from_telepod(req.tpd, acc2.get_keys().m_account_address, tx2, res.status))
{
LOG_ERROR("Failed to build_transaction_from_telepod(...)");
return true;
}
//send transaction to daemon
currency::COMMAND_RPC_SEND_RAW_TX::request req_send_raw;
req_send_raw.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(tx2));
currency::COMMAND_RPC_SEND_RAW_TX::response rsp_send_raw;
bool r = m_wallet.get_core_proxy()->call_COMMAND_RPC_SEND_RAW_TX(req_send_raw, rsp_send_raw);
if (!r || rsp_send_raw.status != CORE_RPC_STATUS_OK)
{
LOG_ERROR("Problem with construct_tx(....), blobl size os too big: " << get_object_blobsize(tx2));
res.status = "INTERNAL_ERROR";
return true;
}
res.tpd.basement_tx_id_hex = string_tools::pod_to_hex(currency::get_transaction_hash(tx2));
std::string acc2_buff = epee::serialization::store_t_to_binary(acc2);
res.tpd.account_keys_hex = string_tools::buff_to_hex_nodelimer(acc2_buff);
res.status = "OK";
LOG_PRINT_GREEN("TELEPOD ISSUED [" << currency::print_money(currency::get_outs_money_amount(tx2)) << "BBR, base_tx_id: ]" << currency::get_transaction_hash(tx2), LOG_LEVEL_0);
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_telepodstatus(const wallet_rpc::COMMAND_RPC_TELEPODSTATUS::request& req, wallet_rpc::COMMAND_RPC_TELEPODSTATUS::response& res, epee::json_rpc::error& er, connection_context& cntx)
{
currency::transaction tx2 = AUTO_VAL_INIT(tx2);
//new destination account
currency::account_base acc2 = AUTO_VAL_INIT(acc2);
acc2.generate();
if (!build_transaction_from_telepod(req.tpd, acc2.get_keys().m_account_address, tx2, res.status))
{
return true;
}
//check if transaction is spent
currency::COMMAND_RPC_CHECK_KEYIMAGES::request req_ki = AUTO_VAL_INIT(req_ki);
currency::COMMAND_RPC_CHECK_KEYIMAGES::response rsp_ki = AUTO_VAL_INIT(rsp_ki);
for (auto& i : tx2.vin)
req_ki.images.push_back(boost::get<currency::txin_to_key>(i).k_image);
if (!m_wallet.get_core_proxy()->call_COMMAND_RPC_COMMAND_RPC_CHECK_KEYIMAGES(req_ki, rsp_ki)
|| rsp_ki.status != CORE_RPC_STATUS_OK
|| rsp_ki.images_stat.size() != req_ki.images.size())
{
LOG_ERROR("Problem with call_COMMAND_RPC_COMMAND_RPC_CHECK_KEYIMAGES(....)");
res.status = "INTERNAL_ERROR";
return true;
}
for (auto s : rsp_ki.images_stat)
{
if (!s)
{
res.status = "SPENT";
return true;
}
}
res.status = "OK";
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_withdrawtelepod(const wallet_rpc::COMMAND_RPC_WITHDRAWTELEPOD::request& req, wallet_rpc::COMMAND_RPC_WITHDRAWTELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx)
{
currency::transaction tx2 = AUTO_VAL_INIT(tx2);
//parse destination add
currency::account_public_address acc_addr = AUTO_VAL_INIT(acc_addr);
if (!currency::get_account_address_from_str(acc_addr, req.addr))
{
LOG_ERROR("Failed to build_transaction_from_telepod(...)");
res.status = "BAD_ADDRESS";
return true;
}
if (!build_transaction_from_telepod(req.tpd, acc_addr, tx2, res.status))
{
LOG_ERROR("Failed to build_transaction_from_telepod(...)");
return true;
}
//send transaction to daemon
currency::COMMAND_RPC_SEND_RAW_TX::request req_send_raw;
req_send_raw.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(tx2));
currency::COMMAND_RPC_SEND_RAW_TX::response rsp_send_raw;
bool r = m_wallet.get_core_proxy()->call_COMMAND_RPC_SEND_RAW_TX(req_send_raw, rsp_send_raw);
if (!r || rsp_send_raw.status != CORE_RPC_STATUS_OK)
{
LOG_ERROR("Problem with construct_tx(....), blobl size os too big: " << get_object_blobsize(tx2));
res.status = "INTERNAL_ERROR";
return true;
}
res.status = "OK";
LOG_PRINT_GREEN("TELEPOD WITHDRAWN [" << currency::print_money(currency::get_outs_money_amount(tx2)) << "BBR, tx_id: ]" << currency::get_transaction_hash(tx2), LOG_LEVEL_0);
return true;
}
}
} // namespace tools

View file

@ -48,12 +48,6 @@ namespace tools
MAP_JON_RPC_WE("split_integrated_address", on_split_integrated_address, wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS)
MAP_JON_RPC_WE("sign_transfer", on_sign_transfer, wallet_rpc::COMMAND_SIGN_TRANSFER)
MAP_JON_RPC_WE("submit_transfer", on_submit_transfer, wallet_rpc::COMMAND_SUBMIT_TRANSFER)
// supernet api
MAP_JON_RPC_WE("maketelepod", on_maketelepod, wallet_rpc::COMMAND_RPC_MAKETELEPOD)
MAP_JON_RPC_WE("clonetelepod", on_clonetelepod, wallet_rpc::COMMAND_RPC_CLONETELEPOD)
MAP_JON_RPC_WE("telepodstatus", on_telepodstatus, wallet_rpc::COMMAND_RPC_TELEPODSTATUS)
MAP_JON_RPC_WE("withdrawtelepod", on_withdrawtelepod, wallet_rpc::COMMAND_RPC_WITHDRAWTELEPOD)
END_JSON_RPC_MAP()
END_URI_MAP2()
@ -69,13 +63,7 @@ namespace tools
bool on_sign_transfer(const wallet_rpc::COMMAND_SIGN_TRANSFER::request& req, wallet_rpc::COMMAND_SIGN_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx);
bool on_submit_transfer(const wallet_rpc::COMMAND_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx);
bool on_maketelepod(const wallet_rpc::COMMAND_RPC_MAKETELEPOD::request& req, wallet_rpc::COMMAND_RPC_MAKETELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx);
bool on_clonetelepod(const wallet_rpc::COMMAND_RPC_CLONETELEPOD::request& req, wallet_rpc::COMMAND_RPC_CLONETELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx);
bool on_telepodstatus(const wallet_rpc::COMMAND_RPC_TELEPODSTATUS::request& req, wallet_rpc::COMMAND_RPC_TELEPODSTATUS::response& res, epee::json_rpc::error& er, connection_context& cntx);
bool on_withdrawtelepod(const wallet_rpc::COMMAND_RPC_WITHDRAWTELEPOD::request& req, wallet_rpc::COMMAND_RPC_WITHDRAWTELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx);
bool handle_command_line(const boost::program_options::variables_map& vm);
bool build_transaction_from_telepod(const wallet_rpc::telepod& tlp, const currency::account_public_address& acc2, currency::transaction& tx2, std::string& status);
private:
wallet2& m_wallet;
@ -83,4 +71,5 @@ namespace tools
std::string m_bind_ip;
bool m_do_mint;
};
}
} // namespace tools

View file

@ -231,9 +231,11 @@ namespace wallet_rpc
struct response
{
std::string tx_hash;
std::string tx_unsigned_hex; // for cold-signing process
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash)
KV_SERIALIZE(tx_unsigned_hex)
END_KV_SERIALIZE_MAP()
};
};