Blink synchronization

- Adds blink signature synchronization and storage through the regular
  p2p network
- Adds wallet support (though this is still currently buggy and needs
  additional fixes - it sees the tx when it arrives in the mempool but
  isn't properly updating when the blink tx gets mined.)
This commit is contained in:
Jason Rhinelander 2019-11-06 02:28:33 -04:00
parent ebb2bea452
commit 442f2182d2
18 changed files with 641 additions and 136 deletions

View file

@ -1235,13 +1235,127 @@ namespace cryptonote
//-----------------------------------------------------------------------------------------------
bool core::handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay)
{
std::vector<cryptonote::blobdata> tx_blobs;
tx_blobs.push_back(tx_blob);
std::vector<tx_verification_context> tvcv(1);
bool r = handle_incoming_txs(tx_blobs, tvcv, keeped_by_block, relayed, do_not_relay);
bool r = handle_incoming_txs({{tx_blob}}, tvcv, keeped_by_block, relayed, do_not_relay);
tvc = tvcv[0];
return r;
}
//-----------------------------------------------------------------------------------------------
bool core::handle_incoming_blinks(const std::vector<serializable_blink_metadata> &blinks, std::vector<crypto::hash> *bad_blinks, std::vector<crypto::hash> *missing_txs)
{
std::vector<uint8_t> store_blink(blinks.size(), false);
size_t store_count = 0;
// Step 1: figure out which referenced transactions we have and want blink info for (i.e. mined
// but not immutable, or still in the mempool).
{
std::vector<crypto::hash> hashes;
hashes.reserve(blinks.size());
for (auto &bm : blinks)
hashes.emplace_back(bm.tx_hash);
// If we don't take out this mempool lock here, before the blockchain_storage, we can deadlock
// when we access the mempool later because it takes both locks in this order. TODO: make this
// lock design stop sucking.
CRITICAL_REGION_LOCAL(m_mempool);
CRITICAL_REGION_LOCAL1(m_blockchain_storage);
auto tx_block_heights = m_blockchain_storage.get_transactions_heights(hashes);
auto immutable_height = m_blockchain_storage.get_immutable_height();
auto &db = m_blockchain_storage.get_db();
for (size_t i = 0; i < blinks.size(); i++) {
if (tx_block_heights[i])
store_blink[i] = tx_block_heights[i] > immutable_height;
else if (db.txpool_has_tx(hashes[i]))
store_blink[i] = true;
else if (missing_txs)
missing_txs->emplace_back(hashes[i]);
if (store_blink[i])
store_count++;
}
}
if (!store_count) return true;
// Step 2: filter out any transactions for which we already have a blink signature
{
auto mempool_lock = m_mempool.blink_shared_lock();
for (size_t i = 0; i < blinks.size(); i++)
{
if (store_blink[i] && m_mempool.get_blink(blinks[i].tx_hash, true /*have lock*/))
{
store_blink[i] = false; // Already have it, move along
store_count--;
}
}
}
if (!store_count) return true;
// Step 3: create new blink_tx objects for txes we want the blink signatures for, making sure
// that all the given signature data is valid and that signatures validate. We can do all of
// this without a lock.
std::vector<std::shared_ptr<blink_tx>> new_blinks;
new_blinks.reserve(store_count);
std::unordered_map<uint64_t, std::shared_ptr<const service_nodes::quorum>> quorum_cache;
bool bad = false;
auto bad_blink = [&bad, bad_blinks](const crypto::hash &h, const char *what) {
MINFO("Invalid blink tx " << h << ": " << what);
bad = true;
if (bad_blinks) bad_blinks->push_back(h);
};
for (size_t i = 0; i < blinks.size(); i++)
{
if (!store_blink[i])
continue;
auto &bdata = blinks[i];
if (bdata.signature.size() != bdata.position.size() || bdata.signature.size() != bdata.quorum.size() ||
bdata.signature.size() < service_nodes::BLINK_MIN_VOTES * tools::enum_count<blink_tx::subquorum> ||
bdata.signature.size() > service_nodes::BLINK_SUBQUORUM_SIZE * tools::enum_count<blink_tx::subquorum> ||
blink_tx::quorum_height(bdata.height, blink_tx::subquorum::base) == 0 ||
!std::all_of(bdata.position.begin(), bdata.position.end(), [](const auto &p) { return p < service_nodes::BLINK_SUBQUORUM_SIZE; }) ||
!std::all_of(bdata.quorum.begin(), bdata.quorum.end(), [](const auto &qi) { return qi < tools::enum_count<blink_tx::subquorum>; })
) {
bad_blink(bdata.tx_hash, "invalid signature data");
continue;
}
auto blink_ptr = std::make_shared<blink_tx>(bdata.height, bdata.tx_hash);
auto &blink = *blink_ptr;
std::array<const std::vector<crypto::public_key> *, tools::enum_count<blink_tx::subquorum>> validators;
for (uint8_t qi = 0; qi < tools::enum_count<blink_tx::subquorum>; qi++)
{
auto q_height = blink_tx::quorum_height(bdata.height, static_cast<blink_tx::subquorum>(qi));
auto &q = quorum_cache[q_height];
if (!q)
q = get_quorum(service_nodes::quorum_type::blink, q_height);
validators[qi] = &q->validators;
}
try {
for (size_t s = 0; s < bdata.signature.size(); s++)
blink.add_signature(static_cast<blink_tx::subquorum>(bdata.quorum[s]), bdata.position[s], true /*approved*/, bdata.signature[s],
validators[bdata.quorum[s]]->at(bdata.position[s]));
} catch (const std::exception &e) {
bad_blink(bdata.tx_hash, e.what());
continue;
}
new_blinks.push_back(std::move(blink_ptr));
}
// Finally lock the blink pool and add all the blink signature data. It's possible that some
// other thread got through the above loop before us and already added some of those, but that's
// okay.
{
auto lock = m_mempool.blink_unique_lock();
for (auto &b : new_blinks)
m_mempool.add_existing_blink(std::move(b), true /*have lock*/);
}
return !bad;
}
//-----------------------------------------------------------------------------------------------
std::future<std::pair<blink_result, std::string>> core::handle_blink_tx(const std::string &tx_blob)
{
@ -1516,7 +1630,7 @@ namespace cryptonote
return m_service_node_list.handle_uptime_proof(proof, my_uptime_proof_confirmation);
}
//-----------------------------------------------------------------------------------------------
void core::on_transaction_relayed(const cryptonote::blobdata& tx_blob)
crypto::hash core::on_transaction_relayed(const cryptonote::blobdata& tx_blob)
{
std::vector<std::pair<crypto::hash, cryptonote::blobdata>> txs;
cryptonote::transaction tx;
@ -1524,10 +1638,11 @@ namespace cryptonote
if (!parse_and_validate_tx_from_blob(tx_blob, tx, tx_hash))
{
LOG_ERROR("Failed to parse relayed transaction");
return;
return crypto::null_hash;
}
txs.push_back(std::make_pair(tx_hash, std::move(tx_blob)));
m_mempool.set_relayed(txs);
return tx_hash;
}
//-----------------------------------------------------------------------------------------------
bool core::relay_service_node_votes()

View file

@ -173,11 +173,36 @@ namespace cryptonote
* @param keeped_by_block if the transactions have been in a block
* @param relayed whether or not the transactions were relayed to us
* @param do_not_relay whether to prevent the transactions from being relayed
* @param blink_sigs the blink signature data that this tx arrived with, if any
*
* @return true if the transactions were accepted, false otherwise
*/
bool handle_incoming_txs(const std::vector<blobdata>& tx_blobs, std::vector<tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay);
/**
* @brief handles received blink transaction signatures
*
* This takes a vector of blink transaction metadata (typically from a p2p peer), validates the
* signatures, and (if valid) records the blink status. Blink transactions and signatures for
* those transactions that are already mined beyond the immutability checkpoint are ignored.
* Blink transactions that already have a set of valid signatures are similarly ignored (and
* not revalidated).
*
* NB: This method is *not* used for partially signed blink transactions during the signing
* process but rather only for valid, signed blinks.
*
* @param blinks vector of serializable_blink_metadata
* @param bad_blinks optional pointer to vector in which to store hashes of any blink tx that
* have invalid signature data (either invalid data, or a signature validation failure).
* @param missing_txs optional pointer to vector in which to store any txes that were not found
* on the blockchain or the mempool.
*
* @return true if there were no failures due to invalid signature data, false if there was one
* or more. (Note that a true return does not indicate anything was added, and a false return
* does not indicate that nothing was added).
*/
bool handle_incoming_blinks(const std::vector<serializable_blink_metadata> &blinks, std::vector<crypto::hash> *bad_blinks = nullptr, std::vector<crypto::hash> *missing_txs = nullptr);
/**
* @brief handles an incoming blink transaction by dispatching it to the service node network
* via quorumnet. If this node is not a service node this will start up quorumnet in
@ -262,9 +287,10 @@ namespace cryptonote
virtual bool get_block_template(block& b, const crypto::hash *prev_block, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce);
/**
* @brief called when a transaction is relayed
* @brief called when a transaction is relayed; return the hash of the parsed tx, or null_hash
* on parse failure.
*/
virtual void on_transaction_relayed(const cryptonote::blobdata& tx);
virtual crypto::hash on_transaction_relayed(const cryptonote::blobdata& tx);
/**
* @brief gets the miner instance
@ -861,6 +887,13 @@ namespace cryptonote
/// Time point at which the storage server and lokinet last pinged us
std::atomic<time_t> m_last_storage_server_ping, m_last_lokinet_ping;
/**
* @brief attempts to relay any transactions in the mempool which need it
*
* @return true
*/
bool relay_txpool_transactions();
private:
/**
@ -978,13 +1011,6 @@ namespace cryptonote
*/
bool check_fork_time();
/**
* @brief attempts to relay any transactions in the mempool which need it
*
* @return true
*/
bool relay_txpool_transactions();
/**
* @brief checks DNS versions
*

View file

@ -49,7 +49,7 @@ crypto::public_key blink_tx::get_sn_pubkey(subquorum q, int position, const serv
auto blink_quorum = snl.get_quorum(quorum_type::blink, qheight);
if (!blink_quorum) {
// TODO FIXME XXX - we don't want a failure here; if this happens we need to go back into state
// history to retrieve the state info.
// history to retrieve the state info. (Or maybe this can't happen?)
MERROR("FIXME: could not get blink quorum for blink_tx");
return crypto::null_pkey;
}
@ -61,24 +61,35 @@ crypto::public_key blink_tx::get_sn_pubkey(subquorum q, int position, const serv
};
crypto::hash blink_tx::hash(bool approved) const {
crypto::hash tx_hash, blink_hash;
if (!cryptonote::get_transaction_hash(tx, tx_hash))
throw std::runtime_error("Cannot compute blink hash: tx hash is not valid");
auto buf = tools::memcpy_le(height, tx_hash.data, uint8_t{approved});
auto buf = tools::memcpy_le(height, get_txhash().data, uint8_t{approved});
crypto::hash blink_hash;
crypto::cn_fast_hash(buf.data(), buf.size(), blink_hash);
return blink_hash;
}
bool blink_tx::add_signature(subquorum q, int position, bool approved, const crypto::signature &sig, const service_node_list &snl) {
void blink_tx::limit_signatures(subquorum q, size_t max_size) {
if (max_size > BLINK_SUBQUORUM_SIZE)
throw std::domain_error("Internal error: too many potential blink signers!");
else if (max_size < BLINK_SUBQUORUM_SIZE)
for (size_t i = max_size; i < BLINK_SUBQUORUM_SIZE; i++)
signatures_[static_cast<uint8_t>(q)][i].status = signature_status::rejected;
}
bool blink_tx::add_signature(subquorum q, int position, bool approved, const crypto::signature &sig, const crypto::public_key &pubkey) {
check_args(q, position, __func__);
if (!crypto::check_signature(hash(approved), get_sn_pubkey(q, position, snl), sig))
if (!crypto::check_signature(hash(approved), pubkey, sig))
throw signature_verification_error("Given blink quorum signature verification failed!");
return add_prechecked_signature(q, position, approved, sig);
}
bool blink_tx::add_signature(subquorum q, int position, bool approved, const crypto::signature &sig, const service_node_list &snl) {
return add_signature(q, position, approved, sig, get_sn_pubkey(q, position, snl));
}
bool blink_tx::add_prechecked_signature(subquorum q, int position, bool approved, const crypto::signature &sig) {
check_args(q, position, __func__);
@ -110,4 +121,30 @@ bool blink_tx::rejected() const {
});
}
void blink_tx::fill_serialization_data(crypto::hash &tx_hash, uint64_t &height, std::vector<uint8_t> &quorum, std::vector<uint8_t> &position, std::vector<crypto::signature> &signature) const {
tx_hash = get_txhash();
height = this->height;
constexpr size_t res_size = tools::enum_count<subquorum> * service_nodes::BLINK_SUBQUORUM_SIZE;
quorum.reserve(res_size);
position.reserve(res_size);
signature.reserve(res_size);
for (uint8_t qi = 0; qi < signatures_.size(); qi++) {
for (uint8_t p = 0; p < signatures_[qi].size(); p++) {
auto &sig = signatures_[qi][p];
if (sig.status == signature_status::approved) {
quorum.push_back(qi);
position.push_back(p);
signature.push_back(sig.sig);
}
}
}
}
crypto::hash blink_tx::tx_hash_visitor::operator()(const transaction &tx) const {
crypto::hash h;
if (!cryptonote::get_transaction_hash(tx, h))
throw std::runtime_error("Failed to calculate transaction hash");
return h;
}
}

View file

@ -52,10 +52,19 @@ public:
/// transaction was created.
const uint64_t height;
/// The encapsulated transaction. Any modifications to this (including getting the tx_hash,
/// which gets cached!) should either take place before the object is shared, or protected by
/// the unique_lock.
transaction tx;
class tx_hash_visitor : public boost::static_visitor<crypto::hash> {
public:
crypto::hash operator()(const crypto::hash &h) const { return h; }
crypto::hash operator()(const transaction &tx) const;
};
/// The blink transaction *or* hash. The transaction is present when building a blink tx for
/// blink quorum signing; for regular blink txes received via p2p this will contain the hash
/// instead.
boost::variant<transaction, crypto::hash> tx;
/// Returns the transaction hash
crypto::hash get_txhash() const { return boost::apply_visitor(tx_hash_visitor{}, tx); }
class signature_verification_error : public std::runtime_error {
using std::runtime_error::runtime_error;
@ -64,13 +73,14 @@ public:
// Not default constructible
blink_tx() = delete;
/** Construct a new blink_tx from just a height; constructs a default transaction.
*/
explicit blink_tx(uint64_t height) : tx{}, height{height} {
assert(quorum_height(subquorum::base) > 0);
for (auto &q : signatures_)
for (auto &s : q)
s.status = signature_status::none;
/// Construct a new blink_tx from just a height; constructs a default transaction.
explicit blink_tx(uint64_t height) : height{height} {
initialize();
}
/// Construct a new blink_tx from a height and a hash
explicit blink_tx(uint64_t height, const crypto::hash &txhash) : height{height}, tx{txhash} {
initialize();
}
/// Obtains a unique lock on this blink tx; required for any signature-mutating method unless
@ -78,11 +88,32 @@ public:
template <typename... Args>
auto unique_lock(Args &&...args) { return std::unique_lock<std::shared_timed_mutex>{mutex_, std::forward<Args>(args)...}; }
/// Obtains a unique lock on this blink tx; required for any signature-dependent method unless
/// Obtains a shared lock on this blink tx; required for any signature-dependent method unless
/// otherwise noted
template <typename... Args>
auto shared_lock(Args &&...args) { return std::shared_lock<std::shared_timed_mutex>{mutex_, std::forward<Args>(args)...}; }
/**
* Sets the maximum number of signatures for the given subquorum type, if the given size is less
* than the ideal subquorum size. All signatures above the given count will be set to rejected.
* Throws a std::domain_error if the given signatures count is too large.
*
* This should only be called immediately after construction (thus not needing a lock) and only
* called (at most) once per subquorum.
*
* You can safely omit calling this if you only care about approvals; this only affects the
* result of `rejected()`.
*/
void limit_signatures(subquorum q, size_t max_size);
/**
* Adds a signature for the given quorum and position given an already-obtained blink subquorum
* validator pubkey. Returns true if the signature was accepted and stored, false if a
* signature was already present for the given quorum and position. Throws a
* `blink_tx::signature_verification_error` if the signature fails validation.
*/
bool add_signature(subquorum q, int position, bool approved, const crypto::signature &sig, const crypto::public_key &pubkey);
/**
* Adds a signature for the given quorum and position. Returns false if a signature was already
* present; true if the signature was accepted and stored; and throws a
@ -105,15 +136,14 @@ public:
/**
* Returns true if this blink tx is valid for inclusion in the blockchain, that is, has the
* required number of approval signatures in each quorum. (Note that it is possible for a blink
* tx to be neither approved() nor rejected()). You must hold a shared_lock when calling this.
* tx to be neither approved() nor rejected()).
*/
bool approved() const;
/**
* Returns true if this blink tx has been definitively rejected, that is, has enough rejection
* signatures in at least one of the quorums that it is impossible for it to become approved().
* (Note that it is possible for a blink tx to be neither approved() nor rejected()). You must
* hold a shared_lock when calling this.
* (Note that it is possible for a blink tx to be neither approved() nor rejected()).
*/
bool rejected() const;
@ -142,7 +172,23 @@ public:
crypto::signature sig;
};
/**
* Fills the given blink serialization struct with the signature data. This is designed to work
* directly with the components of a serializable_blink_metadata (but we don't want to have to
* link to cryptonote_protocol where that is defined).
*
* A shared lock should be held by the caller.
*/
void fill_serialization_data(crypto::hash &tx_hash, uint64_t &height, std::vector<uint8_t> &quorum, std::vector<uint8_t> &position, std::vector<crypto::signature> &signature) const;
private:
void initialize() {
assert(quorum_height(subquorum::base) > 0);
for (auto &q : signatures_)
for (auto &s : q)
s.status = signature_status::none;
}
std::array<std::array<quorum_signature, service_nodes::BLINK_SUBQUORUM_SIZE>, tools::enum_count<subquorum>> signatures_;
std::shared_timed_mutex mutex_;
};

View file

@ -439,35 +439,74 @@ namespace cryptonote
return add_tx(tx, h, bl, get_transaction_weight(tx, bl.size()), tvc, keeped_by_block, relayed, do_not_relay, version);
}
//---------------------------------------------------------------------------------
bool tx_memory_pool::add_blink(const std::shared_ptr<blink_tx> &blink_ptr, tx_verification_context &tvc, bool &blink_exists)
bool tx_memory_pool::add_new_blink(const std::shared_ptr<blink_tx> &blink_ptr, tx_verification_context &tvc, bool &blink_exists)
{
assert((bool) blink_ptr);
std::unique_lock<std::shared_timed_mutex> lock(m_blinks_mutex);
auto lock = blink_unique_lock();
CRITICAL_REGION_LOCAL(m_transactions_lock);
auto &tx = blink_ptr->tx;
auto &blink = *blink_ptr;
auto &tx = boost::get<transaction>(blink.tx); // will throw if just a hash w/o a transaction
auto txhash = get_transaction_hash(tx);
auto &ptr = m_blinks[txhash];
blink_exists = (bool) ptr;
blink_exists = m_blinks.count(txhash);
if (blink_exists)
return false;
ptr = blink_ptr;
auto &blink = *ptr;
bool approved = blink.approved();
auto hf_version = m_blockchain.get_ideal_hard_fork_version(blink.height);
bool ret = add_tx(tx, tvc, false /*kept_by_block*/, false /*relayed*/, true /*do_not_relay*/, hf_version);
if (!ret)
m_blinks.erase(txhash);
return ret;
bool do_not_relay = !approved; // Only relay signed blinks
bool result = add_tx(tx, tvc, false /*kept_by_block*/, false /*relayed*/, do_not_relay, hf_version);
if (result && approved)
m_blinks[txhash] = blink_ptr;
return result;
}
//---------------------------------------------------------------------------------
std::shared_ptr<blink_tx> tx_memory_pool::get_blink(const crypto::hash &tx_hash) const
bool tx_memory_pool::add_existing_blink(std::shared_ptr<blink_tx> blink_ptr, bool have_lock)
{
std::shared_lock<std::shared_timed_mutex> lock(m_blinks_mutex);
assert(blink_ptr && blink_ptr->approved());
auto lock = have_lock ? blink_unique_lock(std::defer_lock) : blink_unique_lock();
auto &ptr = m_blinks[blink_ptr->get_txhash()];
if (ptr)
return false;
ptr = blink_ptr;
return true;
}
//---------------------------------------------------------------------------------
std::shared_ptr<blink_tx> tx_memory_pool::get_blink(const crypto::hash &tx_hash, bool have_lock) const
{
auto lock = have_lock ? blink_shared_lock(std::defer_lock) : blink_shared_lock();
auto it = m_blinks.find(tx_hash);
if (it != m_blinks.end())
return it->second;
return {};
}
//---------------------------------------------------------------------------------
bool tx_memory_pool::has_blink(const crypto::hash &tx_hash, bool have_lock) const
{
auto lock = have_lock ? blink_shared_lock(std::defer_lock) : blink_shared_lock();
return m_blinks.find(tx_hash) != m_blinks.end();
}
//---------------------------------------------------------------------------------
void tx_memory_pool::add_missing_blink_hashes(const std::map<uint64_t, std::vector<crypto::hash>> &from)
{
uint64_t immutable_height = m_blockchain.get_immutable_height();
std::unique_lock<std::shared_timed_mutex> lock(m_blinks_mutex);
for (const auto &h_txes : from) {
if (h_txes.first <= immutable_height)
continue;
for (const auto &hash : h_txes.second) {
if (!m_blinks.count(hash)) {
auto &h = m_missing_blinks[hash];
if (h_txes.first > h)
h = h_txes.first;
}
}
}
}
//---------------------------------------------------------------------------------
size_t tx_memory_pool::get_txpool_weight() const
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
@ -780,7 +819,7 @@ namespace cryptonote
const uint64_t now = time(NULL);
txs.reserve(m_blockchain.get_txpool_tx_count());
m_blockchain.for_all_txpool_txes([this, now, &txs](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *){
if(!meta.do_not_relay && now - meta.last_relayed_time > get_relay_delay(now, meta.receive_time))
if(!meta.do_not_relay && (!meta.relayed || now - meta.last_relayed_time > get_relay_delay(now, meta.receive_time)))
{
// if the tx is older than half the max lifetime, we don't re-relay it, to avoid a problem
// mentioned by smooth where nodes would flush txes at slightly different times, causing
@ -827,22 +866,46 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------------------------
int tx_memory_pool::set_relayable(const std::vector<crypto::hash> &tx_hashes) {
int updated = 0;
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
LockedTXN lock(m_blockchain);
for (auto &tx : tx_hashes)
{
try {
txpool_tx_meta_t meta;
if (m_blockchain.get_txpool_tx_meta(tx, meta) && meta.do_not_relay)
{
meta.do_not_relay = false;
m_blockchain.update_txpool_tx(tx, meta);
++updated;
}
} catch (const std::exception &e) {
MERROR("Failed to upate txpool transaction metadata: " << e.what());
}
}
lock.commit();
return updated;
}
//---------------------------------------------------------------------------------
void tx_memory_pool::set_relayed(const std::vector<std::pair<crypto::hash, cryptonote::blobdata>> &txs)
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
const time_t now = time(NULL);
LockedTXN lock(m_blockchain);
for (auto it = txs.begin(); it != txs.end(); ++it)
for (auto &tx : txs)
{
try
{
txpool_tx_meta_t meta;
if (m_blockchain.get_txpool_tx_meta(it->first, meta))
if (m_blockchain.get_txpool_tx_meta(tx.first, meta))
{
meta.relayed = true;
meta.last_relayed_time = now;
m_blockchain.update_txpool_tx(it->first, meta);
m_blockchain.update_txpool_tx(tx.first, meta);
}
}
catch (const std::exception &e)
@ -1217,12 +1280,22 @@ namespace cryptonote
m_parsed_tx_cache.clear();
return true;
}
//------------------------------------------------------------------
std::vector<uint8_t> tx_memory_pool::have_txs(const std::vector<crypto::hash> &hashes) const
{
std::vector<uint8_t> result(hashes.size(), false);
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
auto &db = m_blockchain.get_db();
for (size_t i = 0; i < hashes.size(); i++)
result[i] = db.txpool_has_tx(hashes[i]);
return result;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool::have_tx(const crypto::hash &id) const
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
return m_blockchain.get_db().txpool_has_tx(id);
return have_txs({{id}})[0];
}
//---------------------------------------------------------------------------------
bool tx_memory_pool::have_tx_keyimges_as_spent(const transaction& tx) const

View file

@ -130,23 +130,74 @@ namespace cryptonote
bool add_tx(transaction &tx, tx_verification_context& tvc, bool kept_by_block, bool relayed, bool do_not_relay, uint8_t version);
/**
* @brief attempts to add a blink transaction to the transaction pool and blink pool. The
* transaction is set for relaying if it has the required blink signatures, and not relayed
* otherwise.
* @brief attempts to add a blink transaction to the transaction pool.
*
* This is only for use for new transactions that should not exist yet on the chain or mempool
* (and will fail if already does). See `add_existing_blink` instead to add blink data about a
* transaction that already exists. This is only meant to be called during the SN blink signing
* phase (and requires that the `tx` transaction be properly set to a full transaction);
* ordinary nodes receiving a blink tx from the network should be going through
* core.handle_incoming_blinks instead.
*
* Whether or not the transaction is added to the known blinks or marked for relaying depends on
* whether the passed-in transaction has an `.approved()` status: if it does, the transaction is
* set for relaying and added to the active blinks immediately; otherwise it is not added to the
* known blinks and will not be relayed.
*
* The transaction is *not* added to the known blinks or marked for relaying unless it is passed
* in with an `.approved()` status.
*
* @param blink - a shared_ptr to the blink details
* @param tvc - the verification results
* @param blink_exists - will be set to true if the addition fails because the blink tx already
* exists
*
* @return true if the tx passes validations and has been added to tx/blink pools
* @return true if the tx passes validations and has been added to the tx pool.
*/
bool add_blink(const std::shared_ptr<blink_tx> &blink, tx_verification_context& tvc, bool &blink_exists);
bool add_new_blink(const std::shared_ptr<blink_tx> &blink, tx_verification_context& tvc, bool &blink_exists);
/**
* @brief accesses blink tx details if the given tx hash is a known blink tx, nullptr otherwise.
* @brief attempts to add blink transaction information about an existing blink transaction
*
* This method takes an approved blink_tx and records it in the known blinks data. No check is
* done that the transaction actually exists on the blockchain or mempool. It is assumed that
* the given shared_ptr is a new blink that is not yet shared between threads (and thus doesn't
* need locking): sharing is expected only after it is added to the blinks via this method.
*
* NB: this function assumes that the given blink tx is valid and approved but does *not* check
* it (except when compiling in debug mode).
*
* @param blink the blink_tx shared_ptr
* @param have_lock can be specified as true to avoid taking out a unique lock on the blinks
* data structure; it should only be specified if a unique lock on the blink data is already
* held externally, i.e. by obtaining a lock with `blink_unique_lock`.
*
* @return true if the blink data was recorded, false if the given tx was already known.
*/
std::shared_ptr<blink_tx> get_blink(const crypto::hash &tx_hash) const;
bool add_existing_blink(std::shared_ptr<blink_tx> blink, bool have_lock = false);
/**
* @brief accesses blink tx details if the given tx hash is a known, approved blink tx, nullptr
* otherwise.
*
* @param tx_hash the hash of the tx to access
* @param have_lock can be specified as true to avoid taking out a shared lock; it should only
* be specified if a shared lock on the blink data is already held externally.
*/
std::shared_ptr<blink_tx> get_blink(const crypto::hash &tx_hash, bool have_lock = false) const;
/**
* Equivalent to `(bool) get_blink(...)`, but slightly more efficient when the blink information
* isn't actually needed beyond an existance test (as it avoids copying the shared_ptr).
*/
bool has_blink(const crypto::hash &tx_hash, bool have_lock = false) const;
/**
* @brief takes a map of blink { height -> [tx_hashes] } and records any that we don't already
* know about (and are not before the immutable height) in the internal "m_want_blinks" to
* request from p2p peers.
*/
void add_missing_blink_hashes(const std::map<uint64_t, std::vector<crypto::hash>> &potential);
/**
* @brief takes a transaction with the given hash from the pool
@ -173,6 +224,16 @@ namespace cryptonote
*/
bool have_tx(const crypto::hash &id) const;
/**
* @brief determines whether the given tx hashes are in the mempool
*
* @param hashes vector of tx hashes
*
* @return vector of the same size as `hashes` of true (1) or false (0) values. (Not using
* std::vector<bool> because it is broken by design).
*/
std::vector<uint8_t> have_txs(const std::vector<crypto::hash> &hashes) const;
/**
* @brief action to take when notified of a block added to the blockchain
*
@ -210,6 +271,19 @@ namespace cryptonote
*/
void unlock() const;
/**
* @brief obtains a unique lock on the approved blink tx pool
*/
template <typename... Args>
auto blink_unique_lock(Args &&...args) const { return std::unique_lock<std::shared_timed_mutex>{m_blinks_mutex, std::forward<Args>(args)...}; }
/**
* @brief obtains a shared lock on the approved blink tx pool
*/
template <typename... Args>
auto blink_shared_lock(Args &&...args) const { return std::shared_lock<std::shared_timed_mutex>{m_blinks_mutex, std::forward<Args>(args)...}; }
// load/store operations
/**
@ -343,6 +417,16 @@ namespace cryptonote
*/
bool get_relayable_transactions(std::vector<std::pair<crypto::hash, cryptonote::blobdata>>& txs) const;
/**
* @brief clear transactions' `do_not_relay` flags (if set) so that they can start being
* relayed. (Note that it still must satisfy the other conditions of
* `get_relayable_transactions` to actually be relayable).
*
* @return the number of txes that were found with an active `do_not_relay` flag that was
* cleared.
*/
int set_relayable(const std::vector<crypto::hash> &tx_hashes);
/**
* @brief tell the pool that certain transactions were just relayed
*
@ -598,9 +682,15 @@ namespace cryptonote
std::unordered_map<crypto::hash, transaction> m_parsed_tx_cache;
mutable std::shared_timed_mutex m_blinks_mutex;
// { height => { txhash => blink_tx, ... }, ... }
// Contains blink metadata for approved blink transactions. { txhash => blink_tx, ... }.
std::unordered_map<crypto::hash, std::shared_ptr<cryptonote::blink_tx>> m_blinks;
// TODO: clean up m_blinks once mined & immutably checkpointed
// Contains blink hashes that we don't have but have been told about by another node. (The
// height is stored here for cleanup purposes).
std::unordered_map<crypto::hash, uint64_t> m_missing_blinks;
// TODO: clean up m_blinks and m_missing_blinks once mined & immutably checkpointed
};
}

View file

@ -135,6 +135,21 @@ namespace cryptonote
END_KV_SERIALIZE_MAP()
};
struct serializable_blink_metadata {
crypto::hash tx_hash;
uint64_t height;
std::vector<uint8_t> quorum;
std::vector<uint8_t> position;
std::vector<crypto::signature> signature;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_VAL_POD_AS_BLOB_N(tx_hash, "#")
KV_SERIALIZE_N(height, "h")
KV_SERIALIZE_CONTAINER_POD_AS_BLOB_N(quorum, "q")
KV_SERIALIZE_CONTAINER_POD_AS_BLOB_N(position, "p")
KV_SERIALIZE_CONTAINER_POD_AS_BLOB_N(signature, "s")
END_KV_SERIALIZE_MAP()
};
/************************************************************************/
/* */
/************************************************************************/
@ -144,11 +159,13 @@ namespace cryptonote
struct request_t
{
std::vector<blobdata> txs;
std::vector<blobdata> txs;
std::vector<serializable_blink_metadata> blinks;
std::string _; // padding
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(txs)
KV_SERIALIZE(blinks)
KV_SERIALIZE(_)
END_KV_SERIALIZE_MAP()
};
@ -183,13 +200,15 @@ namespace cryptonote
std::vector<blobdata> txs;
std::vector<block_complete_entry> blocks;
std::vector<crypto::hash> missed_ids;
uint64_t current_blockchain_height;
uint64_t current_blockchain_height;
std::vector<serializable_blink_metadata> blinks;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(txs)
KV_SERIALIZE(blocks)
KV_SERIALIZE_CONTAINER_POD_AS_BLOB(missed_ids)
KV_SERIALIZE(current_blockchain_height)
KV_SERIALIZE(blinks)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<request_t> request;
@ -198,11 +217,21 @@ namespace cryptonote
struct CORE_SYNC_DATA
{
struct blink_sync_data {
uint64_t height;
std::vector<crypto::hash> hashes;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(height)
KV_SERIALIZE_CONTAINER_POD_AS_BLOB(hashes)
END_KV_SERIALIZE_MAP()
};
uint64_t current_height;
uint64_t cumulative_difficulty;
crypto::hash top_id;
uint8_t top_version;
uint32_t pruning_seed;
std::vector<blink_sync_data> blink_txs;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(current_height)
@ -210,6 +239,7 @@ namespace cryptonote
KV_SERIALIZE_VAL_POD_AS_BLOB(top_id)
KV_SERIALIZE_OPT(top_version, (uint8_t)0)
KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0)
KV_SERIALIZE(blink_txs)
END_KV_SERIALIZE_MAP()
};

View file

@ -101,7 +101,7 @@ namespace cryptonote
bool deinit();
void set_p2p_endpoint(nodetool::i_p2p_endpoint<connection_context>* p2p);
//bool process_handshake_data(const blobdata& data, cryptonote_connection_context& context);
bool process_payload_sync_data(const CORE_SYNC_DATA& hshd, cryptonote_connection_context& context, bool is_inital);
bool process_payload_sync_data(CORE_SYNC_DATA&& hshd, cryptonote_connection_context& context, bool is_inital);
bool get_payload_sync_data(blobdata& data);
bool get_payload_sync_data(CORE_SYNC_DATA& hshd);
bool get_stat_info(core_stat_info& stat_inf);

View file

@ -44,6 +44,7 @@
#include "profile_tools.h"
#include "net/network_throttle-detail.hpp"
#include "common/pruning.h"
#include "common/random.h"
#undef LOKI_DEFAULT_LOG_CATEGORY
#define LOKI_DEFAULT_LOG_CATEGORY "net.cn"
@ -296,7 +297,7 @@ namespace cryptonote
}
//------------------------------------------------------------------------------------------------------------------------
template<class t_core>
bool t_cryptonote_protocol_handler<t_core>::process_payload_sync_data(const CORE_SYNC_DATA& hshd, cryptonote_connection_context& context, bool is_inital)
bool t_cryptonote_protocol_handler<t_core>::process_payload_sync_data(CORE_SYNC_DATA&& hshd, cryptonote_connection_context& context, bool is_inital)
{
if(context.m_state == cryptonote_connection_context::state_before_handshake && !is_inital)
return true;
@ -336,6 +337,32 @@ namespace cryptonote
LOG_INFO_CC(context, "New connection posing as pruning seed " << epee::string_tools::to_string_hex(context.m_pruning_seed) << ", seed address " << &context.m_pruning_seed);
#endif
// Check for any blink txes being advertised that we don't know about
{
// Do this in two passes: first pass just validates and quits on failure
uint64_t last_height = 0;
for (auto &b : hshd.blink_txs) {
if (b.height > last_height) {
last_height = b.height;
} else {
MWARNING(context << " peer sent blink tx heights out of order, which is not valid; disconnecting");
return false;
}
if (b.hashes.empty()) {
MWARNING(context << " peer sent invalid blink tx hash data, disconnecting");
return false;
}
}
// { height -> [hash, ...], ... }, extracted from the serialized [height, ...], [[hash, ...], ...] vector pair.
std::map<uint64_t, std::vector<crypto::hash>> blink_hashes;
for (auto &b : hshd.blink_txs)
blink_hashes.emplace_hint(blink_hashes.end(), b.height, std::move(b.hashes));
m_core.get_pool().add_missing_blink_hashes(blink_hashes);
}
uint64_t target = m_core.get_target_blockchain_height();
if (target == 0)
target = m_core.get_current_blockchain_height();
@ -896,7 +923,7 @@ namespace cryptonote
template<class t_core>
int t_cryptonote_protocol_handler<t_core>::handle_notify_new_transactions(int command, NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& context)
{
MLOG_P2P_MESSAGE("Received NOTIFY_NEW_TRANSACTIONS (" << arg.txs.size() << " txes)");
MLOG_P2P_MESSAGE("Received NOTIFY_NEW_TRANSACTIONS (" << arg.txs.size() << " txes w/ " << arg.blinks.size() << " blinks)");
for (const auto &blob: arg.txs)
MLOGIF_P2P_MESSAGE(cryptonote::transaction tx; crypto::hash hash; bool ret = cryptonote::parse_and_validate_tx_from_blob(blob, tx, hash);, ret, "Including transaction " << hash);
@ -914,27 +941,32 @@ namespace cryptonote
std::vector<cryptonote::blobdata> newtxs;
newtxs.reserve(arg.txs.size());
std::vector<tx_verification_context> tvcs;
bool all_okay = m_core.handle_incoming_txs(arg.txs, tvcs, false /*kept by block*/, true /*relayed*/, false /*do not relay*/);
// If !all_okay we want to drop the connection, but we may still have added some incoming txs
// and so still need to finish handling/relaying them
for (size_t i = 0; i < arg.txs.size(); ++i)
{
cryptonote::tx_verification_context tvc{};
m_core.handle_incoming_tx(arg.txs[i], tvc, false, true, false);
if(tvc.m_verifivation_failed)
{
LOG_PRINT_CCONTEXT_L1("Tx verification failed, dropping connection");
drop_connection(context, false, false);
return 1;
}
if(tvc.m_should_be_relayed)
if (tvcs[i].m_should_be_relayed)
newtxs.push_back(std::move(arg.txs[i]));
}
arg.txs = std::move(newtxs);
bool good_blinks = m_core.handle_incoming_blinks(arg.blinks);
if(arg.txs.size())
{
//TODO: add announce usage here
relay_transactions(arg, context);
}
if (!all_okay || !good_blinks)
{
LOG_PRINT_CCONTEXT_L1((!all_okay && !good_blinks ? "Tx and Blink" : !all_okay ? "Tx" : "Blink") << " verification(s) failed, dropping connection");
drop_connection(context, false, false);
}
return 1;
}
//------------------------------------------------------------------------------------------------------------------------
@ -2245,11 +2277,34 @@ skip:
if (hide_tx_broadcast)
MDEBUG("Attempting to conceal origin of tx via anonymity network connection(s)");
// no check for success, so tell core they're relayed unconditionally
const bool pad_transactions = m_core.pad_transactions() || hide_tx_broadcast;
for(auto tx_blob_it = arg.txs.begin(); tx_blob_it!=arg.txs.end(); ++tx_blob_it)
// no check for success, so tell core they're relayed unconditionally and snag a copy of the
// hash so that we can look up any associated blink data we should include.
std::vector<crypto::hash> relayed_txes;
relayed_txes.reserve(arg.txs.size());
for (auto &tx_blob : arg.txs)
relayed_txes.push_back(
m_core.on_transaction_relayed(tx_blob)
);
// Rebuild arg.blinks from blink data that we have because we don't necessarily have the same
// blink data that got sent to us (we may have additional blink info, or may have rejected some
// of the incoming blink data).
arg.blinks.clear();
{
m_core.on_transaction_relayed(*tx_blob_it);
auto &pool = m_core.get_pool();
auto lock = pool.blink_shared_lock();
for (auto &hash : relayed_txes)
{
if (auto blink = pool.get_blink(hash))
{
arg.blinks.emplace_back();
auto &data = arg.blinks.back();
auto l = blink->shared_lock();
blink->fill_serialization_data(data.tx_hash, data.height, data.quorum, data.position, data.signature);
}
}
}
if (pad_transactions)
@ -2266,7 +2321,7 @@ skip:
const bool broadcast_to_peer =
peer_id &&
(hide_tx_broadcast != bool(current_zone == epee::net_utils::zone::public_)) &&
exclude_context.m_connection_id != context.m_connection_id;
exclude_context.m_connection_id != context.m_connection_id;
if (broadcast_to_peer)
connections.push_back({current_zone, context.m_connection_id});

View file

@ -615,12 +615,13 @@ std::string debug_known_signatures(blink_tx &btx, quorum_array &blink_quorums) {
/// Processes blink signatures; called immediately upon receiving a signature if we know about the
/// tx; otherwise signatures are stored until we learn about the tx and then processed.
void process_blink_signatures(SNNWrapper &snw, blink_tx &btx, quorum_array &blink_quorums, uint64_t quorum_checksum, std::list<pending_signature> &&signatures,
void process_blink_signatures(SNNWrapper &snw, const std::shared_ptr<blink_tx> &btxptr, quorum_array &blink_quorums, uint64_t quorum_checksum, std::list<pending_signature> &&signatures,
uint64_t reply_tag, // > 0 if we are expected to send a status update if it becomes accepted/rejected
const std::string reply_pubkey, // who we are supposed to send the status update to
const std::string &received_from = ""s /* x25519 of the peer that sent this, if available (to avoid trying to pointlessly relay back to them) */) {
bool already_approved = false, already_rejected = false;
auto &btx = *btxptr;
// First check values and discard any signatures for positions we already have.
{
auto lock = btx.shared_lock(); // Don't take out a heavier unique lock until later when we are sure we need
@ -644,9 +645,6 @@ void process_blink_signatures(SNNWrapper &snw, blink_tx &btx, quorum_array &blin
}
++it;
}
already_approved = btx.approved();
already_rejected = btx.rejected();
}
if (signatures.empty())
return;
@ -673,10 +671,13 @@ void process_blink_signatures(SNNWrapper &snw, blink_tx &btx, quorum_array &blin
if (signatures.empty())
return;
bool now_approved = already_approved, now_rejected = already_rejected;
bool became_approved = false, became_rejected = false;
{
auto lock = btx.unique_lock();
bool already_approved = btx.approved(),
already_rejected = !already_approved && btx.rejected();
MTRACE("Before recording new signatures I have existing signatures: " << debug_known_signatures(btx, blink_quorums));
// Now actually add them (and do one last check on them)
@ -691,7 +692,7 @@ void process_blink_signatures(SNNWrapper &snw, blink_tx &btx, quorum_array &blin
auto &validators = blink_quorums[qi]->validators;
if (btx.add_prechecked_signature(subquorum, position, approval, signature)) {
MDEBUG("Validated and stored " << (approval ? "approval" : "rejection") << " signature for tx " << btx.tx.hash << ", subquorum " << int{qi} << ", position " << position);
MDEBUG("Validated and stored " << (approval ? "approval" : "rejection") << " signature for tx " << btx.get_txhash() << ", subquorum " << int{qi} << ", position " << position);
++it;
}
else {
@ -702,12 +703,26 @@ void process_blink_signatures(SNNWrapper &snw, blink_tx &btx, quorum_array &blin
}
if (!signatures.empty()) {
now_approved = btx.approved();
now_rejected = btx.rejected();
MDEBUG("Updated signatures; now have signatures: " << debug_known_signatures(btx, blink_quorums));
if (!already_approved && !already_rejected) {
if (btx.approved()) {
became_approved = true;
} else if (btx.rejected()) {
became_rejected = true;
}
}
}
}
if (became_approved) {
MINFO("Accumulated enough signatures for blink tx: enabling tx relay");
auto &pool = snw.core.get_pool();
pool.add_existing_blink(btxptr);
pool.set_relayable({{btx.get_txhash()}});
snw.core.relay_txpool_transactions();
}
if (signatures.empty())
return;
@ -735,7 +750,7 @@ void process_blink_signatures(SNNWrapper &snw, blink_tx &btx, quorum_array &blin
bt_dict blink_sign_data{
{"h", btx.height},
{"#", get_data_as_string(btx.tx.hash)},
{"#", get_data_as_string(btx.get_txhash())},
{"q", quorum_checksum},
{"i", std::move(i_list)},
{"p", std::move(p_list)},
@ -748,11 +763,11 @@ void process_blink_signatures(SNNWrapper &snw, blink_tx &btx, quorum_array &blin
MTRACE("Done blink signature relay");
if (reply_tag && !reply_pubkey.empty()) {
if (now_approved && !already_approved) {
MINFO("Blink tx is now approved; sending result back to originating node");
if (became_approved) {
MINFO("Blink tx became approved; sending result back to originating node");
snw.snn.send(reply_pubkey, "bl_good", bt_dict{{"!", reply_tag}}, send_option::optional{});
} else if (now_rejected && !already_rejected) {
MINFO("Blink tx is now rejected; sending result back to originating node");
} else if (became_rejected) {
MINFO("Blink tx became rejected; sending result back to originating node");
snw.snn.send(reply_pubkey, "bl_bad", bt_dict{{"!", reply_tag}}, send_option::optional{});
}
}
@ -868,9 +883,6 @@ void handle_blink(SNNetwork::message &m, void *self) {
return;
}
auto btxptr = std::make_shared<blink_tx>(blink_height);
auto &btx = *btxptr;
quorum_array blink_quorums;
uint64_t checksum = get_int<uint64_t>(data.at("q"));
try {
@ -895,9 +907,17 @@ void handle_blink(SNNetwork::message &m, void *self) {
return;
}
auto btxptr = std::make_shared<blink_tx>(blink_height);
auto &btx = *btxptr;
auto &tx = boost::get<cryptonote::transaction>(btx.tx);
// If any quorums are too small set the extra spaces to rejected (this also checks that no
// quorums are too big).
for (size_t qi = 0; qi < blink_quorums.size(); qi++)
btx.limit_signatures(static_cast<blink_tx::subquorum>(qi), blink_quorums[qi]->validators.size());
{
crypto::hash tx_hash_actual;
if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, btx.tx, tx_hash_actual)) {
if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash_actual)) {
MINFO("Rejecting blink tx: failed to parse transaction data");
if (tag)
m.reply("bl_nostart", bt_dict{{"!", tag}, {"e", "Failed to parse transaction data"}});
@ -971,7 +991,7 @@ void handle_blink(SNNetwork::message &m, void *self) {
// Check tx for validity
bool already_in_mempool;
cryptonote::tx_verification_context tvc = {};
bool approved = snw.core.get_pool().add_blink(btxptr, tvc, already_in_mempool);
bool approved = snw.core.get_pool().add_new_blink(btxptr, tvc, already_in_mempool);
MINFO("Blink TX " << tx_hash << (approved ? " approved and added to mempool" : " rejected"));
if (!approved)
@ -990,7 +1010,7 @@ void handle_blink(SNNetwork::message &m, void *self) {
signatures.emplace_back(approved, qi, pinfo.my_position[qi], sig);
}
process_blink_signatures(snw, btx, blink_quorums, checksum, std::move(signatures), tag, m.pubkey);
process_blink_signatures(snw, btxptr, blink_quorums, checksum, std::move(signatures), tag, m.pubkey);
}
template <typename T, typename CopyValue>
@ -1106,7 +1126,7 @@ void handle_blink_signature(SNNetwork::message &m, void *self) {
auto blink_quorums = get_blink_quorums(blink_height, snw.core.get_service_node_list(), &checksum); // throws if bad quorum or checksum mismatch
uint64_t reply_tag;
uint64_t reply_tag = 0;
std::string reply_pubkey;
std::shared_ptr<blink_tx> btxptr;
{
@ -1133,11 +1153,10 @@ void handle_blink_signature(SNNetwork::message &m, void *self) {
return;
}
process_blink_signatures(snw, *btxptr, blink_quorums, checksum, std::move(signatures), reply_tag, reply_pubkey, m.pubkey);
process_blink_signatures(snw, btxptr, blink_quorums, checksum, std::move(signatures), reply_tag, reply_pubkey, m.pubkey);
}
// tag -> {hash, promise, expiry}
using blink_response = std::pair<cryptonote::blink_result, std::string>;
struct blink_result_data {
crypto::hash hash;

View file

@ -1336,6 +1336,7 @@ static void print_pool(const std::vector<cryptonote::tx_info> &transactions, boo
<< "relayed: " << (tx_info.relayed ? boost::lexical_cast<std::string>(tx_info.last_relayed_time) + " (" + get_human_time_ago(tx_info.last_relayed_time, now) + ")" : "no") << "\n"
<< std::boolalpha
<< "do_not_relay: " << tx_info.do_not_relay << "\n"
<< "blink: " << tx_info.blink << "\n"
<< "kept_by_block: " << tx_info.kept_by_block << "\n"
<< "double_spend_seen: " << tx_info.double_spend_seen << "\n"
<< std::noboolalpha

View file

@ -984,7 +984,7 @@ namespace nodetool
std::atomic<bool> hsh_result(false);
bool r = epee::net_utils::async_invoke_remote_command2<typename COMMAND_HANDSHAKE::response>(context_.m_connection_id, COMMAND_HANDSHAKE::ID, arg, zone.m_net_server.get_config_object(),
[this, &pi, &ev, &hsh_result, &just_take_peerlist, &context_](int code, const typename COMMAND_HANDSHAKE::response& rsp, p2p_connection_context& context)
[this, &pi, &ev, &hsh_result, &just_take_peerlist, &context_](int code, typename COMMAND_HANDSHAKE::response&& rsp, p2p_connection_context& context)
{
epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ev.raise();});
@ -1009,7 +1009,7 @@ namespace nodetool
hsh_result = true;
if(!just_take_peerlist)
{
if(!m_payload_handler.process_payload_sync_data(rsp.payload_data, context, true))
if(!m_payload_handler.process_payload_sync_data(std::move(rsp.payload_data), context, true))
{
LOG_WARNING_CC(context, "COMMAND_HANDSHAKE invoked, but process_payload_sync_data returned false, dropping connection.");
hsh_result = false;
@ -1068,7 +1068,7 @@ namespace nodetool
network_zone& zone = m_network_zones.at(context_.m_remote_address.get_zone());
bool r = epee::net_utils::async_invoke_remote_command2<typename COMMAND_TIMED_SYNC::response>(context_.m_connection_id, COMMAND_TIMED_SYNC::ID, arg, zone.m_net_server.get_config_object(),
[this](int code, const typename COMMAND_TIMED_SYNC::response& rsp, p2p_connection_context& context)
[this](int code, typename COMMAND_TIMED_SYNC::response&& rsp, p2p_connection_context& context)
{
context.m_in_timedsync = false;
if(code < 0)
@ -1085,7 +1085,7 @@ namespace nodetool
}
if(!context.m_is_income)
m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.set_peer_just_seen(context.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port);
if (!m_payload_handler.process_payload_sync_data(rsp.payload_data, context, false))
if (!m_payload_handler.process_payload_sync_data(std::move(rsp.payload_data), context, false))
{
m_network_zones.at(context.m_remote_address.get_zone()).m_net_server.get_config_object().close(context.m_connection_id );
}
@ -2152,7 +2152,7 @@ namespace nodetool
template<class t_payload_net_handler>
int node_server<t_payload_net_handler>::handle_timed_sync(int command, typename COMMAND_TIMED_SYNC::request& arg, typename COMMAND_TIMED_SYNC::response& rsp, p2p_connection_context& context)
{
if(!m_payload_handler.process_payload_sync_data(arg.payload_data, context, false))
if(!m_payload_handler.process_payload_sync_data(std::move(arg.payload_data), context, false))
{
LOG_WARNING_CC(context, "Failed to process_payload_sync_data(), dropping connection");
drop_connection(context);
@ -2219,7 +2219,7 @@ namespace nodetool
return 1;
}
if(!m_payload_handler.process_payload_sync_data(arg.payload_data, context, true))
if(!m_payload_handler.process_payload_sync_data(std::move(arg.payload_data), context, true))
{
LOG_WARNING_CC(context, "COMMAND_HANDSHAKE came, but process_payload_sync_data returned false, dropping connection.");
drop_connection(context);

View file

@ -438,7 +438,7 @@ void SNNetwork::worker_thread(std::string worker_id) {
const bool &public_cmd = cmdit->second.second;
if (!public_cmd && !msg.sn) {
// If they aren't valid, tell them so that they can disconnect (and attempt to reconnect later with appropriate authentication)
SN_LOG(warn, worker_id << " (of " << object_id << ") received quorum-only command from an non-SN authenticated remote; replying with a BYE");
SN_LOG(warn, worker_id << "/" << object_id << " received quorum-only command " << msg.command << " from an non-SN authenticated remote; replying with a BYE");
send(msg.pubkey, "BYE", send_option::incoming{});
detail::send_control(get_control_socket(), "DISCONNECT", {{"pubkey",msg.pubkey}});
continue;

View file

@ -721,6 +721,14 @@ namespace cryptonote
}
}
{
auto &pool = m_core.get_pool();
auto lock = pool.blink_shared_lock();
for (size_t i = 0; i < res.txs.size(); i++)
if (pool.has_blink(vh[i], true /*have lock*/))
res.txs[i].blink = true;
}
for(const auto& miss_tx: missed_txs)
{
res.missed_tx.push_back(string_tools::pod_to_hex(miss_tx));

View file

@ -470,6 +470,7 @@ namespace cryptonote
uint64_t block_timestamp; // Unix time at chich the block has been added to the blockchain.
std::vector<uint64_t> output_indices; // List of transaction indexes.
bool relayed;
bool blink; // True if this is an approved, blink transaction (only for in_pool transactions or txes in recent blocks)
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash)
@ -490,6 +491,7 @@ namespace cryptonote
{
KV_SERIALIZE(relayed)
}
KV_SERIALIZE(blink)
END_KV_SERIALIZE_MAP()
};
@ -1457,6 +1459,7 @@ namespace cryptonote
bool do_not_relay; // States if this transaction should not be relayed.
bool double_spend_seen; // States if this transaction has been seen as double spend.
std::string tx_blob; // Hexadecimal blob represnting the transaction.
bool blink; // True if this is a signed blink transaction
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(id_hash)

View file

@ -1,5 +1,5 @@
// Copyright (c) 2014-2019, The Monero Project
// Copyright (c) 2018, The Loki Project
// Copyright (c) 2018-2019, The Loki Project
//
// All rights reserved.
//
@ -8230,7 +8230,7 @@ void simple_wallet::wallet_idle_thread()
uint64_t fetched_blocks;
bool received_money;
if (try_connect_to_daemon(true))
m_wallet->refresh(m_wallet->is_trusted_daemon(), 0, fetched_blocks, received_money, false); // don't check the pool in background mode
m_wallet->refresh(m_wallet->is_trusted_daemon(), 0, fetched_blocks, received_money);
}
catch(...) {}
m_auto_refresh_refreshing = false;

View file

@ -1619,7 +1619,7 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation &
}
}
//----------------------------------------------------------------------------------------------------
void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, const crypto::public_key &tx_pub_key, size_t vout_index, tx_scan_info_t &tx_scan_info, std::vector<tx_money_got_in_out> &tx_money_got_in_outs, std::vector<size_t> &outs, bool pool)
void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, const crypto::public_key &tx_pub_key, size_t vout_index, tx_scan_info_t &tx_scan_info, std::vector<tx_money_got_in_out> &tx_money_got_in_outs, std::vector<size_t> &outs, bool pool, bool blink)
{
THROW_WALLET_EXCEPTION_IF(vout_index >= tx.vout.size(), error::wallet_internal_error, "Invalid vout index");
@ -1630,9 +1630,9 @@ void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, cons
CRITICAL_REGION_LOCAL(password_lock);
if (!m_encrypt_keys_after_refresh)
{
boost::optional<epee::wipeable_string> pwd = m_callback->on_get_password(pool ? "output found in pool" : "output received");
THROW_WALLET_EXCEPTION_IF(!pwd, error::password_needed, tr("Password is needed to compute key image for incoming loki"));
THROW_WALLET_EXCEPTION_IF(!verify_password(*pwd), error::password_needed, tr("Invalid password: password is needed to compute key image for incoming loki"));
boost::optional<epee::wipeable_string> pwd = m_callback->on_get_password(pool ? blink ? "blink output receive in pool" : "output found in pool" : "output received");
THROW_WALLET_EXCEPTION_IF(!pwd, error::password_needed, tr("Password is needed to compute key image for incoming LOKI"));
THROW_WALLET_EXCEPTION_IF(!verify_password(*pwd), error::password_needed, tr("Invalid password: password is needed to compute key image for incoming LOKI"));
decrypt_keys(*pwd);
m_encrypt_keys_after_refresh = *pwd;
}
@ -1723,7 +1723,9 @@ void wallet2::cache_tx_data(const cryptonote::transaction& tx, const crypto::has
}
}
//----------------------------------------------------------------------------------------------------
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, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache)
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 blink, bool double_spend_seen,
const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache)
{
if (tx.type != txtype::standard || tx.version <= txversion::v1)
return;
@ -1731,7 +1733,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
PERF_TIMER(process_new_transaction);
// In this function, tx (probably) only contains the base information
// (that is, the prunable stuff may or may not be included)
if (!miner_tx && !pool)
if (!miner_tx && (!pool || blink))
process_unconfirmed(txid, tx, height);
// NOTE: tx_scan_info contains the decoded amounts from the transaction destined for us
@ -1881,7 +1883,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
if (tx_scan_info[i].received)
{
hwdev.conceal_derivation(tx_scan_info[i].received->derivation, tx_pub_key, additional_tx_pub_keys.data, derivation, additional_derivations);
scan_output(tx, miner_tx, tx_pub_key, i, tx_scan_info[i], tx_money_got_in_outs, outs, pool);
scan_output(tx, miner_tx, tx_pub_key, i, tx_scan_info[i], tx_money_got_in_outs, outs, pool, blink);
}
}
}
@ -1897,7 +1899,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
boost::unique_lock<hw::device> hwdev_lock (hwdev);
hwdev.set_mode(hw::device::NONE);
hwdev.conceal_derivation(tx_scan_info[i].received->derivation, tx_pub_key, additional_tx_pub_keys.data, derivation, additional_derivations);
scan_output(tx, miner_tx, tx_pub_key, i, tx_scan_info[i], tx_money_got_in_outs, outs, pool);
scan_output(tx, miner_tx, tx_pub_key, i, tx_scan_info[i], tx_money_got_in_outs, outs, pool, blink);
}
}
}
@ -1927,7 +1929,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
if (kit == m_pub_keys.end())
{
uint64_t amount = tx.vout[o].amount ? tx.vout[o].amount : tx_scan_info[o].amount;
if (!pool)
if (!pool || blink)
{
pk_to_unlock_times[tx_scan_info[o].in_ephemeral.pub] = tx_scan_info[o].unlock_time;
@ -1935,7 +1937,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
transfer_details& td = m_transfers.back();
td.m_block_height = height;
td.m_internal_output_index = o;
td.m_global_output_index = o_indices[o];
td.m_global_output_index = pool ? 0 : o_indices[o]; // blink tx doesn't have this; will get updated when it gets into a block
td.m_tx = (const cryptonote::transaction_prefix&)tx;
td.m_txid = txid;
td.m_key_image = tx_scan_info[o].ki;
@ -2080,12 +2082,12 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
uint64_t amount = tx.vout[o].amount ? tx.vout[o].amount : tx_scan_info[o].amount;
uint64_t extra_amount = amount - m_transfers[kit->second].amount();
if (!pool)
if (!pool || blink)
{
transfer_details &td = m_transfers[kit->second];
td.m_block_height = height;
td.m_internal_output_index = o;
td.m_global_output_index = o_indices[o];
td.m_global_output_index = pool ? 0 : o_indices[o]; // blink tx doesn't have this; will get updated when it gets into a block
td.m_tx = (const cryptonote::transaction_prefix&)tx;
td.m_txid = txid;
td.m_amount = amount;
@ -2413,7 +2415,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry
{
TIME_MEASURE_START(miner_tx_handle_time);
if (m_refresh_type != RefreshNoCoinbase)
process_new_transaction(get_transaction_hash(b.miner_tx), b.miner_tx, parsed_block.o_indices.indices[0].indices, height, b.timestamp, true, false, false, tx_cache_data[tx_cache_data_offset], output_tracker_cache);
process_new_transaction(get_transaction_hash(b.miner_tx), b.miner_tx, parsed_block.o_indices.indices[0].indices, height, b.timestamp, true, false, false, false, tx_cache_data[tx_cache_data_offset], output_tracker_cache);
++tx_cache_data_offset;
TIME_MEASURE_FINISH(miner_tx_handle_time);
@ -2422,7 +2424,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry
THROW_WALLET_EXCEPTION_IF(bche.txs.size() != parsed_block.txes.size(), error::wallet_internal_error, "Wrong amount of transactions for block");
for (size_t idx = 0; idx < b.tx_hashes.size(); ++idx)
{
process_new_transaction(b.tx_hashes[idx], parsed_block.txes[idx], parsed_block.o_indices.indices[idx+1].indices, height, b.timestamp, false, false, false, tx_cache_data[tx_cache_data_offset++], output_tracker_cache);
process_new_transaction(b.tx_hashes[idx], parsed_block.txes[idx], parsed_block.o_indices.indices[idx+1].indices, height, b.timestamp, false, false, false, false, tx_cache_data[tx_cache_data_offset++], output_tracker_cache);
}
TIME_MEASURE_FINISH(txs_handle_time);
m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx);
@ -2953,11 +2955,11 @@ void wallet2::update_pool_state(bool refreshed)
if (get_pruned_tx(tx_entry, tx, tx_hash))
{
const std::vector<std::pair<crypto::hash, bool>>::const_iterator i = std::find_if(txids.begin(), txids.end(),
auto 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())
{
process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, time(NULL), false, true, tx_entry.double_spend_seen, {});
process_new_transaction(tx_hash, tx, {}, 0, time(NULL), false, true, tx_entry.blink, tx_entry.double_spend_seen, {});
m_scanned_pool_txs[0].insert(tx_hash);
if (m_scanned_pool_txs[0].size() > 5000)
{
@ -3096,7 +3098,7 @@ std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> wallet2::create
return cache;
}
//----------------------------------------------------------------------------------------------------
void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool)
void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money)
{
if (m_offline)
{
@ -3305,7 +3307,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
try
{
// If stop() is called we don't need to check pending transactions
if (check_pool && m_run.load(std::memory_order_relaxed))
if (m_run.load(std::memory_order_relaxed))
update_pool_state(refreshed);
}
catch (...)

View file

@ -895,7 +895,7 @@ private:
bool is_deprecated() const;
void refresh(bool trusted_daemon);
void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched);
void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool = true);
void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money);
bool refresh(bool trusted_daemon, uint64_t & blocks_fetched, bool& received_money, bool& ok);
void set_refresh_type(RefreshType refresh_type) { m_refresh_type = refresh_type; }
@ -1549,7 +1549,7 @@ private:
* \param password Password of wallet file
*/
bool load_keys(const std::string& keys_file_name, const epee::wipeable_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, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
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 blink, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
bool should_skip_block(const cryptonote::block &b, uint64_t height) const;
void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector<tx_cache_data> &tx_cache_data, size_t tx_cache_data_offset, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
void detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
@ -1586,7 +1586,7 @@ private:
bool tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const;
bool should_pick_a_second_output(size_t n_transfers, const std::vector<size_t> &unused_transfers_indices, const std::vector<size_t> &unused_dust_indices) const;
std::vector<size_t> get_only_rct(const std::vector<size_t> &unused_dust_indices, const std::vector<size_t> &unused_transfers_indices) const;
void scan_output(const cryptonote::transaction &tx, bool miner_tx, const crypto::public_key &tx_pub_key, size_t vout_index, tx_scan_info_t &tx_scan_info, std::vector<tx_money_got_in_out> &tx_money_got_in_outs, std::vector<size_t> &outs, bool pool);
void scan_output(const cryptonote::transaction &tx, bool miner_tx, const crypto::public_key &tx_pub_key, size_t vout_index, tx_scan_info_t &tx_scan_info, std::vector<tx_money_got_in_out> &tx_money_got_in_outs, std::vector<size_t> &outs, bool pool, bool blink);
void trim_hashchain();
crypto::key_image get_multisig_composite_key_image(size_t n) const;
rct::multisig_kLRki get_multisig_composite_kLRki(size_t n, const std::unordered_set<crypto::public_key> &ignore_set, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const;