wallet2: correctly handle in/out payments for view-only wallet

This commit is contained in:
stoffu 2017-08-30 11:30:31 +09:00
parent a6403846ef
commit b5cbdce8a6
No known key found for this signature in database
GPG Key ID: 41DAB8343A9EC012
3 changed files with 146 additions and 2 deletions

View File

@ -510,11 +510,12 @@ namespace cryptonote
e.in_pool = pool_tx_hashes.find(tx_hash) != pool_tx_hashes.end(); e.in_pool = pool_tx_hashes.find(tx_hash) != pool_tx_hashes.end();
if (e.in_pool) if (e.in_pool)
{ {
e.block_height = std::numeric_limits<uint64_t>::max(); e.block_height = e.block_timestamp = std::numeric_limits<uint64_t>::max();
} }
else else
{ {
e.block_height = m_core.get_blockchain_storage().get_db().get_tx_block_height(tx_hash); e.block_height = m_core.get_blockchain_storage().get_db().get_tx_block_height(tx_hash);
e.block_timestamp = m_core.get_blockchain_storage().get_db().get_block_timestamp(e.block_height);
} }
// fill up old style responses too, in case an old wallet asks // fill up old style responses too, in case an old wallet asks

View File

@ -49,7 +49,7 @@ namespace cryptonote
// advance which version they will stop working with // advance which version they will stop working with
// Don't go over 32767 for any of these // Don't go over 32767 for any of these
#define CORE_RPC_VERSION_MAJOR 1 #define CORE_RPC_VERSION_MAJOR 1
#define CORE_RPC_VERSION_MINOR 13 #define CORE_RPC_VERSION_MINOR 14
#define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor))
#define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR) #define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR)
@ -215,6 +215,7 @@ namespace cryptonote
std::string as_json; std::string as_json;
bool in_pool; bool in_pool;
uint64_t block_height; uint64_t block_height;
uint64_t block_timestamp;
std::vector<uint64_t> output_indices; std::vector<uint64_t> output_indices;
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
@ -223,6 +224,7 @@ namespace cryptonote
KV_SERIALIZE(as_json) KV_SERIALIZE(as_json)
KV_SERIALIZE(in_pool) KV_SERIALIZE(in_pool)
KV_SERIALIZE(block_height) KV_SERIALIZE(block_height)
KV_SERIALIZE(block_timestamp)
KV_SERIALIZE(output_indices) KV_SERIALIZE(output_indices)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };

View File

@ -5381,6 +5381,9 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
} }
spent = 0; spent = 0;
unspent = 0; unspent = 0;
std::unordered_set<crypto::hash> spent_txids; // For each spent key image, search for a tx in m_transfers that uses it as input.
std::vector<size_t> swept_transfers; // If such a spending tx wasn't found in m_transfers, this means the spending tx
// was created by sweep_all, so we can't know the spent height and other detailed info.
for(size_t i = 0; i < m_transfers.size(); ++i) for(size_t i = 0; i < m_transfers.size(); ++i)
{ {
transfer_details &td = m_transfers[i]; transfer_details &td = m_transfers[i];
@ -5391,8 +5394,146 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
unspent += amount; unspent += amount;
LOG_PRINT_L2("Transfer " << i << ": " << print_money(amount) << " (" << td.m_global_output_index << "): " LOG_PRINT_L2("Transfer " << i << ": " << print_money(amount) << " (" << td.m_global_output_index << "): "
<< (td.m_spent ? "spent" : "unspent") << " (key image " << req.key_images[i] << ")"); << (td.m_spent ? "spent" : "unspent") << " (key image " << req.key_images[i] << ")");
if (i < daemon_resp.spent_status.size() && daemon_resp.spent_status[i] == COMMAND_RPC_IS_KEY_IMAGE_SPENT::SPENT_IN_BLOCKCHAIN)
{
bool is_spent_tx_found = false;
for (auto it = m_transfers.rbegin(); &(*it) != &td; ++it)
{
bool is_spent_tx = false;
for(const cryptonote::txin_v& in : it->m_tx.vin)
{
if(in.type() == typeid(cryptonote::txin_to_key) && td.m_key_image == boost::get<cryptonote::txin_to_key>(in).k_image)
{
is_spent_tx = true;
break;
}
}
if (is_spent_tx)
{
is_spent_tx_found = true;
spent_txids.insert(it->m_txid);
break;
}
}
if (!is_spent_tx_found)
swept_transfers.push_back(i);
}
} }
MDEBUG("Total: " << print_money(spent) << " spent, " << print_money(unspent) << " unspent"); MDEBUG("Total: " << print_money(spent) << " spent, " << print_money(unspent) << " unspent");
if (check_spent)
{
// query outgoing txes
COMMAND_RPC_GET_TRANSACTIONS::request gettxs_req;
COMMAND_RPC_GET_TRANSACTIONS::response gettxs_res;
gettxs_req.decode_as_json = false;
for (const crypto::hash& spent_txid : spent_txids)
gettxs_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(spent_txid));
m_daemon_rpc_mutex.lock();
bool r = epee::net_utils::invoke_http_json("/gettransactions", gettxs_req, gettxs_res, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions");
THROW_WALLET_EXCEPTION_IF(gettxs_res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions");
THROW_WALLET_EXCEPTION_IF(gettxs_res.txs.size() != spent_txids.size(), error::wallet_internal_error,
"daemon returned wrong response for gettransactions, wrong count = " + std::to_string(gettxs_res.txs.size()) + ", expected " + std::to_string(spent_txids.size()));
// process each outgoing tx
auto spent_txid = spent_txids.begin();
for (const COMMAND_RPC_GET_TRANSACTIONS::entry& e : gettxs_res.txs)
{
THROW_WALLET_EXCEPTION_IF(e.in_pool, error::wallet_internal_error, "spent tx isn't supposed to be in txpool");
// parse tx
cryptonote::blobdata bd;
THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(e.as_hex, bd), error::wallet_internal_error, "parse_hexstr_to_binbuff failed");
cryptonote::transaction spent_tx;
crypto::hash spnet_txid_parsed, spent_txid_prefix;
THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, spent_tx, spnet_txid_parsed, spent_txid_prefix), error::wallet_internal_error, "parse_and_validate_tx_from_blob failed");
THROW_WALLET_EXCEPTION_IF(*spent_txid != spnet_txid_parsed, error::wallet_internal_error, "parsed txid mismatch");
// get received (change) amount
uint64_t tx_money_got_in_outs = 0;
const cryptonote::account_keys& keys = m_account.get_keys();
const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(spent_tx);
crypto::key_derivation derivation;
generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation);
size_t output_index = 0;
for (const cryptonote::tx_out& out : spent_tx.vout)
{
uint64_t money_transfered = 0;
bool error = false, received = false;
check_acc_out_precomp(keys.m_account_address.m_spend_public_key, out, derivation, output_index, received, money_transfered, error);
THROW_WALLET_EXCEPTION_IF(error, error::wallet_internal_error, "check_acc_out_precomp failed");
if (received)
{
if (money_transfered == 0)
{
rct::key mask;
money_transfered = tools::decodeRct(spent_tx.rct_signatures, tx_pub_key, keys.m_view_secret_key, output_index, mask);
}
tx_money_got_in_outs += money_transfered;
}
++output_index;
}
// get spent amount
uint64_t tx_money_spent_in_ins = 0;
for (const cryptonote::txin_v& in : spent_tx.vin)
{
if (in.type() != typeid(cryptonote::txin_to_key))
continue;
auto it = m_key_images.find(boost::get<cryptonote::txin_to_key>(in).k_image);
if (it != m_key_images.end())
{
const transfer_details& td = m_transfers[it->second];
uint64_t amount = boost::get<cryptonote::txin_to_key>(in).amount;
if (amount > 0)
{
THROW_WALLET_EXCEPTION_IF(amount != td.amount(), error::wallet_internal_error,
std::string("Inconsistent amount in tx input: got ") + print_money(amount) +
std::string(", expected ") + print_money(td.amount()));
}
amount = td.amount();
tx_money_spent_in_ins += amount;
LOG_PRINT_L0("Spent money: " << print_money(amount) << ", with tx: " << *spent_txid);
set_spent(it->second, e.block_height);
if (m_callback)
m_callback->on_money_spent(e.block_height, *spent_txid, spent_tx, amount, spent_tx);
}
}
// create outgoing payment
process_outgoing(*spent_txid, spent_tx, e.block_height, e.block_timestamp, tx_money_spent_in_ins, tx_money_got_in_outs);
// erase corresponding incoming payment
for (auto j = m_payments.begin(); j != m_payments.end(); ++j)
{
if (j->second.m_tx_hash == *spent_txid)
{
m_payments.erase(j);
break;
}
}
++spent_txid;
}
for (size_t n : swept_transfers)
{
const transfer_details& td = m_transfers[n];
confirmed_transfer_details pd;
pd.m_change = (uint64_t)-1; // cahnge is unknown
pd.m_amount_in = pd.m_amount_out = td.amount(); // fee is unknown
std::string err;
pd.m_block_height = get_daemon_blockchain_height(err); // spent block height is unknown, so hypothetically set to the highest
crypto::hash spent_txid = crypto::rand<crypto::hash>(); // spent txid is unknown, so hypothetically set to random
m_confirmed_txs.insert(std::make_pair(spent_txid, pd));
}
}
return m_transfers[signed_key_images.size() - 1].m_block_height; return m_transfers[signed_key_images.size() - 1].m_block_height;
} }
wallet2::payment_container wallet2::export_payments() const wallet2::payment_container wallet2::export_payments() const