track double spending in the txpool
Transactions in the txpool are marked when another transaction is seen double spending one or more of its inputs. This is then exposed wherever appropriate. Note that being marked with this "double spend seen" flag does NOT mean this transaction IS a double spend and will never be mined: it just means that the network has seen at least another transaction spending at least one of the same inputs, so care should be taken to wait for a few confirmations before acting upon that transaction (ie, mostly of use for merchants wanting to accept unconfirmed transactions).
This commit is contained in:
parent
3dd31d33fa
commit
ccf53a566c
@ -147,8 +147,9 @@ struct txpool_tx_meta_t
|
|||||||
uint8_t kept_by_block;
|
uint8_t kept_by_block;
|
||||||
uint8_t relayed;
|
uint8_t relayed;
|
||||||
uint8_t do_not_relay;
|
uint8_t do_not_relay;
|
||||||
|
uint8_t double_spend_seen: 1;
|
||||||
|
|
||||||
uint8_t padding[77]; // till 192 bytes
|
uint8_t padding[76]; // till 192 bytes
|
||||||
};
|
};
|
||||||
|
|
||||||
#define DBF_SAFE 1
|
#define DBF_SAFE 1
|
||||||
|
@ -3166,9 +3166,9 @@ bool Blockchain::flush_txes_from_pool(const std::list<crypto::hash> &txids)
|
|||||||
cryptonote::transaction tx;
|
cryptonote::transaction tx;
|
||||||
size_t blob_size;
|
size_t blob_size;
|
||||||
uint64_t fee;
|
uint64_t fee;
|
||||||
bool relayed, do_not_relay;
|
bool relayed, do_not_relay, double_spend_seen;
|
||||||
MINFO("Removing txid " << txid << " from the pool");
|
MINFO("Removing txid " << txid << " from the pool");
|
||||||
if(m_tx_pool.have_tx(txid) && !m_tx_pool.take_tx(txid, tx, blob_size, fee, relayed, do_not_relay))
|
if(m_tx_pool.have_tx(txid) && !m_tx_pool.take_tx(txid, tx, blob_size, fee, relayed, do_not_relay, double_spend_seen))
|
||||||
{
|
{
|
||||||
MERROR("Failed to remove txid " << txid << " from the pool");
|
MERROR("Failed to remove txid " << txid << " from the pool");
|
||||||
res = false;
|
res = false;
|
||||||
@ -3351,7 +3351,7 @@ leave:
|
|||||||
transaction tx;
|
transaction tx;
|
||||||
size_t blob_size = 0;
|
size_t blob_size = 0;
|
||||||
uint64_t fee = 0;
|
uint64_t fee = 0;
|
||||||
bool relayed = false, do_not_relay = false;
|
bool relayed = false, do_not_relay = false, double_spend_seen = false;
|
||||||
TIME_MEASURE_START(aa);
|
TIME_MEASURE_START(aa);
|
||||||
|
|
||||||
// XXX old code does not check whether tx exists
|
// XXX old code does not check whether tx exists
|
||||||
@ -3368,7 +3368,7 @@ leave:
|
|||||||
TIME_MEASURE_START(bb);
|
TIME_MEASURE_START(bb);
|
||||||
|
|
||||||
// get transaction with hash <tx_id> from tx_pool
|
// get transaction with hash <tx_id> from tx_pool
|
||||||
if(!m_tx_pool.take_tx(tx_id, tx, blob_size, fee, relayed, do_not_relay))
|
if(!m_tx_pool.take_tx(tx_id, tx, blob_size, fee, relayed, do_not_relay, double_spend_seen))
|
||||||
{
|
{
|
||||||
MERROR_VER("Block with id: " << id << " has at least one unknown transaction with id: " << tx_id);
|
MERROR_VER("Block with id: " << id << " has at least one unknown transaction with id: " << tx_id);
|
||||||
bvc.m_verifivation_failed = true;
|
bvc.m_verifivation_failed = true;
|
||||||
@ -4381,12 +4381,12 @@ void Blockchain::load_compiled_in_block_hashes()
|
|||||||
|
|
||||||
size_t blob_size;
|
size_t blob_size;
|
||||||
uint64_t fee;
|
uint64_t fee;
|
||||||
bool relayed, do_not_relay;
|
bool relayed, do_not_relay, double_spend_seen;
|
||||||
transaction pool_tx;
|
transaction pool_tx;
|
||||||
for(const transaction &tx : txs)
|
for(const transaction &tx : txs)
|
||||||
{
|
{
|
||||||
crypto::hash tx_hash = get_transaction_hash(tx);
|
crypto::hash tx_hash = get_transaction_hash(tx);
|
||||||
m_tx_pool.take_tx(tx_hash, pool_tx, blob_size, fee, relayed, do_not_relay);
|
m_tx_pool.take_tx(tx_hash, pool_tx, blob_size, fee, relayed, do_not_relay, double_spend_seen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -187,6 +187,7 @@ namespace cryptonote
|
|||||||
{
|
{
|
||||||
if(have_tx_keyimges_as_spent(tx))
|
if(have_tx_keyimges_as_spent(tx))
|
||||||
{
|
{
|
||||||
|
mark_double_spend(tx);
|
||||||
LOG_PRINT_L1("Transaction with id= "<< id << " used already spent key images");
|
LOG_PRINT_L1("Transaction with id= "<< id << " used already spent key images");
|
||||||
tvc.m_verifivation_failed = true;
|
tvc.m_verifivation_failed = true;
|
||||||
tvc.m_double_spend = true;
|
tvc.m_double_spend = true;
|
||||||
@ -228,6 +229,7 @@ namespace cryptonote
|
|||||||
meta.last_relayed_time = time(NULL);
|
meta.last_relayed_time = time(NULL);
|
||||||
meta.relayed = relayed;
|
meta.relayed = relayed;
|
||||||
meta.do_not_relay = do_not_relay;
|
meta.do_not_relay = do_not_relay;
|
||||||
|
meta.double_spend_seen = have_tx_keyimges_as_spent(tx);
|
||||||
memset(meta.padding, 0, sizeof(meta.padding));
|
memset(meta.padding, 0, sizeof(meta.padding));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -266,6 +268,7 @@ namespace cryptonote
|
|||||||
meta.last_relayed_time = time(NULL);
|
meta.last_relayed_time = time(NULL);
|
||||||
meta.relayed = relayed;
|
meta.relayed = relayed;
|
||||||
meta.do_not_relay = do_not_relay;
|
meta.do_not_relay = do_not_relay;
|
||||||
|
meta.double_spend_seen = false;
|
||||||
memset(meta.padding, 0, sizeof(meta.padding));
|
memset(meta.padding, 0, sizeof(meta.padding));
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -354,7 +357,7 @@ namespace cryptonote
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
//---------------------------------------------------------------------------------
|
//---------------------------------------------------------------------------------
|
||||||
bool tx_memory_pool::take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed, bool &do_not_relay)
|
bool tx_memory_pool::take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed, bool &do_not_relay, bool &double_spend_seen)
|
||||||
{
|
{
|
||||||
CRITICAL_REGION_LOCAL(m_transactions_lock);
|
CRITICAL_REGION_LOCAL(m_transactions_lock);
|
||||||
CRITICAL_REGION_LOCAL1(m_blockchain);
|
CRITICAL_REGION_LOCAL1(m_blockchain);
|
||||||
@ -377,6 +380,7 @@ namespace cryptonote
|
|||||||
fee = meta.fee;
|
fee = meta.fee;
|
||||||
relayed = meta.relayed;
|
relayed = meta.relayed;
|
||||||
do_not_relay = meta.do_not_relay;
|
do_not_relay = meta.do_not_relay;
|
||||||
|
double_spend_seen = meta.double_spend_seen;
|
||||||
|
|
||||||
// remove first, in case this throws, so key images aren't removed
|
// remove first, in case this throws, so key images aren't removed
|
||||||
m_blockchain.remove_txpool_tx(id);
|
m_blockchain.remove_txpool_tx(id);
|
||||||
@ -594,6 +598,8 @@ namespace cryptonote
|
|||||||
uint64_t age = now - meta.receive_time + (now == meta.receive_time);
|
uint64_t age = now - meta.receive_time + (now == meta.receive_time);
|
||||||
agebytes[age].txs++;
|
agebytes[age].txs++;
|
||||||
agebytes[age].bytes += meta.blob_size;
|
agebytes[age].bytes += meta.blob_size;
|
||||||
|
if (meta.double_spend_seen)
|
||||||
|
++stats.num_double_spends;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
stats.bytes_med = epee::misc_utils::median(sizes);
|
stats.bytes_med = epee::misc_utils::median(sizes);
|
||||||
@ -649,6 +655,7 @@ namespace cryptonote
|
|||||||
m_blockchain.for_all_txpool_txes([&tx_infos, key_image_infos](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){
|
m_blockchain.for_all_txpool_txes([&tx_infos, key_image_infos](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){
|
||||||
tx_info txi;
|
tx_info txi;
|
||||||
txi.id_hash = epee::string_tools::pod_to_hex(txid);
|
txi.id_hash = epee::string_tools::pod_to_hex(txid);
|
||||||
|
txi.tx_blob = *bd;
|
||||||
transaction tx;
|
transaction tx;
|
||||||
if (!parse_and_validate_tx_from_blob(*bd, tx))
|
if (!parse_and_validate_tx_from_blob(*bd, tx))
|
||||||
{
|
{
|
||||||
@ -668,6 +675,7 @@ namespace cryptonote
|
|||||||
txi.relayed = meta.relayed;
|
txi.relayed = meta.relayed;
|
||||||
txi.last_relayed_time = meta.last_relayed_time;
|
txi.last_relayed_time = meta.last_relayed_time;
|
||||||
txi.do_not_relay = meta.do_not_relay;
|
txi.do_not_relay = meta.do_not_relay;
|
||||||
|
txi.double_spend_seen = meta.double_spend_seen;
|
||||||
tx_infos.push_back(txi);
|
tx_infos.push_back(txi);
|
||||||
return true;
|
return true;
|
||||||
}, true);
|
}, true);
|
||||||
@ -712,6 +720,7 @@ namespace cryptonote
|
|||||||
txi.relayed = meta.relayed;
|
txi.relayed = meta.relayed;
|
||||||
txi.last_relayed_time = meta.last_relayed_time;
|
txi.last_relayed_time = meta.last_relayed_time;
|
||||||
txi.do_not_relay = meta.do_not_relay;
|
txi.do_not_relay = meta.do_not_relay;
|
||||||
|
txi.double_spend_seen = meta.double_spend_seen;
|
||||||
tx_infos.push_back(txi);
|
tx_infos.push_back(txi);
|
||||||
return true;
|
return true;
|
||||||
}, true);
|
}, true);
|
||||||
@ -843,7 +852,10 @@ namespace cryptonote
|
|||||||
}
|
}
|
||||||
//if we here, transaction seems valid, but, anyway, check for key_images collisions with blockchain, just to be sure
|
//if we here, transaction seems valid, but, anyway, check for key_images collisions with blockchain, just to be sure
|
||||||
if(m_blockchain.have_tx_keyimges_as_spent(tx))
|
if(m_blockchain.have_tx_keyimges_as_spent(tx))
|
||||||
|
{
|
||||||
|
txd.double_spend_seen = true;
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
//transaction is ok.
|
//transaction is ok.
|
||||||
return true;
|
return true;
|
||||||
@ -871,6 +883,39 @@ namespace cryptonote
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
//---------------------------------------------------------------------------------
|
//---------------------------------------------------------------------------------
|
||||||
|
void tx_memory_pool::mark_double_spend(const transaction &tx)
|
||||||
|
{
|
||||||
|
CRITICAL_REGION_LOCAL(m_transactions_lock);
|
||||||
|
CRITICAL_REGION_LOCAL1(m_blockchain);
|
||||||
|
LockedTXN lock(m_blockchain);
|
||||||
|
for(size_t i = 0; i!= tx.vin.size(); i++)
|
||||||
|
{
|
||||||
|
CHECKED_GET_SPECIFIC_VARIANT(tx.vin[i], const txin_to_key, itk, void());
|
||||||
|
const key_images_container::const_iterator it = m_spent_key_images.find(itk.k_image);
|
||||||
|
if (it != m_spent_key_images.end())
|
||||||
|
{
|
||||||
|
for (const crypto::hash &txid: it->second)
|
||||||
|
{
|
||||||
|
txpool_tx_meta_t meta = m_blockchain.get_txpool_tx_meta(txid);
|
||||||
|
if (!meta.double_spend_seen)
|
||||||
|
{
|
||||||
|
MDEBUG("Marking " << txid << " as double spending " << itk.k_image);
|
||||||
|
meta.double_spend_seen = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_blockchain.update_txpool_tx(txid, meta);
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
MERROR("Failed to update tx meta: " << e.what());
|
||||||
|
// continue, not fatal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//---------------------------------------------------------------------------------
|
||||||
std::string tx_memory_pool::print_pool(bool short_format) const
|
std::string tx_memory_pool::print_pool(bool short_format) const
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
@ -890,6 +935,7 @@ namespace cryptonote
|
|||||||
ss << "blob_size: " << meta.blob_size << std::endl
|
ss << "blob_size: " << meta.blob_size << std::endl
|
||||||
<< "fee: " << print_money(meta.fee) << std::endl
|
<< "fee: " << print_money(meta.fee) << std::endl
|
||||||
<< "kept_by_block: " << (meta.kept_by_block ? 'T' : 'F') << std::endl
|
<< "kept_by_block: " << (meta.kept_by_block ? 'T' : 'F') << std::endl
|
||||||
|
<< "double_spend_seen: " << (meta.double_spend_seen ? 'T' : 'F') << std::endl
|
||||||
<< "max_used_block_height: " << meta.max_used_block_height << std::endl
|
<< "max_used_block_height: " << meta.max_used_block_height << std::endl
|
||||||
<< "max_used_block_id: " << meta.max_used_block_id << std::endl
|
<< "max_used_block_id: " << meta.max_used_block_id << std::endl
|
||||||
<< "last_failed_height: " << meta.last_failed_height << std::endl
|
<< "last_failed_height: " << meta.last_failed_height << std::endl
|
||||||
|
@ -137,10 +137,11 @@ namespace cryptonote
|
|||||||
* @param fee the transaction fee
|
* @param fee the transaction fee
|
||||||
* @param relayed return-by-reference was transaction relayed to us by the network?
|
* @param relayed return-by-reference was transaction relayed to us by the network?
|
||||||
* @param do_not_relay return-by-reference is transaction not to be relayed to the network?
|
* @param do_not_relay return-by-reference is transaction not to be relayed to the network?
|
||||||
|
* @param double_spend_seen return-by-reference was a double spend seen for that transaction?
|
||||||
*
|
*
|
||||||
* @return true unless the transaction cannot be found in the pool
|
* @return true unless the transaction cannot be found in the pool
|
||||||
*/
|
*/
|
||||||
bool take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed, bool &do_not_relay);
|
bool take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed, bool &do_not_relay, bool &double_spend_seen);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief checks if the pool has a transaction with the given hash
|
* @brief checks if the pool has a transaction with the given hash
|
||||||
@ -391,6 +392,8 @@ namespace cryptonote
|
|||||||
time_t last_relayed_time; //!< the last time the transaction was relayed to the network
|
time_t last_relayed_time; //!< the last time the transaction was relayed to the network
|
||||||
bool relayed; //!< whether or not the transaction has been relayed to the network
|
bool relayed; //!< whether or not the transaction has been relayed to the network
|
||||||
bool do_not_relay; //!< to avoid relay this transaction to the network
|
bool do_not_relay; //!< to avoid relay this transaction to the network
|
||||||
|
|
||||||
|
bool double_spend_seen; //!< true iff another tx was seen double spending this one
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -478,6 +481,11 @@ namespace cryptonote
|
|||||||
*/
|
*/
|
||||||
bool is_transaction_ready_to_go(txpool_tx_meta_t& txd, transaction &tx) const;
|
bool is_transaction_ready_to_go(txpool_tx_meta_t& txd, transaction &tx) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief mark all transactions double spending the one passed
|
||||||
|
*/
|
||||||
|
void mark_double_spend(const transaction &tx);
|
||||||
|
|
||||||
//TODO: confirm the below comments and investigate whether or not this
|
//TODO: confirm the below comments and investigate whether or not this
|
||||||
// is the desired behavior
|
// is the desired behavior
|
||||||
//! map key images to transactions which spent them
|
//! map key images to transactions which spent them
|
||||||
|
@ -840,6 +840,7 @@ bool t_rpc_command_executor::print_transaction_pool_long() {
|
|||||||
<< "relayed: " << [&](const cryptonote::tx_info &tx_info)->std::string { if (!tx_info.relayed) return "no"; return boost::lexical_cast<std::string>(tx_info.last_relayed_time) + " (" + get_human_time_ago(tx_info.last_relayed_time, now) + ")"; } (tx_info) << std::endl
|
<< "relayed: " << [&](const cryptonote::tx_info &tx_info)->std::string { if (!tx_info.relayed) return "no"; return boost::lexical_cast<std::string>(tx_info.last_relayed_time) + " (" + get_human_time_ago(tx_info.last_relayed_time, now) + ")"; } (tx_info) << std::endl
|
||||||
<< "do_not_relay: " << (tx_info.do_not_relay ? 'T' : 'F') << std::endl
|
<< "do_not_relay: " << (tx_info.do_not_relay ? 'T' : 'F') << std::endl
|
||||||
<< "kept_by_block: " << (tx_info.kept_by_block ? 'T' : 'F') << std::endl
|
<< "kept_by_block: " << (tx_info.kept_by_block ? 'T' : 'F') << std::endl
|
||||||
|
<< "double_spend_seen: " << (tx_info.double_spend_seen ? 'T' : 'F') << std::endl
|
||||||
<< "max_used_block_height: " << tx_info.max_used_block_height << std::endl
|
<< "max_used_block_height: " << tx_info.max_used_block_height << std::endl
|
||||||
<< "max_used_block_id: " << tx_info.max_used_block_id_hash << std::endl
|
<< "max_used_block_id: " << tx_info.max_used_block_id_hash << std::endl
|
||||||
<< "last_failed_height: " << tx_info.last_failed_height << std::endl
|
<< "last_failed_height: " << tx_info.last_failed_height << std::endl
|
||||||
@ -922,6 +923,7 @@ bool t_rpc_command_executor::print_transaction_pool_short() {
|
|||||||
<< "relayed: " << [&](const cryptonote::tx_info &tx_info)->std::string { if (!tx_info.relayed) return "no"; return boost::lexical_cast<std::string>(tx_info.last_relayed_time) + " (" + get_human_time_ago(tx_info.last_relayed_time, now) + ")"; } (tx_info) << std::endl
|
<< "relayed: " << [&](const cryptonote::tx_info &tx_info)->std::string { if (!tx_info.relayed) return "no"; return boost::lexical_cast<std::string>(tx_info.last_relayed_time) + " (" + get_human_time_ago(tx_info.last_relayed_time, now) + ")"; } (tx_info) << std::endl
|
||||||
<< "do_not_relay: " << (tx_info.do_not_relay ? 'T' : 'F') << std::endl
|
<< "do_not_relay: " << (tx_info.do_not_relay ? 'T' : 'F') << std::endl
|
||||||
<< "kept_by_block: " << (tx_info.kept_by_block ? 'T' : 'F') << std::endl
|
<< "kept_by_block: " << (tx_info.kept_by_block ? 'T' : 'F') << std::endl
|
||||||
|
<< "double_spend_seen: " << (tx_info.double_spend_seen ? 'T' : 'F') << std::endl
|
||||||
<< "max_used_block_height: " << tx_info.max_used_block_height << std::endl
|
<< "max_used_block_height: " << tx_info.max_used_block_height << std::endl
|
||||||
<< "max_used_block_id: " << tx_info.max_used_block_id_hash << std::endl
|
<< "max_used_block_id: " << tx_info.max_used_block_id_hash << std::endl
|
||||||
<< "last_failed_height: " << tx_info.last_failed_height << std::endl
|
<< "last_failed_height: " << tx_info.last_failed_height << std::endl
|
||||||
@ -984,7 +986,7 @@ bool t_rpc_command_executor::print_transaction_pool_stats() {
|
|||||||
|
|
||||||
tools::msg_writer() << n_transactions << " tx(es), " << res.pool_stats.bytes_total << " bytes total (min " << res.pool_stats.bytes_min << ", max " << res.pool_stats.bytes_max << ", avg " << avg_bytes << ", median " << res.pool_stats.bytes_med << ")" << std::endl
|
tools::msg_writer() << n_transactions << " tx(es), " << res.pool_stats.bytes_total << " bytes total (min " << res.pool_stats.bytes_min << ", max " << res.pool_stats.bytes_max << ", avg " << avg_bytes << ", median " << res.pool_stats.bytes_med << ")" << std::endl
|
||||||
<< "fees " << cryptonote::print_money(res.pool_stats.fee_total) << " (avg " << cryptonote::print_money(n_transactions ? res.pool_stats.fee_total / n_transactions : 0) << " per tx" << ", " << cryptonote::print_money(res.pool_stats.bytes_total ? res.pool_stats.fee_total / res.pool_stats.bytes_total : 0) << " per byte)" << std::endl
|
<< "fees " << cryptonote::print_money(res.pool_stats.fee_total) << " (avg " << cryptonote::print_money(n_transactions ? res.pool_stats.fee_total / n_transactions : 0) << " per tx" << ", " << cryptonote::print_money(res.pool_stats.bytes_total ? res.pool_stats.fee_total / res.pool_stats.bytes_total : 0) << " per byte)" << std::endl
|
||||||
<< res.pool_stats.num_not_relayed << " not relayed, " << res.pool_stats.num_failing << " failing, " << res.pool_stats.num_10m << " older than 10 minutes (oldest " << (res.pool_stats.oldest == 0 ? "-" : get_human_time_ago(res.pool_stats.oldest, now)) << "), " << backlog_message;
|
<< res.pool_stats.num_double_spends << " double spends, " << res.pool_stats.num_not_relayed << " not relayed, " << res.pool_stats.num_failing << " failing, " << res.pool_stats.num_10m << " older than 10 minutes (oldest " << (res.pool_stats.oldest == 0 ? "-" : get_human_time_ago(res.pool_stats.oldest, now)) << "), " << backlog_message;
|
||||||
|
|
||||||
if (n_transactions > 1 && res.pool_stats.histo.size())
|
if (n_transactions > 1 && res.pool_stats.histo.size())
|
||||||
{
|
{
|
||||||
|
@ -478,15 +478,17 @@ namespace cryptonote
|
|||||||
// try the pool for any missing txes
|
// try the pool for any missing txes
|
||||||
size_t found_in_pool = 0;
|
size_t found_in_pool = 0;
|
||||||
std::unordered_set<crypto::hash> pool_tx_hashes;
|
std::unordered_set<crypto::hash> pool_tx_hashes;
|
||||||
|
std::unordered_map<crypto::hash, bool> double_spend_seen;
|
||||||
if (!missed_txs.empty())
|
if (!missed_txs.empty())
|
||||||
{
|
{
|
||||||
std::list<transaction> pool_txs;
|
std::vector<tx_info> pool_tx_info;
|
||||||
bool r = m_core.get_pool_transactions(pool_txs);
|
std::vector<spent_key_image_info> pool_key_image_info;
|
||||||
|
bool r = m_core.get_pool_transactions_and_spent_keys_info(pool_tx_info, pool_key_image_info);
|
||||||
if(r)
|
if(r)
|
||||||
{
|
{
|
||||||
// sort to match original request
|
// sort to match original request
|
||||||
std::list<transaction> sorted_txs;
|
std::list<transaction> sorted_txs;
|
||||||
std::list<cryptonote::transaction>::const_iterator i;
|
std::vector<tx_info>::const_iterator i;
|
||||||
for (const crypto::hash &h: vh)
|
for (const crypto::hash &h: vh)
|
||||||
{
|
{
|
||||||
if (std::find(missed_txs.begin(), missed_txs.end(), h) == missed_txs.end())
|
if (std::find(missed_txs.begin(), missed_txs.end(), h) == missed_txs.end())
|
||||||
@ -500,11 +502,26 @@ namespace cryptonote
|
|||||||
sorted_txs.push_back(std::move(txs.front()));
|
sorted_txs.push_back(std::move(txs.front()));
|
||||||
txs.pop_front();
|
txs.pop_front();
|
||||||
}
|
}
|
||||||
else if ((i = std::find_if(pool_txs.begin(), pool_txs.end(), [h](cryptonote::transaction &tx) { return h == cryptonote::get_transaction_hash(tx); })) != pool_txs.end())
|
else if ((i = std::find_if(pool_tx_info.begin(), pool_tx_info.end(), [h](const tx_info &txi) { return epee::string_tools::pod_to_hex(h) == txi.id_hash; })) != pool_tx_info.end())
|
||||||
{
|
{
|
||||||
sorted_txs.push_back(*i);
|
cryptonote::transaction tx;
|
||||||
|
if (!cryptonote::parse_and_validate_tx_from_blob(i->tx_blob, tx))
|
||||||
|
{
|
||||||
|
res.status = "Failed to parse and validate tx from blob";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
sorted_txs.push_back(tx);
|
||||||
missed_txs.remove(h);
|
missed_txs.remove(h);
|
||||||
pool_tx_hashes.insert(h);
|
pool_tx_hashes.insert(h);
|
||||||
|
const std::string hash_string = epee::string_tools::pod_to_hex(h);
|
||||||
|
for (const auto &ti: pool_tx_info)
|
||||||
|
{
|
||||||
|
if (ti.id_hash == hash_string)
|
||||||
|
{
|
||||||
|
double_spend_seen.insert(std::make_pair(h, ti.double_spend_seen));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
++found_in_pool;
|
++found_in_pool;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -530,11 +547,21 @@ namespace cryptonote
|
|||||||
if (e.in_pool)
|
if (e.in_pool)
|
||||||
{
|
{
|
||||||
e.block_height = e.block_timestamp = std::numeric_limits<uint64_t>::max();
|
e.block_height = e.block_timestamp = std::numeric_limits<uint64_t>::max();
|
||||||
|
if (double_spend_seen.find(tx_hash) != double_spend_seen.end())
|
||||||
|
{
|
||||||
|
e.double_spend_seen = double_spend_seen[tx_hash];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MERROR("Failed to determine double spend status for " << tx_hash);
|
||||||
|
e.double_spend_seen = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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);
|
e.block_timestamp = m_core.get_blockchain_storage().get_db().get_block_timestamp(e.block_height);
|
||||||
|
e.double_spend_seen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// fill up old style responses too, in case an old wallet asks
|
// fill up old style responses too, in case an old wallet asks
|
||||||
|
@ -566,6 +566,7 @@ namespace cryptonote
|
|||||||
std::string as_hex;
|
std::string as_hex;
|
||||||
std::string as_json;
|
std::string as_json;
|
||||||
bool in_pool;
|
bool in_pool;
|
||||||
|
bool double_spend_seen;
|
||||||
uint64_t block_height;
|
uint64_t block_height;
|
||||||
uint64_t block_timestamp;
|
uint64_t block_timestamp;
|
||||||
std::vector<uint64_t> output_indices;
|
std::vector<uint64_t> output_indices;
|
||||||
@ -575,6 +576,7 @@ namespace cryptonote
|
|||||||
KV_SERIALIZE(as_hex)
|
KV_SERIALIZE(as_hex)
|
||||||
KV_SERIALIZE(as_json)
|
KV_SERIALIZE(as_json)
|
||||||
KV_SERIALIZE(in_pool)
|
KV_SERIALIZE(in_pool)
|
||||||
|
KV_SERIALIZE(double_spend_seen)
|
||||||
KV_SERIALIZE(block_height)
|
KV_SERIALIZE(block_height)
|
||||||
KV_SERIALIZE(block_timestamp)
|
KV_SERIALIZE(block_timestamp)
|
||||||
KV_SERIALIZE(output_indices)
|
KV_SERIALIZE(output_indices)
|
||||||
@ -1357,6 +1359,8 @@ namespace cryptonote
|
|||||||
bool relayed;
|
bool relayed;
|
||||||
uint64_t last_relayed_time;
|
uint64_t last_relayed_time;
|
||||||
bool do_not_relay;
|
bool do_not_relay;
|
||||||
|
bool double_spend_seen;
|
||||||
|
std::string tx_blob;
|
||||||
|
|
||||||
BEGIN_KV_SERIALIZE_MAP()
|
BEGIN_KV_SERIALIZE_MAP()
|
||||||
KV_SERIALIZE(id_hash)
|
KV_SERIALIZE(id_hash)
|
||||||
@ -1372,6 +1376,8 @@ namespace cryptonote
|
|||||||
KV_SERIALIZE(relayed)
|
KV_SERIALIZE(relayed)
|
||||||
KV_SERIALIZE(last_relayed_time)
|
KV_SERIALIZE(last_relayed_time)
|
||||||
KV_SERIALIZE(do_not_relay)
|
KV_SERIALIZE(do_not_relay)
|
||||||
|
KV_SERIALIZE(double_spend_seen)
|
||||||
|
KV_SERIALIZE(tx_blob)
|
||||||
END_KV_SERIALIZE_MAP()
|
END_KV_SERIALIZE_MAP()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1480,6 +1486,7 @@ namespace cryptonote
|
|||||||
uint32_t num_not_relayed;
|
uint32_t num_not_relayed;
|
||||||
uint64_t histo_98pc;
|
uint64_t histo_98pc;
|
||||||
std::vector<txpool_histo> histo;
|
std::vector<txpool_histo> histo;
|
||||||
|
uint32_t num_double_spends;
|
||||||
|
|
||||||
BEGIN_KV_SERIALIZE_MAP()
|
BEGIN_KV_SERIALIZE_MAP()
|
||||||
KV_SERIALIZE(bytes_total)
|
KV_SERIALIZE(bytes_total)
|
||||||
@ -1494,6 +1501,7 @@ namespace cryptonote
|
|||||||
KV_SERIALIZE(num_not_relayed)
|
KV_SERIALIZE(num_not_relayed)
|
||||||
KV_SERIALIZE(histo_98pc)
|
KV_SERIALIZE(histo_98pc)
|
||||||
KV_SERIALIZE_CONTAINER_POD_AS_BLOB(histo)
|
KV_SERIALIZE_CONTAINER_POD_AS_BLOB(histo)
|
||||||
|
KV_SERIALIZE(num_double_spends)
|
||||||
END_KV_SERIALIZE_MAP()
|
END_KV_SERIALIZE_MAP()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -95,6 +95,7 @@ namespace rpc
|
|||||||
uint64_t last_relayed_time;
|
uint64_t last_relayed_time;
|
||||||
bool relayed;
|
bool relayed;
|
||||||
bool do_not_relay;
|
bool do_not_relay;
|
||||||
|
bool double_spend_seen;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::unordered_map<crypto::key_image, std::vector<crypto::hash> > key_images_with_tx_hashes;
|
typedef std::unordered_map<crypto::key_image, std::vector<crypto::hash> > key_images_with_tx_hashes;
|
||||||
|
@ -755,6 +755,7 @@ void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::tx_in_pool& tx
|
|||||||
INSERT_INTO_JSON_OBJECT(val, doc, last_relayed_time, tx.last_relayed_time);
|
INSERT_INTO_JSON_OBJECT(val, doc, last_relayed_time, tx.last_relayed_time);
|
||||||
INSERT_INTO_JSON_OBJECT(val, doc, relayed, tx.relayed);
|
INSERT_INTO_JSON_OBJECT(val, doc, relayed, tx.relayed);
|
||||||
INSERT_INTO_JSON_OBJECT(val, doc, do_not_relay, tx.do_not_relay);
|
INSERT_INTO_JSON_OBJECT(val, doc, do_not_relay, tx.do_not_relay);
|
||||||
|
INSERT_INTO_JSON_OBJECT(val, doc, double_spend_seen, tx.double_spend_seen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -777,6 +778,7 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::tx_in_pool& tx)
|
|||||||
GET_FROM_JSON_OBJECT(val, tx.last_relayed_time, last_relayed_time);
|
GET_FROM_JSON_OBJECT(val, tx.last_relayed_time, last_relayed_time);
|
||||||
GET_FROM_JSON_OBJECT(val, tx.relayed, relayed);
|
GET_FROM_JSON_OBJECT(val, tx.relayed, relayed);
|
||||||
GET_FROM_JSON_OBJECT(val, tx.do_not_relay, do_not_relay);
|
GET_FROM_JSON_OBJECT(val, tx.do_not_relay, do_not_relay);
|
||||||
|
GET_FROM_JSON_OBJECT(val, tx.double_spend_seen, double_spend_seen);
|
||||||
}
|
}
|
||||||
|
|
||||||
void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::hard_fork_info& info, rapidjson::Value& val)
|
void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::hard_fork_info& info, rapidjson::Value& val)
|
||||||
|
@ -4415,15 +4415,18 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
m_wallet->update_pool_state();
|
m_wallet->update_pool_state();
|
||||||
std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments;
|
std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> payments;
|
||||||
m_wallet->get_unconfirmed_payments(payments, m_current_subaddress_account, subaddr_indices);
|
m_wallet->get_unconfirmed_payments(payments, m_current_subaddress_account, subaddr_indices);
|
||||||
for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
|
for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
|
||||||
const tools::wallet2::payment_details &pd = i->second;
|
const tools::wallet2::payment_details &pd = i->second.m_pd;
|
||||||
std::string payment_id = string_tools::pod_to_hex(i->first);
|
std::string payment_id = string_tools::pod_to_hex(i->first);
|
||||||
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
|
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
|
||||||
payment_id = payment_id.substr(0,16);
|
payment_id = payment_id.substr(0,16);
|
||||||
std::string note = m_wallet->get_tx_note(pd.m_tx_hash);
|
std::string note = m_wallet->get_tx_note(pd.m_tx_hash);
|
||||||
message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %d %s %s") % "pool" % "in" % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % pd.m_subaddr_index.minor % "-" % note).str();
|
std::string double_spend_note;
|
||||||
|
if (i->second.m_double_spend_seen)
|
||||||
|
double_spend_note = tr("[Double spend seen on the network: this transaction may or may not end up being mined] ");
|
||||||
|
message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %d %s %s%s") % "pool" % "in" % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % pd.m_subaddr_index.minor % "-" % note % double_spend_note).str();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
catch (const std::exception& e)
|
||||||
@ -5439,10 +5442,10 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args)
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
m_wallet->update_pool_state();
|
m_wallet->update_pool_state();
|
||||||
std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> pool_payments;
|
std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> pool_payments;
|
||||||
m_wallet->get_unconfirmed_payments(pool_payments);
|
m_wallet->get_unconfirmed_payments(pool_payments);
|
||||||
for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) {
|
for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) {
|
||||||
const tools::wallet2::payment_details &pd = i->second;
|
const tools::wallet2::payment_details &pd = i->second.m_pd;
|
||||||
if (pd.m_tx_hash == txid)
|
if (pd.m_tx_hash == txid)
|
||||||
{
|
{
|
||||||
std::string payment_id = string_tools::pod_to_hex(i->first);
|
std::string payment_id = string_tools::pod_to_hex(i->first);
|
||||||
@ -5455,6 +5458,8 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args)
|
|||||||
success_msg_writer() << "Payment ID: " << payment_id;
|
success_msg_writer() << "Payment ID: " << payment_id;
|
||||||
success_msg_writer() << "Address index: " << pd.m_subaddr_index.minor;
|
success_msg_writer() << "Address index: " << pd.m_subaddr_index.minor;
|
||||||
success_msg_writer() << "Note: " << m_wallet->get_tx_note(txid);
|
success_msg_writer() << "Note: " << m_wallet->get_tx_note(txid);
|
||||||
|
if (i->second.m_double_spend_seen)
|
||||||
|
success_msg_writer() << tr("Double spend seen on the network: this transaction may or may not end up being mined");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -217,10 +217,10 @@ void TransactionHistoryImpl::refresh()
|
|||||||
|
|
||||||
|
|
||||||
// unconfirmed payments (tx pool)
|
// unconfirmed payments (tx pool)
|
||||||
std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> upayments;
|
std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> upayments;
|
||||||
m_wallet->m_wallet->get_unconfirmed_payments(upayments);
|
m_wallet->m_wallet->get_unconfirmed_payments(upayments);
|
||||||
for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) {
|
for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) {
|
||||||
const tools::wallet2::payment_details &pd = i->second;
|
const tools::wallet2::payment_details &pd = i->second.m_pd;
|
||||||
std::string payment_id = string_tools::pod_to_hex(i->first);
|
std::string payment_id = string_tools::pod_to_hex(i->first);
|
||||||
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
|
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
|
||||||
payment_id = payment_id.substr(0,16);
|
payment_id = payment_id.substr(0,16);
|
||||||
|
@ -444,6 +444,21 @@ std::string strjoin(const std::vector<size_t> &V, const char *sep)
|
|||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void emplace_or_replace(std::unordered_multimap<crypto::hash, tools::wallet2::pool_payment_details> &container,
|
||||||
|
const crypto::hash &key, const tools::wallet2::pool_payment_details &pd)
|
||||||
|
{
|
||||||
|
auto range = container.equal_range(key);
|
||||||
|
for (auto i = range.first; i != range.second; ++i)
|
||||||
|
{
|
||||||
|
if (i->second.m_pd.m_tx_hash == pd.m_pd.m_tx_hash)
|
||||||
|
{
|
||||||
|
i->second = pd;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
container.emplace(key, pd);
|
||||||
|
}
|
||||||
|
|
||||||
} //namespace
|
} //namespace
|
||||||
|
|
||||||
namespace tools
|
namespace tools
|
||||||
@ -793,7 +808,7 @@ void wallet2::scan_output(const cryptonote::account_keys &keys, const cryptonote
|
|||||||
++num_vouts_received;
|
++num_vouts_received;
|
||||||
}
|
}
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool)
|
void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen)
|
||||||
{
|
{
|
||||||
// In this function, tx (probably) only contains the base information
|
// In this function, tx (probably) only contains the base information
|
||||||
// (that is, the prunable stuff may or may not be included)
|
// (that is, the prunable stuff may or may not be included)
|
||||||
@ -1163,7 +1178,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
|
|||||||
payment.m_timestamp = ts;
|
payment.m_timestamp = ts;
|
||||||
payment.m_subaddr_index = i.first;
|
payment.m_subaddr_index = i.first;
|
||||||
if (pool) {
|
if (pool) {
|
||||||
m_unconfirmed_payments.emplace(payment_id, payment);
|
emplace_or_replace(m_unconfirmed_payments, payment_id, pool_payment_details{payment, double_spend_seen});
|
||||||
if (0 != m_callback)
|
if (0 != m_callback)
|
||||||
m_callback->on_unconfirmed_money_received(height, txid, tx, payment.m_amount, payment.m_subaddr_index);
|
m_callback->on_unconfirmed_money_received(height, txid, tx, payment.m_amount, payment.m_subaddr_index);
|
||||||
}
|
}
|
||||||
@ -1241,7 +1256,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry
|
|||||||
if(b.timestamp + 60*60*24 > m_account.get_createtime() && height >= m_refresh_from_block_height)
|
if(b.timestamp + 60*60*24 > m_account.get_createtime() && height >= m_refresh_from_block_height)
|
||||||
{
|
{
|
||||||
TIME_MEASURE_START(miner_tx_handle_time);
|
TIME_MEASURE_START(miner_tx_handle_time);
|
||||||
process_new_transaction(get_transaction_hash(b.miner_tx), b.miner_tx, o_indices.indices[txidx++].indices, height, b.timestamp, true, false);
|
process_new_transaction(get_transaction_hash(b.miner_tx), b.miner_tx, o_indices.indices[txidx++].indices, height, b.timestamp, true, false, false);
|
||||||
TIME_MEASURE_FINISH(miner_tx_handle_time);
|
TIME_MEASURE_FINISH(miner_tx_handle_time);
|
||||||
|
|
||||||
TIME_MEASURE_START(txs_handle_time);
|
TIME_MEASURE_START(txs_handle_time);
|
||||||
@ -1252,7 +1267,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry
|
|||||||
cryptonote::transaction tx;
|
cryptonote::transaction tx;
|
||||||
bool r = parse_and_validate_tx_base_from_blob(txblob, tx);
|
bool r = parse_and_validate_tx_base_from_blob(txblob, tx);
|
||||||
THROW_WALLET_EXCEPTION_IF(!r, error::tx_parse_error, txblob);
|
THROW_WALLET_EXCEPTION_IF(!r, error::tx_parse_error, txblob);
|
||||||
process_new_transaction(b.tx_hashes[idx], tx, o_indices.indices[txidx++].indices, height, b.timestamp, false, false);
|
process_new_transaction(b.tx_hashes[idx], tx, o_indices.indices[txidx++].indices, height, b.timestamp, false, false, false);
|
||||||
++idx;
|
++idx;
|
||||||
}
|
}
|
||||||
TIME_MEASURE_FINISH(txs_handle_time);
|
TIME_MEASURE_FINISH(txs_handle_time);
|
||||||
@ -1520,10 +1535,10 @@ void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_hei
|
|||||||
void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes)
|
void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes)
|
||||||
{
|
{
|
||||||
// remove pool txes to us that aren't in the pool anymore
|
// remove pool txes to us that aren't in the pool anymore
|
||||||
std::unordered_multimap<crypto::hash, wallet2::payment_details>::iterator uit = m_unconfirmed_payments.begin();
|
std::unordered_multimap<crypto::hash, wallet2::pool_payment_details>::iterator uit = m_unconfirmed_payments.begin();
|
||||||
while (uit != m_unconfirmed_payments.end())
|
while (uit != m_unconfirmed_payments.end())
|
||||||
{
|
{
|
||||||
const crypto::hash &txid = uit->second.m_tx_hash;
|
const crypto::hash &txid = uit->second.m_pd.m_tx_hash;
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for (const auto &it2: tx_hashes)
|
for (const auto &it2: tx_hashes)
|
||||||
{
|
{
|
||||||
@ -1626,23 +1641,27 @@ void wallet2::update_pool_state(bool refreshed)
|
|||||||
MDEBUG("update_pool_state done second loop");
|
MDEBUG("update_pool_state done second loop");
|
||||||
|
|
||||||
// gather txids of new pool txes to us
|
// gather txids of new pool txes to us
|
||||||
std::vector<crypto::hash> txids;
|
std::vector<std::pair<crypto::hash, bool>> txids;
|
||||||
for (const auto &txid: res.tx_hashes)
|
for (const auto &txid: res.tx_hashes)
|
||||||
{
|
{
|
||||||
if (m_scanned_pool_txs[0].find(txid) != m_scanned_pool_txs[0].end() || m_scanned_pool_txs[1].find(txid) != m_scanned_pool_txs[1].end())
|
|
||||||
{
|
|
||||||
LOG_PRINT_L2("Already seen " << txid << ", skipped");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
bool txid_found_in_up = false;
|
bool txid_found_in_up = false;
|
||||||
for (const auto &up: m_unconfirmed_payments)
|
for (const auto &up: m_unconfirmed_payments)
|
||||||
{
|
{
|
||||||
if (up.second.m_tx_hash == txid)
|
if (up.second.m_pd.m_tx_hash == txid)
|
||||||
{
|
{
|
||||||
txid_found_in_up = true;
|
txid_found_in_up = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (m_scanned_pool_txs[0].find(txid) != m_scanned_pool_txs[0].end() || m_scanned_pool_txs[1].find(txid) != m_scanned_pool_txs[1].end())
|
||||||
|
{
|
||||||
|
// if it's for us, we want to keep track of whether we saw a double spend, so don't bail out
|
||||||
|
if (!txid_found_in_up)
|
||||||
|
{
|
||||||
|
LOG_PRINT_L2("Already seen " << txid << ", and not for us, skipped");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!txid_found_in_up)
|
if (!txid_found_in_up)
|
||||||
{
|
{
|
||||||
LOG_PRINT_L1("Found new pool tx: " << txid);
|
LOG_PRINT_L1("Found new pool tx: " << txid);
|
||||||
@ -1670,7 +1689,7 @@ void wallet2::update_pool_state(bool refreshed)
|
|||||||
if (!found)
|
if (!found)
|
||||||
{
|
{
|
||||||
// not one of those we sent ourselves
|
// not one of those we sent ourselves
|
||||||
txids.push_back(txid);
|
txids.push_back({txid, false});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1680,6 +1699,7 @@ void wallet2::update_pool_state(bool refreshed)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
LOG_PRINT_L1("Already saw that one, it's for us");
|
LOG_PRINT_L1("Already saw that one, it's for us");
|
||||||
|
txids.push_back({txid, true});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1688,8 +1708,8 @@ void wallet2::update_pool_state(bool refreshed)
|
|||||||
{
|
{
|
||||||
cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req;
|
cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req;
|
||||||
cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res;
|
cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res;
|
||||||
for (const auto &txid: txids)
|
for (const auto &p: txids)
|
||||||
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
|
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(p.first));
|
||||||
MDEBUG("asking for " << txids.size() << " transactions");
|
MDEBUG("asking for " << txids.size() << " transactions");
|
||||||
req.decode_as_json = false;
|
req.decode_as_json = false;
|
||||||
m_daemon_rpc_mutex.lock();
|
m_daemon_rpc_mutex.lock();
|
||||||
@ -1711,10 +1731,11 @@ void wallet2::update_pool_state(bool refreshed)
|
|||||||
{
|
{
|
||||||
if (cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash))
|
if (cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash))
|
||||||
{
|
{
|
||||||
const std::vector<crypto::hash>::const_iterator i = std::find(txids.begin(), txids.end(), tx_hash);
|
const std::vector<std::pair<crypto::hash, bool>>::const_iterator i = std::find_if(txids.begin(), txids.end(),
|
||||||
|
[tx_hash](const std::pair<crypto::hash, bool> &e) { return e.first == tx_hash; });
|
||||||
if (i != txids.end())
|
if (i != txids.end())
|
||||||
{
|
{
|
||||||
process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, time(NULL), false, true);
|
process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, time(NULL), false, true, tx_entry.double_spend_seen);
|
||||||
m_scanned_pool_txs[0].insert(tx_hash);
|
m_scanned_pool_txs[0].insert(tx_hash);
|
||||||
if (m_scanned_pool_txs[0].size() > 5000)
|
if (m_scanned_pool_txs[0].size() > 5000)
|
||||||
{
|
{
|
||||||
@ -3073,11 +3094,11 @@ void wallet2::get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wall
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
void wallet2::get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const
|
void wallet2::get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::pool_payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const
|
||||||
{
|
{
|
||||||
for (auto i = m_unconfirmed_payments.begin(); i != m_unconfirmed_payments.end(); ++i) {
|
for (auto i = m_unconfirmed_payments.begin(); i != m_unconfirmed_payments.end(); ++i) {
|
||||||
if ((!subaddr_account || *subaddr_account == i->second.m_subaddr_index.major) &&
|
if ((!subaddr_account || *subaddr_account == i->second.m_pd.m_subaddr_index.major) &&
|
||||||
(subaddr_indices.empty() || subaddr_indices.count(i->second.m_subaddr_index.minor) == 1))
|
(subaddr_indices.empty() || subaddr_indices.count(i->second.m_pd.m_subaddr_index.minor) == 1))
|
||||||
unconfirmed_payments.push_back(*i);
|
unconfirmed_payments.push_back(*i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5129,7 +5150,7 @@ void wallet2::light_wallet_get_address_txs()
|
|||||||
payments_txs.push_back(p.second.m_tx_hash);
|
payments_txs.push_back(p.second.m_tx_hash);
|
||||||
std::vector<crypto::hash> unconfirmed_payments_txs;
|
std::vector<crypto::hash> unconfirmed_payments_txs;
|
||||||
for(const auto &up: m_unconfirmed_payments)
|
for(const auto &up: m_unconfirmed_payments)
|
||||||
unconfirmed_payments_txs.push_back(up.second.m_tx_hash);
|
unconfirmed_payments_txs.push_back(up.second.m_pd.m_tx_hash);
|
||||||
|
|
||||||
// for balance calculation
|
// for balance calculation
|
||||||
uint64_t wallet_total_sent = 0;
|
uint64_t wallet_total_sent = 0;
|
||||||
@ -5195,7 +5216,11 @@ void wallet2::light_wallet_get_address_txs()
|
|||||||
if (t.mempool) {
|
if (t.mempool) {
|
||||||
if (std::find(unconfirmed_payments_txs.begin(), unconfirmed_payments_txs.end(), tx_hash) == unconfirmed_payments_txs.end()) {
|
if (std::find(unconfirmed_payments_txs.begin(), unconfirmed_payments_txs.end(), tx_hash) == unconfirmed_payments_txs.end()) {
|
||||||
pool_txs.push_back(tx_hash);
|
pool_txs.push_back(tx_hash);
|
||||||
m_unconfirmed_payments.emplace(tx_hash, payment);
|
// assume false as we don't get that info from the light wallet server
|
||||||
|
crypto::hash payment_id;
|
||||||
|
THROW_WALLET_EXCEPTION_IF(!epee::string_tools::hex_to_pod(t.payment_id, payment_id),
|
||||||
|
error::wallet_internal_error, "Failed to parse payment id");
|
||||||
|
emplace_or_replace(m_unconfirmed_payments, payment_id, pool_payment_details{payment, false});
|
||||||
if (0 != m_callback) {
|
if (0 != m_callback) {
|
||||||
m_callback->on_lw_unconfirmed_money_received(t.height, payment.m_tx_hash, payment.m_amount);
|
m_callback->on_lw_unconfirmed_money_received(t.height, payment.m_tx_hash, payment.m_amount);
|
||||||
}
|
}
|
||||||
|
@ -244,6 +244,12 @@ namespace tools
|
|||||||
bool m_incoming;
|
bool m_incoming;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct pool_payment_details
|
||||||
|
{
|
||||||
|
payment_details m_pd;
|
||||||
|
bool m_double_spend_seen;
|
||||||
|
};
|
||||||
|
|
||||||
struct unconfirmed_transfer_details
|
struct unconfirmed_transfer_details
|
||||||
{
|
{
|
||||||
cryptonote::transaction_prefix m_tx;
|
cryptonote::transaction_prefix m_tx;
|
||||||
@ -530,7 +536,7 @@ namespace tools
|
|||||||
void get_payments_out(std::list<std::pair<crypto::hash,wallet2::confirmed_transfer_details>>& confirmed_payments,
|
void get_payments_out(std::list<std::pair<crypto::hash,wallet2::confirmed_transfer_details>>& confirmed_payments,
|
||||||
uint64_t min_height, uint64_t max_height = (uint64_t)-1, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
|
uint64_t min_height, uint64_t max_height = (uint64_t)-1, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
|
||||||
void get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wallet2::unconfirmed_transfer_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
|
void get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wallet2::unconfirmed_transfer_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
|
||||||
void get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
|
void get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::pool_payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
|
||||||
|
|
||||||
uint64_t get_blockchain_current_height() const { return m_local_bc_height; }
|
uint64_t get_blockchain_current_height() const { return m_local_bc_height; }
|
||||||
void rescan_spent();
|
void rescan_spent();
|
||||||
@ -585,7 +591,7 @@ namespace tools
|
|||||||
std::unordered_map<crypto::hash, payment_details> m;
|
std::unordered_map<crypto::hash, payment_details> m;
|
||||||
a & m;
|
a & m;
|
||||||
for (std::unordered_map<crypto::hash, payment_details>::const_iterator i = m.begin(); i != m.end(); ++i)
|
for (std::unordered_map<crypto::hash, payment_details>::const_iterator i = m.begin(); i != m.end(); ++i)
|
||||||
m_unconfirmed_payments.insert(*i);
|
m_unconfirmed_payments.insert(std::make_pair(i->first, pool_payment_details{i->second, false}));
|
||||||
}
|
}
|
||||||
if(ver < 14)
|
if(ver < 14)
|
||||||
return;
|
return;
|
||||||
@ -607,7 +613,15 @@ namespace tools
|
|||||||
a & m_address_book;
|
a & m_address_book;
|
||||||
if(ver < 17)
|
if(ver < 17)
|
||||||
return;
|
return;
|
||||||
a & m_unconfirmed_payments;
|
if (ver < 21)
|
||||||
|
{
|
||||||
|
// we're loading an old version, where m_unconfirmed_payments payload was payment_details
|
||||||
|
std::unordered_map<crypto::hash, payment_details> m;
|
||||||
|
a & m;
|
||||||
|
for (const auto &i: m)
|
||||||
|
m_unconfirmed_payments.insert(std::make_pair(i.first, pool_payment_details{i.second, false}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(ver < 18)
|
if(ver < 18)
|
||||||
return;
|
return;
|
||||||
a & m_scanned_pool_txs[0];
|
a & m_scanned_pool_txs[0];
|
||||||
@ -621,6 +635,9 @@ namespace tools
|
|||||||
if(ver < 21)
|
if(ver < 21)
|
||||||
return;
|
return;
|
||||||
a & m_attributes;
|
a & m_attributes;
|
||||||
|
if(ver < 22)
|
||||||
|
return;
|
||||||
|
a & m_unconfirmed_payments;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -797,7 +814,7 @@ namespace tools
|
|||||||
* \param password Password of wallet file
|
* \param password Password of wallet file
|
||||||
*/
|
*/
|
||||||
bool load_keys(const std::string& keys_file_name, const std::string& password);
|
bool load_keys(const std::string& keys_file_name, const std::string& password);
|
||||||
void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool);
|
void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen);
|
||||||
void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height, const cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices &o_indices);
|
void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height, const cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices &o_indices);
|
||||||
void detach_blockchain(uint64_t height);
|
void detach_blockchain(uint64_t height);
|
||||||
void get_short_chain_history(std::list<crypto::hash>& ids) const;
|
void get_short_chain_history(std::list<crypto::hash>& ids) const;
|
||||||
@ -846,7 +863,7 @@ namespace tools
|
|||||||
std::atomic<uint64_t> m_local_bc_height; //temporary workaround
|
std::atomic<uint64_t> m_local_bc_height; //temporary workaround
|
||||||
std::unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs;
|
std::unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs;
|
||||||
std::unordered_map<crypto::hash, confirmed_transfer_details> m_confirmed_txs;
|
std::unordered_map<crypto::hash, confirmed_transfer_details> m_confirmed_txs;
|
||||||
std::unordered_multimap<crypto::hash, payment_details> m_unconfirmed_payments;
|
std::unordered_multimap<crypto::hash, pool_payment_details> m_unconfirmed_payments;
|
||||||
std::unordered_map<crypto::hash, crypto::secret_key> m_tx_keys;
|
std::unordered_map<crypto::hash, crypto::secret_key> m_tx_keys;
|
||||||
cryptonote::checkpoints m_checkpoints;
|
cryptonote::checkpoints m_checkpoints;
|
||||||
std::unordered_map<crypto::hash, std::vector<crypto::secret_key>> m_additional_tx_keys;
|
std::unordered_map<crypto::hash, std::vector<crypto::secret_key>> m_additional_tx_keys;
|
||||||
@ -908,9 +925,10 @@ namespace tools
|
|||||||
std::unordered_map<crypto::public_key, std::map<uint64_t, crypto::key_image> > m_key_image_cache;
|
std::unordered_map<crypto::public_key, std::map<uint64_t, crypto::key_image> > m_key_image_cache;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
BOOST_CLASS_VERSION(tools::wallet2, 21)
|
BOOST_CLASS_VERSION(tools::wallet2, 22)
|
||||||
BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 8)
|
BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 8)
|
||||||
BOOST_CLASS_VERSION(tools::wallet2::payment_details, 2)
|
BOOST_CLASS_VERSION(tools::wallet2::payment_details, 2)
|
||||||
|
BOOST_CLASS_VERSION(tools::wallet2::pool_payment_details, 1)
|
||||||
BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 7)
|
BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 7)
|
||||||
BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 5)
|
BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 5)
|
||||||
BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 17)
|
BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 17)
|
||||||
@ -1138,6 +1156,13 @@ namespace boost
|
|||||||
a & x.m_subaddr_index;
|
a & x.m_subaddr_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class Archive>
|
||||||
|
inline void serialize(Archive& a, tools::wallet2::pool_payment_details& x, const boost::serialization::version_type ver)
|
||||||
|
{
|
||||||
|
a & x.m_pd;
|
||||||
|
a & x.m_double_spend_seen;
|
||||||
|
}
|
||||||
|
|
||||||
template <class Archive>
|
template <class Archive>
|
||||||
inline void serialize(Archive& a, tools::wallet2::address_book_row& x, const boost::serialization::version_type ver)
|
inline void serialize(Archive& a, tools::wallet2::address_book_row& x, const boost::serialization::version_type ver)
|
||||||
{
|
{
|
||||||
|
@ -299,8 +299,9 @@ namespace tools
|
|||||||
entry.subaddr_index = { pd.m_subaddr_account, 0 };
|
entry.subaddr_index = { pd.m_subaddr_account, 0 };
|
||||||
}
|
}
|
||||||
//------------------------------------------------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------------------------------------------------
|
||||||
void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd)
|
void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::pool_payment_details &ppd)
|
||||||
{
|
{
|
||||||
|
const tools::wallet2::payment_details &pd = ppd.m_pd;
|
||||||
entry.txid = string_tools::pod_to_hex(pd.m_tx_hash);
|
entry.txid = string_tools::pod_to_hex(pd.m_tx_hash);
|
||||||
entry.payment_id = string_tools::pod_to_hex(payment_id);
|
entry.payment_id = string_tools::pod_to_hex(payment_id);
|
||||||
if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos)
|
if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos)
|
||||||
@ -311,6 +312,7 @@ namespace tools
|
|||||||
entry.unlock_time = pd.m_unlock_time;
|
entry.unlock_time = pd.m_unlock_time;
|
||||||
entry.fee = 0; // TODO
|
entry.fee = 0; // TODO
|
||||||
entry.note = m_wallet->get_tx_note(pd.m_tx_hash);
|
entry.note = m_wallet->get_tx_note(pd.m_tx_hash);
|
||||||
|
entry.double_spend_seen = ppd.m_double_spend_seen;
|
||||||
entry.type = "pool";
|
entry.type = "pool";
|
||||||
entry.subaddr_index = pd.m_subaddr_index;
|
entry.subaddr_index = pd.m_subaddr_index;
|
||||||
}
|
}
|
||||||
@ -1357,9 +1359,9 @@ namespace tools
|
|||||||
{
|
{
|
||||||
m_wallet->update_pool_state();
|
m_wallet->update_pool_state();
|
||||||
|
|
||||||
std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments;
|
std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> payments;
|
||||||
m_wallet->get_unconfirmed_payments(payments, req.account_index, req.subaddr_indices);
|
m_wallet->get_unconfirmed_payments(payments, req.account_index, req.subaddr_indices);
|
||||||
for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
|
for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) {
|
||||||
res.pool.push_back(wallet_rpc::transfer_entry());
|
res.pool.push_back(wallet_rpc::transfer_entry());
|
||||||
fill_transfer_entry(res.pool.back(), i->first, i->second);
|
fill_transfer_entry(res.pool.back(), i->first, i->second);
|
||||||
}
|
}
|
||||||
@ -1430,10 +1432,10 @@ namespace tools
|
|||||||
|
|
||||||
m_wallet->update_pool_state();
|
m_wallet->update_pool_state();
|
||||||
|
|
||||||
std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> pool_payments;
|
std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> pool_payments;
|
||||||
m_wallet->get_unconfirmed_payments(pool_payments);
|
m_wallet->get_unconfirmed_payments(pool_payments);
|
||||||
for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) {
|
for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) {
|
||||||
if (i->second.m_tx_hash == txid)
|
if (i->second.m_pd.m_tx_hash == txid)
|
||||||
{
|
{
|
||||||
fill_transfer_entry(res.transfer, i->first, i->second);
|
fill_transfer_entry(res.transfer, i->first, i->second);
|
||||||
return true;
|
return true;
|
||||||
|
@ -163,7 +163,7 @@ namespace tools
|
|||||||
void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd);
|
void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd);
|
||||||
void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::confirmed_transfer_details &pd);
|
void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::confirmed_transfer_details &pd);
|
||||||
void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::unconfirmed_transfer_details &pd);
|
void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::unconfirmed_transfer_details &pd);
|
||||||
void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd);
|
void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::pool_payment_details &pd);
|
||||||
bool not_open(epee::json_rpc::error& er);
|
bool not_open(epee::json_rpc::error& er);
|
||||||
uint64_t adjust_mixin(uint64_t mixin);
|
uint64_t adjust_mixin(uint64_t mixin);
|
||||||
void handle_rpc_exception(const std::exception_ptr& e, epee::json_rpc::error& er, int default_error_code);
|
void handle_rpc_exception(const std::exception_ptr& e, epee::json_rpc::error& er, int default_error_code);
|
||||||
|
@ -794,6 +794,7 @@ namespace wallet_rpc
|
|||||||
std::string type;
|
std::string type;
|
||||||
uint64_t unlock_time;
|
uint64_t unlock_time;
|
||||||
cryptonote::subaddress_index subaddr_index;
|
cryptonote::subaddress_index subaddr_index;
|
||||||
|
bool double_spend_seen;
|
||||||
|
|
||||||
BEGIN_KV_SERIALIZE_MAP()
|
BEGIN_KV_SERIALIZE_MAP()
|
||||||
KV_SERIALIZE(txid);
|
KV_SERIALIZE(txid);
|
||||||
@ -807,6 +808,7 @@ namespace wallet_rpc
|
|||||||
KV_SERIALIZE(type);
|
KV_SERIALIZE(type);
|
||||||
KV_SERIALIZE(unlock_time)
|
KV_SERIALIZE(unlock_time)
|
||||||
KV_SERIALIZE(subaddr_index);
|
KV_SERIALIZE(subaddr_index);
|
||||||
|
KV_SERIALIZE(double_spend_seen)
|
||||||
END_KV_SERIALIZE_MAP()
|
END_KV_SERIALIZE_MAP()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user