Service Node Deregister Part 5 (#89)

* Retrieve quorum list from height, reviewed

* Setup data structures for de/register TX

* Submit and validate partial/full deregisters

* Add P2P relaying of partial deregistration votes

* Code review adjustments for deregistration part 1

 - Fix check_tx_semantic
 - Remove signature_pod as votes are now stored as blobs. Serialization
   overrides don't intefere with crypto::signature anymore.

* deregistration_vote_pool - changed sign/verify interface and removed repeated code

* Misc review, fix sign/verify api, vote threshold

* Deregister/tx edge case handling for combinatoric votes

* core, service_node_list: separated address from service node pubkey

* Retrieve quorum list from height, reviewed

* Setup data structures for de/register TX

* Submit and validate partial/full deregisters

* Add P2P relaying of partial deregistration votes

* Code review adjustments for deregistration part 1

 - Fix check_tx_semantic
 - Remove signature_pod as votes are now stored as blobs. Serialization
   overrides don't intefere with crypto::signature anymore.

* deregistration_vote_pool - changed sign/verify interface and removed repeated code

* Misc review, fix sign/verify api, vote threshold

* Deregister/tx edge case handling for combinatoric votes

* Store service node lists for the duration of deregister lifetimes

* Quorum min/max bug, sort node list, fix node to test list

* Change quorum to store acc pub address, fix oob bug

* Code review for expiring votes, acc keys to pub_key, improve err msgs

* Add early out for is_deregistration_tx and protect against quorum changes

* Remove debug code, fix segfault

* Remove irrelevant check for tx v3 in blockchain, fix >= height for pruning quorum states

Incorrect assumption that a transaction can be kept in the chain if it could
eventually become invalid, because if it were the chain would be split and
eventually these transaction would be dropped. But also that we should not
override the pre-existing logic which handles this case anyway.
This commit is contained in:
Doyle 2018-07-18 12:42:47 +10:00 committed by jcktm
parent 471f487203
commit fb66b7e00b
39 changed files with 1527 additions and 129 deletions

View File

@ -262,8 +262,9 @@ int main(int argc, char* argv[])
Blockchain m_blockchain;
tx_memory_pool m_mempool;
service_nodes::service_node_list m_service_node_list;
loki::deregister_vote_pool m_deregister_vote_pool;
BlockchainObjects() :
m_blockchain(m_mempool, m_service_node_list),
m_blockchain(m_mempool, m_service_node_list, m_deregister_vote_pool),
m_service_node_list(m_blockchain),
m_mempool(m_blockchain) { }
};

View File

@ -157,8 +157,9 @@ int main(int argc, char* argv[])
Blockchain m_blockchain;
tx_memory_pool m_mempool;
service_nodes::service_node_list m_service_node_list;
loki::deregister_vote_pool m_deregister_vote_pool;
BlockchainObjects() :
m_blockchain(m_mempool, m_service_node_list),
m_blockchain(m_mempool, m_service_node_list, m_deregister_vote_pool),
m_service_node_list(m_blockchain),
m_mempool(m_blockchain) { }
};

View File

@ -175,8 +175,9 @@ int main(int argc, char* argv[])
Blockchain m_blockchain;
tx_memory_pool m_mempool;
service_nodes::service_node_list m_service_node_list;
loki::deregister_vote_pool m_deregister_vote_pool;
BlockchainObjects() :
m_blockchain(m_mempool, m_service_node_list),
m_blockchain(m_mempool, m_service_node_list, m_deregister_vote_pool),
m_service_node_list(m_blockchain),
m_mempool(m_blockchain) { }
};

View File

@ -157,6 +157,14 @@ namespace cryptonote
{
public:
enum version
{
version_0 = 0,
version_1,
version_2,
version_3_deregister_tx,
};
// tx information
size_t version;
uint64_t unlock_time; //number of block (or time), used as a limitation like: spend this tx not early then block/time

View File

@ -399,22 +399,27 @@ namespace cryptonote
return get_tx_pub_key_from_extra(tx.extra, pk_index);
}
//---------------------------------------------------------------
bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key)
static void add_data_to_tx_extra(std::vector<uint8_t>& tx_extra, char const *data, size_t data_size, uint8_t tag)
{
return add_tx_pub_key_to_extra(tx.extra, tx_pub_key);
size_t pos = tx_extra.size();
tx_extra.resize(tx_extra.size() + sizeof(tag) + data_size);
tx_extra[pos++] = tag;
std::memcpy(&tx_extra[pos], data, data_size);
}
//---------------------------------------------------------------
bool add_tx_pub_key_to_extra(transaction_prefix& tx, const crypto::public_key& tx_pub_key)
void add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key)
{
return add_tx_pub_key_to_extra(tx.extra, tx_pub_key);
add_tx_pub_key_to_extra(tx.extra, tx_pub_key);
}
//---------------------------------------------------------------
bool add_tx_pub_key_to_extra(std::vector<uint8_t>& tx_extra, const crypto::public_key& tx_pub_key)
void add_tx_pub_key_to_extra(transaction_prefix& tx, const crypto::public_key& tx_pub_key)
{
tx_extra.resize(tx_extra.size() + 1 + sizeof(crypto::public_key));
tx_extra[tx_extra.size() - 1 - sizeof(crypto::public_key)] = TX_EXTRA_TAG_PUBKEY;
*reinterpret_cast<crypto::public_key*>(&tx_extra[tx_extra.size() - sizeof(crypto::public_key)]) = tx_pub_key;
return true;
add_tx_pub_key_to_extra(tx.extra, tx_pub_key);
}
//---------------------------------------------------------------
void add_tx_pub_key_to_extra(std::vector<uint8_t>& tx_extra, const crypto::public_key& tx_pub_key)
{
add_data_to_tx_extra(tx_extra, reinterpret_cast<const char *>(&tx_pub_key), sizeof(tx_pub_key), TX_EXTRA_TAG_PUBKEY);
}
//---------------------------------------------------------------
std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const std::vector<uint8_t>& tx_extra)
@ -467,37 +472,42 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------
bool add_account_public_address_to_tx_extra(std::vector<uint8_t>& tx_extra, const cryptonote::account_public_address& address, const crypto::public_key& service_node_key)
bool add_service_node_deregister_to_tx_extra(std::vector<uint8_t>& tx_extra, const tx_extra_service_node_deregister& deregistration)
{
tx_extra.resize(tx_extra.size() + 1 + sizeof(cryptonote::account_public_address) + sizeof(crypto::public_key));
tx_extra[tx_extra.size() - 1 - sizeof(cryptonote::account_public_address) - sizeof(crypto::public_key)] = TX_EXTRA_TAG_ACCOUNT_PUBLIC_ADDRESS;
*reinterpret_cast<cryptonote::account_public_address*>(&tx_extra[tx_extra.size() - sizeof(cryptonote::account_public_address) - sizeof(crypto::public_key)]) = address;
*reinterpret_cast<crypto::public_key*>(&tx_extra[tx_extra.size() - sizeof(crypto::public_key)]) = service_node_key;
tx_extra_field field = tx_extra_service_node_deregister{deregistration.block_height, deregistration.service_node_index, deregistration.votes};
std::ostringstream oss;
binary_archive<true> ar(oss);
bool r = ::do_serialize(ar, field);
CHECK_AND_ASSERT_MES(r, false, "failed to serialize tx extra service node deregister");
std::string tx_extra_str = oss.str();
size_t pos = tx_extra.size();
tx_extra.resize(tx_extra.size() + tx_extra_str.size());
memcpy(&tx_extra[pos], tx_extra_str.data(), tx_extra_str.size());
return true;
}
//---------------------------------------------------------------
cryptonote::account_public_address get_account_public_address_from_tx_extra(const std::vector<uint8_t>& tx_extra)
void add_service_node_register_to_tx_extra(std::vector<uint8_t>& tx_extra, const tx_extra_service_node_register& registration)
{
// parse
std::vector<tx_extra_field> tx_extra_fields;
parse_tx_extra(tx_extra, tx_extra_fields);
// find corresponding field
tx_extra_account_public_address address;
if (!find_tx_extra_field_by_type(tx_extra_fields, address))
return cryptonote::account_public_address{ crypto::null_pkey, crypto::null_pkey };
return cryptonote::account_public_address{ address.m_spend_public_key, address.m_view_public_key };
add_data_to_tx_extra(tx_extra, reinterpret_cast<const char*>(&registration), sizeof(registration), TX_EXTRA_TAG_SERVICE_NODE_REGISTER);
}
//---------------------------------------------------------------
crypto::public_key get_service_node_key_from_tx_extra(const std::vector<uint8_t>& tx_extra)
bool get_service_node_register_from_tx_extra(const std::vector<uint8_t>& tx_extra, tx_extra_service_node_register &registration)
{
// parse
std::vector<tx_extra_field> tx_extra_fields;
parse_tx_extra(tx_extra, tx_extra_fields);
// find corresponding field
tx_extra_account_public_address address;
if (!find_tx_extra_field_by_type(tx_extra_fields, address))
return crypto::null_pkey;
return address.m_service_node_key;
bool result = find_tx_extra_field_by_type(tx_extra_fields, registration);
return result;
}
//---------------------------------------------------------------
bool get_service_node_deregister_from_tx_extra(const std::vector<uint8_t>& tx_extra, tx_extra_service_node_deregister &deregistration)
{
std::vector<tx_extra_field> tx_extra_fields;
parse_tx_extra(tx_extra, tx_extra_fields);
bool result = find_tx_extra_field_by_type(tx_extra_fields, deregistration);
return result;
}
//---------------------------------------------------------------
bool remove_field_from_tx_extra(std::vector<uint8_t>& tx_extra, const std::type_info &type)

View File

@ -67,12 +67,13 @@ namespace cryptonote
crypto::public_key get_tx_pub_key_from_extra(const std::vector<uint8_t>& tx_extra, size_t pk_index = 0);
crypto::public_key get_tx_pub_key_from_extra(const transaction_prefix& tx, size_t pk_index = 0);
crypto::public_key get_tx_pub_key_from_extra(const transaction& tx, size_t pk_index = 0);
bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key);
bool add_tx_pub_key_to_extra(transaction_prefix& tx, const crypto::public_key& tx_pub_key);
bool add_tx_pub_key_to_extra(std::vector<uint8_t>& tx_extra, const crypto::public_key& tx_pub_key);
bool add_account_public_address_to_tx_extra(std::vector<uint8_t>& tx_extra, const cryptonote::account_public_address& address, const crypto::public_key& service_node_key);
cryptonote::account_public_address get_account_public_address_from_tx_extra(const std::vector<uint8_t>& tx_extra);
crypto::public_key get_service_node_key_from_tx_extra(const std::vector<uint8_t>& tx_extra);
void add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key);
void add_tx_pub_key_to_extra(transaction_prefix& tx, const crypto::public_key& tx_pub_key);
void add_tx_pub_key_to_extra(std::vector<uint8_t>& tx_extra, const crypto::public_key& tx_pub_key);
bool add_service_node_deregister_to_tx_extra(std::vector<uint8_t>& tx_extra, const tx_extra_service_node_deregister& deregistration);
void add_service_node_register_to_tx_extra(std::vector<uint8_t>& tx_extra, const tx_extra_service_node_register& registration);
bool get_service_node_register_from_tx_extra(const std::vector<uint8_t>& tx_extra, tx_extra_service_node_register& registration);
bool get_service_node_deregister_from_tx_extra(const std::vector<uint8_t>& tx_extra, tx_extra_service_node_deregister& deregistration);
std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const std::vector<uint8_t>& tx_extra);
std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const transaction_prefix& tx);
bool add_additional_tx_pub_keys_to_extra(std::vector<uint8_t>& tx_extra, const std::vector<crypto::public_key>& additional_pub_keys);

View File

@ -30,19 +30,20 @@
#pragma once
#define TX_EXTRA_PADDING_MAX_COUNT 255
#define TX_EXTRA_NONCE_MAX_COUNT 255
#define TX_EXTRA_PADDING_MAX_COUNT 255
#define TX_EXTRA_NONCE_MAX_COUNT 255
#define TX_EXTRA_TAG_PADDING 0x00
#define TX_EXTRA_TAG_PUBKEY 0x01
#define TX_EXTRA_NONCE 0x02
#define TX_EXTRA_MERGE_MINING_TAG 0x03
#define TX_EXTRA_TAG_ADDITIONAL_PUBKEYS 0x04
#define TX_EXTRA_TAG_ACCOUNT_PUBLIC_ADDRESS 0x70
#define TX_EXTRA_MYSTERIOUS_MINERGATE_TAG 0xDE
#define TX_EXTRA_TAG_PADDING 0x00
#define TX_EXTRA_TAG_PUBKEY 0x01
#define TX_EXTRA_NONCE 0x02
#define TX_EXTRA_MERGE_MINING_TAG 0x03
#define TX_EXTRA_TAG_ADDITIONAL_PUBKEYS 0x04
#define TX_EXTRA_TAG_SERVICE_NODE_REGISTER 0x70
#define TX_EXTRA_TAG_SERVICE_NODE_DEREGISTER 0x71
#define TX_EXTRA_MYSTERIOUS_MINERGATE_TAG 0xDE
#define TX_EXTRA_NONCE_PAYMENT_ID 0x00
#define TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID 0x01
#define TX_EXTRA_NONCE_PAYMENT_ID 0x00
#define TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID 0x01
namespace cryptonote
{
@ -170,19 +171,6 @@ namespace cryptonote
END_SERIALIZE()
};
struct tx_extra_account_public_address
{
crypto::public_key m_spend_public_key;
crypto::public_key m_view_public_key;
crypto::public_key m_service_node_key;
BEGIN_SERIALIZE()
FIELD(m_spend_public_key)
FIELD(m_view_public_key)
FIELD(m_service_node_key)
END_SERIALIZE()
};
struct tx_extra_mysterious_minergate
{
std::string data;
@ -192,6 +180,38 @@ namespace cryptonote
END_SERIALIZE()
};
struct tx_extra_service_node_register
{
crypto::public_key public_view_key;
crypto::public_key public_spend_key;
crypto::public_key service_node_key;
BEGIN_SERIALIZE()
FIELD(public_view_key)
FIELD(public_spend_key)
FIELD(service_node_key)
END_SERIALIZE()
};
struct tx_extra_service_node_deregister
{
struct vote
{
crypto::signature signature;
uint32_t voters_quorum_index;
};
uint64_t block_height;
uint32_t service_node_index;
std::vector<vote> votes;
BEGIN_SERIALIZE()
FIELD(block_height)
FIELD(service_node_index)
FIELD(votes)
END_SERIALIZE()
};
// tx_extra_field format, except tx_extra_padding and tx_extra_pub_key:
// varint tag;
// varint size;
@ -201,14 +221,18 @@ namespace cryptonote
tx_extra_nonce,
tx_extra_merge_mining_tag,
tx_extra_additional_pub_keys,
tx_extra_account_public_address,
tx_extra_mysterious_minergate> tx_extra_field;
tx_extra_mysterious_minergate,
tx_extra_service_node_register,
tx_extra_service_node_deregister> tx_extra_field;
}
VARIANT_TAG(binary_archive, cryptonote::tx_extra_padding, TX_EXTRA_TAG_PADDING);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_pub_key, TX_EXTRA_TAG_PUBKEY);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_nonce, TX_EXTRA_NONCE);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_merge_mining_tag, TX_EXTRA_MERGE_MINING_TAG);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_additional_pub_keys, TX_EXTRA_TAG_ADDITIONAL_PUBKEYS);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_account_public_address, TX_EXTRA_TAG_ACCOUNT_PUBLIC_ADDRESS);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_mysterious_minergate, TX_EXTRA_MYSTERIOUS_MINERGATE_TAG);
BLOB_SERIALIZER(cryptonote::tx_extra_service_node_deregister::vote);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_padding, TX_EXTRA_TAG_PADDING);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_pub_key, TX_EXTRA_TAG_PUBKEY);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_nonce, TX_EXTRA_NONCE);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_merge_mining_tag, TX_EXTRA_MERGE_MINING_TAG);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_additional_pub_keys, TX_EXTRA_TAG_ADDITIONAL_PUBKEYS);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_mysterious_minergate, TX_EXTRA_MYSTERIOUS_MINERGATE_TAG);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_service_node_register, TX_EXTRA_TAG_SERVICE_NODE_REGISTER);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_service_node_deregister, TX_EXTRA_TAG_SERVICE_NODE_DEREGISTER);

View File

@ -34,6 +34,18 @@ namespace cryptonote
/************************************************************************/
/* */
/************************************************************************/
struct vote_verification_context
{
bool m_verification_failed;
bool m_invalid_block_height;
bool m_voters_quorum_index_out_of_bounds;
bool m_service_node_index_out_of_bounds;
bool m_signature_not_valid;
bool m_added_to_pool;
bool m_full_tx_deregister_made;
bool m_not_enough_votes;
};
struct tx_verification_context
{
bool m_should_be_relayed;
@ -48,6 +60,8 @@ namespace cryptonote
bool m_overspend;
bool m_fee_too_low;
bool m_not_rct;
vote_verification_context m_vote_ctx;
};
struct block_verification_context

View File

@ -41,7 +41,7 @@
#define CRYPTONOTE_MAX_TX_SIZE 1000000000
#define CRYPTONOTE_PUBLIC_ADDRESS_TEXTBLOB_VER 0
#define CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW 30
#define CURRENT_TRANSACTION_VERSION 2
#define CURRENT_TRANSACTION_VERSION 3
#define CURRENT_BLOCK_MAJOR_VERSION 6
#define CURRENT_BLOCK_MINOR_VERSION 6
#define CURRENT_BLOCK_MAJOR_VERSION_TESTNET 7
@ -49,6 +49,7 @@
#define CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT_V2 60*10
#define CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE 10
#define STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS 20
#define STAKING_REQUIREMENT_LOCK_BLOCKS (30*24*31)
#define STAKING_RELOCK_WINDOW_BLOCKS (30*6)

View File

@ -31,6 +31,7 @@ set(cryptonote_core_sources
blockchain.cpp
cryptonote_core.cpp
service_node_list.cpp
service_node_deregister.cpp
tx_pool.cpp
cryptonote_tx_utils.cpp)
@ -41,6 +42,7 @@ set(cryptonote_core_private_headers
blockchain.h
service_node_list.h
cryptonote_core.h
service_node_deregister.h
tx_pool.h
cryptonote_tx_utils.h)

View File

@ -40,6 +40,7 @@
#include "blockchain.h"
#include "blockchain_db/blockchain_db.h"
#include "cryptonote_basic/cryptonote_boost_serialization.h"
#include "cryptonote_core/service_node_deregister.h"
#include "cryptonote_config.h"
#include "cryptonote_basic/miner.h"
#include "misc_language.h"
@ -56,6 +57,7 @@
#if defined(PER_BLOCK_CHECKPOINT)
#include "blocks/blocks.h"
#endif
#include "service_node_deregister.h"
#include "service_node_list.h"
#undef LOKI_DEFAULT_LOG_CATEGORY
@ -117,12 +119,13 @@ static const struct {
};
//------------------------------------------------------------------
Blockchain::Blockchain(tx_memory_pool& tx_pool, service_nodes::service_node_list& service_node_list) :
Blockchain::Blockchain(tx_memory_pool& tx_pool, service_nodes::service_node_list& service_node_list, loki::deregister_vote_pool& deregister_vote_pool):
m_db(), m_tx_pool(tx_pool), m_hardfork(NULL), m_timestamps_and_difficulties_height(0), m_current_block_cumul_sz_limit(0), m_current_block_cumul_sz_median(0),
m_enforce_dns_checkpoints(false), m_max_prepare_blocks_threads(4), m_db_blocks_per_sync(1), m_db_sync_mode(db_async), m_db_default_sync(false), m_fast_sync(true), m_show_time_stats(false), m_sync_counter(0), m_cancel(false),
m_difficulty_for_next_block_top_hash(crypto::null_hash),
m_difficulty_for_next_block(1),
m_service_node_list(service_node_list)
m_service_node_list(service_node_list),
m_deregister_vote_pool(deregister_vote_pool)
{
LOG_PRINT_L3("Blockchain::" << __func__);
}
@ -2651,7 +2654,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
// from hard fork 2, we require mixin at least 2 unless one output cannot mix with 2 others
// if one output cannot mix with 2 others, we accept at most 1 output that can mix
if (hf_version >= 2)
if (hf_version >= 2 && tx.version != transaction::version_3_deregister_tx)
{
size_t n_unmixable = 0, n_mixable = 0;
size_t mixin = std::numeric_limits<size_t>::max();
@ -2860,7 +2863,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
}
}
}
else
else if (tx.version == transaction::version_2)
{
if (!expand_transaction_2(tx, tx_prefix_hash, pubkeys))
{
@ -3002,6 +3005,68 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
return false;
}
}
else if (tx.version == transaction::version_3_deregister_tx)
{
// Check the inputs (votes) of the transaction have not been already been
// submitted to the blockchain under another transaction using a different
// combination of votes.
tx_extra_service_node_deregister deregister;
if (!get_service_node_deregister_from_tx_extra(tx.extra, deregister))
{
MERROR_VER("TX version deregister did not have the deregister metadata in the tx_extra");
return false;
}
std::shared_ptr<service_nodes::quorum_state> quorum_state = m_service_node_list.get_quorum_state(deregister.block_height);
if (!quorum_state)
{
MERROR_VER("TX version 3 deregister_tx could not get quorum for height: " << deregister.block_height);
return false;
}
if (!loki::service_node_deregister::verify_deregister(deregister, tvc.m_vote_ctx, *quorum_state))
{
tvc.m_verifivation_failed = true;
MERROR_VER("tx " << get_transaction_hash(tx) << ": version 3 deregister_tx could not be completely verified.");
return false;
}
const uint64_t height = deregister.block_height;
const size_t num_blocks_to_check = loki::service_node_deregister::DEREGISTER_LIFETIME_BY_HEIGHT;
std::list<std::pair<cryptonote::blobdata,block>> blocks;
std::list<cryptonote::blobdata> txs;
if (get_blocks(height, num_blocks_to_check, blocks, txs))
{
for (blobdata const &blob : txs)
{
transaction existing_tx;
if (!parse_and_validate_tx_from_blob(blob, existing_tx))
{
MERROR_VER("tx could not be validated from blob, possibly corrupt blockchain");
continue;
}
if (existing_tx.version != transaction::version_3_deregister_tx)
continue;
tx_extra_service_node_deregister existing_deregister;
if (!get_service_node_deregister_from_tx_extra(existing_tx.extra, existing_deregister))
{
MERROR_VER("could not get service node deregister from tx extra, possibly corrupt tx");
continue;
}
if (existing_deregister.block_height == deregister.block_height &&
existing_deregister.service_node_index == deregister.service_node_index)
{
tvc.m_double_spend = true;
return false;
}
}
}
}
return true;
}
@ -3659,6 +3724,8 @@ leave:
// appears to be a NOP *and* is called elsewhere. wat?
m_tx_pool.on_blockchain_inc(new_height, id);
m_deregister_vote_pool.remove_expired_votes(new_height);
m_deregister_vote_pool.remove_used_votes(txs);
return true;
}

View File

@ -60,6 +60,11 @@ namespace service_nodes
class service_node_list;
};
namespace loki
{
class deregister_vote_pool;
};
namespace cryptonote
{
class tx_memory_pool;
@ -134,7 +139,7 @@ namespace cryptonote
*
* @param tx_pool a reference to the transaction pool to be kept by the Blockchain
*/
Blockchain(tx_memory_pool& tx_pool, service_nodes::service_node_list& service_node_list);
Blockchain(tx_memory_pool& tx_pool, service_nodes::service_node_list& service_node_list, loki::deregister_vote_pool &deregister_vote_pool);
/**
* @brief Initialize the Blockchain state
@ -1026,6 +1031,7 @@ namespace cryptonote
tx_memory_pool& m_tx_pool;
service_nodes::service_node_list& m_service_node_list;
loki::deregister_vote_pool& m_deregister_vote_pool;
mutable epee::critical_section m_blockchain_lock; // TODO: add here reader/writer lock

View File

@ -36,6 +36,7 @@
using namespace epee;
#include <unordered_set>
#include "cryptonote_core.h"
#include "common/command_line.h"
#include "common/util.h"
@ -168,7 +169,7 @@ namespace cryptonote
core::core(i_cryptonote_protocol* pprotocol):
m_mempool(m_blockchain_storage),
m_service_node_list(m_blockchain_storage),
m_blockchain_storage(m_mempool, m_service_node_list),
m_blockchain_storage(m_mempool, m_service_node_list, m_deregister_vote_pool),
m_miner(this),
m_miner_address(boost::value_initialized<account_public_address>()),
m_starter_message_showed(false),
@ -680,11 +681,12 @@ namespace cryptonote
}
bad_semantics_txes_lock.unlock();
uint8_t version = m_blockchain_storage.get_current_hard_fork_version();
const size_t max_tx_version = version == 1 ? 1 : 2;
int version = m_blockchain_storage.get_current_hard_fork_version();
unsigned int max_tx_version = version == 1 ? 1 : version < 8 ? 2 : 3;
if (tx.version == 0 || tx.version > max_tx_version)
{
// v2 is the latest one we know
// v3 is the latest one we know
tvc.m_verifivation_failed = true;
return false;
}
@ -818,11 +820,10 @@ namespace cryptonote
st_inf.top_block_id_str = epee::string_tools::pod_to_hex(m_blockchain_storage.get_tail_id());
return true;
}
//-----------------------------------------------------------------------------------------------
bool core::check_tx_semantic(const transaction& tx, bool keeped_by_block) const
{
if(!tx.vin.size())
if(!tx.vin.size() && tx.version != transaction::version_3_deregister_tx)
{
MERROR_VER("tx with empty inputs, rejected for tx id= " << get_transaction_hash(tx));
return false;
@ -839,7 +840,8 @@ namespace cryptonote
MERROR_VER("tx with invalid outputs, rejected for tx id= " << get_transaction_hash(tx));
return false;
}
if (tx.version > 1)
if (tx.version == transaction::version_2)
{
if (tx.rct_signatures.outPk.size() != tx.vout.size())
{
@ -854,7 +856,7 @@ namespace cryptonote
return false;
}
if (tx.version == 1)
if (tx.version == transaction::version_1)
{
uint64_t amount_in = 0;
get_inputs_money_amount(tx, amount_in);
@ -866,7 +868,6 @@ namespace cryptonote
return false;
}
}
// for version > 1, ringct signatures check verifies amounts match
if(!keeped_by_block && get_object_blobsize(tx) >= m_blockchain_storage.get_current_cumulative_blocksize_limit() - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE)
{
@ -874,7 +875,6 @@ namespace cryptonote
return false;
}
//check if tx use different key images
if(!check_tx_inputs_keyimages_diff(tx))
{
MERROR_VER("tx uses a single key image more than once");
@ -893,7 +893,7 @@ namespace cryptonote
return false;
}
if (tx.version >= 2)
if (tx.version == transaction::version_2) // ringct signatures check verifies amounts match
{
const rct::rctSig &rv = tx.rct_signatures;
switch (rv.type) {
@ -909,6 +909,7 @@ namespace cryptonote
return false;
}
break;
case rct::RCTTypeFull:
case rct::RCTTypeFullBulletproof:
if (!rct::verRct(rv, true))
@ -922,7 +923,6 @@ namespace cryptonote
return false;
}
}
return true;
}
//-----------------------------------------------------------------------------------------------
@ -1090,10 +1090,29 @@ namespace cryptonote
LOG_ERROR("Failed to parse relayed transaction");
return;
}
txs.push_back(std::make_pair(tx_hash, std::move(tx_blob)));
m_mempool.set_relayed(txs);
}
//-----------------------------------------------------------------------------------------------
void core::set_deregister_votes_relayed(const std::vector<loki::service_node_deregister::vote>& votes)
{
m_deregister_vote_pool.set_relayed(votes);
}
//-----------------------------------------------------------------------------------------------
bool core::relay_deregister_votes()
{
NOTIFY_NEW_DEREGISTER_VOTE::request req;
req.votes = m_deregister_vote_pool.get_relayable_votes();
if (!req.votes.empty())
{
cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context);
get_protocol()->relay_deregister_votes(req, fake_context);
}
return true;
}
//-----------------------------------------------------------------------------------------------
bool core::get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce)
{
return m_blockchain_storage.create_block_template(b, adr, diffic, height, expected_reward, ex_nonce);
@ -1407,6 +1426,7 @@ namespace cryptonote
m_fork_moaner.do_call(boost::bind(&core::check_fork_time, this));
m_txpool_auto_relayer.do_call(boost::bind(&core::relay_txpool_transactions, this));
m_deregisters_auto_relayer.do_call(boost::bind(&core::relay_deregister_votes, this));
m_check_updates_interval.do_call(boost::bind(&core::check_updates, this));
m_check_disk_space_interval.do_call(boost::bind(&core::check_disk_space, this));
m_miner.on_idle();
@ -1610,6 +1630,69 @@ namespace cryptonote
return si.available;
}
//-----------------------------------------------------------------------------------------------
const std::shared_ptr<service_nodes::quorum_state> core::get_quorum_state(uint64_t height) const
{
const std::shared_ptr<service_nodes::quorum_state> result = m_service_node_list.get_quorum_state(height);
return result;
}
//-----------------------------------------------------------------------------------------------
bool core::add_deregister_vote(const loki::service_node_deregister::vote& vote, vote_verification_context &vvc)
{
{
uint64_t latest_block_height = std::max(get_current_blockchain_height(), get_target_blockchain_height());
uint64_t delta_height = latest_block_height - vote.block_height;
if (vote.block_height < latest_block_height && delta_height > loki::service_node_deregister::VOTE_LIFETIME_BY_HEIGHT)
{
LOG_ERROR("Received vote for height: " << vote.block_height
<< " and service node: " << vote.service_node_index
<< ", is older than: " << loki::service_node_deregister::VOTE_LIFETIME_BY_HEIGHT
<< " blocks and has been rejected.");
vvc.m_invalid_block_height = true;
}
else if (vote.block_height > latest_block_height)
{
LOG_ERROR("Received vote for height: " << vote.block_height
<< " and service node: " << vote.service_node_index
<< ", is newer than: " << latest_block_height
<< " (latest block height) and has been rejected.");
vvc.m_invalid_block_height = true;
}
if (vvc.m_invalid_block_height)
{
vvc.m_verification_failed = true;
return false;
}
}
const std::shared_ptr<service_nodes::quorum_state> quorum_state = m_service_node_list.get_quorum_state(vote.block_height);
if (!quorum_state)
{
vvc.m_verification_failed = true;
vvc.m_invalid_block_height = true;
LOG_ERROR("Could not get quorum state for height: " << vote.block_height);
return false;
}
cryptonote::transaction deregister_tx;
bool result = m_deregister_vote_pool.add_vote(vote, vvc, *quorum_state, deregister_tx);
if (result && vvc.m_full_tx_deregister_made)
{
tx_verification_context tvc;
blobdata const tx_blob = tx_to_blob(deregister_tx);
result = handle_incoming_tx(tx_blob, tvc, false /*keeped_by_block*/, false /*relayed*/, false /*do_not_relay*/);
if (!result || tvc.m_verifivation_failed)
{
LOG_ERROR("A full deregister tx for height: " << vote.block_height << " and service node: "
<< vote.service_node_index << " could not be verified and was not added to the memory pool.");
}
}
return result;
}
//-----------------------------------------------------------------------------------------------
std::time_t core::get_start_time() const
{
return start_time;

View File

@ -41,6 +41,7 @@
#include "common/download.h"
#include "common/threadpool.h"
#include "common/command_line.h"
#include "service_node_deregister.h"
#include "tx_pool.h"
#include "blockchain.h"
#include "service_node_list.h"
@ -210,6 +211,10 @@ namespace cryptonote
*/
virtual void on_transaction_relayed(const cryptonote::blobdata& tx);
/**
* @brief mark the deregister vote as having been relayed in the vote pool
*/
virtual void set_deregister_votes_relayed(const std::vector<loki::service_node_deregister::vote>& votes);
/**
* @brief gets the miner instance
@ -774,6 +779,24 @@ namespace cryptonote
*/
bool offline() const { return m_offline; }
/**
* @brief Get the deterministic list of service node's public keys for quorum testing
*
* @param height Block height to deterministically recreate the quorum list from
* @return Null shared ptr if quorum has not been determined yet for height
*/
const std::shared_ptr<service_nodes::quorum_state> get_quorum_state(uint64_t height) const;
/**
* @brief Add a vote to deregister a service node from network
*
* @param vote The vote for deregistering a service node.
* @return Whether the vote was added to the partial deregister pool
*/
bool add_deregister_vote(const loki::service_node_deregister::vote& vote, vote_verification_context &vvc);
private:
/**
@ -926,6 +949,13 @@ namespace cryptonote
*/
bool relay_txpool_transactions();
/**
* @brief attempt to relay the pooled deregister votes
*
* @return true, necessary for binding this function to a periodic invoker
*/
bool relay_deregister_votes();
/**
* @brief checks DNS versions
*
@ -951,6 +981,7 @@ namespace cryptonote
uint64_t m_test_drop_download_height = 0; //!< height under which to drop incoming blocks, if doing so
loki::deregister_vote_pool m_deregister_vote_pool;
tx_memory_pool m_mempool; //!< transaction pool instance
Blockchain m_blockchain_storage; //!< Blockchain instance
service_nodes::service_node_list m_service_node_list;
@ -970,6 +1001,7 @@ namespace cryptonote
epee::math_helper::once_a_time_seconds<60*60*12, false> m_store_blockchain_interval; //!< interval for manual storing of Blockchain, if enabled
epee::math_helper::once_a_time_seconds<60*60*2, true> m_fork_moaner; //!< interval for checking HardFork status
epee::math_helper::once_a_time_seconds<60*2, false> m_txpool_auto_relayer; //!< interval for checking re-relaying txpool transactions
epee::math_helper::once_a_time_seconds<60*2, false> m_deregisters_auto_relayer; //!< interval for checking re-relaying deregister votes
epee::math_helper::once_a_time_seconds<60*60*12, true> m_check_updates_interval; //!< interval for checking for new versions
epee::math_helper::once_a_time_seconds<60*10, true> m_check_disk_space_interval; //!< interval for checking for disk space

View File

@ -0,0 +1,317 @@
// Copyright (c) 2018, The Loki Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "service_node_deregister.h"
#include "service_node_list.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "cryptonote_basic/verification_context.h"
#include "cryptonote_basic/connection_context.h"
#include "cryptonote_protocol/cryptonote_protocol_defs.h"
#include "cryptonote_core/service_node_list.h"
#include "cryptonote_core/blockchain.h"
#include "misc_log_ex.h"
#include "string_tools.h"
#include <random>
#include <string>
#include <vector>
#undef LOKI_DEFAULT_LOG_CATEGORY
#define LOKI_DEFAULT_LOG_CATEGORY "service_nodes"
namespace loki
{
static crypto::hash make_hash_from(uint64_t block_height, uint32_t service_node_index)
{
const int buf_size = sizeof(block_height) + sizeof(service_node_index);
char buf[buf_size];
memcpy(buf, reinterpret_cast<void *>(&block_height), sizeof(block_height));
memcpy(buf + sizeof(block_height), reinterpret_cast<void *>(&service_node_index), sizeof(service_node_index));
crypto::hash result;
crypto::cn_fast_hash(buf, buf_size, result);
return result;
}
crypto::signature service_node_deregister::sign_vote(uint64_t block_height, uint32_t service_node_index, const crypto::public_key& pub, const crypto::secret_key& sec)
{
crypto::signature result;
crypto::generate_signature(make_hash_from(block_height, service_node_index), pub, sec, result);
return result;
}
bool service_node_deregister::verify_vote_signature(uint64_t block_height, uint32_t service_node_index, crypto::public_key p, crypto::signature s)
{
std::vector<std::pair<crypto::public_key, crypto::signature>> keys_and_sigs{ std::make_pair(p, s) };
return verify_votes_signature(block_height, service_node_index, keys_and_sigs);
}
bool service_node_deregister::verify_votes_signature(uint64_t block_height, uint32_t service_node_index, const std::vector<std::pair<crypto::public_key, crypto::signature>>& keys_and_sigs)
{
crypto::hash hash = make_hash_from(block_height, service_node_index);
for (auto& key_and_sig : keys_and_sigs)
{
if (!crypto::check_signature(hash, key_and_sig.first, key_and_sig.second))
{
return false;
}
}
return true;
}
static bool verify_votes_helper(const cryptonote::tx_extra_service_node_deregister& deregister,
cryptonote::vote_verification_context &vvc,
const service_nodes::quorum_state &quorum_state)
{
if (deregister.service_node_index >= quorum_state.nodes_to_test.size())
{
vvc.m_service_node_index_out_of_bounds = true;
LOG_PRINT_L1("Service node index in deregister vote was out of bounds: " << deregister.service_node_index << ", expected to be in range of: [0, " << quorum_state.nodes_to_test.size() << "]");
return false;
}
const std::vector<crypto::public_key>& quorum = quorum_state.quorum_nodes;
for (const cryptonote::tx_extra_service_node_deregister::vote &vote : deregister.votes)
{
bool all_votes_verified = true;
std::vector<std::pair<crypto::public_key, crypto::signature>> keys_and_sigs;
for (const cryptonote::tx_extra_service_node_deregister::vote& vote : deregister.votes)
{
if (vote.voters_quorum_index >= quorum.size())
{
vvc.m_voters_quorum_index_out_of_bounds = true;
LOG_PRINT_L1("Voter's index in deregister vote was out of bounds: " << vote.voters_quorum_index << ", expected to be in range of: [0, " << quorum.size() << "]");
return false;
}
keys_and_sigs.push_back(std::make_pair(quorum[vote.voters_quorum_index], vote.signature));
}
return service_node_deregister::verify_votes_signature(deregister.block_height, deregister.service_node_index, keys_and_sigs);
}
vvc.m_verification_failed = true;
return false;
}
bool service_node_deregister::verify_deregister(const cryptonote::tx_extra_service_node_deregister& deregister,
cryptonote::vote_verification_context &vvc,
const service_nodes::quorum_state &quorum_state)
{
if (deregister.votes.size() < service_nodes::MIN_VOTES_TO_KICK_SERVICE_NODE)
{
vvc.m_not_enough_votes = true;
return false;
}
bool result = verify_votes_helper(deregister, vvc, quorum_state);
return result;
}
bool service_node_deregister::verify_vote(const vote& v, cryptonote::vote_verification_context &vvc,
const service_nodes::quorum_state &quorum_state)
{
cryptonote::tx_extra_service_node_deregister deregister;
deregister.block_height = v.block_height;
deregister.service_node_index = v.service_node_index;
deregister.votes.push_back(cryptonote::tx_extra_service_node_deregister::vote{ v.signature, v.voters_quorum_index });
return verify_votes_helper(deregister, vvc, quorum_state);
}
void deregister_vote_pool::set_relayed(const std::vector<service_node_deregister::vote>& votes)
{
CRITICAL_REGION_LOCAL(m_lock);
const time_t now = time(NULL);
for (const service_node_deregister::vote &find_vote : votes)
{
deregister_group desired_group = {};
desired_group.block_height = find_vote.block_height;
desired_group.service_node_index = find_vote.service_node_index;
auto deregister_entry = m_deregisters.find(desired_group);
if (deregister_entry != m_deregisters.end())
{
std::vector<deregister> &deregister_vector = deregister_entry->second;
for (auto &deregister : deregister_vector)
{
if (deregister.m_vote.voters_quorum_index == find_vote.voters_quorum_index)
{
deregister.m_time_last_sent_p2p = now;
break;
}
}
}
}
}
std::vector<service_node_deregister::vote> deregister_vote_pool::get_relayable_votes() const
{
CRITICAL_REGION_LOCAL(m_lock);
const cryptonote::cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context);
// TODO(doyle): Rate-limiting: A better threshold value that follows suite with transaction relay time back-off
const time_t now = time(NULL);
const time_t THRESHOLD = 60 * 2;
std::vector<service_node_deregister::vote> result;
for (const auto &deregister_entry : m_deregisters)
{
const std::vector<deregister>& deregister_vector = deregister_entry.second;
for (const deregister &entry : deregister_vector)
{
const time_t last_sent = now - entry.m_time_last_sent_p2p;
if (last_sent > THRESHOLD)
{
result.push_back(entry.m_vote);
}
}
}
return result;
}
bool deregister_vote_pool::add_vote(const service_node_deregister::vote& new_vote,
cryptonote::vote_verification_context& vvc,
const service_nodes::quorum_state &quorum_state,
cryptonote::transaction &tx)
{
if (!service_node_deregister::verify_vote(new_vote, vvc, quorum_state))
{
LOG_PRINT_L1("Signature verification failed for deregister vote");
return false;
}
CRITICAL_REGION_LOCAL(m_lock);
time_t const now = time(NULL);
std::vector<deregister> *deregister_votes;
{
deregister_group desired_group = {};
desired_group.block_height = new_vote.block_height;
desired_group.service_node_index = new_vote.service_node_index;
deregister_votes = &m_deregisters[desired_group];
}
bool new_deregister_is_unique = true;
for (const auto &entry : *deregister_votes)
{
if (entry.m_vote.voters_quorum_index == new_vote.voters_quorum_index)
{
new_deregister_is_unique = false;
break;
}
}
if (new_deregister_is_unique)
{
vvc.m_added_to_pool = true;
deregister_votes->emplace_back(deregister(0 /*time_last_sent_p2p*/, new_vote));
if (deregister_votes->size() >= service_nodes::MIN_VOTES_TO_KICK_SERVICE_NODE)
{
cryptonote::tx_extra_service_node_deregister deregister;
deregister.block_height = new_vote.block_height;
deregister.service_node_index = new_vote.service_node_index;
deregister.votes.reserve(deregister_votes->size());
for (const auto& entry : *deregister_votes)
{
cryptonote::tx_extra_service_node_deregister::vote tx_vote = {};
tx_vote.signature = new_vote.signature;
tx_vote.voters_quorum_index = new_vote.voters_quorum_index;
deregister.votes.push_back(tx_vote);
}
vvc.m_full_tx_deregister_made = true;
tx.version = cryptonote::transaction::version_3_deregister_tx;
vvc.m_full_tx_deregister_made = cryptonote::add_service_node_deregister_to_tx_extra(tx.extra, deregister);
if (vvc.m_full_tx_deregister_made)
{
tx.version = cryptonote::transaction::version_3_deregister_tx;
}
else
{
LOG_PRINT_L1("Could not create version 3 deregistration transaction from votes");
}
}
}
return true;
}
void deregister_vote_pool::remove_used_votes(std::vector<cryptonote::transaction> const &txs)
{
CRITICAL_REGION_LOCAL(m_lock);
for (const cryptonote::transaction &tx : txs)
{
if (tx.version != cryptonote::transaction::version_3_deregister_tx)
continue;
cryptonote::tx_extra_service_node_deregister deregister;
if (!get_service_node_deregister_from_tx_extra(tx.extra, deregister))
{
LOG_ERROR("Could not get deregister from tx version 3, possibly corrupt tx");
continue;
}
deregister_group desired_group = {};
desired_group.block_height = deregister.block_height;
desired_group.service_node_index = deregister.service_node_index;
m_deregisters.erase(desired_group);
}
}
void deregister_vote_pool::remove_expired_votes(uint64_t height)
{
if (height < service_node_deregister::VOTE_LIFETIME_BY_HEIGHT)
{
return;
}
CRITICAL_REGION_LOCAL(m_lock);
uint64_t minimum_height = height - service_node_deregister::VOTE_LIFETIME_BY_HEIGHT;
for (auto it = m_deregisters.begin(); it != m_deregisters.end();)
{
const deregister_group &deregister_for = it->first;
if (deregister_for.block_height < minimum_height)
{
it = m_deregisters.erase(it);
}
else
{
it++;
}
}
}
}; // namespace loki

View File

@ -0,0 +1,134 @@
// Copyright (c) 2018, The Loki Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <vector>
#include <unordered_map>
#include <utility>
#include "crypto/crypto.h"
#include "cryptonote_basic/cryptonote_basic.h"
#include "math_helper.h"
#include "syncobj.h"
namespace cryptonote
{
struct vote_verification_context;
};
namespace service_nodes
{
struct quorum_state;
};
namespace loki
{
namespace service_node_deregister
{
const uint64_t VOTE_LIFETIME_BY_HEIGHT = (60 * 60 * 2) / DIFFICULTY_TARGET_V2;
const uint64_t DEREGISTER_LIFETIME_BY_HEIGHT = VOTE_LIFETIME_BY_HEIGHT;
struct vote
{
uint64_t block_height;
uint32_t service_node_index;
uint32_t voters_quorum_index;
crypto::signature signature;
};
crypto::signature sign_vote(uint64_t block_height, uint32_t service_node_index, const crypto::public_key& pub, const crypto::secret_key& sec);
bool verify_vote_signature (uint64_t block_height, uint32_t service_node_index, crypto::public_key p, crypto::signature s);
bool verify_votes_signature(uint64_t block_height, uint32_t service_node_index, const std::vector<std::pair<crypto::public_key, crypto::signature>>& keys_and_sigs);
bool verify_deregister(const cryptonote::tx_extra_service_node_deregister& deregister,
cryptonote::vote_verification_context& vvc,
const service_nodes::quorum_state &quorum);
bool verify_vote(const vote& v, cryptonote::vote_verification_context &vvc,
const service_nodes::quorum_state &quorum);
};
class deregister_vote_pool
{
public:
/**
* @return True if vote was valid and in the pool already or just added (check vote verfication for specific case).
*/
bool add_vote(const service_node_deregister::vote& new_vote,
cryptonote::vote_verification_context& vvc,
const service_nodes::quorum_state &quorum_state,
cryptonote::transaction &tx);
// TODO(loki): Review relay behaviour and all the cases when it should be triggered
void set_relayed (const std::vector<service_node_deregister::vote>& votes);
void remove_expired_votes(uint64_t height);
void remove_used_votes (std::vector<cryptonote::transaction> const &txs);
std::vector<service_node_deregister::vote> get_relayable_votes () const;
private:
struct deregister
{
deregister(uint64_t time_last_sent_p2p, service_node_deregister::vote vote)
: m_time_last_sent_p2p(time_last_sent_p2p), m_vote(vote) {}
uint64_t m_time_last_sent_p2p;
service_node_deregister::vote m_vote;
};
struct deregister_group
{
uint64_t block_height;
uint32_t service_node_index;
time_t time_group_created;
bool operator==(const deregister_group &other) const
{
bool result = (block_height == other.block_height) && (service_node_index == other.service_node_index);
return result;
}
};
struct deregister_group_hasher
{
size_t operator()(const deregister_group& deregister) const
{
size_t res = 17;
res = res * 31 + std::hash<uint64_t>()(deregister.block_height);
res = res * 31 + std::hash<uint32_t>()(deregister.service_node_index);
res = res * 31 + std::hash<uint32_t>()(deregister.time_group_created);
return res;
}
};
std::unordered_map<deregister_group, std::vector<deregister>, deregister_group_hasher> m_deregisters;
mutable epee::critical_section m_lock;
};
}; // namespace loki

View File

@ -27,10 +27,12 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <functional>
#include <random>
#include "ringct/rctSigs.h"
#include "wallet/wallet2.h"
#include "cryptonote_tx_utils.h"
#include "cryptonote_basic/tx_extra.h"
#include "service_node_list.h"
@ -39,6 +41,7 @@
namespace service_nodes
{
service_node_list::service_node_list(cryptonote::Blockchain& blockchain)
: m_blockchain(blockchain)
{
@ -67,6 +70,7 @@ namespace service_nodes
{
start_height = current_height - STAKING_REQUIREMENT_LOCK_BLOCKS - STAKING_RELOCK_WINDOW_BLOCKS;
}
for (uint64_t height = start_height; height <= current_height; height += 1000)
{
std::list<std::pair<cryptonote::blobdata, cryptonote::block>> blocks;
@ -75,6 +79,7 @@ namespace service_nodes
LOG_ERROR("Unable to initialize service nodes list");
return;
}
for (const auto& block_pair : blocks)
{
const cryptonote::block& block = block_pair.second;
@ -85,6 +90,7 @@ namespace service_nodes
LOG_ERROR("Unable to get transactions for block " << block.hash);
return;
}
block_added_generic(block, txs);
}
}
@ -92,16 +98,33 @@ namespace service_nodes
m_rollback_events.push_back(std::unique_ptr<rollback_event>(new prevent_rollback(current_height)));
}
std::vector<crypto::public_key> service_node_list::get_service_nodes_pubkeys() const
std::vector<crypto::public_key> service_node_list::get_service_node_pubkeys() const
{
std::vector<crypto::public_key> ret;
std::vector<crypto::public_key> result;
for (const auto& iter : m_service_nodes_keys)
ret.push_back(iter.second);
std::sort(ret.begin(), ret.end(),
[](const crypto::public_key& a, const crypto::public_key& b) {
return memcmp(reinterpret_cast<const void*>(&a), reinterpret_cast<const void*>(&b), sizeof(a)) < 0;
});
return ret;
result.push_back(iter.second);
std::sort(result.begin(), result.end(),
[](const crypto::public_key &a, const crypto::public_key &b) {
return memcmp(reinterpret_cast<const void*>(&a), reinterpret_cast<const void*>(&b), sizeof(a)) < 0;
});
return result;
}
const std::shared_ptr<quorum_state> service_node_list::get_quorum_state(uint64_t height) const
{
std::shared_ptr<service_nodes::quorum_state> result;
const auto &it = m_quorum_states.find(height);
if (it == m_quorum_states.end())
{
// TODO(loki): Not being able to find the quorum is going to be a fatal error.
}
else
{
result = it->second;
}
return result;
}
bool service_node_list::is_service_node(const crypto::public_key& pubkey) const
@ -120,8 +143,20 @@ namespace service_nodes
bool service_node_list::reg_tx_extract_fields(const cryptonote::transaction& tx, cryptonote::account_public_address& address, crypto::public_key& service_node_key, crypto::public_key& tx_pub_key) const
{
address = cryptonote::get_account_public_address_from_tx_extra(tx.extra);
service_node_key = cryptonote::get_service_node_key_from_tx_extra(tx.extra);
cryptonote::tx_extra_service_node_register register_;
if (cryptonote::get_service_node_register_from_tx_extra(tx.extra, register_))
{
address.m_spend_public_key = register_.public_spend_key;
address.m_view_public_key = register_.public_view_key;
service_node_key = register_.service_node_key;
}
else
{
address.m_spend_public_key = crypto::null_pkey;
address.m_view_public_key = crypto::null_pkey;
service_node_key = crypto::null_pkey;
}
tx_pub_key = cryptonote::get_tx_pub_key_from_extra(tx.extra);
return address.m_spend_public_key != crypto::null_pkey &&
address.m_view_public_key != crypto::null_pkey &&
@ -167,11 +202,52 @@ namespace service_nodes
return money_transferred >= m_blockchain.get_staking_requirement(block_height);
}
bool service_node_list::is_deregistration_tx(const cryptonote::transaction& tx, cryptonote::account_public_address &address) const
{
if (tx.version != cryptonote::transaction::version_3_deregister_tx)
{
return false;
}
cryptonote::tx_extra_service_node_deregister deregister;
if (!cryptonote::get_service_node_deregister_from_tx_extra(tx.extra, deregister))
{
LOG_ERROR("Transaction deregister did not have deregister data in tx extra, possibly corrupt tx in blockchain");
return false;
}
if (const std::shared_ptr<quorum_state> state = get_quorum_state(deregister.block_height))
{
if (deregister.service_node_index >= state->nodes_to_test.size())
{
LOG_ERROR("Service node index to vote off has become invalid, quorum rules have changed without a hardfork.");
return false;
}
const crypto::public_key &snode_pubkey = state->nodes_to_test[deregister.service_node_index];
for (const auto& key_it : m_service_nodes_keys)
{
if (key_it.second == snode_pubkey)
{
address = key_it.first;
return true;
}
}
}
else
{
// TODO(loki): Not being able to find a quorum is fatal! We want better caching abilities.
LOG_ERROR("Quorum state for height: " << deregister.block_height << ", was not stored by the daemon");
}
return false;
}
// This function takes a tx and returns true if it is a staking transaction.
// It also sets the address argument to the public spendkey and pub viewkey of
// the transaction.
//
bool service_node_list::process_registration_tx(const cryptonote::transaction& tx, uint64_t block_height, cryptonote::account_public_address& address, crypto::public_key& key) const
bool service_node_list::is_registration_tx(const cryptonote::transaction& tx, uint64_t block_height, cryptonote::account_public_address& address, crypto::public_key& key) const
{
if (!reg_tx_has_correct_unlock_time(tx, block_height))
{
@ -251,6 +327,7 @@ namespace service_nodes
return null_address;
}
template<typename T>
void service_node_list::block_added_generic(const cryptonote::block& block, const T& txs)
{
@ -294,7 +371,7 @@ namespace service_nodes
{
cryptonote::account_public_address address;
crypto::public_key key;
if (process_registration_tx(tx, block_height, address, key))
if (is_registration_tx(tx, block_height, address, key))
{
auto iter = m_service_nodes_last_reward.find(address);
if (iter == m_service_nodes_last_reward.end())
@ -311,8 +388,38 @@ namespace service_nodes
service_node_key = key;
}
}
else if (is_deregistration_tx(tx, address))
{
auto iter = m_service_nodes_last_reward.find(address);
if (iter != m_service_nodes_last_reward.end())
{
crypto::public_key& service_node_key = m_service_nodes_keys[address];
m_rollback_events.push_back(std::unique_ptr<rollback_event>(new rollback_change(block_height, address, iter->second, service_node_key)));
m_service_nodes_last_reward.erase(iter);
m_service_nodes_keys.erase(address);
}
else
{
MWARNING("Tried to kick off a service node that is no longer registered");
}
}
index++;
}
const uint64_t curr_height = m_blockchain.get_current_blockchain_height();
const size_t QUORUM_LIFETIME = loki::service_node_deregister::DEREGISTER_LIFETIME_BY_HEIGHT;
const size_t cache_state_from_height = (curr_height < QUORUM_LIFETIME) ? 0 : curr_height - QUORUM_LIFETIME;
if (block_height >= cache_state_from_height)
{
store_quorum_state_from_rewards_list(block_height);
while (!m_quorum_states.empty() && m_quorum_states.begin()->first < cache_state_from_height)
{
m_quorum_states.erase(m_quorum_states.begin());
}
}
}
void service_node_list::blockchain_detached(uint64_t height)
@ -326,6 +433,11 @@ namespace service_nodes
}
m_rollback_events.pop_back();
}
while (!m_quorum_states.empty() && (--m_quorum_states.end())->first >= height)
{
m_quorum_states.erase(--m_quorum_states.end());
}
}
std::vector<cryptonote::account_public_address> service_node_list::get_expired_nodes(uint64_t block_height) const
@ -357,7 +469,7 @@ namespace service_nodes
{
cryptonote::account_public_address address;
crypto::public_key unused_key;
if (process_registration_tx(tx, expired_nodes_block_height, address, unused_key))
if (is_registration_tx(tx, expired_nodes_block_height, address, unused_key))
{
expired_nodes.push_back(address);
}
@ -427,6 +539,67 @@ namespace service_nodes
return true;
}
void service_node_list::store_quorum_state_from_rewards_list(uint64_t height)
{
const crypto::hash block_hash = m_blockchain.get_block_id_by_height(height);
if (block_hash == crypto::null_hash)
{
MERROR("Block height: " << height << " returned null hash");
return;
}
std::vector<crypto::public_key> full_node_list = get_service_node_pubkeys();
std::vector<size_t> pub_keys_indexes(full_node_list.size());
{
size_t index = 0;
for (size_t i = 0; i < full_node_list.size(); i++) { pub_keys_indexes[i] = i; }
// Shuffle indexes
uint64_t seed = 0;
std::memcpy(&seed, block_hash.data, std::min(sizeof(seed), sizeof(block_hash.data)));
std::mt19937_64 mersenne_twister(seed);
std::shuffle(pub_keys_indexes.begin(), pub_keys_indexes.end(), mersenne_twister);
}
// Assign indexes from shuffled list into quorum and list of nodes to test
if (!m_quorum_states[height])
m_quorum_states[height] = std::shared_ptr<quorum_state>(new quorum_state());
std::shared_ptr<quorum_state> state = m_quorum_states[height];
state->clear();
{
std::vector<crypto::public_key>& quorum = state->quorum_nodes;
{
quorum.clear();
quorum.resize(std::min(full_node_list.size(), QUORUM_SIZE));
for (size_t i = 0; i < quorum.size(); i++)
{
size_t node_index = pub_keys_indexes[i];
const crypto::public_key &key = full_node_list[node_index];
quorum[i] = key;
}
}
std::vector<crypto::public_key>& nodes_to_test = state->nodes_to_test;
{
size_t num_remaining_nodes = pub_keys_indexes.size() - quorum.size();
size_t num_nodes_to_test = std::max(num_remaining_nodes/NTH_OF_THE_NETWORK_TO_TEST,
std::min(MIN_NODES_TO_TEST, num_remaining_nodes));
nodes_to_test.clear();
nodes_to_test.resize(num_nodes_to_test);
const int pub_keys_offset = quorum.size();
for (size_t i = 0; i < nodes_to_test.size(); i++)
{
size_t node_index = pub_keys_indexes[pub_keys_offset + i];
const crypto::public_key &key = full_node_list[node_index];
nodes_to_test[i] = key;
}
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
service_node_list::rollback_event::rollback_event(uint64_t block_height) : m_block_height(block_height)

View File

@ -34,6 +34,18 @@
namespace service_nodes
{
const size_t QUORUM_SIZE = 3;
const size_t MIN_VOTES_TO_KICK_SERVICE_NODE = 3;
const size_t NTH_OF_THE_NETWORK_TO_TEST = 100;
const size_t MIN_NODES_TO_TEST = 50;
struct quorum_state
{
void clear() { quorum_nodes.clear(); nodes_to_test.clear(); }
std::vector<crypto::public_key> quorum_nodes;
std::vector<crypto::public_key> nodes_to_test;
};
class service_node_list
: public cryptonote::Blockchain::BlockAddedHook,
public cryptonote::Blockchain::BlockchainDetachedHook,
@ -50,12 +62,15 @@ namespace service_nodes
std::vector<cryptonote::account_public_address> get_expired_nodes(uint64_t block_height) const;
cryptonote::account_public_address select_winner(const crypto::hash& prev_id);
std::vector<crypto::public_key> get_service_nodes_pubkeys() const;
bool is_service_node(const crypto::public_key& pubkey) const;
const std::shared_ptr<quorum_state> get_quorum_state(uint64_t height) const;
private:
bool process_registration_tx(const cryptonote::transaction& tx, uint64_t block_height, cryptonote::account_public_address& address, crypto::public_key& key) const;
bool is_registration_tx(const cryptonote::transaction& tx, uint64_t block_height, cryptonote::account_public_address& address, crypto::public_key& key) const;
bool is_deregistration_tx(const cryptonote::transaction& tx, cryptonote::account_public_address& address) const;
std::vector<crypto::public_key> get_service_node_pubkeys() const;
template<typename T>
void block_added_generic(const cryptonote::block& block, const T& txs);
@ -65,6 +80,8 @@ namespace service_nodes
cryptonote::account_public_address find_service_node_from_miner_tx(const cryptonote::transaction& miner_tx, uint64_t block_height) const;
void store_quorum_state_from_rewards_list(uint64_t height);
class rollback_event
{
public:
@ -110,6 +127,8 @@ namespace service_nodes
std::list<std::unique_ptr<rollback_event>> m_rollback_events;
cryptonote::Blockchain& m_blockchain;
using block_height = uint64_t;
std::map<block_height, std::shared_ptr<quorum_state>> m_quorum_states;
};
const static cryptonote::account_public_address null_address{ crypto::null_pkey, crypto::null_pkey };

View File

@ -106,6 +106,42 @@ namespace cryptonote
tx_memory_pool::tx_memory_pool(Blockchain& bchs): m_blockchain(bchs), m_txpool_max_size(DEFAULT_TXPOOL_MAX_SIZE), m_txpool_size(0)
{
}
//---------------------------------------------------------------------------------
bool tx_memory_pool::have_deregister_tx_already(transaction const &tx) const
{
if (tx.version != transaction::version_3_deregister_tx)
return false;
tx_extra_service_node_deregister deregister;
if (!get_service_node_deregister_from_tx_extra(tx.extra, deregister))
{
MERROR("Could not get service node deregister from tx v3, possibly corrupt tx in your blockchain");
return false;
}
std::list<transaction> pool_txs;
get_transactions(pool_txs);
for (const transaction& pool_tx : pool_txs)
{
if (pool_tx.version != transaction::version_3_deregister_tx)
continue;
tx_extra_service_node_deregister pool_tx_deregister;
if (!get_service_node_deregister_from_tx_extra(pool_tx.extra, pool_tx_deregister))
{
MERROR("Could not get service node deregister from tx v3, possibly corrupt tx in your blockchain");
continue;
}
if ((pool_tx_deregister.block_height == deregister.block_height) &&
(pool_tx_deregister.service_node_index == deregister.service_node_index))
{
return true;
}
}
return false;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool::add_tx(transaction &tx, /*const crypto::hash& tx_prefix_hash,*/ const crypto::hash &id, size_t blob_size, tx_verification_context& tvc, bool kept_by_block, bool relayed, bool do_not_relay, uint8_t version)
@ -114,7 +150,7 @@ namespace cryptonote
CRITICAL_REGION_LOCAL(m_transactions_lock);
PERF_TIMER(add_tx);
if (tx.version == 0)
if (tx.version == transaction::version_0)
{
// v0 never accepted
LOG_PRINT_L1("transaction version 0 is invalid");
@ -142,7 +178,7 @@ namespace cryptonote
// fee per kilobyte, size rounded up.
uint64_t fee;
if (tx.version == 1)
if (tx.version == transaction::version_1)
{
uint64_t inputs_amount = 0;
if(!get_inputs_money_amount(tx, inputs_amount))
@ -174,7 +210,7 @@ namespace cryptonote
fee = tx.rct_signatures.txnFee;
}
if (!kept_by_block && !m_blockchain.check_fee(blob_size, fee))
if (!kept_by_block && !m_blockchain.check_fee(blob_size, fee) && tx.version != transaction::version_3_deregister_tx)
{
tvc.m_verifivation_failed = true;
tvc.m_fee_too_low = true;
@ -205,6 +241,49 @@ namespace cryptonote
}
}
if (have_deregister_tx_already(tx))
{
mark_double_spend(tx);
LOG_PRINT_L1("Transaction version 3 with id= "<< id << " already has a deregister for height");
tvc.m_verifivation_failed = true;
tvc.m_double_spend = true;
return false;
}
if (tx.version == transaction::version_3_deregister_tx)
{
tx_extra_service_node_deregister deregister;
if (!get_service_node_deregister_from_tx_extra(tx.extra, deregister))
{
LOG_PRINT_L1("Could not get service node deregister from tx v3, possibly corrupt tx in your blockchain");
return false;
}
const uint64_t curr_height = m_blockchain.get_current_blockchain_height();
if (deregister.block_height >= curr_height)
{
LOG_PRINT_L1("Received deregister tx for height: " << deregister.block_height
<< " and service node: " << deregister.service_node_index
<< ", is newer than current height: " << curr_height
<< " blocks and has been rejected.");
tvc.m_vote_ctx.m_invalid_block_height = true;
tvc.m_verifivation_failed = true;
return false;
}
uint64_t delta_height = curr_height - deregister.block_height;
if (delta_height > loki::service_node_deregister::DEREGISTER_LIFETIME_BY_HEIGHT)
{
LOG_PRINT_L1("Received deregister tx for height: " << deregister.block_height
<< " and service node: " << deregister.service_node_index
<< ", is older than: " << loki::service_node_deregister::DEREGISTER_LIFETIME_BY_HEIGHT
<< " blocks and has been rejected.");
tvc.m_vote_ctx.m_invalid_block_height = true;
tvc.m_verifivation_failed = true;
return false;
}
}
if (!m_blockchain.check_tx_outputs(tx, tvc))
{
LOG_PRINT_L1("Transaction with id= "<< id << " has at least one invalid output");
@ -298,7 +377,7 @@ namespace cryptonote
}
tvc.m_added_to_pool = true;
if(meta.fee > 0 && !do_not_relay)
if((meta.fee > 0 || tx.version == 3) && !do_not_relay)
tvc.m_should_be_relayed = true;
}
@ -563,8 +642,7 @@ namespace cryptonote
CRITICAL_REGION_LOCAL1(m_blockchain);
const uint64_t now = time(NULL);
m_blockchain.for_all_txpool_txes([this, now, &txs](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *){
// 0 fee transactions are never relayed
if(meta.fee > 0 && !meta.do_not_relay && now - meta.last_relayed_time > get_relay_delay(now, meta.receive_time))
if(!meta.do_not_relay && 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
@ -575,6 +653,16 @@ namespace cryptonote
try
{
cryptonote::blobdata bd = m_blockchain.get_txpool_tx_blob(txid);
if (meta.fee == 0)
{
cryptonote::transaction tx;
if (cryptonote::parse_and_validate_tx_from_blob(bd, tx) &&
tx.version != transaction::version_3_deregister_tx)
{
return true;
}
}
txs.push_back(std::make_pair(txid, bd));
}
catch (const std::exception &e)

View File

@ -448,6 +448,14 @@ namespace cryptonote
*/
bool have_tx_keyimg_as_spent(const crypto::key_image& key_im) const;
/**
* @brief check if the deregistration tx already exists in the pool.
* @return true if it already exists
*
*/
bool have_deregister_tx_already(transaction const &tx) const;
/**
* @brief check if any spent key image in a transaction is in the pool
*

View File

@ -34,6 +34,8 @@
#include "serialization/keyvalue_serialization.h"
#include "cryptonote_basic/cryptonote_basic.h"
#include "cryptonote_basic/blobdatatype.h"
#include "cryptonote_core/service_node_deregister.h"
namespace cryptonote
{
@ -280,5 +282,21 @@ namespace cryptonote
END_KV_SERIALIZE_MAP()
};
};
/************************************************************************/
/* */
/************************************************************************/
struct NOTIFY_NEW_DEREGISTER_VOTE
{
const static int ID = BC_COMMANDS_POOL_BASE + 10;
struct request
{
std::vector<loki::service_node_deregister::vote> votes;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_CONTAINER_POD_AS_BLOB(votes)
END_KV_SERIALIZE_MAP()
};
};
}

View File

@ -36,6 +36,7 @@
#include <boost/program_options/variables_map.hpp>
#include <string>
#include <unordered_map>
#include "math_helper.h"
#include "storages/levin_abstract_invoke2.h"
@ -90,6 +91,7 @@ namespace cryptonote
HANDLE_NOTIFY_T2(NOTIFY_RESPONSE_CHAIN_ENTRY, &cryptonote_protocol_handler::handle_response_chain_entry)
HANDLE_NOTIFY_T2(NOTIFY_NEW_FLUFFY_BLOCK, &cryptonote_protocol_handler::handle_notify_new_fluffy_block)
HANDLE_NOTIFY_T2(NOTIFY_REQUEST_FLUFFY_MISSING_TX, &cryptonote_protocol_handler::handle_request_fluffy_missing_tx)
HANDLE_NOTIFY_T2(NOTIFY_NEW_DEREGISTER_VOTE, &cryptonote_protocol_handler::handle_notify_new_deregister_vote)
END_INVOKE_MAP2()
bool on_idle();
@ -119,10 +121,12 @@ namespace cryptonote
int handle_response_chain_entry(int command, NOTIFY_RESPONSE_CHAIN_ENTRY::request& arg, cryptonote_connection_context& context);
int handle_notify_new_fluffy_block(int command, NOTIFY_NEW_FLUFFY_BLOCK::request& arg, cryptonote_connection_context& context);
int handle_request_fluffy_missing_tx(int command, NOTIFY_REQUEST_FLUFFY_MISSING_TX::request& arg, cryptonote_connection_context& context);
int handle_notify_new_deregister_vote(int command, NOTIFY_NEW_DEREGISTER_VOTE::request& arg, cryptonote_connection_context& context);
//----------------- i_bc_protocol_layout ---------------------------------------
virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& exclude_context);
virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context);
virtual bool relay_deregister_votes(NOTIFY_NEW_DEREGISTER_VOTE::request& arg, cryptonote_connection_context& exclude_context);
//----------------------------------------------------------------------------------
//bool get_payload_sync_data(HANDSHAKE_DATA::request& hshd, cryptonote_connection_context& context);
bool request_missing_objects(cryptonote_connection_context& context, bool check_having_blocks, bool force_next_span = false);

View File

@ -759,7 +759,51 @@ namespace cryptonote
post_notify<NOTIFY_NEW_FLUFFY_BLOCK>(fluffy_response, context);
return 1;
}
//------------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------------
template<class t_core>
int t_cryptonote_protocol_handler<t_core>::handle_notify_new_deregister_vote(int command, NOTIFY_NEW_DEREGISTER_VOTE::request& arg, cryptonote_connection_context& context)
{
MLOG_P2P_MESSAGE("Received NOTIFY_NEW_DEREGISTER_VOTE (" << arg.votes.size() << " txes)");
if(context.m_state != cryptonote_connection_context::state_normal)
return 1;
if(!is_synchronized())
{
LOG_DEBUG_CC(context, "Received new deregister vote while syncing, ignored");
return 1;
}
for(auto it = arg.votes.begin(); it != arg.votes.end();)
{
cryptonote::vote_verification_context vvc = {};
m_core.add_deregister_vote(*it, vvc);
if (vvc.m_verification_failed)
{
LOG_PRINT_CCONTEXT_L1("Deregister vote verification failed, dropping connection");
drop_connection(context, true /*add_fail*/, false /*flush_all_spans i.e. delete cached block data from this peer*/);
return 1;
}
if (vvc.m_added_to_pool)
{
it++;
}
else
{
it = arg.votes.erase(it);
}
}
if (arg.votes.size())
{
relay_deregister_votes(arg, context);
}
return 1;
}
//------------------------------------------------------------------------------------------------------------------------
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)
{
@ -1703,6 +1747,14 @@ skip:
}
//------------------------------------------------------------------------------------------------------------------------
template<class t_core>
bool t_cryptonote_protocol_handler<t_core>::relay_deregister_votes(NOTIFY_NEW_DEREGISTER_VOTE::request& arg, cryptonote_connection_context& exclude_context)
{
bool result = relay_post_notify<NOTIFY_NEW_DEREGISTER_VOTE>(arg, exclude_context);
m_core.set_deregister_votes_relayed(arg.votes);
return result;
}
//------------------------------------------------------------------------------------------------------------------------
template<class t_core>
bool t_cryptonote_protocol_handler<t_core>::relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context)
{
// no check for success, so tell core they're relayed unconditionally

View File

@ -43,6 +43,7 @@ namespace cryptonote
virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& exclude_context)=0;
virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context)=0;
//virtual bool request_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote_connection_context& context)=0;
virtual bool relay_deregister_votes(NOTIFY_NEW_DEREGISTER_VOTE::request& arg, cryptonote_connection_context& exclude_context)=0;
};
/************************************************************************/
@ -58,6 +59,9 @@ namespace cryptonote
{
return false;
}
virtual bool relay_deregister_votes(NOTIFY_NEW_DEREGISTER_VOTE::request& arg, cryptonote_connection_context& exclude_context)
{
return false;
}
};
}

View File

@ -125,6 +125,25 @@ bool t_command_parser_executor::print_blockchain_info(const std::vector<std::str
return m_executor.print_blockchain_info(start_index, end_index);
}
bool t_command_parser_executor::print_quorum_state(const std::vector<std::string>& args)
{
if(args.size() != 1)
{
std::cout << "need block height parameter" << std::endl;
return false;
}
uint64_t height = 0;
if(!epee::string_tools::get_xtype_from_string(height, args[0]))
{
std::cout << "wrong block height parameter" << std::endl;
return false;
}
return m_executor.print_quorum_state(height);
}
bool t_command_parser_executor::set_log_level(const std::vector<std::string>& args)
{
if(args.size() > 1)

View File

@ -75,6 +75,8 @@ public:
bool print_blockchain_info(const std::vector<std::string>& args);
bool print_quorum_state(const std::vector<std::string>& args);
bool set_log_level(const std::vector<std::string>& args);
bool set_log_categories(const std::vector<std::string>& args);

View File

@ -95,6 +95,12 @@ t_command_server::t_command_server(
, "print_tx <transaction_hash> [+hex] [+json]"
, "Print a given transaction."
);
m_command_lookup.set_handler(
"print_quorum_state"
, std::bind(&t_command_parser_executor::print_quorum_state, &m_parser, p::_1)
, "print_quorum_state <height>"
, "Print the quorum state for the block height."
);
m_command_lookup.set_handler(
"is_key_image_spent"
, std::bind(&t_command_parser_executor::is_key_image_spent, &m_parser, p::_1)

View File

@ -567,6 +567,50 @@ bool t_rpc_command_executor::print_blockchain_info(uint64_t start_block_index, u
return true;
}
bool t_rpc_command_executor::print_quorum_state(uint64_t height)
{
cryptonote::COMMAND_RPC_GET_QUORUM_STATE::request req;
cryptonote::COMMAND_RPC_GET_QUORUM_STATE::response res;
epee::json_rpc::error error_resp;
req.height = height;
std::string fail_message = "Unsuccessful";
if (m_is_rpc)
{
if (!m_rpc_client->json_rpc_request(req, res, "get_quorum_state", fail_message.c_str()))
{
return true;
}
}
else
{
if (!m_rpc_server->on_get_quorum_state_json(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK)
{
tools::fail_msg_writer() << make_error(fail_message, res.status);
return true;
}
}
tools::msg_writer() << "Quorum Service Nodes [" << res.quorum_nodes.size() << "]";
for (size_t i = 0; i < res.quorum_nodes.size(); i++)
{
const std::string &entry = res.quorum_nodes[i];
tools::msg_writer() << "[" << i << "] " << entry;
}
tools::msg_writer() << "Service Nodes To Test [" << res.nodes_to_test.size() << "]";
for (size_t i = 0; i < res.nodes_to_test.size(); i++)
{
const std::string &entry = res.nodes_to_test[i];
tools::msg_writer() << "[" << i << "] " << entry;
}
return true;
}
bool t_rpc_command_executor::set_log_level(int8_t level) {
cryptonote::COMMAND_RPC_SET_LOG_LEVEL::request req;
cryptonote::COMMAND_RPC_SET_LOG_LEVEL::response res;

View File

@ -86,6 +86,8 @@ public:
bool print_blockchain_info(uint64_t start_block_index, uint64_t end_block_index);
bool print_quorum_state(uint64_t height);
bool set_log_level(int8_t level);
bool set_log_categories(const std::string &categories);

View File

@ -782,6 +782,19 @@ namespace cryptonote
add_reason(res.reason, "fee too low");
if ((res.not_rct = tvc.m_not_rct))
add_reason(res.reason, "tx is not ringct");
const vote_verification_context &vvc = tvc.m_vote_ctx;
if ((res.invalid_block_height = vvc.m_invalid_block_height))
add_reason(res.reason, "block height was invalid");
if ((res.voters_quorum_index_out_of_bounds = vvc.m_voters_quorum_index_out_of_bounds))
add_reason(res.reason, "voters quorum index specified out of bounds");
if ((res.service_node_index_out_of_bounds = vvc.m_service_node_index_out_of_bounds))
add_reason(res.reason, "service node index specified out of bounds");
if ((res.signature_not_valid = vvc.m_signature_not_valid))
add_reason(res.reason, "signature was not valid");
if ((res.not_enough_votes = vvc.m_not_enough_votes))
add_reason(res.reason, "not enough votes");
const std::string punctuation = res.reason.empty() ? "" : ": ";
if (tvc.m_verifivation_failed)
{
@ -806,6 +819,7 @@ namespace cryptonote
NOTIFY_NEW_TRANSACTIONS::request r;
r.txs.push_back(tx_blob);
m_core.get_protocol()->relay_transactions(r, fake_context);
//TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes
res.status = CORE_RPC_STATUS_OK;
return true;
@ -1977,6 +1991,88 @@ namespace cryptonote
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_quorum_state(const COMMAND_RPC_GET_QUORUM_STATE::request& req, COMMAND_RPC_GET_QUORUM_STATE::response& res)
{
PERF_TIMER(on_get_quorum_state);
bool r;
if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_QUORUM_STATE>(invoke_http_mode::JON, "/get_quorum_state", req, res, r))
{
return r;
}
const std::shared_ptr<service_nodes::quorum_state> quorum_state = m_core.get_quorum_state(req.height);
r = (quorum_state != nullptr);
if (r)
{
res.status = CORE_RPC_STATUS_OK;
res.quorum_nodes.reserve (quorum_state->quorum_nodes.size());
res.nodes_to_test.reserve(quorum_state->nodes_to_test.size());
for (const auto &key : quorum_state->quorum_nodes)
res.quorum_nodes.push_back(epee::string_tools::pod_to_hex(key));
for (const auto &key : quorum_state->nodes_to_test)
res.nodes_to_test.push_back(epee::string_tools::pod_to_hex(key));
}
else
{
res.status = "Block height: " + req.height;
res.status += ", returned null hash or failed to derive quorum list";
}
return r;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_submit_deregister_vote(const COMMAND_RPC_SEND_DEREGISTER_VOTE::request& req, COMMAND_RPC_SEND_DEREGISTER_VOTE::response &resp)
{
PERF_TIMER(on_submit_deregister_vote);
vote_verification_context vvc = {};
if(!m_core.add_deregister_vote(req.vote, vvc))
{
resp.status = "Failed";
resp.reason = "";
if ((resp.invalid_block_height = vvc.m_invalid_block_height))
add_reason(resp.reason, "could not get quorum for block height");
if ((resp.voters_quorum_index_out_of_bounds = vvc.m_voters_quorum_index_out_of_bounds))
add_reason(resp.reason, "quorum index was not in bounds of quorum");
if ((resp.service_node_index_out_of_bounds = vvc.m_service_node_index_out_of_bounds))
add_reason(resp.reason, "service node index was not in bounds of the service node list");
if ((resp.signature_not_valid = vvc.m_signature_not_valid))
add_reason(resp.reason, "signature could not be verified with the voter's key");
const std::string punctuation = resp.reason.empty() ? "" : ": ";
if (vvc.m_verification_failed)
{
LOG_PRINT_L0("[on_submit_deregister_vote]: deregister vote verification failed" << punctuation << resp.reason);
}
else
{
LOG_PRINT_L0("[on_submit_deregister_vote]: Failed to process deregister vote" << punctuation << resp.reason);
}
return true;
}
if (vvc.m_added_to_pool)
{
NOTIFY_NEW_DEREGISTER_VOTE::request r;
r.votes.push_back(req.vote);
cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context);
m_core.get_protocol()->relay_deregister_votes(r, fake_context);
}
resp.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_relay_tx(const COMMAND_RPC_RELAY_TX::request& req, COMMAND_RPC_RELAY_TX::response& res, epee::json_rpc::error& error_resp)
{
PERF_TIMER(on_relay_tx);
@ -2165,6 +2261,30 @@ namespace cryptonote
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_quorum_state_json(const COMMAND_RPC_GET_QUORUM_STATE::request& req, COMMAND_RPC_GET_QUORUM_STATE::response& res, epee::json_rpc::error& error_resp)
{
PERF_TIMER(on_get_quorum_list_json);
bool r;
if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_QUORUM_STATE>(invoke_http_mode::JON_RPC, "get_quorum_list", req, res, r))
{
return r;
}
r = on_get_quorum_state(req, res);
if (r)
{
res.status = CORE_RPC_STATUS_OK;
}
else
{
error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT;
error_resp.message = res.status;
}
return r;
}
//------------------------------------------------------------------------------------------------------------------------------
const command_line::arg_descriptor<std::string, false, true, 2> core_rpc_server::arg_rpc_bind_port = {

View File

@ -120,6 +120,8 @@ namespace cryptonote
MAP_URI_AUTO_JON2_IF("/stop_save_graph", on_stop_save_graph, COMMAND_RPC_STOP_SAVE_GRAPH, !m_restricted)
MAP_URI_AUTO_JON2("/get_outs", on_get_outs, COMMAND_RPC_GET_OUTPUTS)
MAP_URI_AUTO_JON2_IF("/update", on_update, COMMAND_RPC_UPDATE, !m_restricted)
MAP_URI_AUTO_JON2("/get_quorum_state", on_get_quorum_state, COMMAND_RPC_GET_QUORUM_STATE)
MAP_URI_AUTO_JON2("/submit_deregister_vote", on_submit_deregister_vote, COMMAND_RPC_SEND_DEREGISTER_VOTE)
BEGIN_JSON_RPC_MAP("/json_rpc")
MAP_JON_RPC("get_block_count", on_getblockcount, COMMAND_RPC_GETBLOCKCOUNT)
MAP_JON_RPC("getblockcount", on_getblockcount, COMMAND_RPC_GETBLOCKCOUNT)
@ -154,6 +156,7 @@ namespace cryptonote
MAP_JON_RPC_WE_IF("sync_info", on_sync_info, COMMAND_RPC_SYNC_INFO, !m_restricted)
MAP_JON_RPC_WE("get_txpool_backlog", on_get_txpool_backlog, COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG)
MAP_JON_RPC_WE("get_output_distribution", on_get_output_distribution, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION)
MAP_JON_RPC_WE("get_quorum_state", on_get_quorum_state_json, COMMAND_RPC_GET_QUORUM_STATE)
END_JSON_RPC_MAP()
END_URI_MAP2()
@ -190,7 +193,9 @@ namespace cryptonote
bool on_start_save_graph(const COMMAND_RPC_START_SAVE_GRAPH::request& req, COMMAND_RPC_START_SAVE_GRAPH::response& res);
bool on_stop_save_graph(const COMMAND_RPC_STOP_SAVE_GRAPH::request& req, COMMAND_RPC_STOP_SAVE_GRAPH::response& res);
bool on_update(const COMMAND_RPC_UPDATE::request& req, COMMAND_RPC_UPDATE::response& res);
bool on_get_quorum_state(const COMMAND_RPC_GET_QUORUM_STATE::request& req, COMMAND_RPC_GET_QUORUM_STATE::response& res);
bool on_submit_deregister_vote(const COMMAND_RPC_SEND_DEREGISTER_VOTE::request& req, COMMAND_RPC_SEND_DEREGISTER_VOTE::response& resp);
//json_rpc
bool on_getblockcount(const COMMAND_RPC_GETBLOCKCOUNT::request& req, COMMAND_RPC_GETBLOCKCOUNT::response& res);
bool on_getblockhash(const COMMAND_RPC_GETBLOCKHASH::request& req, COMMAND_RPC_GETBLOCKHASH::response& res, epee::json_rpc::error& error_resp);
@ -216,6 +221,7 @@ namespace cryptonote
bool on_sync_info(const COMMAND_RPC_SYNC_INFO::request& req, COMMAND_RPC_SYNC_INFO::response& res, epee::json_rpc::error& error_resp);
bool on_get_txpool_backlog(const COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::response& res, epee::json_rpc::error& error_resp);
bool on_get_output_distribution(const COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request& req, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response& res, epee::json_rpc::error& error_resp);
bool on_get_quorum_state_json(const COMMAND_RPC_GET_QUORUM_STATE::request& req, COMMAND_RPC_GET_QUORUM_STATE::response& res, epee::json_rpc::error& error_resp);
//-----------------------
private:

View File

@ -31,8 +31,10 @@
#pragma once
#include "cryptonote_protocol/cryptonote_protocol_defs.h"
#include "cryptonote_basic/cryptonote_basic.h"
#include "cryptonote_basic/verification_context.h"
#include "cryptonote_basic/difficulty.h"
#include "crypto/hash.h"
#include "cryptonote_core/service_node_deregister.h"
namespace cryptonote
{
@ -49,7 +51,7 @@ namespace cryptonote
// advance which version they will stop working with
// Don't go over 32767 for any of these
#define CORE_RPC_VERSION_MAJOR 1
#define CORE_RPC_VERSION_MINOR 20
#define CORE_RPC_VERSION_MINOR 1
#define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor))
#define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR)
@ -882,6 +884,12 @@ namespace cryptonote
bool not_rct;
bool untrusted;
bool invalid_block_height;
bool voters_quorum_index_out_of_bounds;
bool service_node_index_out_of_bounds;
bool signature_not_valid;
bool not_enough_votes;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(status)
KV_SERIALIZE(reason)
@ -895,6 +903,12 @@ namespace cryptonote
KV_SERIALIZE(fee_too_low)
KV_SERIALIZE(not_rct)
KV_SERIALIZE(untrusted)
KV_SERIALIZE(invalid_block_height)
KV_SERIALIZE(voters_quorum_index_out_of_bounds)
KV_SERIALIZE(service_node_index_out_of_bounds)
KV_SERIALIZE(signature_not_valid)
KV_SERIALIZE(not_enough_votes)
END_KV_SERIALIZE_MAP()
};
};
@ -2263,4 +2277,62 @@ namespace cryptonote
};
};
struct COMMAND_RPC_GET_QUORUM_STATE
{
struct request
{
uint64_t height;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(height)
END_KV_SERIALIZE_MAP()
};
struct response
{
std::string status;
std::vector<std::string> quorum_nodes;
std::vector<std::string> nodes_to_test;
bool untrusted;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(status)
KV_SERIALIZE(quorum_nodes)
KV_SERIALIZE(nodes_to_test)
KV_SERIALIZE(untrusted)
END_KV_SERIALIZE_MAP()
};
};
struct COMMAND_RPC_SEND_DEREGISTER_VOTE
{
struct request
{
loki::service_node_deregister::vote vote;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_VAL_POD_AS_BLOB(vote)
END_KV_SERIALIZE_MAP()
};
struct response
{
std::string status;
std::string reason;
bool invalid_block_height;
bool voters_quorum_index_out_of_bounds;
bool service_node_index_out_of_bounds;
bool signature_not_valid;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(status)
KV_SERIALIZE(reason)
KV_SERIALIZE(invalid_block_height)
KV_SERIALIZE(voters_quorum_index_out_of_bounds)
KV_SERIALIZE(service_node_index_out_of_bounds)
KV_SERIALIZE(signature_not_valid)
END_KV_SERIALIZE_MAP()
};
};
}

View File

@ -53,6 +53,7 @@
#include "common/base58.h"
#include "common/scoped_message_writer.h"
#include "cryptonote_protocol/cryptonote_protocol_handler.h"
#include "cryptonote_core/service_node_deregister.h"
#include "simplewallet.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "storages/http_abstract_invoke.h"
@ -4710,19 +4711,12 @@ bool simple_wallet::stake_all(const std::vector<std::string> &args_)
return true;
}
crypto::public_key service_node_key;
if (!epee::string_tools::hex_to_pod(local_args[0], service_node_key))
{
fail_msg_writer() << tr("failed to parse service node pubkey");
return true;
}
priority = m_wallet->adjust_priority(priority);
size_t mixins = DEFAULT_MIX;
uint64_t unlock_block = 0;
uint64_t locked_blocks = STAKING_REQUIREMENT_LOCK_BLOCKS;
uint64_t locked_blocks = STAKING_REQUIREMENT_LOCK_BLOCKS + STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS;
std::string err;
uint64_t bc_height = get_daemon_blockchain_height(err);
@ -4737,11 +4731,15 @@ bool simple_wallet::stake_all(const std::vector<std::string> &args_)
std::vector<uint8_t> extra;
if (!add_account_public_address_to_tx_extra(extra, address, service_node_key))
tx_extra_service_node_register register_;
register_.public_view_key = address.m_view_public_key;
register_.public_spend_key = address.m_spend_public_key;
if (!epee::string_tools::hex_to_pod(local_args[0], register_.service_node_key))
{
fail_msg_writer() << tr("failed to add account public address to tx extra");
fail_msg_writer() << tr("failed to parse service node pubkey");
return true;
}
add_service_node_register_to_tx_extra(extra, register_);
LOCK_IDLE_SCOPE();
@ -4782,8 +4780,8 @@ bool simple_wallet::stake_all(const std::vector<std::string> &args_)
return true;
if (ptx_vector.size() > 1) {
prompt << boost::format(tr("Staking %s for %u blocks in %llu transactions for a total fee of %s. Is this okay? (Y/Yes/N/No): ")) %
locked_blocks %
print_money(total_sent) %
locked_blocks %
((unsigned long long)ptx_vector.size()) %
print_money(total_fee);
}
@ -4846,7 +4844,6 @@ bool simple_wallet::stake_all(const std::vector<std::string> &args_)
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_)
{
if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }

View File

@ -153,8 +153,8 @@ namespace cryptonote
bool transfer(const std::vector<std::string> &args);
bool transfer_new(const std::vector<std::string> &args);
bool locked_transfer(const std::vector<std::string> &args);
bool stake_all(const std::vector<std::string> &args_);
bool locked_sweep_all(const std::vector<std::string> &args);
bool stake_all(const std::vector<std::string> &args);
bool sweep_main(uint64_t below, bool locked, const std::vector<std::string> &args);
bool sweep_all(const std::vector<std::string> &args);
bool sweep_below(const std::vector<std::string> &args);

View File

@ -4583,7 +4583,29 @@ crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const
}
return payment_id;
}
//----------------------------------------------------------------------------------------------------
void wallet2::commit_deregister_vote(loki::service_node_deregister::vote& vote)
{
if (m_light_wallet)
{
LOG_PRINT_L1("Deregister vote can not be sent in a light wallet.");
}
else
{
COMMAND_RPC_SEND_DEREGISTER_VOTE::request req;
req.vote = vote;
COMMAND_RPC_SEND_DEREGISTER_VOTE::response resp;
m_daemon_rpc_mutex.lock();
bool r = epee::net_utils::invoke_http_json("/submit_deregister_vote", req, resp, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "submit_deregister_vote");
THROW_WALLET_EXCEPTION_IF(resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "submit_deregister_vote");
THROW_WALLET_EXCEPTION_IF(resp.status != CORE_RPC_STATUS_OK, error::vote_rejected, resp.status, resp.reason);
}
}
//----------------------------------------------------------------------------------------------------
// take a pending tx and actually send it to the daemon
void wallet2::commit_tx(pending_tx& ptx)

View File

@ -44,6 +44,7 @@
#include "cryptonote_basic/account.h"
#include "cryptonote_basic/account_boost_serialization.h"
#include "cryptonote_basic/cryptonote_basic_impl.h"
#include "cryptonote_core/service_node_deregister.h"
#include "net/http_client.h"
#include "storages/http_abstract_invoke.h"
#include "rpc/core_rpc_server_commands_defs.h"
@ -686,6 +687,7 @@ namespace tools
std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, bool bulletproof, bool is_staking_tx=false);
void commit_deregister_vote(loki::service_node_deregister::vote& vote);
void commit_tx(pending_tx& ptx_vector);
void commit_tx(std::vector<pending_tx>& ptx_vector);
bool save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename) const;

View File

@ -77,6 +77,7 @@ namespace tools
// tx_not_possible
// not_enough_outs_to_mix
// tx_not_constructed
// vote_rejected
// tx_rejected
// tx_sum_overflow
// tx_too_big
@ -620,6 +621,34 @@ namespace tools
std::string m_reason;
};
//----------------------------------------------------------------------------------------------------
struct vote_rejected : public transfer_error
{
explicit vote_rejected(std::string&& loc, const std::string& status, const std::string& reason)
: transfer_error(std::move(loc), "vote was rejected by daemon")
, m_status(status)
, m_reason(reason)
{
}
const std::string& status() const { return m_status; }
const std::string& reason() const { return m_reason; }
std::string to_string() const
{
std::ostringstream ss;
ss << transfer_error::to_string() << ", status = " << m_status;
if (!m_reason.empty())
{
ss << " (" << m_reason << ")";
}
return ss.str();
}
private:
std::string m_status;
std::string m_reason;
};
//----------------------------------------------------------------------------------------------------
struct tx_sum_overflow : public transfer_error
{
explicit tx_sum_overflow(

View File

@ -34,6 +34,7 @@
#include "cryptonote_basic/cryptonote_basic_impl.h"
#include "cryptonote_basic/verification_context.h"
#include "cryptonote_core/service_node_deregister.h"
#include <unordered_map>
namespace tests
@ -103,5 +104,9 @@ namespace tests
cryptonote::difficulty_type get_block_cumulative_difficulty(uint64_t height) const { return 0; }
bool fluffy_blocks_enabled() const { return false; }
uint64_t prevalidate_block_hashes(uint64_t height, const std::list<crypto::hash> &hashes) { return 0; }
// TODO(loki): Write tests
virtual void set_deregister_votes_relayed(const std::vector<loki::service_node_deregister::vote>& votes) {}
bool add_deregister_vote(const loki::service_node_deregister::vote& vote, cryptonote::vote_verification_context &vvc) { return false; }
};
}

View File

@ -83,6 +83,10 @@ public:
bool fluffy_blocks_enabled() const { return false; }
uint64_t prevalidate_block_hashes(uint64_t height, const std::list<crypto::hash> &hashes) { return 0; }
void stop() {}
// TODO(loki): Write tests
bool add_deregister_vote(const loki::service_node_deregister::vote& vote, cryptonote::vote_verification_context &vvc) { return true; }
virtual void set_deregister_votes_relayed(const std::vector<loki::service_node_deregister::vote>& votes) {}
};
typedef nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<test_core>> Server;