Skip to content
Permalink
Browse files

wallet_rpc_server: add describe_transfer

* add describe_transfer endpoint
* optionally return tx keys in sign_transfer
* add multisig tx sets to describe_transfer rpc endpoint
  • Loading branch information...
spoke0 committed Mar 14, 2019
1 parent 6cadbdc commit bf20d4e0bba8dcfbc0aa7f8b41664ddd6b00bddb
@@ -5614,6 +5614,10 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin
ptx.construction_data = sd;

txs.push_back(ptx);

// add tx keys only to ptx
txs.back().tx_key = tx_key;
txs.back().additional_tx_keys = additional_tx_keys;
}

// add key images
@@ -5871,17 +5875,17 @@ bool wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector, const
return epee::file_io_utils::save_string_to_file(filename, ciphertext);
}
//----------------------------------------------------------------------------------------------------
bool wallet2::load_multisig_tx(cryptonote::blobdata s, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func)
bool wallet2::parse_multisig_tx_from_str(std::string multisig_tx_st, multisig_tx_set &exported_txs) const
{
const size_t magiclen = strlen(MULTISIG_UNSIGNED_TX_PREFIX);
if (strncmp(s.c_str(), MULTISIG_UNSIGNED_TX_PREFIX, magiclen))
if (strncmp(multisig_tx_st.c_str(), MULTISIG_UNSIGNED_TX_PREFIX, magiclen))
{
LOG_PRINT_L0("Bad magic from multisig tx data");
return false;
}
try
{
s = decrypt_with_view_secret_key(std::string(s, magiclen));
multisig_tx_st = decrypt_with_view_secret_key(std::string(multisig_tx_st, magiclen));
}
catch (const std::exception &e)
{
@@ -5890,7 +5894,7 @@ bool wallet2::load_multisig_tx(cryptonote::blobdata s, multisig_tx_set &exported
}
try
{
std::istringstream iss(s);
std::istringstream iss(multisig_tx_st);
boost::archive::portable_binary_iarchive ar(iss);
ar >> exported_txs;
}
@@ -5912,6 +5916,17 @@ bool wallet2::load_multisig_tx(cryptonote::blobdata s, multisig_tx_set &exported
CHECK_AND_ASSERT_MES(ptx.construction_data.sources.size() == ptx.tx.vin.size(), false, "Mismatched sources/vin sizes");
}

return true;
}
//----------------------------------------------------------------------------------------------------
bool wallet2::load_multisig_tx(cryptonote::blobdata s, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func)
{
if(!parse_multisig_tx_from_str(s, exported_txs))
{
LOG_PRINT_L0("Failed to parse multisig transaction from string");
return false;
}

LOG_PRINT_L1("Loaded multisig tx unsigned data from binary: " << exported_txs.m_ptx.size() << " transactions");
for (auto &ptx: exported_txs.m_ptx) LOG_PRINT_L0(cryptonote::obj_to_json_str(ptx.tx));

@@ -764,6 +764,7 @@ namespace tools
std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices);
std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra);
std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra);
bool parse_multisig_tx_from_str(std::string multisig_tx_st, multisig_tx_set &exported_txs) const;
bool load_multisig_tx(cryptonote::blobdata blob, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func = NULL);
bool load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func = NULL);
bool sign_multisig_tx_from_file(const std::string &filename, std::vector<crypto::hash> &txids, std::function<bool(const multisig_tx_set&)> accept_func);
@@ -983,6 +983,8 @@ namespace tools
for (auto &ptx: ptxs)
{
res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
if (req.get_tx_keys)
res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key));
}

if (req.export_raw)
@@ -996,6 +998,201 @@ namespace tools
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_describe_transfer(const wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::response& res, epee::json_rpc::error& er)
{
if (!m_wallet) return not_open(er);
if (m_restricted)
{
er.code = WALLET_RPC_ERROR_CODE_DENIED;
er.message = "Command unavailable in restricted mode.";
return false;
}
if (m_wallet->key_on_device())
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "command not supported by HW wallet";
return false;
}
if(m_wallet->watch_only())
{
er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY;
er.message = "command not supported by watch-only wallet";
return false;
}
if(req.unsigned_txset.empty() && req.multisig_txset.empty())
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "no txset provided";
return false;
}

std::vector <wallet2::tx_construction_data> tx_constructions;
if (!req.unsigned_txset.empty()) {
try {
tools::wallet2::unsigned_tx_set exported_txs;
cryptonote::blobdata blob;
if (!epee::string_tools::parse_hexstr_to_binbuff(req.unsigned_txset, blob)) {
er.code = WALLET_RPC_ERROR_CODE_BAD_HEX;
er.message = "Failed to parse hex.";
return false;
}
if (!m_wallet->parse_unsigned_tx_from_str(blob, exported_txs)) {
er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA;
er.message = "cannot load unsigned_txset";
return false;
}
tx_constructions = exported_txs.txes;
}
catch (const std::exception &e) {
er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA;
er.message = "failed to parse unsigned transfers: " + std::string(e.what());
return false;
}
} else if (!req.multisig_txset.empty()) {
try {
tools::wallet2::multisig_tx_set exported_txs;
cryptonote::blobdata blob;
if (!epee::string_tools::parse_hexstr_to_binbuff(req.multisig_txset, blob)) {
er.code = WALLET_RPC_ERROR_CODE_BAD_HEX;
er.message = "Failed to parse hex.";
return false;
}
if (!m_wallet->parse_multisig_tx_from_str(blob, exported_txs)) {
er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_TX_DATA;
er.message = "cannot load multisig_txset";
return false;
}

for (size_t n = 0; n < exported_txs.m_ptx.size(); ++n) {
tx_constructions.push_back(exported_txs.m_ptx[n].construction_data);
}
}
catch (const std::exception &e) {
er.code = WALLET_RPC_ERROR_CODE_BAD_MULTISIG_TX_DATA;
er.message = "failed to parse multisig transfers: " + std::string(e.what());
return false;
}
}

std::vector<tools::wallet2::pending_tx> ptx;
try
{
// gather info to ask the user
std::unordered_map<cryptonote::account_public_address, std::pair<std::string, uint64_t>> dests;
int first_known_non_zero_change_index = -1;
for (size_t n = 0; n < tx_constructions.size(); ++n)
{
const tools::wallet2::tx_construction_data &cd = tx_constructions[n];
res.desc.push_back({0, 0, std::numeric_limits<uint32_t>::max(), 0, {}, "", 0, "", 0, 0, ""});
wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::transfer_description &desc = res.desc.back();

std::vector<cryptonote::tx_extra_field> tx_extra_fields;
bool has_encrypted_payment_id = false;
crypto::hash8 payment_id8 = crypto::null_hash8;
if (cryptonote::parse_tx_extra(cd.extra, tx_extra_fields))
{
cryptonote::tx_extra_nonce extra_nonce;
if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce))
{
crypto::hash payment_id;
if(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8))
{
desc.payment_id = epee::string_tools::pod_to_hex(payment_id8);
has_encrypted_payment_id = true;
}
else if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id))
{
desc.payment_id = epee::string_tools::pod_to_hex(payment_id);
}
}
}

for (size_t s = 0; s < cd.sources.size(); ++s)
{
desc.amount_in += cd.sources[s].amount;
size_t ring_size = cd.sources[s].outputs.size();
if (ring_size < desc.ring_size)
desc.ring_size = ring_size;
}
for (size_t d = 0; d < cd.splitted_dsts.size(); ++d)
{
const cryptonote::tx_destination_entry &entry = cd.splitted_dsts[d];
std::string address = cryptonote::get_account_address_as_str(m_wallet->nettype(), entry.is_subaddress, entry.addr);
if (has_encrypted_payment_id && !entry.is_subaddress)
address = cryptonote::get_account_integrated_address_as_str(m_wallet->nettype(), entry.addr, payment_id8);
auto i = dests.find(entry.addr);
if (i == dests.end())
dests.insert(std::make_pair(entry.addr, std::make_pair(address, entry.amount)));
else
i->second.second += entry.amount;
desc.amount_out += entry.amount;
}
if (cd.change_dts.amount > 0)
{
auto it = dests.find(cd.change_dts.addr);
if (it == dests.end())
{
er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA;
er.message = "Claimed change does not go to a paid address";
return false;
}
if (it->second.second < cd.change_dts.amount)
{
er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA;
er.message = "Claimed change is larger than payment to the change address";
return false;
}
if (cd.change_dts.amount > 0)
{
if (first_known_non_zero_change_index == -1)
first_known_non_zero_change_index = n;
const tools::wallet2::tx_construction_data &cdn = tx_constructions[first_known_non_zero_change_index];
if (memcmp(&cd.change_dts.addr, &cdn.change_dts.addr, sizeof(cd.change_dts.addr)))
{
er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA;
er.message = "Change goes to more than one address";
return false;
}
}
desc.change_amount += cd.change_dts.amount;
it->second.second -= cd.change_dts.amount;
if (it->second.second == 0)
dests.erase(cd.change_dts.addr);
}

size_t n_dummy_outputs = 0;
for (auto i = dests.begin(); i != dests.end(); )
{
if (i->second.second > 0)
{
desc.recipients.push_back({i->second.first, i->second.second});
}
else
++desc.dummy_outputs;
++i;
}

if (desc.change_amount > 0)
{
const tools::wallet2::tx_construction_data &cd0 = tx_constructions[0];
desc.change_address = get_account_address_as_str(m_wallet->nettype(), cd0.subaddr_account > 0, cd0.change_dts.addr);
}

desc.fee = desc.amount_in - desc.amount_out;
desc.unlock_time = cd.unlock_time;
desc.extra = epee::to_hex::string({cd.extra.data(), cd.extra.size()});
}
}
catch (const std::exception &e)
{
er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA;
er.message = "failed to parse unsigned transfers";
return false;
}

return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_submit_transfer(const wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er)
{
if (!m_wallet) return not_open(er);
@@ -86,6 +86,7 @@ namespace tools
MAP_JON_RPC_WE("transfer", on_transfer, wallet_rpc::COMMAND_RPC_TRANSFER)
MAP_JON_RPC_WE("transfer_split", on_transfer_split, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT)
MAP_JON_RPC_WE("sign_transfer", on_sign_transfer, wallet_rpc::COMMAND_RPC_SIGN_TRANSFER)
MAP_JON_RPC_WE("describe_transfer", on_describe_transfer, wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER)
MAP_JON_RPC_WE("submit_transfer", on_submit_transfer, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER)
MAP_JON_RPC_WE("sweep_dust", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST)
MAP_JON_RPC_WE("sweep_unmixable", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST)
@@ -166,6 +167,7 @@ namespace tools
bool on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er);
bool on_transfer_split(const wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::request& req, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::response& res, epee::json_rpc::error& er);
bool on_sign_transfer(const wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::response& res, epee::json_rpc::error& er);
bool on_describe_transfer(const wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::response& res, epee::json_rpc::error& er);
bool on_submit_transfer(const wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er);
bool on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er);
bool on_sweep_all(const wallet_rpc::COMMAND_RPC_SWEEP_ALL::request& req, wallet_rpc::COMMAND_RPC_SWEEP_ALL::response& res, epee::json_rpc::error& er);
@@ -47,7 +47,7 @@
// advance which version they will stop working with
// Don't go over 32767 for any of these
#define WALLET_RPC_VERSION_MAJOR 1
#define WALLET_RPC_VERSION_MINOR 4
#define WALLET_RPC_VERSION_MINOR 5
#define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor))
#define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR)
namespace tools
@@ -531,16 +531,81 @@ namespace wallet_rpc
};
};

struct COMMAND_RPC_DESCRIBE_TRANSFER
{
struct recipient
{
std::string address;
uint64_t amount;

BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(address)
KV_SERIALIZE(amount)
END_KV_SERIALIZE_MAP()
};

struct transfer_description
{
uint64_t amount_in;
uint64_t amount_out;
uint32_t ring_size;
uint64_t unlock_time;
std::list<recipient> recipients;
std::string payment_id;
uint64_t change_amount;
std::string change_address;
uint64_t fee;
uint32_t dummy_outputs;
std::string extra;

BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(amount_in)
KV_SERIALIZE(amount_out)
KV_SERIALIZE(ring_size)
KV_SERIALIZE(unlock_time)
KV_SERIALIZE(recipients)
KV_SERIALIZE(payment_id)
KV_SERIALIZE(change_amount)
KV_SERIALIZE(change_address)
KV_SERIALIZE(fee)
KV_SERIALIZE(dummy_outputs)
KV_SERIALIZE(extra)
END_KV_SERIALIZE_MAP()
};

struct request
{
std::string unsigned_txset;
std::string multisig_txset;

BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(unsigned_txset)
KV_SERIALIZE(multisig_txset)
END_KV_SERIALIZE_MAP()
};

struct response
{
std::list<transfer_description> desc;

BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(desc)
END_KV_SERIALIZE_MAP()
};
};

struct COMMAND_RPC_SIGN_TRANSFER
{
struct request
{
std::string unsigned_txset;
bool export_raw;
bool get_tx_keys;

BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(unsigned_txset)
KV_SERIALIZE_OPT(export_raw, false)
KV_SERIALIZE_OPT(get_tx_keys, false)
END_KV_SERIALIZE_MAP()
};

@@ -549,11 +614,13 @@ namespace wallet_rpc
std::string signed_txset;
std::list<std::string> tx_hash_list;
std::list<std::string> tx_raw_list;
std::list<std::string> tx_key_list;

BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(signed_txset)
KV_SERIALIZE(tx_hash_list)
KV_SERIALIZE(tx_raw_list)
KV_SERIALIZE(tx_key_list)
END_KV_SERIALIZE_MAP()
};
};

0 comments on commit bf20d4e

Please sign in to comment.
You can’t perform that action at this time.