Infinite Staking Part 2 (#406)

* Cleanup and undoing some protocol breakages

* Simplify expiration of nodes

* Request unlock schedules entire node for expiration

* Fix off by one in expiring nodes

* Undo expiring code for pre v10 nodes

* Fix RPC returning register as unlock height and not checking 0

* Rename key image unlock height const

* Undo testnet hardfork debug changes

* Remove is_type for get_type, fix missing var rename

* Move serialisable data into public namespace

* Serialise tx types properly

* Fix typo in no service node known msg

* Code review

* Fix == to >= on serialising tx type

* Code review 2

* Fix tests and key image unlock

* Add command to print locked key images

* Update ui to display lock stakes, query in print cmd blacklist

* Modify print stakes to be less slow

* Remove autostaking code

* Refactor staking into sweep functions

It appears staking was derived off stake_main written separately at
implementation at the beginning. This merges them back into a common
code path, after removing autostake there's only some minor differences.

It also makes sure that any changes to sweeping upstream are going to be
considered in the staking process which we want.

* Display unlock height for stakes

* Begin creating output blacklist

* Make blacklist output a migration step

* Implement get_output_blacklist for lmdb

* In wallet output selection ignore blacklisted outputs

* Apply blacklisted outputs to output selection

* Fix broken tests, switch key image unlock

* Fix broken unit_tests

* Begin change to limit locked key images to 4 globally

* Revamp prepare registration for new min contribution rules

* Fix up old back case in prepare registration

* Remove debug code

* Cleanup debug code and some unecessary changes

* Fix migration step on mainnet db

* Fix blacklist outputs for pre-existing DB's

* Remove irrelevant note

* Tweak scanning addresses for locked stakes

Since we only now allow contributions from the primary address we can
skip checking all subaddress + lookahead to speed up wallet scanning

* Define macro for SCNu64 for Mingw

* Fix failure on empty DB

* Add missing error msg, remove contributor from stake

* Improve staking messages

* Flush prompt to always display

* Return the msg from stake failure and fix stake parsing error

* Tweak fork rules for smaller bulletproofs

* Tweak pooled nodes minimum amounts

* Fix crash on exit, there's no need to store on destructor

Since all information about service nodes is derived from the blockchain
and we store state every time we receive a block, storing in the
destructor is redundant as there is no new information to store.

* Make prompt be consistent with CLI

* Check max number of key images from per user to node

* Implement error message on get_output_blacklist failure

* Remove resolved TODO's/comments

* Handle infinite staking in print_sn

* Atoi->strtol, fix prepare_registration, virtual override, stale msgs
This commit is contained in:
Doyle 2019-02-14 12:12:57 +11:00 committed by GitHub
parent 88f3015d58
commit 43436b081d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1619 additions and 1240 deletions

View File

@ -136,12 +136,19 @@ void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const transacti
{
tx_hash = *tx_hash_ptr;
}
bool has_blacklisted_outputs = false;
if (tx.version >= 2)
{
if (!tx_prunable_hash_ptr)
tx_prunable_hash = get_transaction_prunable_hash(tx);
else
tx_prunable_hash = *tx_prunable_hash_ptr;
crypto::secret_key secret_tx_key;
cryptonote::account_public_address address;
if (get_tx_secret_key_from_tx_extra(tx.extra, secret_tx_key) && get_service_node_contributor_from_tx_extra(tx.extra, address))
has_blacklisted_outputs = true;
}
for (const txin_v& tx_input : tx.vin)
@ -203,6 +210,10 @@ void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const transacti
tx.version > 1 ? &tx.rct_signatures.outPk[i].mask : NULL);
}
}
if (has_blacklisted_outputs)
add_output_blacklist(amount_output_indices);
add_tx_amount_output_indices(tx_id, amount_output_indices);
}

View File

@ -1626,6 +1626,9 @@ public:
virtual bool get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t to_height, std::vector<uint64_t> &distribution, uint64_t &base) const = 0;
virtual bool get_output_blacklist(std::vector<uint64_t> &blacklist) const = 0;
virtual void add_output_blacklist(std::vector<uint64_t> const &blacklist) = 0;
/**
* @brief is BlockchainDB in read-only mode?
*

View File

@ -54,7 +54,7 @@ using epee::string_tools::pod_to_hex;
using namespace crypto;
// Increase when the DB structure changes
#define VERSION 3
#define VERSION 4
namespace
{
@ -216,6 +216,7 @@ const char* const LMDB_TX_OUTPUTS = "tx_outputs";
const char* const LMDB_OUTPUT_TXS = "output_txs";
const char* const LMDB_OUTPUT_AMOUNTS = "output_amounts";
const char* const LMDB_OUTPUT_BLACKLIST = "output_blacklist";
const char* const LMDB_SPENT_KEYS = "spent_keys";
const char* const LMDB_TXPOOL_META = "txpool_meta";
@ -227,6 +228,7 @@ const char* const LMDB_SERVICE_NODE_DATA = "service_node_data";
const char* const LMDB_PROPERTIES = "properties";
const char zerokey[8] = {0};
const MDB_val zerokval = { sizeof(zerokey), (void *)zerokey };
@ -1360,6 +1362,7 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags)
lmdb_db_open(txn, LMDB_OUTPUT_TXS, MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_output_txs, "Failed to open db handle for m_output_txs");
lmdb_db_open(txn, LMDB_OUTPUT_AMOUNTS, MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED | MDB_CREATE, m_output_amounts, "Failed to open db handle for m_output_amounts");
lmdb_db_open(txn, LMDB_OUTPUT_BLACKLIST, MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED | MDB_INTEGERDUP, m_output_blacklist, "Failed to open db handle for m_output_blacklist");
lmdb_db_open(txn, LMDB_SPENT_KEYS, MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_spent_keys, "Failed to open db handle for m_spent_keys");
@ -1383,6 +1386,7 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags)
mdb_set_dupsort(txn, m_tx_indices, compare_hash32);
mdb_set_dupsort(txn, m_output_amounts, compare_uint64);
mdb_set_dupsort(txn, m_output_txs, compare_uint64);
mdb_set_dupsort(txn, m_output_blacklist, compare_uint64);
mdb_set_dupsort(txn, m_block_info, compare_uint64);
if (!(mdb_flags & MDB_RDONLY))
mdb_set_dupsort(txn, m_txs_prunable_tip, compare_uint64);
@ -1556,6 +1560,8 @@ void BlockchainLMDB::reset()
throw0(DB_ERROR(lmdb_error("Failed to drop m_output_txs: ", result).c_str()));
if (auto result = mdb_drop(txn, m_output_amounts, 0))
throw0(DB_ERROR(lmdb_error("Failed to drop m_output_amounts: ", result).c_str()));
if (auto result = mdb_drop(txn, m_output_blacklist, 0))
throw0(DB_ERROR(lmdb_error("Failed to drop m_output_blacklist: ", result).c_str()));
if (auto result = mdb_drop(txn, m_spent_keys, 0))
throw0(DB_ERROR(lmdb_error("Failed to drop m_spent_keys: ", result).c_str()));
(void)mdb_drop(txn, m_hf_starting_heights, 0); // this one is dropped in new code
@ -3842,6 +3848,66 @@ bool BlockchainLMDB::get_output_distribution(uint64_t amount, uint64_t from_heig
return true;
}
bool BlockchainLMDB::get_output_blacklist(std::vector<uint64_t> &blacklist) const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
TXN_PREFIX_RDONLY();
RCURSOR(output_blacklist);
MDB_stat db_stat;
if (int result = mdb_stat(m_txn, m_output_blacklist, &db_stat))
throw0(DB_ERROR(lmdb_error("Failed to query output blacklist stats: ", result).c_str()));
MDB_val key = zerokval;
MDB_val val;
blacklist.reserve(db_stat.ms_entries);
if (int ret = mdb_cursor_get(m_cur_output_blacklist, &key, &val, MDB_FIRST))
{
if (ret != MDB_NOTFOUND)
{
throw0(DB_ERROR(lmdb_error("Failed to enumerate output blacklist: ", ret).c_str()));
}
}
else
{
for(MDB_cursor_op op = MDB_GET_MULTIPLE;; op = MDB_NEXT_MULTIPLE)
{
int ret = mdb_cursor_get(m_cur_output_blacklist, &key, &val, op);
if (ret == MDB_NOTFOUND) break;
if (ret) throw0(DB_ERROR(lmdb_error("Failed to enumerate output blacklist: ", ret).c_str()));
uint64_t const *outputs = (uint64_t const *)val.mv_data;
int num_outputs = val.mv_size / sizeof(*outputs);
for (int i = 0; i < num_outputs; i++)
blacklist.push_back(outputs[i]);
}
}
TXN_POSTFIX_RDONLY();
return true;
}
void BlockchainLMDB::add_output_blacklist(std::vector<uint64_t> const &blacklist)
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
mdb_txn_cursors *m_cursors = &m_wcursors;
CURSOR(output_blacklist);
MDB_val put_entries[2] = {};
put_entries[0].mv_size = sizeof(uint64_t);
put_entries[0].mv_data = (uint64_t *)blacklist.data();
put_entries[1].mv_size = blacklist.size();
if (int ret = mdb_cursor_put(m_cur_output_blacklist, (MDB_val *)&zerokval, (MDB_val *)put_entries, MDB_MULTIPLE))
throw0(DB_ERROR(lmdb_error("Failed to add blacklisted output to db transaction: ", ret).c_str()));
}
void BlockchainLMDB::check_hard_fork_info()
{
}
@ -4774,6 +4840,103 @@ void BlockchainLMDB::migrate_2_3()
txn.commit();
}
void BlockchainLMDB::migrate_3_4()
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
MGINFO_YELLOW("Migrating blockchain from DB version 3 to 4 - this may take a while:");
mdb_txn_safe txn(false);
{
int result = mdb_txn_begin(m_env, NULL, 0, txn);
if (result)
throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str()));
}
{
std::vector<uint64_t> global_output_indexes;
{
uint64_t total_tx_count = get_tx_count();
TXN_PREFIX_RDONLY();
RCURSOR(txs_pruned);
RCURSOR(txs_prunable);
RCURSOR(tx_indices);
MDB_val key, val;
uint64_t tx_count = 0;
blobdata bd;
for(MDB_cursor_op op = MDB_FIRST;; op = MDB_NEXT, bd.clear())
{
transaction tx;
txindex const *tx_index = (txindex const *)val.mv_data;
{
int ret = mdb_cursor_get(m_cur_tx_indices, &key, &val, op);
if (ret == MDB_NOTFOUND) break;
if (ret) throw0(DB_ERROR(lmdb_error("Failed to enumerate transactions: ", ret).c_str()));
const crypto::hash &hash = tx_index->key;
tx_index = (txindex const *)val.mv_data;
key.mv_data = (void *)&tx_index->data.tx_id;
key.mv_size = sizeof(tx_index->data.tx_id);
{
ret = mdb_cursor_get(m_cur_txs_pruned, &key, &val, MDB_SET);
if (ret == MDB_NOTFOUND) break;
if (ret) throw0(DB_ERROR(lmdb_error("Failed to enumerate transactions: ", ret).c_str()));
bd.append(reinterpret_cast<char*>(val.mv_data), val.mv_size);
ret = mdb_cursor_get(m_cur_txs_prunable, &key, &val, MDB_SET);
if (ret) throw0(DB_ERROR(lmdb_error("Failed to get prunable tx data the db: ", ret).c_str()));
bd.append(reinterpret_cast<char*>(val.mv_data), val.mv_size);
if (!parse_and_validate_tx_from_blob(bd, tx)) throw0(DB_ERROR("Failed to parse tx from blob retrieved from the db"));
}
}
if (++tx_count % 1000 == 0)
{
LOGIF(el::Level::Info) {
std::cout << tx_count << " / " << total_tx_count << " \r" << std::flush;
}
}
if (tx.get_type() != transaction::type_standard || tx.vout.size() == 0)
continue;
crypto::secret_key secret_tx_key;
if (!cryptonote::get_tx_secret_key_from_tx_extra(tx.extra, secret_tx_key))
continue;
std::vector<std::vector<uint64_t>> outputs = get_tx_amount_output_indices(tx_index->data.tx_id, 1);
for (uint64_t output_index : outputs[0])
global_output_indexes.push_back(output_index);
}
TXN_POSTFIX_RDONLY();
}
mdb_txn_cursors *m_cursors = &m_wcursors;
if (int result = mdb_cursor_open(txn, m_output_blacklist, &m_cur_output_blacklist))
throw0(DB_ERROR(lmdb_error("Failed to open cursor: ", result).c_str()));
std::sort(global_output_indexes.begin(), global_output_indexes.end());
add_output_blacklist(global_output_indexes);
}
txn.commit();
if (int result = mdb_txn_begin(m_env, NULL, 0, txn))
throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str()));
uint32_t version = 4;
MDB_val val = {};
val.mv_data = (void *)&version;
val.mv_size = sizeof(version);
MDB_val_str(key, "version");
if (int result = mdb_put(txn, m_properties, &key, &val, 0))
throw0(DB_ERROR(lmdb_error("Failed to update version for the db: ", result).c_str()));
txn.commit();
}
void BlockchainLMDB::migrate(const uint32_t oldversion)
{
if (oldversion < 1)
@ -4782,6 +4945,8 @@ void BlockchainLMDB::migrate(const uint32_t oldversion)
migrate_1_2();
if (oldversion < 3)
migrate_2_3();
if (oldversion < 4)
migrate_3_4();
}

View File

@ -70,6 +70,7 @@ typedef struct mdb_txn_cursors
MDB_cursor *m_txc_hf_versions;
MDB_cursor *m_txc_service_node_data;
MDB_cursor *m_txc_output_blacklist;
MDB_cursor *m_txc_properties;
} mdb_txn_cursors;
@ -78,6 +79,7 @@ typedef struct mdb_txn_cursors
#define m_cur_block_info m_cursors->m_txc_block_info
#define m_cur_output_txs m_cursors->m_txc_output_txs
#define m_cur_output_amounts m_cursors->m_txc_output_amounts
#define m_cur_output_blacklist m_cursors->m_txc_output_blacklist
#define m_cur_txs m_cursors->m_txc_txs
#define m_cur_txs_pruned m_cursors->m_txc_txs_pruned
#define m_cur_txs_prunable m_cursors->m_txc_txs_prunable
@ -100,6 +102,7 @@ typedef struct mdb_rflags
bool m_rf_block_info;
bool m_rf_output_txs;
bool m_rf_output_amounts;
bool m_rf_output_blacklist;
bool m_rf_txs;
bool m_rf_txs_pruned;
bool m_rf_txs_prunable;
@ -329,6 +332,8 @@ public:
std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked, uint64_t recent_cutoff, uint64_t min_count) const;
bool get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t to_height, std::vector<uint64_t> &distribution, uint64_t &base) const;
virtual bool get_output_blacklist(std::vector<uint64_t> &blacklist) const override;
virtual void add_output_blacklist(std::vector<uint64_t> const &blacklist) override;
// helper functions
static int compare_uint64(const MDB_val *a, const MDB_val *b);
@ -407,6 +412,7 @@ private:
// migrate from DB version 2 to 3
void migrate_2_3();
void migrate_3_4();
void cleanup_batch();
@ -432,6 +438,7 @@ private:
MDB_dbi m_output_txs;
MDB_dbi m_output_amounts;
MDB_dbi m_output_blacklist;
MDB_dbi m_spent_keys;

View File

@ -317,7 +317,7 @@ loki_add_executable(blockchain_prune
set_property(TARGET blockchain_prune
PROPERTY
OUTPUT_NAME "monero-blockchain-prune")
OUTPUT_NAME "loki-blockchain-prune")
install(TARGETS blockchain_prune DESTINATION bin)
target_link_libraries(blockchain_prune

View File

@ -43,32 +43,39 @@ namespace command_line
}
}
bool is_yes(const std::string& str)
static bool str_compare_with_boost(const std::string &str, char const *check_str)
{
if (str == "y" || str == "Y")
return true;
boost::algorithm::is_iequal ignore_case{};
if (boost::algorithm::equals("yes", str, ignore_case))
if (boost::algorithm::equals(check_str, str, ignore_case))
return true;
if (boost::algorithm::equals(command_line::tr("yes"), str, ignore_case))
if (boost::algorithm::equals(command_line::tr(check_str), str, ignore_case))
return true;
return false;
}
bool is_yes(const std::string& str)
{
bool result = (str == "y" || str == "Y") || str_compare_with_boost(str, "yes");
return result;
}
bool is_no(const std::string& str)
{
if (str == "n" || str == "N")
return true;
bool result = (str == "n" || str == "N") || str_compare_with_boost(str, "no");
return result;
}
boost::algorithm::is_iequal ignore_case{};
if (boost::algorithm::equals("no", str, ignore_case))
return true;
if (boost::algorithm::equals(command_line::tr("no"), str, ignore_case))
return true;
bool is_cancel(const std::string& str)
{
bool result = (str == "c" || str == "C") || str_compare_with_boost(str, "cancel");
return result;
}
return false;
bool is_back(const std::string& str)
{
bool result = (str == "b" || str == "B") || str_compare_with_boost(str, "back");
return result;
}
const arg_descriptor<bool> arg_help = {"help", "Produce help message"};

View File

@ -48,6 +48,8 @@ namespace command_line
bool is_yes(const std::string& str);
//! \return True if `str` is `is_iequal("n" || "no" || `tr("no"))`.
bool is_no(const std::string& str);
bool is_cancel(const std::string& str);
bool is_back(const std::string& str);
template<typename T, bool required = false, bool dependent = false, int NUM_DEPS = 1>
struct arg_descriptor;

View File

@ -59,7 +59,6 @@ static_assert(STAKING_PORTIONS % 2 == 0, "Use a multiple of two, so that it divi
static_assert(STAKING_PORTIONS % 3 == 0, "Use a multiple of three, so that it divides easily by three contributors.");
#define STAKING_AUTHORIZATION_EXPIRATION_WINDOW (60*60*24*7*2) // 2 weeks
#define STAKING_AUTHORIZATION_EXPIRATION_AUTOSTAKE (60*60*24*365*2) // 2 years
#define BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW 11

View File

@ -1909,6 +1909,7 @@ bool Blockchain::get_output_distribution(uint64_t amount, uint64_t from_height,
return false;
if (start_height >= db_height || to_height >= db_height)
return false;
if (amount == 0)
{
std::vector<uint64_t> heights;
@ -1930,6 +1931,11 @@ bool Blockchain::get_output_distribution(uint64_t amount, uint64_t from_height,
}
}
//------------------------------------------------------------------
bool Blockchain::get_output_blacklist(std::vector<uint64_t> &blacklist) const
{
return m_db->get_output_blacklist(blacklist);
}
//------------------------------------------------------------------
// This function takes a list of block hashes from another node
// on the network to find where the split point is between us and them.
// This is used to see what to send another node that needs to sync.

View File

@ -538,6 +538,13 @@ namespace cryptonote
*/
bool get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t to_height, uint64_t &start_height, std::vector<uint64_t> &distribution, uint64_t &base) const;
/**
* @brief gets global output indexes that should not be used, i.e. registration tx outputs
*
* @param return-by-reference blacklist global indexes of rct outputs to ignore
*/
bool get_output_blacklist(std::vector<uint64_t> &blacklist) const;
/**
* @brief gets the global indices for outputs from a given transaction
*

View File

@ -1367,6 +1367,11 @@ namespace cryptonote
return m_blockchain_storage.get_output_distribution(amount, from_height, to_height, start_height, distribution, base);
}
//-----------------------------------------------------------------------------------------------
bool core::get_output_blacklist(std::vector<uint64_t> &blacklist) const
{
return m_blockchain_storage.get_output_blacklist(blacklist);
}
//-----------------------------------------------------------------------------------------------
bool core::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector<uint64_t>& indexs) const
{
return m_blockchain_storage.get_tx_outputs_gindexs(tx_id, indexs);

View File

@ -588,6 +588,8 @@ namespace cryptonote
*/
bool get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t to_height, uint64_t &start_height, std::vector<uint64_t> &distribution, uint64_t &base) const;
bool get_output_blacklist(std::vector<uint64_t> &blacklist) const;
/**
* @copydoc miner::pause
*

View File

@ -71,11 +71,6 @@ namespace service_nodes
{
}
service_node_list::~service_node_list()
{
store();
}
void service_node_list::register_hooks(service_nodes::quorum_cop &quorum_cop)
{
std::lock_guard<boost::recursive_mutex> lock(m_sn_mutex);
@ -364,7 +359,7 @@ namespace service_nodes
{
key_image_blacklist_entry entry = {};
entry.key_image = contribution.key_image;
entry.unlock_height = block_height + staking_initial_num_lock_blocks(m_blockchain.nettype());
entry.unlock_height = block_height + staking_num_lock_blocks(m_blockchain.nettype());
m_key_image_blacklist.push_back(entry);
const bool adding_to_blacklist = true;
@ -599,10 +594,6 @@ namespace service_nodes
if (hard_fork_version >= cryptonote::network_version_11_swarms)
{
// TODO(doyle): INF_STAKING(doyle): On the boundary of the hardfork, if
// someone submits an old style staking TX, it will fail and lock up
// funds. Make sure to put in rules to prevent this.
cryptonote::tx_extra_tx_key_image_proofs key_image_proofs;
if (!get_tx_key_image_proofs_from_tx_extra(tx.extra, key_image_proofs))
{
@ -661,7 +652,7 @@ namespace service_nodes
unlock_time = tx.output_unlock_times[i];
has_correct_unlock_time = unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER &&
unlock_time >= block_height + staking_initial_num_lock_blocks(nettype);
unlock_time >= block_height + staking_num_lock_blocks(nettype);
}
if (has_correct_unlock_time)
@ -695,7 +686,6 @@ namespace service_nodes
int hf_version = m_blockchain.get_hard_fork_version(block_height);
// check the portions
if (!check_service_node_portions(hf_version, service_node_portions)) return false;
if (portions_for_operator > STAKING_PORTIONS)
@ -744,10 +734,10 @@ namespace service_nodes
return false;
}
const uint64_t min_transfer = info.staking_requirement / MAX_NUMBER_OF_CONTRIBUTORS;
const uint64_t min_transfer = get_min_node_contribution(hf_version, info.staking_requirement, info.total_reserved, info.total_num_locked_contributions());
if (parsed_contribution.transferred < min_transfer)
{
MERROR("Register TX: Contribution didn't meet the minimum transfer requirement: " << min_transfer << " on height: " << block_height << " for tx: " << cryptonote::get_transaction_hash(tx));
MERROR("Register TX: Contribution transferred: " << parsed_contribution.transferred << " didn't meet the minimum transfer requirement: " << min_transfer << " on height: " << block_height << " for tx: " << cryptonote::get_transaction_hash(tx));
return false;
}
@ -836,7 +826,7 @@ namespace service_nodes
if (hard_fork_version >= cryptonote::network_version_10_bulletproofs)
{
service_node_info const &old_info = iter->second;
uint64_t expiry_height = old_info.registration_height + staking_initial_num_lock_blocks(m_blockchain.nettype());
uint64_t expiry_height = old_info.registration_height + staking_num_lock_blocks(m_blockchain.nettype());
if (block_height < expiry_height)
return false;
@ -933,7 +923,7 @@ namespace service_nodes
/// Check that the contribution is large enough
const uint8_t hf_version = m_blockchain.get_hard_fork_version(block_height);
const uint64_t min_contribution = get_min_node_contribution(hf_version, info.staking_requirement, info.total_reserved, contributors.size());
const uint64_t min_contribution = get_min_node_contribution(hf_version, info.staking_requirement, info.total_reserved, info.total_num_locked_contributions());
if (parsed_contribution.transferred < min_contribution)
{
LOG_PRINT_L1("Contribution TX: Amount " << parsed_contribution.transferred <<
@ -979,14 +969,24 @@ namespace service_nodes
info.last_reward_block_height = block_height;
info.last_reward_transaction_index = index;
const size_t max_contributions_per_node = service_nodes::MAX_KEY_IMAGES_PER_CONTRIBUTOR * MAX_NUMBER_OF_CONTRIBUTORS;
if (hf_version >= cryptonote::network_version_11_swarms)
{
// TODO(doyle): INF_STAKING(doyle): Set a limit on the number of key images allowed
std::vector<service_node_info::contribution_t> &locked_contributions = contributor.locked_contributions;
locked_contributions.reserve(locked_contributions.size() + parsed_contribution.locked_contributions.size());
for (const service_node_info::contribution_t &contribution : parsed_contribution.locked_contributions)
contributor.locked_contributions.push_back(contribution);
{
if (info.total_num_locked_contributions() < max_contributions_per_node)
contributor.locked_contributions.push_back(contribution);
else
{
LOG_PRINT_L1("Contribution TX: Already hit the max number of contributions: " << max_contributions_per_node <<
" for contributor: " << cryptonote::get_account_address_as_str(m_blockchain.nettype(), false, contributor.address) <<
" on height: " << block_height <<
" for tx: " << cryptonote::get_transaction_hash(tx));
break;
}
}
}
LOG_PRINT_L1("Contribution of " << parsed_contribution.transferred << " received for service node " << pubkey);
@ -1260,7 +1260,7 @@ namespace service_nodes
std::vector<crypto::public_key> service_node_list::update_and_get_expired_nodes(const std::vector<cryptonote::transaction> &txs, uint64_t block_height)
{
std::vector<crypto::public_key> expired_nodes;
uint64_t const lock_blocks = staking_initial_num_lock_blocks(m_blockchain.nettype());
uint64_t const lock_blocks = staking_num_lock_blocks(m_blockchain.nettype());
// TODO(loki): This should really use the registration height instead of getting the block and expiring nodes.
// But there's something subtly off when using registration height causing syncing problems.
@ -1714,24 +1714,24 @@ namespace service_nodes
m_height = hardfork_9_from_height;
}
size_t service_node_info::total_num_locked_contributions() const
{
size_t result = 0;
for (service_node_info::contributor_t const &contributor : this->contributors)
result += contributor.locked_contributions.size();
return result;
}
bool convert_registration_args(cryptonote::network_type nettype,
std::vector<std::string> args,
const std::vector<std::string>& args,
std::vector<cryptonote::account_public_address>& addresses,
std::vector<uint64_t>& portions,
uint64_t& portions_for_operator,
bool& autostake,
boost::optional<std::string&> err_msg)
{
autostake = false;
if (!args.empty() && args[0] == "auto")
{
autostake = true;
args.erase(args.begin());
}
if (args.size() % 2 == 0 || args.size() < 3)
{
MERROR(tr("Usage: [auto] <operator cut> <address> <fraction> [<address> <fraction> [...]]]"));
MERROR(tr("Usage: <operator cut> <address> <fraction> [<address> <fraction> [...]]]"));
return false;
}
if ((args.size()-1)/ 2 > MAX_NUMBER_OF_CONTRIBUTORS)
@ -1815,14 +1815,13 @@ namespace service_nodes
std::vector<cryptonote::account_public_address> addresses;
std::vector<uint64_t> portions;
uint64_t operator_portions;
bool autostake;
if (!convert_registration_args(nettype, args, addresses, portions, operator_portions, autostake, err_msg))
if (!convert_registration_args(nettype, args, addresses, portions, operator_portions, err_msg))
{
MERROR(tr("Could not convert registration args"));
return false;
}
uint64_t exp_timestamp = time(nullptr) + (autostake ? STAKING_AUTHORIZATION_EXPIRATION_AUTOSTAKE : STAKING_AUTHORIZATION_EXPIRATION_WINDOW);
uint64_t exp_timestamp = time(nullptr) + STAKING_AUTHORIZATION_EXPIRATION_WINDOW;
crypto::hash hash;
bool hashed = cryptonote::get_registration_hash(addresses, operator_portions, portions, exp_timestamp, hash);

View File

@ -36,19 +36,6 @@
namespace service_nodes
{
constexpr size_t QUORUM_SIZE = 10;
constexpr size_t QUORUM_LIFETIME = (6 * deregister_vote::DEREGISTER_LIFETIME_BY_HEIGHT);
constexpr size_t MIN_VOTES_TO_KICK_SERVICE_NODE = 7;
constexpr size_t NTH_OF_THE_NETWORK_TO_TEST = 100;
constexpr size_t MIN_NODES_TO_TEST = 50;
constexpr size_t MAX_SWARM_SIZE = 10;
// We never create a new swarm unless there are SWARM_BUFFER extra nodes
// available in the queue.
constexpr size_t SWARM_BUFFER = 5;
// if a swarm has strictly less nodes than this, it is considered unhealthy
// and nearby swarms will mirror it's data. It will disappear, and is already considered gone.
constexpr size_t MIN_SWARM_SIZE = 5;
class quorum_cop;
struct quorum_state
@ -64,14 +51,8 @@ namespace service_nodes
using swarm_id_t = uint64_t;
// TODO(doyle): INF_STAKING(doyle): Review behaviour of existing nodes on the old system when we switch over.
struct service_node_info // registration information
{
// INF_STAKING(doyle): Now that we have locked key images, we should enforce
// a minimum staking amount. Currently contributors can contribute piece
// meal to a service node, they can trivially attack the network by staking
// 1 loki each time to bloat up the key images
struct contribution_t
{
uint8_t version = version_2_infinite_staking;
@ -134,9 +115,9 @@ namespace service_nodes
swarm_id_t swarm_id;
cryptonote::account_public_address operator_address;
bool is_fully_funded() const { return total_contributed >= staking_requirement; }
service_node_info() = default;
bool is_fully_funded() const { return total_contributed >= staking_requirement; }
size_t total_num_locked_contributions() const;
BEGIN_SERIALIZE()
VARINT_FIELD(version)
@ -185,9 +166,6 @@ namespace service_nodes
template<typename T>
void loki_shuffle(std::vector<T>& a, uint64_t seed);
static constexpr uint64_t QUEUE_SWARM_ID = 0;
static constexpr uint64_t KEY_IMAGE_AWAITING_UNLOCK_HEIGHT = 0;
class service_node_list
: public cryptonote::Blockchain::BlockAddedHook,
public cryptonote::Blockchain::BlockchainDetachedHook,
@ -196,7 +174,6 @@ namespace service_nodes
{
public:
service_node_list(cryptonote::Blockchain& blockchain);
~service_node_list();
void block_added(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs) override;
void blockchain_detached(uint64_t height) override;
void register_hooks(service_nodes::quorum_cop &quorum_cop);
@ -369,7 +346,7 @@ namespace service_nodes
};
bool reg_tx_extract_fields(const cryptonote::transaction& tx, std::vector<cryptonote::account_public_address>& addresses, uint64_t& portions_for_operator, std::vector<uint64_t>& portions, uint64_t& expiration_timestamp, crypto::public_key& service_node_key, crypto::signature& signature, crypto::public_key& tx_pub_key);
bool convert_registration_args(cryptonote::network_type nettype, std::vector<std::string> args, std::vector<cryptonote::account_public_address>& addresses, std::vector<uint64_t>& portions, uint64_t& portions_for_operator, bool& autostake, boost::optional<std::string&> err_msg);
bool convert_registration_args(cryptonote::network_type nettype, const std::vector<std::string>& args, std::vector<cryptonote::account_public_address>& addresses, std::vector<uint64_t>& portions, uint64_t& portions_for_operator, boost::optional<std::string&> err_msg);
bool make_registration_cmd(cryptonote::network_type nettype, const std::vector<std::string>& args, const crypto::public_key& service_node_pubkey,
const crypto::secret_key &service_node_key, std::string &cmd, bool make_friendly, boost::optional<std::string&> err_msg);

View File

@ -11,18 +11,18 @@ namespace service_nodes {
uint64_t get_staking_requirement(cryptonote::network_type m_nettype, uint64_t height)
{
if (m_nettype == cryptonote::TESTNET || m_nettype == cryptonote::FAKECHAIN)
return COIN * 100;
if (m_nettype == cryptonote::TESTNET || m_nettype == cryptonote::FAKECHAIN)
return COIN * 100;
uint64_t hardfork_height = m_nettype == cryptonote::MAINNET ? 101250 : 96210 /* stagenet */;
if (height < hardfork_height) height = hardfork_height;
uint64_t hardfork_height = m_nettype == cryptonote::MAINNET ? 101250 : 96210 /* stagenet */;
if (height < hardfork_height) height = hardfork_height;
uint64_t height_adjusted = height - hardfork_height;
uint64_t base = 10000 * COIN;
uint64_t variable = (35000.0 * COIN) / loki::exp2(height_adjusted/129600.0);
uint64_t linear_up = (uint64_t)(5 * COIN * height / 2592) + 8000 * COIN;
uint64_t flat = 15000 * COIN;
return std::max(base + variable, height < 3628800 ? linear_up : flat);
uint64_t height_adjusted = height - hardfork_height;
uint64_t base = 10000 * COIN;
uint64_t variable = (35000.0 * COIN) / loki::exp2(height_adjusted/129600.0);
uint64_t linear_up = (uint64_t)(5 * COIN * height / 2592) + 8000 * COIN;
uint64_t flat = 15000 * COIN;
return std::max(base + variable, height < 3628800 ? linear_up : flat);
}
uint64_t portions_to_amount(uint64_t portions, uint64_t staking_requirement)
@ -38,10 +38,11 @@ bool check_service_node_portions(uint8_t hf_version, const std::vector<uint64_t>
if (portions.size() > MAX_NUMBER_OF_CONTRIBUTORS) return false;
uint64_t reserved = 0;
for (auto i = 0u; i < portions.size(); ++i) {
const uint64_t min_portions = get_min_node_contribution(hf_version, STAKING_PORTIONS, reserved, i);
if (portions[i] < min_portions) return false;
reserved += portions[i];
for (auto i = 0u; i < portions.size(); ++i)
{
const uint64_t min_portions = get_min_node_contribution(hf_version, STAKING_PORTIONS, reserved, i);
if (portions[i] < min_portions) return false;
reserved += portions[i];
}
return reserved <= STAKING_PORTIONS;
@ -65,41 +66,35 @@ crypto::hash generate_request_stake_unlock_hash(uint32_t nonce)
uint64_t get_locked_key_image_unlock_height(cryptonote::network_type nettype, uint64_t node_register_height, uint64_t curr_height)
{
uint64_t blocks_to_lock = staking_initial_num_lock_blocks(nettype);
if (curr_height < node_register_height)
{
// Unexpected current_height less than node_register_height, developer error?
assert(curr_height >= node_register_height);
curr_height = node_register_height;
}
uint64_t delta_height = curr_height - node_register_height;
uint64_t remainder = delta_height % blocks_to_lock;
uint64_t result = curr_height + blocks_to_lock - remainder;
uint64_t blocks_to_lock = staking_num_lock_blocks(nettype);
uint64_t result = curr_height + (blocks_to_lock / 2);
return result;
}
static uint64_t get_min_node_contribution_pre_v11(uint64_t staking_requirement, uint64_t total_reserved)
{
return std::min(staking_requirement - total_reserved, staking_requirement / MAX_NUMBER_OF_CONTRIBUTORS);
return std::min(staking_requirement - total_reserved, staking_requirement / MAX_NUMBER_OF_CONTRIBUTORS);
}
uint64_t get_min_node_contribution(uint8_t version, uint64_t staking_requirement, uint64_t total_reserved, size_t contrib_count)
uint64_t get_min_node_contribution(uint8_t version, uint64_t staking_requirement, uint64_t total_reserved, size_t num_contributions)
{
if (version < cryptonote::network_version_11_swarms) {
return get_min_node_contribution_pre_v11(staking_requirement, total_reserved);
}
if (version < cryptonote::network_version_11_swarms)
return get_min_node_contribution_pre_v11(staking_requirement, total_reserved);
const uint64_t needed = staking_requirement - total_reserved;
const size_t vacant = MAX_NUMBER_OF_CONTRIBUTORS - contrib_count;
const uint64_t needed = staking_requirement - total_reserved;
const size_t max_num_of_contributions = MAX_NUMBER_OF_CONTRIBUTORS * MAX_KEY_IMAGES_PER_CONTRIBUTOR;
assert(max_num_of_contributions > num_contributions);
if (max_num_of_contributions <= num_contributions) return 0;
assert(contrib_count < MAX_NUMBER_OF_CONTRIBUTORS);
/// This function is not called when the node is full, but if
/// it does in the future, at least we don't cause undefined behaviour
if (vacant == 0) return 0;
const size_t num_contributions_remaining_avail = max_num_of_contributions - num_contributions;
return needed / num_contributions_remaining_avail;
}
return needed / vacant;
uint64_t get_min_node_contribution_in_portions(uint8_t version, uint64_t staking_requirement, uint64_t total_reserved, size_t num_contributions)
{
uint64_t atomic_amount = get_min_node_contribution(version, staking_requirement, total_reserved, num_contributions);
uint64_t result = get_portions_to_make_amount(staking_requirement, atomic_amount);
return result;
}
uint64_t get_portions_to_make_amount(uint64_t staking_requirement, uint64_t amount)

View File

@ -2,10 +2,28 @@
#include "crypto/crypto.h"
#include "cryptonote_config.h"
#include "service_node_deregister.h"
namespace service_nodes {
constexpr size_t QUORUM_SIZE = 10;
constexpr size_t QUORUM_LIFETIME = (6 * deregister_vote::DEREGISTER_LIFETIME_BY_HEIGHT);
constexpr size_t MIN_VOTES_TO_KICK_SERVICE_NODE = 7;
constexpr size_t NTH_OF_THE_NETWORK_TO_TEST = 100;
constexpr size_t MIN_NODES_TO_TEST = 50;
constexpr size_t MAX_SWARM_SIZE = 10;
// We never create a new swarm unless there are SWARM_BUFFER extra nodes
// available in the queue.
constexpr size_t SWARM_BUFFER = 5;
// if a swarm has strictly less nodes than this, it is considered unhealthy
// and nearby swarms will mirror it's data. It will disappear, and is already considered gone.
constexpr size_t MIN_SWARM_SIZE = 5;
constexpr int MAX_KEY_IMAGES_PER_CONTRIBUTOR = 1;
constexpr uint64_t QUEUE_SWARM_ID = 0;
constexpr uint64_t KEY_IMAGE_AWAITING_UNLOCK_HEIGHT = 0;
inline uint64_t staking_initial_num_lock_blocks(cryptonote::network_type nettype)
inline uint64_t staking_num_lock_blocks(cryptonote::network_type nettype)
{
switch(nettype)
{
@ -15,7 +33,8 @@ inline uint64_t staking_initial_num_lock_blocks(cryptonote::network_type nettype
}
}
uint64_t get_min_node_contribution(uint8_t version, uint64_t staking_requirement, uint64_t total_reserved, size_t contrib_count);
uint64_t get_min_node_contribution (uint8_t version, uint64_t staking_requirement, uint64_t total_reserved, size_t num_contributions);
uint64_t get_min_node_contribution_in_portions(uint8_t version, uint64_t staking_requirement, uint64_t total_reserved, size_t num_contributions);
uint64_t get_staking_requirement(cryptonote::network_type nettype, uint64_t height);

View File

@ -52,6 +52,56 @@
namespace daemonize {
namespace {
enum class input_line_result { yes, no, cancel, back, };
std::string input_line(std::string const &prompt)
{
std::cout << prompt << std::flush;
std::string result;
#ifdef HAVE_READLINE
rdln::suspend_readline pause_readline;
#endif
std::cin >> result;
return result;
}
input_line_result input_line_yes_no_back_cancel(char const *msg)
{
std::string prompt = std::string(msg);
prompt += " (Y/Yes/N/No/B/Back/C/Cancel): ";
std::string input = input_line(prompt);
if (command_line::is_yes(input)) return input_line_result::yes;
if (command_line::is_no(input)) return input_line_result::no;
if (command_line::is_back(input)) return input_line_result::back;
return input_line_result::cancel;
}
input_line_result input_line_yes_no_cancel(char const *msg)
{
std::string prompt = msg;
prompt += " (Y/Yes/N/No/C/Cancel): ";
std::string input = input_line(prompt);
if (command_line::is_yes(input)) return input_line_result::yes;
if (command_line::is_no(input)) return input_line_result::no;
return input_line_result::cancel;
}
input_line_result input_line_back_cancel_get_input(char const *msg, std::string &input)
{
std::string prompt = msg;
prompt += " (B/Back/C/Cancel): ";
input = input_line(prompt);
if (command_line::is_back(input)) return input_line_result::back;
if (command_line::is_cancel(input)) return input_line_result::cancel;
return input_line_result::yes;
}
void print_peer(std::string const & prefix, cryptonote::peer const & peer)
{
time_t now;
@ -2094,29 +2144,47 @@ static void print_service_node_list_state(cryptonote::network_type nettype, int
tools::msg_writer() << indent2 << "Total Reserved: " << cryptonote::print_money(entry.total_reserved);
}
// TODO(doyle): Fix up for infinite staking changes
// Print Expiry Info
// Print expiry information
{
uint64_t expiry_height = entry.registration_height + service_nodes::staking_initial_num_lock_blocks(nettype);
if (hard_fork_version >= cryptonote::network_version_10_bulletproofs)
expiry_height += STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS;
if (curr_height)
uint64_t const now = time(nullptr);
uint64_t expiry_height = 0;
if (hard_fork_version >= cryptonote::network_version_11_swarms)
{
uint64_t now = time(nullptr);
uint64_t delta_height = expiry_height - *curr_height;
uint64_t expiry_epoch_time = now + (delta_height * DIFFICULTY_TARGET_V2);
tools::msg_writer() << indent2 << "Registration Height/Expiry Height: " << entry.registration_height << "/" << expiry_height << " (in " << delta_height << " blocks)";
tools::msg_writer() << indent2 << "Expiry Date (Estimated UTC): " << get_date_time(expiry_epoch_time) << " (" << get_human_time_ago(expiry_epoch_time, now) << ")";
expiry_height = entry.requested_unlock_height;
}
else if (hard_fork_version >= cryptonote::network_version_10_bulletproofs)
{
expiry_height = entry.registration_height + service_nodes::staking_num_lock_blocks(nettype);
expiry_height += STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS;
}
else
{
tools::msg_writer() << indent2 << "Registration Height/Expiry Height: " << entry.registration_height << " / " << expiry_height << " (in ?? blocks) ";
tools::msg_writer() << indent2 << "Expiry Date (Estimated UTC): ?? (Could not get current blockchain height)";
expiry_height = entry.registration_height + service_nodes::staking_num_lock_blocks(nettype);
}
if (expiry_height == 0)
{
tools::msg_writer() << indent2 << "Registration Height/Expiry Height: Staking Infinitely (stake unlock not requested yet)";
}
else
{
if (curr_height)
{
uint64_t delta_height = expiry_height - *curr_height;
uint64_t expiry_epoch_time = now + (delta_height * DIFFICULTY_TARGET_V2);
tools::msg_writer() << indent2 << "Registration Height/Expiry Height: " << entry.registration_height << "/" << expiry_height << " (in " << delta_height << " blocks)";
tools::msg_writer() << indent2 << "Expiry Date (Estimated UTC): " << get_date_time(expiry_epoch_time) << " (" << get_human_time_ago(expiry_epoch_time, now) << ")";
}
else
{
tools::msg_writer() << indent2 << "Registration Height/Expiry Height: " << entry.registration_height << " / " << expiry_height << " (in ?? blocks) ";
tools::msg_writer() << indent2 << "Expiry Date (Estimated UTC): ?? (Could not get current blockchain height)";
}
}
}
// Print reward status
if (is_registered)
{
@ -2457,18 +2525,23 @@ bool t_rpc_command_executor::prepare_registration()
// Query the latest known block height and nettype
uint64_t block_height = 0;
int hf_version = cryptonote::network_version_9_service_nodes;
cryptonote::network_type nettype = cryptonote::UNDEFINED;
{
cryptonote::COMMAND_RPC_GET_INFO::request req;
cryptonote::COMMAND_RPC_GET_INFO::response res;
cryptonote::COMMAND_RPC_HARD_FORK_INFO::request hf_req;
cryptonote::COMMAND_RPC_HARD_FORK_INFO::response hf_res;
std::string const info_fail_message = "Could not get current blockchain info";
if (m_is_rpc)
{
if (!m_rpc_client->rpc_request(req, res, "/getinfo", info_fail_message.c_str()))
return true;
if (!m_rpc_client->rpc_request(hf_req, hf_res, "hard_fork_info", info_fail_message.c_str()))
return true;
if (res.mainnet) nettype = cryptonote::MAINNET;
else if (res.stagenet) nettype = cryptonote::STAGENET;
else if (res.testnet) nettype = cryptonote::TESTNET;
@ -2480,8 +2553,18 @@ bool t_rpc_command_executor::prepare_registration()
tools::fail_msg_writer() << make_error(info_fail_message, res.status);
return true;
}
epee::json_rpc::error error_resp;
if (!m_rpc_server->on_hard_fork_info(hf_req, hf_res, error_resp) || hf_res.status != CORE_RPC_STATUS_OK)
{
tools::fail_msg_writer() << make_error(info_fail_message, hf_res.status);
return true;
}
nettype = m_rpc_server->nettype();
}
hf_version = hf_res.version;
block_height = std::max(res.height, res.target_height);
}
@ -2531,272 +2614,437 @@ bool t_rpc_command_executor::prepare_registration()
}
}
#ifdef HAVE_READLINE
rdln::suspend_readline pause_readline;
#endif
size_t number_participants = 1;
uint64_t operating_cost_portions = STAKING_PORTIONS;
bool is_solo_stake = false;
std::vector<std::string> addresses;
std::vector<uint64_t> contributions;
const uint64_t staking_requirement =
std::max(service_nodes::get_staking_requirement(nettype, block_height),
service_nodes::get_staking_requirement(nettype, block_height + 30 * 24)); // allow 1 day
uint64_t portions_remaining = STAKING_PORTIONS;
uint64_t total_reserved_contributions = 0;
// anything less than DUST will be added to operator stake
const uint64_t DUST = MAX_NUMBER_OF_CONTRIBUTORS;
std::cout << "Current staking requirement: " << cryptonote::print_money(staking_requirement) << " " << cryptonote::get_unit() << std::endl;
std::string solo_stake;
std::cout << "Will the operator contribute the entire stake? (Y/Yes/N/No): ";
std::cin >> solo_stake;
if(command_line::is_yes(solo_stake))
{
is_solo_stake = true;
}
else if(command_line::is_no(solo_stake))
{
is_solo_stake = false;
}
else
{
std::cout << "Invalid answer. Aborted." << std::endl;
return true;
}
if(is_solo_stake)
enum struct register_step
{
contributions.push_back(STAKING_PORTIONS);
portions_remaining = 0;
total_reserved_contributions += get_actual_amount(staking_requirement, STAKING_PORTIONS);
}
else
ask_is_solo_stake = 0,
is_solo_stake__operator_address_to_reserve,
is_open_stake__get_operator_fee,
is_open_stake__do_you_want_to_reserve_other_contributors,
is_open_stake__how_many_more_contributors,
is_open_stake__operator_amount_to_reserve,
is_open_stake__operator_address_to_reserve,
is_open_stake__contributor_address_to_reserve,
is_open_stake__contributor_amount_to_reserve,
is_open_stake__summary_info,
final_summary,
cancelled_by_user,
};
struct prepare_registration_state
{
std::string operating_cost_string;
std::cout << "What percentage of the total staking reward would the operator like to reserve as an operator fee [0-100]%: ";
std::cin >> operating_cost_string;
register_step prev_step = register_step::ask_is_solo_stake;
bool is_solo_stake;
size_t num_participants = 1;
uint64_t operator_fee_portions = STAKING_PORTIONS;
uint64_t portions_remaining = STAKING_PORTIONS;
uint64_t total_reserved_contributions = 0;
std::vector<std::string> addresses;
std::vector<uint64_t> contributions;
};
bool res = service_nodes::get_portions_from_percent_str(operating_cost_string, operating_cost_portions);
prepare_registration_state state = {};
std::stack<prepare_registration_state> state_stack;
state_stack.push(state);
if (!res) {
std::cout << "Invalid value: " << operating_cost_string << ". Should be between [0-100]" << std::endl;
return true;
bool finished = false;
register_step step = register_step::ask_is_solo_stake;
for (input_line_result last_input_result = input_line_result::yes; !finished;)
{
if (last_input_result == input_line_result::back)
{
step = state.prev_step;
state_stack.pop();
state = state_stack.top();
std::cout << std::endl;
}
const uint64_t min_contribution_portions = std::min(portions_remaining, MIN_PORTIONS);
const uint64_t min_contribution = get_amount_to_make_portions(staking_requirement, min_contribution_portions);
std::cout << "Minimum amount that can be reserved: " << cryptonote::print_money(min_contribution) << " " << cryptonote::get_unit() << std::endl;
std::cout << "How much loki does the operator want to reserve in the stake? ";
std::string contribution_string;
std::cin >> contribution_string;
uint64_t operator_cut;
if(!cryptonote::parse_amount(operator_cut, contribution_string))
switch(step)
{
std::cout << "Invalid amount. Aborted." << std::endl;
return true;
}
uint64_t portions = service_nodes::get_portions_to_make_amount(staking_requirement, operator_cut);
if(portions < min_contribution_portions)
{
std::cout << "The operator needs to contribute at least 25% of the stake requirement (" << cryptonote::print_money(min_contribution) << " " << cryptonote::get_unit() << "). Aborted." << std::endl;
return true;
}
else if(portions > portions_remaining)
{
std::cout << "The operator contribution is higher than the staking requirement. Any excess contribution will be locked for the staking duration, but won't yield any additional reward." << std::endl;
portions = portions_remaining;
}
contributions.push_back(portions);
portions_remaining -= portions;
total_reserved_contributions += get_actual_amount(staking_requirement, portions);
}
if (!is_solo_stake)
{
std::string allow_multiple_contributors;
std::cout << "Do you want to reserve portions of the stake for other specific contributors? (Y/Yes/N/No): ";
std::cin >> allow_multiple_contributors;
if(command_line::is_yes(allow_multiple_contributors))
{
std::cout << "Number of additional contributors [1-" << (MAX_NUMBER_OF_CONTRIBUTORS - 1) << "]: ";
int additional_contributors;
if(!(std::cin >> additional_contributors) || additional_contributors < 1 || additional_contributors > (MAX_NUMBER_OF_CONTRIBUTORS - 1))
case register_step::ask_is_solo_stake:
{
std::cout << "Invalid value. Should be between [1-" << (MAX_NUMBER_OF_CONTRIBUTORS - 1) << "]" << std::endl;
return true;
last_input_result = input_line_yes_no_cancel("Will the operator contribute the entire stake?");
if(last_input_result == input_line_result::cancel)
{
step = register_step::cancelled_by_user;
continue;
}
state.is_solo_stake = (last_input_result == input_line_result::yes);
if (state.is_solo_stake)
{
std::cout << std::endl;
step = register_step::is_solo_stake__operator_address_to_reserve;
}
else
{
step = register_step::is_open_stake__get_operator_fee;
}
state_stack.push(state);
continue;
}
number_participants += static_cast<size_t>(additional_contributors);
}
}
for (size_t contributor_index = 0; contributor_index < number_participants; ++contributor_index)
{
const bool is_operator = (contributor_index == 0);
const std::string contributor_name = is_operator ? "the operator" : "contributor " + std::to_string(contributor_index);
if (!is_operator)
{
const uint64_t min_contribution_portions = std::min(portions_remaining, MIN_PORTIONS);
const uint64_t min_contribution = get_amount_to_make_portions(staking_requirement, min_contribution_portions);
const uint64_t amount_left = get_amount_to_make_portions(staking_requirement, portions_remaining);
std::cout << "The minimum amount possible to contribute is " << cryptonote::print_money(min_contribution) << " " << cryptonote::get_unit() << std::endl;
std::cout << "There is " << cryptonote::print_money(amount_left) << " " << cryptonote::get_unit() << " left to meet the staking requirement." << std::endl;
std::cout << "How much loki does " << contributor_name << " want to reserve in the stake? ";
uint64_t contribution_amount;
std::string contribution_string;
std::cin >> contribution_string;
if (!cryptonote::parse_amount(contribution_amount, contribution_string))
case register_step::is_solo_stake__operator_address_to_reserve:
{
std::cout << "Invalid amount. Aborted." << std::endl;
return true;
std::string address_str;
last_input_result = input_line_back_cancel_get_input("Enter the loki address for the solo staker", address_str);
if (last_input_result == input_line_result::back)
continue;
if (last_input_result == input_line_result::cancel)
{
step = register_step::cancelled_by_user;
continue;
}
state.addresses.push_back(address_str); // the addresses will be validated later down the line
state.contributions.push_back(STAKING_PORTIONS);
state.portions_remaining = 0;
state.total_reserved_contributions += get_actual_amount(staking_requirement, STAKING_PORTIONS);
state.prev_step = step;
step = register_step::final_summary;
state_stack.push(state);
continue;
}
uint64_t portions = service_nodes::get_portions_to_make_amount(staking_requirement, contribution_amount);
if (portions < min_contribution_portions)
case register_step::is_open_stake__get_operator_fee:
{
std::cout << "Invalid amount. Aborted." << std::endl;
return true;
std::string operator_fee_str;
last_input_result = input_line_back_cancel_get_input("What percentage of the total staking reward would the operator like to reserve as an operator fee [0-100]%", operator_fee_str);
if (last_input_result == input_line_result::back)
continue;
if (last_input_result == input_line_result::cancel)
{
step = register_step::cancelled_by_user;
continue;
}
if (!service_nodes::get_portions_from_percent_str(operator_fee_str, state.operator_fee_portions))
{
std::cout << "Invalid value: " << operator_fee_str << ". Should be between [0-100]" << std::endl;
continue;
}
step = register_step::is_open_stake__do_you_want_to_reserve_other_contributors;
state_stack.push(state);
continue;
}
if (portions > portions_remaining)
portions = portions_remaining;
contributions.push_back(portions);
portions_remaining -= portions;
total_reserved_contributions += get_actual_amount(staking_requirement, portions);
}
std::cout << "Enter the loki address for " << contributor_name << ": ";
std::string address_string;
// the addresses will be validated later down the line
if(!(std::cin >> address_string))
{
std::cout << "Invalid address. Aborted." << std::endl;
return true;
}
addresses.push_back(address_string);
}
assert(addresses.size() == contributions.size());
const uint64_t amount_left = staking_requirement - total_reserved_contributions;
if (!is_solo_stake)
{
std::cout << "Total staking contributions reserved: " << cryptonote::print_money(total_reserved_contributions) << " " << cryptonote::get_unit() << std::endl;
if (amount_left > DUST)
{
std::cout << "Your total reservations do not equal the staking requirement." << std::endl;
std::cout << "You will leave the remaining portion of " << cryptonote::print_money(amount_left) << " " << cryptonote::get_unit() << " open to contributions from anyone, and the Service Node will not activate until the full staking requirement is filled." << std::endl;
std::cout << "Is this ok? (Y/Yes/N/No): ";
std::string accept_pool_staking;
std::cin >> accept_pool_staking;
if(command_line::is_yes(accept_pool_staking))
case register_step::is_open_stake__do_you_want_to_reserve_other_contributors:
{
// All good
last_input_result = input_line_yes_no_back_cancel("Do you want to reserve portions of the stake for other specific contributors?");
if (last_input_result == input_line_result::back)
continue;
if (last_input_result == input_line_result::cancel)
{
step = register_step::cancelled_by_user;
continue;
}
state.prev_step = step;
if(last_input_result == input_line_result::yes)
{
step = register_step::is_open_stake__how_many_more_contributors;
}
else
{
std::cout << std::endl;
step = register_step::is_open_stake__operator_address_to_reserve;
}
state_stack.push(state);
continue;
}
else if(command_line::is_no(accept_pool_staking))
case register_step::is_open_stake__how_many_more_contributors:
{
std::cout << "Staking requirements not met. Aborted." << std::endl;
return true;
std::string prompt = "Number of additional contributors [1-" + std::to_string(MAX_NUMBER_OF_CONTRIBUTORS - 1) + "]";
std::string input;
last_input_result = input_line_back_cancel_get_input(prompt.c_str(), input);
if (last_input_result == input_line_result::back)
continue;
if (last_input_result == input_line_result::cancel)
{
step = register_step::cancelled_by_user;
continue;
}
long additional_contributors = strtol(input.c_str(), NULL, 10 /*base 10*/);
if(additional_contributors < 1 || additional_contributors > (MAX_NUMBER_OF_CONTRIBUTORS - 1))
{
std::cout << "Invalid value. Should be between [1-" << (MAX_NUMBER_OF_CONTRIBUTORS - 1) << "]" << std::endl;
continue;
}
std::cout << std::endl;
state.num_participants += static_cast<size_t>(additional_contributors);
state.prev_step = step;
step = register_step::is_open_stake__operator_address_to_reserve;
state_stack.push(state);
continue;
}
else
case register_step::is_open_stake__operator_address_to_reserve:
{
std::cout << "Invalid answer. Aborted." << std::endl;
std::string address_str;
last_input_result = input_line_back_cancel_get_input("Enter the loki address for the operator", address_str);
if (last_input_result == input_line_result::back)
continue;
if (last_input_result == input_line_result::cancel)
{
step = register_step::cancelled_by_user;
continue;
}
state.addresses.push_back(address_str); // the addresses will be validated later down the line
state.prev_step = step;
step = register_step::is_open_stake__operator_amount_to_reserve;
state_stack.push(state);
continue;
}
case register_step::is_open_stake__operator_amount_to_reserve:
{
uint64_t min_contribution_portions = service_nodes::get_min_node_contribution_in_portions(hf_version, staking_requirement, 0, 0);
const uint64_t min_contribution = get_amount_to_make_portions(staking_requirement, min_contribution_portions);
std::cout << "Minimum amount that can be reserved: " << cryptonote::print_money(min_contribution) << " " << cryptonote::get_unit() << std::endl;
std::string contribution_str;
last_input_result = input_line_back_cancel_get_input("How much loki does the operator want to reserve in the stake?", contribution_str);
if (last_input_result == input_line_result::back)
continue;
if (last_input_result == input_line_result::cancel)
{
step = register_step::cancelled_by_user;
continue;
}
uint64_t contribution;
if(!cryptonote::parse_amount(contribution, contribution_str))
{
std::cout << "Invalid amount." << std::endl;
continue;
}
uint64_t portions = service_nodes::get_portions_to_make_amount(staking_requirement, contribution);
if(portions < min_contribution_portions)
{
std::cout << "The operator needs to contribute at least 25% of the stake requirement (" << cryptonote::print_money(min_contribution) << " " << cryptonote::get_unit() << "). Aborted." << std::endl;
continue;
}
if(portions > state.portions_remaining)
{
std::cout << "The operator contribution is higher than the staking requirement. Any excess contribution will be locked for the staking duration, but won't yield any additional reward." << std::endl;
portions = state.portions_remaining;
}
state.contributions.push_back(portions);
state.portions_remaining -= portions;
state.total_reserved_contributions += get_actual_amount(staking_requirement, portions);
state.prev_step = step;
if (state.num_participants > 1)
{
step = register_step::is_open_stake__contributor_address_to_reserve;
}
else
{
step = register_step::is_open_stake__summary_info;
}
std::cout << std::endl;
state_stack.push(state);
continue;
}
case register_step::is_open_stake__contributor_address_to_reserve:
{
std::string const prompt = "Enter the loki address for contributor " + std::to_string(state.contributions.size() + 1);
std::string address_str;
last_input_result = input_line_back_cancel_get_input(prompt.c_str(), address_str);
if (last_input_result == input_line_result::back)
continue;
if (last_input_result == input_line_result::cancel)
{
step = register_step::cancelled_by_user;
continue;
}
// the addresses will be validated later down the line
state.addresses.push_back(address_str);
state.prev_step = step;
step = register_step::is_open_stake__contributor_amount_to_reserve;
state_stack.push(state);
continue;
}
case register_step::is_open_stake__contributor_amount_to_reserve:
{
const uint64_t amount_left = staking_requirement - state.total_reserved_contributions;
uint64_t min_contribution_portions = service_nodes::get_min_node_contribution_in_portions(hf_version, staking_requirement, state.total_reserved_contributions, state.contributions.size());
const uint64_t min_contribution = get_amount_to_make_portions(staking_requirement, min_contribution_portions);
std::cout << "The minimum amount possible to contribute is " << cryptonote::print_money(min_contribution) << " " << cryptonote::get_unit() << std::endl;
std::cout << "There is " << cryptonote::print_money(amount_left) << " " << cryptonote::get_unit() << " left to meet the staking requirement." << std::endl;
std::string contribution_str;
std::string const prompt = "How much loki does contributor " + std::to_string(state.contributions.size() + 1) + " want to reserve in the stake?";
last_input_result = input_line_back_cancel_get_input(prompt.c_str(), contribution_str);
if (last_input_result == input_line_result::back)
continue;
if (last_input_result == input_line_result::cancel)
{
step = register_step::cancelled_by_user;
continue;
}
uint64_t contribution;
if (!cryptonote::parse_amount(contribution, contribution_str))
{
std::cout << "Invalid amount." << std::endl;
continue;
}
uint64_t portions = service_nodes::get_portions_to_make_amount(staking_requirement, contribution);
if (portions < min_contribution_portions)
{
std::cout << "The amount is too small." << std::endl;
continue;
}
if (portions > state.portions_remaining)
portions = state.portions_remaining;
state.contributions.push_back(portions);
state.portions_remaining -= portions;
state.total_reserved_contributions += get_actual_amount(staking_requirement, portions);
state.prev_step = step;
if (state.contributions.size() == state.num_participants)
step = register_step::is_open_stake__summary_info;
else
step = register_step::is_open_stake__contributor_address_to_reserve;
std::cout << std::endl;
state_stack.push(state);
continue;
}
case register_step::is_open_stake__summary_info:
{
const uint64_t amount_left = staking_requirement - state.total_reserved_contributions;
std::cout << "Total staking contributions reserved: " << cryptonote::print_money(state.total_reserved_contributions) << " " << cryptonote::get_unit() << std::endl;
if (amount_left > DUST)
{
std::cout << "Your total reservations do not equal the staking requirement." << std::endl;
std::cout << "You will leave the remaining portion of " << cryptonote::print_money(amount_left) << " " << cryptonote::get_unit() << " open to contributions from anyone, and the Service Node will not activate until the full staking requirement is filled." << std::endl;
last_input_result = input_line_yes_no_back_cancel("Is this ok?\n");
if(last_input_result == input_line_result::no || last_input_result == input_line_result::cancel)
{
step = register_step::cancelled_by_user;
continue;
}
if(last_input_result == input_line_result::back)
continue;
state_stack.push(state);
state.prev_step = step;
}
step = register_step::final_summary;
continue;
}
case register_step::final_summary:
{
assert(state.addresses.size() == state.contributions.size());
const uint64_t amount_left = staking_requirement - state.total_reserved_contributions;
std::cout << "Summary:" << std::endl;
std::cout << "Operating costs as % of reward: " << (state.operator_fee_portions * 100.0 / STAKING_PORTIONS) << "%" << std::endl;
printf("%-16s%-9s%-19s%-s\n", "Contributor", "Address", "Contribution", "Contribution(%)");
printf("%-16s%-9s%-19s%-s\n", "___________", "_______", "____________", "_______________");
for (size_t i = 0; i < state.num_participants; ++i)
{
const std::string participant_name = (i==0) ? "Operator" : "Contributor " + std::to_string(i);
uint64_t amount = get_actual_amount(staking_requirement, state.contributions[i]);
if (amount_left <= DUST && i == 0)
amount += amount_left; // add dust to the operator.
printf("%-16s%-9s%-19s%-.9f\n", participant_name.c_str(), state.addresses[i].substr(0,6).c_str(), cryptonote::print_money(amount).c_str(), (double)state.contributions[i] * 100 / STAKING_PORTIONS);
}
if (amount_left > DUST)
{
printf("%-16s%-9s%-19s%-.2f\n", "(open)", "", cryptonote::print_money(amount_left).c_str(), amount_left * 100.0 / staking_requirement);
}
else if (amount_left > 0)
{
std::cout << "\nActual amounts may differ slightly from specification. This is due to\n" << std::endl;
std::cout << "limitations on the way fractions are represented internally.\n" << std::endl;
}
std::cout << "\nBecause the actual requirement will depend on the time that you register, the\n";
std::cout << "amounts shown here are used as a guide only, and the percentages will remain\n";
std::cout << "the same." << std::endl << std::endl;
last_input_result = input_line_yes_no_back_cancel("Do you confirm the information above is correct?");
if(last_input_result == input_line_result::no || last_input_result == input_line_result::cancel)
{
step = register_step::cancelled_by_user;
continue;
}
if(last_input_result == input_line_result::back)
continue;
finished = true;
continue;
}
case register_step::cancelled_by_user:
{
std::cout << "Cancel requested in prepare registration. Aborting." << std::endl;
return true;
}
}
}
bool autostaking = false;
std::cout << "Do you wish to enable automatic re-staking [Y/N]: ";
std::string autostake_str;
std::cin >> autostake_str;
if (command_line::is_yes(autostake_str))
{
autostaking = true;
}
else if (command_line::is_no(autostake_str))
{
autostaking = false;
}
else
{
std::cout << "Invalid answer. Aborted." << std::endl;
return true;
}
std::cout << "Summary:" << std::endl;
std::cout << "Operating costs as % of reward: " << (operating_cost_portions * 100.0 / STAKING_PORTIONS) << "%" << std::endl;
printf("%-16s%-9s%-19s%-s\n", "Contributor", "Address", "Contribution", "Contribution(%)");
printf("%-16s%-9s%-19s%-s\n", "___________", "_______", "____________", "_______________");
for (size_t i = 0; i < number_participants; ++i)
{
const std::string participant_name = (i==0) ? "Operator" : "Contributor " + std::to_string(i);
uint64_t amount = get_actual_amount(staking_requirement, contributions[i]);
if (amount_left <= DUST && i == 0)
amount += amount_left; // add dust to the operator.
printf("%-16s%-9s%-19s%-.9f\n", participant_name.c_str(), addresses[i].substr(0,6).c_str(), cryptonote::print_money(amount).c_str(), (double)contributions[i] * 100 / STAKING_PORTIONS);
}
if (amount_left > DUST)
{
printf("%-16s%-9s%-19s%-.2f\n", "(open)", "", cryptonote::print_money(amount_left).c_str(), amount_left * 100.0 / staking_requirement);
}
else if (amount_left > 0)
{
std::cout << "\nActual amounts may differ slightly from specification. This is due to\n" << std::endl;
std::cout << "limitations on the way fractions are represented internally.\n" << std::endl;
}
std::cout << "\nBecause the actual requirement will depend on the time that you register, the\n";
std::cout << "amounts shown here are used as a guide only, and the percentages will remain\n";
std::cout << "the same." << std::endl;
std::cout << "\nDo you confirm the information above is correct? (Y/Yes/N/No): ";
std::string confirm_string;
std::cin >> confirm_string;
if(command_line::is_yes(confirm_string))
{
// all good
}
else if(command_line::is_no(confirm_string))
{
std::cout << "Aborted by user." << std::endl;
return true;
}
else
{
std::cout << "Invalid answer. Aborted." << std::endl;
return true;
}
// [auto] <operator cut> <address> <fraction> [<address> <fraction> [...]]]
// <operator cut> <address> <fraction> [<address> <fraction> [...]]]
std::vector<std::string> args;
if (autostaking)
args.push_back("auto");
args.push_back(std::to_string(operating_cost_portions));
for (size_t i = 0; i < number_participants; ++i)
args.push_back(std::to_string(state.operator_fee_portions));
for (size_t i = 0; i < state.num_participants; ++i)
{
args.push_back(addresses[i]);
args.push_back(std::to_string(contributions[i]));
args.push_back(state.addresses[i]);
args.push_back(std::to_string(state.contributions[i]));
}
for (size_t i = 0; i < addresses.size(); i++)
for (size_t i = 0; i < state.addresses.size(); i++)
{
for (size_t j = 0; j < i; j++)
{
if (addresses[i] == addresses[j])
if (state.addresses[i] == state.addresses[j])
{
std::cout << "Must not provide the same address twice" << std::endl;
return true;

View File

@ -1020,6 +1020,33 @@ namespace cryptonote
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
//
// Loki
//
bool core_rpc_server::on_get_output_blacklist_bin(const COMMAND_RPC_GET_OUTPUT_BLACKLIST::request& req, COMMAND_RPC_GET_OUTPUT_BLACKLIST::response& res, const connection_context *ctx)
{
PERF_TIMER(on_get_output_blacklist_bin);
bool r;
if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_OUTPUT_BLACKLIST>(invoke_http_mode::BIN, "/get_output_blacklist.bin", req, res, r))
return r;
res.status = "Failed";
try
{
m_core.get_output_blacklist(res.blacklist);
}
catch (const std::exception &e)
{
res.status = "Failed to get output blacklist";
return false;
}
res.status = CORE_RPC_STATUS_OK;
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_getblockcount(const COMMAND_RPC_GETBLOCKCOUNT::request& req, COMMAND_RPC_GETBLOCKCOUNT::response& res, const connection_context *ctx)
{
PERF_TIMER(on_getblockcount);
@ -2444,10 +2471,6 @@ namespace cryptonote
std::vector<std::string> args;
if (req.autostake) {
args.push_back("auto");
}
uint64_t staking_requirement = service_nodes::get_staking_requirement(m_core.get_nettype(), m_core.get_current_blockchain_height());
{

View File

@ -118,6 +118,7 @@ namespace cryptonote
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_BIN2("/get_output_distribution.bin", on_get_output_distribution_bin, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION)
MAP_URI_AUTO_BIN2("/get_output_blacklist.bin", on_get_output_blacklist_bin, COMMAND_RPC_GET_OUTPUT_BLACKLIST)
MAP_URI_AUTO_JON2_IF("/pop_blocks", on_pop_blocks, COMMAND_RPC_POP_BLOCKS, !m_restricted)
BEGIN_JSON_RPC_MAP("/json_rpc")
MAP_JON_RPC("get_block_count", on_getblockcount, COMMAND_RPC_GETBLOCKCOUNT)
@ -206,7 +207,12 @@ namespace cryptonote
bool on_update(const COMMAND_RPC_UPDATE::request& req, COMMAND_RPC_UPDATE::response& res, const connection_context *ctx = NULL);
bool on_get_output_distribution_bin(const COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request& req, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response& res, const connection_context *ctx = NULL);
bool on_pop_blocks(const COMMAND_RPC_POP_BLOCKS::request& req, COMMAND_RPC_POP_BLOCKS::response& res, const connection_context *ctx = NULL);
//
// Loki
//
bool on_get_output_blacklist_bin(const COMMAND_RPC_GET_OUTPUT_BLACKLIST::request& req, COMMAND_RPC_GET_OUTPUT_BLACKLIST::response& res, const connection_context *ctx = NULL);
//json_rpc
bool on_getblockcount(const COMMAND_RPC_GETBLOCKCOUNT::request& req, COMMAND_RPC_GETBLOCKCOUNT::response& res, const connection_context *ctx = NULL);
bool on_getblockhash(const COMMAND_RPC_GETBLOCKHASH::request& req, COMMAND_RPC_GETBLOCKHASH::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);

View File

@ -2507,12 +2507,10 @@ namespace cryptonote
struct request
{
bool autostake;
std::string operator_cut;
std::vector<contribution_t> contributions;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(autostake)
KV_SERIALIZE(operator_cut)
KV_SERIALIZE(contributions)
END_KV_SERIALIZE_MAP()
@ -2679,4 +2677,21 @@ namespace cryptonote
END_KV_SERIALIZE_MAP()
};
};
struct COMMAND_RPC_GET_OUTPUT_BLACKLIST
{
struct request { BEGIN_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP() };
struct response
{
std::vector<uint64_t> blacklist;
std::string status;
bool untrusted;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(blacklist)
KV_SERIALIZE(status)
KV_SERIALIZE(untrusted)
END_KV_SERIALIZE_MAP()
};
};
}

File diff suppressed because it is too large Load Diff

View File

@ -145,7 +145,6 @@ namespace cryptonote
bool set_ignore_fractional_outputs(const std::vector<std::string> &args = std::vector<std::string>());
bool set_track_uses(const std::vector<std::string> &args = std::vector<std::string>());
bool set_device_name(const std::vector<std::string> &args = std::vector<std::string>());
bool set_fork_on_autostake(const std::vector<std::string> &args = std::vector<std::string>());
bool help(const std::vector<std::string> &args = std::vector<std::string>());
bool start_mining(const std::vector<std::string> &args);
bool stop_mining(const std::vector<std::string> &args);
@ -163,7 +162,12 @@ namespace cryptonote
bool stake(const std::vector<std::string> &args_);
bool register_service_node(const std::vector<std::string> &args_);
bool request_stake_unlock(const std::vector<std::string> &args_);
bool print_locked_stakes(const std::vector<std::string> &args_);
bool print_locked_stakes_main(const std::vector<std::string> &args_, bool print_result);
bool locked_sweep_all(const std::vector<std::string> &args);
enum class sweep_type_t { stake, register_stake, all_or_below, single };
bool sweep_main_internal(sweep_type_t sweep_type, std::vector<tools::wallet2::pending_tx> &ptx_vector, cryptonote::address_parse_info const &dest);
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);
@ -245,8 +249,7 @@ namespace cryptonote
bool version(const std::vector<std::string>& args);
bool cold_sign_tx(const std::vector<tools::wallet2::pending_tx>& ptx_vector, tools::wallet2::signed_tx_set &exported_txs, std::vector<cryptonote::address_parse_info> &dsts_info, std::function<bool(const tools::wallet2::signed_tx_set &)> accept_func);
bool register_service_node_main(const std::vector<std::string>& service_node_key_as_str, uint64_t expiration_timestamp, const cryptonote::account_public_address& address, uint32_t priority, const std::vector<uint64_t>& portions, const std::vector<uint8_t>& extra, std::set<uint32_t>& subaddr_indices, bool autostake);
bool stake_main(const crypto::public_key& service_node_key, const cryptonote::address_parse_info& parse_info, uint32_t priority, std::set<uint32_t>& subaddr_indices, uint64_t amount, double amount_fraction, bool autostake);
bool register_service_node_main(const std::vector<std::string>& service_node_key_as_str, uint64_t expiration_timestamp, const cryptonote::account_public_address& address, uint32_t priority, const std::vector<uint64_t>& portions, const std::vector<uint8_t>& extra, std::set<uint32_t>& subaddr_indices);
uint64_t get_daemon_blockchain_height(std::string& err);
bool try_connect_to_daemon(bool silent = false, uint32_t* version = nullptr);
@ -396,6 +399,7 @@ namespace cryptonote
uint64_t m_restore_height; // optional
bool m_do_not_relay;
bool m_use_english_language_names;
bool m_has_locked_key_images;
epee::console_handlers_binder m_cmd_binder;

View File

@ -2370,12 +2370,10 @@ PendingTransaction* WalletImpl::stakePending(const std::string& sn_key_str, cons
/// Note(maxim): need to be careful to call `WalletImpl::disposeTransaction` when it is no longer needed
PendingTransactionImpl * transaction = new PendingTransactionImpl(*this);
transaction->m_pending_tx = m_wallet->create_stake_tx(sn_key, addr_info, amount);
// TODO: provide a more helpful error in this case
if (transaction->m_pending_tx.empty())
wallet2::stake_result stake_result = m_wallet->create_stake_tx(transaction->m_pending_tx, sn_key, addr_info, amount);
if (stake_result != wallet2::stake_result::success)
{
error_msg = "Failed to create a stake transaction";
error_msg = "Failed to create a stake transaction: " + stake_result.msg;
return nullptr;
}

View File

@ -929,7 +929,6 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended):
m_ignore_fractional_outputs(true),
m_track_uses(false),
m_is_initialized(false),
m_fork_on_autostake(true),
m_kdf_rounds(kdf_rounds),
is_old_file_format(false),
m_watch_only(false),
@ -3138,6 +3137,50 @@ bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector<uint64_t>
return true;
}
//----------------------------------------------------------------------------------------------------
bool wallet2::get_output_blacklist(std::vector<uint64_t> &blacklist)
{
uint32_t rpc_version;
boost::optional<std::string> result = m_node_rpc_proxy.get_rpc_version(rpc_version);
if (result)
{
// empty string -> not connection
THROW_WALLET_EXCEPTION_IF(result->empty(), tools::error::no_connection_to_daemon, "getversion");
THROW_WALLET_EXCEPTION_IF(*result == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, "getversion");
if (*result != CORE_RPC_STATUS_OK)
{
MDEBUG("Cannot determine daemon RPC version, not requesting output blacklist");
return false;
}
}
else
{
if (rpc_version >= MAKE_CORE_RPC_VERSION(2, 3))
{
MDEBUG("Daemon is recent enough, not requesting output blacklist");
}
else
{
MDEBUG("Daemon is too old, not requesting output blacklist");
return false;
}
}
cryptonote::COMMAND_RPC_GET_OUTPUT_BLACKLIST::request req = {};
cryptonote::COMMAND_RPC_GET_OUTPUT_BLACKLIST::response res = {};
m_daemon_rpc_mutex.lock();
bool r = net_utils::invoke_http_bin("/get_output_blacklist.bin", req, res, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
if (!r)
{
MWARNING("Failed to request output blacklist: no connection to daemon");
return false;
}
blacklist = std::move(res.blacklist);
return true;
}
//----------------------------------------------------------------------------------------------------
void wallet2::detach_blockchain(uint64_t height)
{
LOG_PRINT_L0("Detaching blockchain on height " << height);
@ -5556,14 +5599,20 @@ bool wallet2::is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height,
return true;
}
cryptonote::account_public_address const primary_address = get_address();
for (cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry const &entry : service_nodes_states)
{
for (cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::contributor const &contributor : entry.contributors)
{
address_parse_info address_info = {};
cryptonote::get_account_address_from_str(address_info, nettype(), contributor.address);
if (!contains_address(address_info.address))
break;
if (!cryptonote::get_account_address_from_str(address_info, nettype(), contributor.address))
{
MERROR("Failed to parse string representation of address: " << contributor.address);
continue;
}
if (primary_address != address_info.address)
continue;
for (cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::contribution const &contribution : contributor.locked_contributions)
{
@ -6035,7 +6084,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin
if (sd.v3_use_bulletproofs)
{
rct_config.range_proof_type = rct::RangeProofPaddedBulletproof;
rct_config.bp_version = use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1;
rct_config.bp_version = use_fork_rules(HF_VERSION_SMALLER_BP, 2) ? 2 : 1;
}
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
@ -6512,7 +6561,7 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto
if (sd.v3_use_bulletproofs)
{
rct_config.range_proof_type = rct::RangeProofPaddedBulletproof;
rct_config.bp_version = use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1;
rct_config.bp_version = use_fork_rules(HF_VERSION_SMALLER_BP, 2) ? 2 : 1;
}
loki_construct_tx_params tx_params = {};
@ -7013,24 +7062,31 @@ bool wallet2::is_output_blackballed(const std::pair<uint64_t, uint64_t> &output)
catch (const std::exception &e) { return false; }
}
stake_check_result wallet2::check_stake_allowed(const crypto::public_key& sn_key, const cryptonote::address_parse_info& addr_info, uint64_t& amount, double fraction) {
wallet2::stake_result wallet2::check_stake_allowed(const crypto::public_key& sn_key, const cryptonote::address_parse_info& addr_info, uint64_t& amount, double fraction)
{
wallet2::stake_result result = {};
result.msg.reserve(128);
if (addr_info.has_payment_id)
{
MERROR(tr("Do not use payment ids for staking."));
return stake_check_result::not_allowed;
}
if (!this->contains_address(addr_info.address))
{
MERROR(tr("The specified address is not owned by this wallet."));
return stake_check_result::not_allowed;
result.status = stake_result_status::payment_id_disallowed;
result.msg = tr("Payment IDs cannot be used in a staking transaction");
return result;
}
if (addr_info.is_subaddress)
{
MERROR(tr("Service nodes do not support subaddresses."));
return stake_check_result::not_allowed;
result.status = stake_result_status::subaddress_disallowed;
result.msg = tr("Subaddresses cannot be used in a staking transaction");
return result;
}
cryptonote::account_public_address const primary_address = get_address();
if (primary_address != addr_info.address)
{
result.status = stake_result_status::address_must_be_primary;
result.msg = tr("The specified address must be owned by this wallet and be the primary address of the wallet");
return result;
}
/// check that the service node is registered
@ -7038,39 +7094,37 @@ stake_check_result wallet2::check_stake_allowed(const crypto::public_key& sn_key
const auto& response = this->get_service_nodes({ epee::string_tools::pod_to_hex(sn_key) }, failed);
if (failed)
{
LOG_ERROR(*failed);
return stake_check_result::try_later;
result.status = stake_result_status::service_node_list_query_failed;
result.msg.reserve(failed->size() + 128);
result.msg = tr("Failed to query daemon for service node list");
result.msg += *failed;
}
if (response.size() != 1)
{
MERROR(tr("Could not find service node in service node list, please make sure it is registered first."));
return stake_check_result::try_later;
result.status = stake_result_status::service_node_not_registered;
result.msg = tr("Could not find service node in service node list, please make sure it is registered first.");
return result;
}
const auto& snode_info = response.front();
if (amount == 0)
amount = snode_info.staking_requirement * fraction;
const bool full = snode_info.contributors.size() >= MAX_NUMBER_OF_CONTRIBUTORS;
/// maximum to contribute (unless we have some amount reserved for us)
uint64_t max_contrib_total = snode_info.staking_requirement - snode_info.total_reserved;
uint64_t expected_to_contrib = 0;
const boost::optional<uint8_t> res = m_node_rpc_proxy.get_network_version();
/// Use stricter rules when not sure what version we are on
uint8_t hf_version = cryptonote::network_version_9_service_nodes;
if (res) {
hf_version = *res;
} else {
MERROR(tr("Could not obtain the current network version, defaulting to v9"));
if (!res)
{
result.status = stake_result_status::network_version_query_failed;
result.msg = tr("Could not query the current network version, try later");
return result;
}
uint64_t min_contrib_total = service_nodes::get_min_node_contribution(hf_version, snode_info.staking_requirement, snode_info.total_reserved, snode_info.contributors.size());
const auto& snode_info = response.front();
if (amount == 0) amount = snode_info.staking_requirement * fraction;
size_t total_num_locked_contributions = 0;
for (COMMAND_RPC_GET_SERVICE_NODES::response::contributor const &contributor : snode_info.contributors)
total_num_locked_contributions += contributor.locked_contributions.size();
uint8_t const hf_version = *res;
uint64_t max_contrib_total = snode_info.staking_requirement - snode_info.total_reserved;
uint64_t min_contrib_total = service_nodes::get_min_node_contribution(hf_version, snode_info.staking_requirement, snode_info.total_reserved, total_num_locked_contributions);
bool is_preexisting_contributor = false;
for (const auto& contributor : snode_info.contributors)
@ -7081,75 +7135,87 @@ stake_check_result wallet2::check_stake_allowed(const crypto::public_key& sn_key
if (info.address == addr_info.address)
{
/// reserved for us, so we can contribute some more
max_contrib_total += contributor.reserved - contributor.amount;
expected_to_contrib += contributor.reserved - contributor.amount;
/// in this case we can contribute as little as we want
min_contrib_total = 0;
uint64_t const reserved_amount_not_contributed_yet = contributor.reserved - contributor.amount;
max_contrib_total += reserved_amount_not_contributed_yet;
is_preexisting_contributor = true;
min_contrib_total = std::max(min_contrib_total, reserved_amount_not_contributed_yet);
if (hf_version <= cryptonote::network_version_10_bulletproofs)
min_contrib_total = 0; // Allowed to contribute incremental amounts, post v10, we lock key images so each user has a limit on number of contributions
break;
}
}
if (max_contrib_total == 0)
{
MERROR(tr("You may not contribute any more loki to this service node"));
return stake_check_result::not_allowed;
result.status = stake_result_status::service_node_contribution_maxed;
result.msg = tr("The service node cannot receive any more Loki from this wallet");
return result;
}
/// a. Check if there is room for us
const bool full = snode_info.contributors.size() >= MAX_NUMBER_OF_CONTRIBUTORS;
if (full && !is_preexisting_contributor)
{
MERROR(tr("This service node already has the maximum number of participants, and the specified address is not one of them."));
return stake_check_result::not_allowed;
result.status = stake_result_status::service_node_contributors_maxed;
result.msg = tr("The service node already has the maximum number of participants and this wallet is not one of them");
return result;
}
/// b. Check if the amount is too small
if (amount < min_contrib_total)
{
const uint64_t DUST = MAX_NUMBER_OF_CONTRIBUTORS;
if (min_contrib_total - amount <= DUST) {
amount = min_contrib_total;
MINFO(tr("Seeing as this is insufficient by dust amounts, amount was increased automatically to ") << print_money(min_contrib_total));
} else {
MERROR(tr("You must contribute at least ") << print_money(min_contrib_total) << tr(" loki to become a contributor for this service node."));
return stake_check_result::try_later;
}
const uint64_t DUST = MAX_NUMBER_OF_CONTRIBUTORS;
if (min_contrib_total - amount <= DUST)
{
amount = min_contrib_total;
result.msg += tr("Seeing as this is insufficient by dust amounts, amount was increased automatically to ");
result.msg += print_money(min_contrib_total);
result.msg += "\n";
}
else
{
result.status = stake_result_status::service_node_insufficient_contribution;
result.msg.reserve(128);
result.msg = tr("You must contribute at least ");
result.msg += print_money(min_contrib_total);
result.msg += tr(" loki to become a contributor for this service node.");
return result;
}
}
/// c. Check if the amount is too big
if (amount > max_contrib_total)
{
MINFO(tr("You may only contribute up to ") << print_money(max_contrib_total) << tr(" more loki to this service node."));
MINFO(tr("Reducing your stake from ") << print_money(amount) << tr(" to ") << print_money(max_contrib_total));
result.msg += tr("You may only contribute up to ");
result.msg += print_money(max_contrib_total);
result.msg += tr(" more loki to this service node. ");
result.msg += tr("Reducing your stake from ");
result.msg += print_money(amount);
result.msg += tr(" to ");
result.msg += print_money(max_contrib_total);
result.msg += tr("\n");
amount = max_contrib_total;
}
/// Issue a warning if there is more loki reserved from this contributor
if (amount < expected_to_contrib)
{
MWARNING(tr("Warning: You must contribute ") << print_money(expected_to_contrib)
<< tr(" loki to meet your registration requirements for this service node"));
}
return stake_check_result::allowed;
result.status = stake_result_status::success;
return result;
}
std::vector<wallet2::pending_tx> wallet2::create_stake_tx(const crypto::public_key& service_node_key, const cryptonote::address_parse_info& addr_info, uint64_t amount)
wallet2::stake_result wallet2::create_stake_tx(std::vector<pending_tx> &ptx, const crypto::public_key& service_node_key, const cryptonote::address_parse_info& addr_info, uint64_t amount, double amount_fraction, uint32_t priority, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices)
{
wallet2::stake_result result = {};
ptx.clear();
try
{
/// check stake parameters (this might adjust the amount)
if (check_stake_allowed(service_node_key, addr_info, amount) != stake_check_result::allowed)
{
LOG_ERROR("Invalid stake parameters");
return {};
}
result = check_stake_allowed(service_node_key, addr_info, amount, amount_fraction);
if (result.status != stake_result_status::success)
return result;
}
catch (const std::exception& e)
{
MERROR(e.what());
return {};
result.status = stake_result_status::exception_thrown;
result.msg = tr("Exception thrown, staking process could not be completed");
result.msg += e.what();
return result;
}
const cryptonote::account_public_address& address = addr_info.address;
@ -7165,35 +7231,46 @@ std::vector<wallet2::pending_tx> wallet2::create_stake_tx(const crypto::public_k
de.amount = amount;
dsts.push_back(de);
const uint64_t staking_requirement_lock_blocks = service_nodes::staking_initial_num_lock_blocks(m_nettype);
const uint64_t locked_blocks = staking_requirement_lock_blocks + STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS;
std::string err, err2;
const uint64_t bc_height = std::max(get_daemon_blockchain_height(err),
get_daemon_blockchain_target_height(err2));
get_daemon_blockchain_target_height(err2));
if (!err.empty() || !err2.empty())
{
LOG_ERROR("unable to get network blockchain height from daemon: " << (err.empty() ? err2 : err));
return {};
result.msg = tr("Could not query the current network block height, try later: ");
result.msg += (err.empty() ? err2 : err);
result.status = stake_result_status::network_height_query_failed;
return result;
}
const uint64_t unlock_at_block = bc_height + locked_blocks;
const uint32_t priority = adjust_priority(0);
const uint64_t staking_requirement_lock_blocks = service_nodes::staking_num_lock_blocks(m_nettype);
const uint64_t locked_blocks = staking_requirement_lock_blocks + STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS;
uint64_t unlock_at_block = bc_height + locked_blocks;
if (use_fork_rules(cryptonote::network_version_11_swarms, 1))
unlock_at_block = 0; // Infinite staking, no time lock
/// Default values
const uint32_t m_current_subaddress_account = 0;
std::set<uint32_t> subaddr_indices;
try {
auto ptx_vector = create_transactions_2(dsts, CRYPTONOTE_DEFAULT_TX_MIXIN, unlock_at_block, priority, extra, m_current_subaddress_account, subaddr_indices, true);
if (ptx_vector.size() == 1) { return ptx_vector; }
} catch (const std::exception& e) {
LOG_ERROR("Exception raised on creating tx: " << e.what());
try
{
priority = adjust_priority(priority);
ptx = create_transactions_2(dsts, CRYPTONOTE_DEFAULT_TX_MIXIN, unlock_at_block, priority, extra, subaddr_account, subaddr_indices, true);
}
catch (const std::exception& e)
{
result.msg = tr("Exception thrown on create tx: ");
result.msg += e.what();
result.status = stake_result_status::network_height_query_failed;
return result;
}
return {};
if (ptx.size() == 1) result.status = stake_result_status::success;
else
{
result.status = stake_result_status::too_many_transactions_constructed;
result.msg = tr("Constructed too many transations, please sweep_all first");
ptx.clear();
}
return result;
}
bool wallet2::lock_keys_file()
@ -7374,6 +7451,15 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
has_rct = true;
max_rct_index = std::max(max_rct_index, m_transfers[idx].m_global_output_index);
}
// TODO(doyle): Write the error message
std::vector<uint64_t> output_blacklist;
if (bool get_output_blacklist_failed = !get_output_blacklist(output_blacklist))
{
THROW_WALLET_EXCEPTION_IF(get_output_blacklist_failed, error::get_output_distribution, "Couldn't retrive list of outputs that are to be exlcuded from selection");
}
std::sort(output_blacklist.begin(), output_blacklist.end());
const bool has_rct_distribution = has_rct && get_rct_distribution(rct_start_height, rct_offsets);
if (has_rct_distribution)
{
@ -7514,7 +7600,26 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
if (n_rct == 0)
return rct_offsets[block_offset] ? rct_offsets[block_offset] - 1 : 0;
MDEBUG("Picking 1/" << n_rct << " in " << (last_block_offset - first_block_offset + 1) << " blocks centered around " << block_offset + rct_start_height);
return first_rct + crypto::rand<uint64_t>() % n_rct;
uint64_t pick = first_rct + crypto::rand<uint64_t>() % n_rct;
{
double percent_of_outputs_blacklisted = output_blacklist.size() / (double)n_rct;
if (static_cast<int>(percent_of_outputs_blacklisted + 1) > 5)
MWARNING("More than 5 percent of available outputs are blacklisted, please notify the Loki developers");
}
for (;;)
{
if (std::binary_search(output_blacklist.begin(), output_blacklist.end(), pick))
{
pick = first_rct + crypto::rand<uint64_t>() % n_rct;
}
else
{
return pick;
}
}
};
size_t num_selected_transfers = 0;
@ -9093,7 +9198,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);
const rct::RCTConfig rct_config {
bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean,
bulletproof ? (use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0
bulletproof ? (use_fork_rules(HF_VERSION_SMALLER_BP, 2) ? 2 : 1) : 0
};
const uint64_t base_fee = get_base_fee();
@ -9668,7 +9773,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);
const rct::RCTConfig rct_config {
bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean,
bulletproof ? (use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0,
bulletproof ? (use_fork_rules(HF_VERSION_SMALLER_BP, 2) ? 2 : 1) : 0,
};
const uint64_t base_fee = get_base_fee();
const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm());
@ -12660,6 +12765,12 @@ bool wallet2::contains_address(const cryptonote::account_public_address& address
return false;
}
//----------------------------------------------------------------------------------------------------
bool wallet2::contains_key_image(const crypto::key_image& key_image) const {
const auto &key_image_it = m_key_images.find(key_image);
bool result = (key_image_it != m_key_images.end());
return result;
}
//----------------------------------------------------------------------------------------------------
bool wallet2::generate_signature_for_request_stake_unlock(crypto::key_image const &key_image, crypto::signature &signature, uint32_t &nonce) const
{
const auto &key_image_it = m_key_images.find(key_image);

View File

@ -776,6 +776,7 @@ namespace tools
void set_subaddress_lookahead(size_t major, size_t minor);
std::pair<size_t, size_t> get_subaddress_lookahead() const { return {m_subaddress_lookahead_major, m_subaddress_lookahead_minor}; }
bool contains_address(const cryptonote::account_public_address& address) const;
bool contains_key_image(const crypto::key_image& key_image) const;
bool generate_signature_for_request_stake_unlock(crypto::key_image const &key_image, crypto::signature &signature, uint32_t &nonce) const;
/*!
* \brief Tells if the wallet file is deprecated.
@ -859,7 +860,11 @@ namespace tools
void get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wallet2::unconfirmed_transfer_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
void get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::pool_payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const;
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry> get_service_nodes(std::vector<std::string> const &pubkeys, boost::optional<std::string> &failed) { return m_node_rpc_proxy.get_service_nodes(pubkeys, failed); }
// NOTE(loki): get_all_service_node caches the result, get_service_nodes doesn't
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry> get_all_service_nodes(boost::optional<std::string> &failed) const { return m_node_rpc_proxy.get_all_service_nodes(failed); }
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODES::response::entry> get_service_nodes (std::vector<std::string> const &pubkeys, boost::optional<std::string> &failed) const { return m_node_rpc_proxy.get_service_nodes(pubkeys, failed); }
std::vector<cryptonote::COMMAND_RPC_GET_SERVICE_NODE_BLACKLISTED_KEY_IMAGES::entry> get_service_node_blacklisted_key_images(boost::optional<std::string> &failed) const { return m_node_rpc_proxy.get_service_node_blacklisted_key_images(failed); }
uint64_t get_blockchain_current_height() const { return m_light_wallet_blockchain_height ? m_light_wallet_blockchain_height : m_blockchain.size(); }
void rescan_spent();
void rescan_blockchain(bool hard, bool refresh = true);
@ -1047,8 +1052,6 @@ namespace tools
void device_name(const std::string & device_name) { m_device_name = device_name; }
const std::string & device_derivation_path() const { return m_device_derivation_path; }
void device_derivation_path(const std::string &device_derivation_path) { m_device_derivation_path = device_derivation_path; }
bool fork_on_autostake() const { return m_fork_on_autostake; }
void fork_on_autostake(bool value) { m_fork_on_autostake = value; }
bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const;
void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys);
@ -1278,11 +1281,34 @@ namespace tools
bool unblackball_output(const std::pair<uint64_t, uint64_t> &output);
bool is_output_blackballed(const std::pair<uint64_t, uint64_t> &output) const;
/// Checks arguments for staking. Modifies the amount to maximum possible if too large,
/// but rejects if insufficient. `fraction` is only used to determine the amount if specified zero.
stake_check_result check_stake_allowed(const crypto::public_key& sn_key, const cryptonote::address_parse_info& addr_info, uint64_t& amount, double fraction = 0);
enum struct stake_result_status
{
success,
exception_thrown,
payment_id_disallowed,
subaddress_disallowed,
address_must_be_primary,
service_node_list_query_failed,
service_node_not_registered,
network_version_query_failed,
network_height_query_failed,
service_node_contribution_maxed,
service_node_contributors_maxed,
service_node_insufficient_contribution,
too_many_transactions_constructed,
};
std::vector<wallet2::pending_tx> create_stake_tx(const crypto::public_key& service_node_key, const cryptonote::address_parse_info& addr_info, uint64_t amount);
struct stake_result
{
stake_result_status status;
std::string msg;
};
/// Modifies the `amount` to maximum possible if too large, but rejects if insufficient.
/// `fraction` is only used to determine the amount if specified zero.
stake_result check_stake_allowed(const crypto::public_key& sn_key, const cryptonote::address_parse_info& addr_info, uint64_t& amount, double fraction = 0);
stake_result create_stake_tx (std::vector<pending_tx> &ptx, const crypto::public_key& service_node_key, const cryptonote::address_parse_info& addr_info, uint64_t amount,
double amount_fraction = 0, uint32_t priority = 0, uint32_t subaddr_account = 0, std::set<uint32_t> subaddr_indices = {});
// MMS -------------------------------------------------------------------------------------------------
mms::message_store& get_message_store() { return m_message_store; };
@ -1367,6 +1393,7 @@ namespace tools
hw::device& lookup_device(const std::string & device_descriptor);
bool get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution);
bool get_output_blacklist(std::vector<uint64_t> &blacklist);
uint64_t get_segregation_fork_height() const;
void unpack_multisig_info(const std::vector<std::string>& info,
@ -1473,7 +1500,6 @@ namespace tools
std::string m_device_name;
std::string m_device_derivation_path;
uint64_t m_device_last_key_image_sync;
bool m_fork_on_autostake;
// Aux transaction data from device
std::unordered_map<crypto::hash, std::string> m_tx_device;

View File

@ -808,6 +808,14 @@ namespace tools
}
};
//----------------------------------------------------------------------------------------------------
struct get_output_blacklist : public wallet_rpc_error
{
explicit get_output_blacklist(std::string&& loc, const std::string& request)
: wallet_rpc_error(std::move(loc), "failed to get output blacklist", request)
{
}
};
//----------------------------------------------------------------------------------------------------
struct wallet_files_doesnt_correspond : public wallet_logic_error
{
explicit wallet_files_doesnt_correspond(std::string&& loc, const std::string& keys_file, const std::string& wallet_file)

View File

@ -221,7 +221,7 @@ cryptonote::transaction linear_chain_generator::create_registration_tx(const cry
const cryptonote::keypair& sn_keys)
{
const sn_contributor_t contr = { acc.get_keys().m_account_address, STAKING_PORTIONS };
const uint32_t expires = height() + service_nodes::staking_initial_num_lock_blocks(cryptonote::FAKECHAIN);
const uint32_t expires = height() + service_nodes::staking_num_lock_blocks(cryptonote::FAKECHAIN);
const auto reg_idx = registration_buffer_.size();
registration_buffer_.push_back({ expires, sn_keys, contr, { height(), reg_idx } });
@ -631,7 +631,7 @@ cryptonote::transaction make_registration_tx(std::vector<test_event_entry>& even
uint64_t amount = service_nodes::portions_to_amount(portions[0], staking_requirement);
cryptonote::transaction tx;
const auto unlock_time = new_height + service_nodes::staking_initial_num_lock_blocks(cryptonote::FAKECHAIN);
const auto unlock_time = new_height + service_nodes::staking_num_lock_blocks(cryptonote::FAKECHAIN);
std::vector<uint8_t> extra;
add_service_node_pubkey_to_tx_extra(extra, service_node_keys.pub);

View File

@ -756,9 +756,6 @@ inline bool replay_events_through_core(cryptonote::core& cr, const std::vector<t
push_core_event_visitor<t_test_class> visitor(cr, events, validator);
for(size_t i = 1; i < events.size() && r; ++i)
{
if (i == 79) {
volatile int break_here = 5;
}
visitor.event_index(i);
r = boost::apply_visitor(visitor, events[i]);
}

View File

@ -124,9 +124,6 @@ int main(int argc, char* argv[])
GENERATE_AND_PLAY(deregister_too_old);
GENERATE_AND_PLAY(sn_test_rollback);
GENERATE_AND_PLAY(test_swarms_basic);
#else
GENERATE_AND_PLAY(gen_bp_tx_valid_1);
GENERATE_AND_PLAY(gen_bp_tx_valid_2);
#endif
}

View File

@ -145,7 +145,7 @@ bool gen_service_nodes::generate(std::vector<test_event_entry> &events) const
DO_CALLBACK(events, "check_registered");
for (auto i = 0u; i < service_nodes::staking_initial_num_lock_blocks(cryptonote::FAKECHAIN); ++i) {
for (auto i = 0u; i < service_nodes::staking_num_lock_blocks(cryptonote::FAKECHAIN); ++i) {
gen.create_block();
}
@ -186,7 +186,7 @@ bool gen_service_nodes::check_expired(cryptonote::core& c, size_t ev_index, cons
cryptonote::account_base alice = boost::get<cryptonote::account_base>(events[1]);
const auto stake_lock_time = service_nodes::staking_initial_num_lock_blocks(cryptonote::FAKECHAIN);
const auto stake_lock_time = service_nodes::staking_num_lock_blocks(cryptonote::FAKECHAIN);
std::vector<block> blocks;
size_t count = 15 + (2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW) + stake_lock_time;
@ -335,7 +335,7 @@ bool test_zero_fee_deregister::generate(std::vector<test_event_entry> &events)
// Test if a person registers onto the network and they get included in the nodes to test (i.e. heights 0, 5, 10). If
// they get dereigstered in the nodes to test, height 5, and rejoin the network before height 10 (and are in the nodes
// to test), they don't get deregistered. This scenario will most likely happen with an autostaker.
// to test), they don't get deregistered.
test_deregister_safety_buffer::test_deregister_safety_buffer() {
REGISTER_CALLBACK_METHOD(test_deregister_safety_buffer, mark_invalid_tx);
}

View File

@ -467,10 +467,15 @@ TEST(service_nodes, min_stake_amount)
/// post v11
hf_version = cryptonote::network_version_11_swarms;
{
/// Should be able to contribute roughly one sixth of the total requirement, but not less
const uint64_t reserved = stake_requirement / 2;
const uint64_t min_stake = service_nodes::get_min_node_contribution(hf_version, stake_requirement, reserved, 1);
ASSERT_EQ(min_stake, stake_requirement / 6);
// 50% reserved, with 1 contribution, max of 4- the minimum stake should be (50% / 3)
const uint64_t reserved = stake_requirement / 2;
const uint64_t remaining = stake_requirement - reserved;
uint64_t min_stake = service_nodes::get_min_node_contribution(hf_version, stake_requirement, reserved, 1 /*num_contributions_locked*/);
ASSERT_EQ(min_stake, remaining / 3);
// As above, but with 2 contributions locked up, minimum stake should be (50% / 2)
min_stake = service_nodes::get_min_node_contribution(hf_version, stake_requirement, reserved, 2 /*num_contributions_locked*/);
ASSERT_EQ(min_stake, remaining / 2);
}
{
@ -480,6 +485,12 @@ TEST(service_nodes, min_stake_amount)
ASSERT_FALSE(min_stake <= stake_requirement / 6);
}
{
// Cannot contribute less than 25% as first contributor
const uint64_t min_stake = service_nodes::get_min_node_contribution(hf_version, stake_requirement, 0, 0/*num_contributions_locked*/);
ASSERT_TRUE(min_stake >= stake_requirement / 4);
}
}
// Test service node receive rewards proportionate to the amount they contributed.
@ -501,28 +512,31 @@ TEST(service_nodes, service_node_rewards_proportional_to_portions)
TEST(service_nodes, service_node_get_locked_key_image_unlock_height)
{
uint64_t lock_duration = service_nodes::staking_initial_num_lock_blocks(cryptonote::MAINNET);
uint64_t lock_duration = service_nodes::staking_num_lock_blocks(cryptonote::MAINNET) / 2;
{
uint64_t expected = lock_duration;
uint64_t unlock_height = service_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, 0, 100);
uint64_t curr_height = 100;
uint64_t expected = curr_height + lock_duration;
uint64_t unlock_height = service_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, 0, curr_height);
ASSERT_EQ(unlock_height, expected);
}
{
uint64_t expected = lock_duration;
uint64_t unlock_height = service_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, 0, lock_duration - 1);
uint64_t curr_height = lock_duration - 1;
uint64_t expected = curr_height + lock_duration;
uint64_t unlock_height = service_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, 0, curr_height);
ASSERT_EQ(unlock_height, expected);
}
{
uint64_t expected = lock_duration * 2;
uint64_t unlock_height = service_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, 0, lock_duration + 100);
uint64_t curr_height = lock_duration + 100;
uint64_t expected = curr_height + lock_duration;
uint64_t unlock_height = service_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, 0, curr_height);
ASSERT_EQ(unlock_height, expected);
}
{
uint64_t expected = lock_duration * 2;
uint64_t expected = lock_duration + lock_duration;
uint64_t unlock_height = service_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, lock_duration, lock_duration);
ASSERT_EQ(unlock_height, expected);
}
@ -530,7 +544,7 @@ TEST(service_nodes, service_node_get_locked_key_image_unlock_height)
{
uint64_t register_height = lock_duration + 1;
uint64_t curr_height = register_height + 2;
uint64_t expected = register_height + lock_duration;
uint64_t expected = curr_height + lock_duration;
uint64_t unlock_height = service_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, register_height, curr_height);
ASSERT_EQ(unlock_height, expected);
}

View File

@ -114,6 +114,8 @@ public:
virtual bool is_read_only() const { return false; }
virtual std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked, uint64_t recent_cutoff, uint64_t min_count) const { return std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>>(); }
virtual bool get_output_distribution(uint64_t amount, uint64_t from_height, uint64_t to_height, std::vector<uint64_t> &distribution, uint64_t &base) const { return false; }
virtual bool get_output_blacklist(std::vector<uint64_t> &blacklist) const { return false; }
virtual void add_output_blacklist(std::vector<uint64_t> const &blacklist) { }
virtual void add_txpool_tx(const crypto::hash &txid, const cryptonote::blobdata &blob, const cryptonote::txpool_tx_meta_t& details) {}
virtual void update_txpool_tx(const crypto::hash &txid, const cryptonote::txpool_tx_meta_t& details) {}