mirror of https://github.com/oxen-io/oxen-core.git
Increase max number of contributors to 10
This requires the operator to still contribute 25% of the service node but another 9 nodes will be allowed to stake to the node makeing 10 contributors total rather than our previous 4.
This commit is contained in:
parent
3ac7e55056
commit
9edc2bb52d
|
@ -233,7 +233,7 @@ namespace cryptonote {
|
|||
// This calculates the operator fee using (operator_portion / max_operator_portion) * distribution_amount but using 128 bit integer math
|
||||
uint64_t hi, lo, resulthi, resultlo;
|
||||
lo = mul128(sn_info.portions_for_operator, distribution_amount, &hi);
|
||||
div128_64(hi, lo, STAKING_PORTIONS, &resulthi, &resultlo);
|
||||
div128_64(hi, lo, STAKING_PORTIONS_V1, &resulthi, &resultlo);
|
||||
if (resulthi > 0)
|
||||
throw std::logic_error("overflow from calculating sn operator fee");
|
||||
operator_fee = resultlo;
|
||||
|
|
|
@ -1321,12 +1321,12 @@ namespace cryptonote
|
|||
LOG_ERROR("get_registration_hash addresses.size() != portions.size()");
|
||||
return false;
|
||||
}
|
||||
uint64_t portions_left = STAKING_PORTIONS;
|
||||
uint64_t portions_left = STAKING_PORTIONS_V1;
|
||||
for (uint64_t portion : portions)
|
||||
{
|
||||
if (portion > portions_left)
|
||||
{
|
||||
LOG_ERROR(tr("Your registration has more than ") << STAKING_PORTIONS << tr(" portions, this registration is invalid!"));
|
||||
LOG_ERROR(tr("Your registration has more than ") << STAKING_PORTIONS_V1 << tr(" portions, this registration is invalid!"));
|
||||
return false;
|
||||
}
|
||||
portions_left -= portion;
|
||||
|
|
|
@ -51,11 +51,12 @@ using namespace std::literals;
|
|||
#define CRYPTONOTE_DEFAULT_TX_MIXIN 9
|
||||
|
||||
#define STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS 20
|
||||
#define STAKING_PORTIONS UINT64_C(0xfffffffffffffffc)
|
||||
#define MAX_NUMBER_OF_CONTRIBUTORS 4
|
||||
#define MIN_PORTIONS (STAKING_PORTIONS / MAX_NUMBER_OF_CONTRIBUTORS)
|
||||
#define STAKING_PORTIONS_V1 UINT64_C(0xfffffffffffffffc)
|
||||
#define MAX_NUMBER_OF_CONTRIBUTORS_V1 4
|
||||
#define MAX_NUMBER_OF_CONTRIBUTORS_V2 10
|
||||
#define MIN_PORTIONS_V2 (STAKING_PORTIONS_V1 / MAX_NUMBER_OF_CONTRIBUTORS_V2)
|
||||
|
||||
static_assert(STAKING_PORTIONS % 12 == 0, "Use a multiple of twelve, so that it divides evenly by two, three, or four contributors.");
|
||||
static_assert(STAKING_PORTIONS_V1 % 12 == 0, "Use a multiple of 12, so that it divides evenly by two, three, or four contributors.");
|
||||
|
||||
#define STAKING_AUTHORIZATION_EXPIRATION_WINDOW (60*60*24*7*2) // 2 weeks
|
||||
|
||||
|
|
|
@ -210,7 +210,7 @@ namespace cryptonote
|
|||
{
|
||||
uint64_t hi, lo, rewardhi, rewardlo;
|
||||
lo = mul128(total_service_node_reward, portions, &hi);
|
||||
div128_64(hi, lo, STAKING_PORTIONS, &rewardhi, &rewardlo);
|
||||
div128_64(hi, lo, STAKING_PORTIONS_V1, &rewardhi, &rewardlo);
|
||||
return rewardlo;
|
||||
}
|
||||
|
||||
|
|
|
@ -333,13 +333,14 @@ namespace service_nodes
|
|||
|
||||
void validate_contributor_args(uint8_t hf_version, contributor_args_t const &contributor_args)
|
||||
{
|
||||
const size_t max_contributors = hf_version >= 19 ? MAX_NUMBER_OF_CONTRIBUTORS_V2 : MAX_NUMBER_OF_CONTRIBUTORS_V1;
|
||||
if (contributor_args.portions.empty())
|
||||
throw invalid_contributions{"No portions given"};
|
||||
if (contributor_args.portions.size() != contributor_args.addresses.size())
|
||||
throw invalid_contributions{"Number of portions (" + std::to_string(contributor_args.portions.size()) + ") doesn't match the number of addresses (" + std::to_string(contributor_args.portions.size()) + ")"};
|
||||
if (contributor_args.portions.size() > MAX_NUMBER_OF_CONTRIBUTORS)
|
||||
if (contributor_args.portions.size() > max_contributors)
|
||||
throw invalid_contributions{"Too many contributors"};
|
||||
if (contributor_args.portions_for_operator > STAKING_PORTIONS)
|
||||
if (contributor_args.portions_for_operator > STAKING_PORTIONS_V1)
|
||||
throw invalid_contributions{"Operator portions are too high"};
|
||||
|
||||
if (!check_service_node_portions(hf_version, contributor_args.portions))
|
||||
|
@ -940,10 +941,10 @@ namespace service_nodes
|
|||
|
||||
// Don't need this check for HF16+ because the number of reserved spots is already checked in
|
||||
// the registration details, and we disallow a non-operator registration.
|
||||
if (total_num_of_addr > MAX_NUMBER_OF_CONTRIBUTORS)
|
||||
if (total_num_of_addr > MAX_NUMBER_OF_CONTRIBUTORS_V1)
|
||||
{
|
||||
LOG_PRINT_L1("Register TX: Number of participants: " << total_num_of_addr <<
|
||||
" exceeded the max number of contributors: " << MAX_NUMBER_OF_CONTRIBUTORS <<
|
||||
" exceeded the max number of contributors: " << MAX_NUMBER_OF_CONTRIBUTORS_V1 <<
|
||||
" on height: " << block_height <<
|
||||
" for tx: " << cryptonote::get_transaction_hash(tx));
|
||||
return false;
|
||||
|
@ -976,7 +977,7 @@ namespace service_nodes
|
|||
|
||||
uint64_t hi, lo, resulthi, resultlo;
|
||||
lo = mul128(info.staking_requirement, contributor_args.portions[i], &hi);
|
||||
div128_64(hi, lo, STAKING_PORTIONS, &resulthi, &resultlo);
|
||||
div128_64(hi, lo, STAKING_PORTIONS_V1, &resulthi, &resultlo);
|
||||
|
||||
info.contributors.emplace_back();
|
||||
auto &contributor = info.contributors.back();
|
||||
|
@ -1145,20 +1146,23 @@ namespace service_nodes
|
|||
// Check node contributor counts
|
||||
{
|
||||
bool too_many_contributions = false;
|
||||
if (hf_version >= cryptonote::network_version_16_pulse)
|
||||
if (hf_version >= cryptonote::network_version_19)
|
||||
// As of HF19 we allow up to 10 stakes total
|
||||
too_many_contributions = existing_contributions + stake.locked_contributions.size() > MAX_NUMBER_OF_CONTRIBUTORS_V2;
|
||||
else if (hf_version >= cryptonote::network_version_16_pulse)
|
||||
// Before HF16 we didn't properly take into account unfilled reservation spots
|
||||
too_many_contributions = existing_contributions + other_reservations + 1 > MAX_NUMBER_OF_CONTRIBUTORS;
|
||||
too_many_contributions = existing_contributions + other_reservations + 1 > MAX_NUMBER_OF_CONTRIBUTORS_V1;
|
||||
else if (hf_version >= cryptonote::network_version_11_infinite_staking)
|
||||
// As of HF11 we allow up to 4 stakes total (except for the loophole closed above)
|
||||
too_many_contributions = existing_contributions + stake.locked_contributions.size() > MAX_NUMBER_OF_CONTRIBUTORS;
|
||||
too_many_contributions = existing_contributions + stake.locked_contributions.size() > MAX_NUMBER_OF_CONTRIBUTORS_V1;
|
||||
else
|
||||
// Before HF11 we allowed up to 4 contributors, but each can contribute multiple times
|
||||
too_many_contributions = new_contributor && contributors.size() >= MAX_NUMBER_OF_CONTRIBUTORS;
|
||||
too_many_contributions = new_contributor && contributors.size() >= MAX_NUMBER_OF_CONTRIBUTORS_V1;
|
||||
|
||||
if (too_many_contributions)
|
||||
{
|
||||
LOG_PRINT_L1("TX: Already hit the max number of contributions: "
|
||||
<< MAX_NUMBER_OF_CONTRIBUTORS
|
||||
<< (hf_version >= 19 ? MAX_NUMBER_OF_CONTRIBUTORS_V2 : MAX_NUMBER_OF_CONTRIBUTORS_V1)
|
||||
<< " for contributor: " << cryptonote::get_account_address_as_str(nettype, false, stake.address)
|
||||
<< " on height: " << block_height << " for tx: " << cryptonote::get_transaction_hash(tx));
|
||||
return false;
|
||||
|
@ -3665,24 +3669,25 @@ namespace service_nodes
|
|||
return result;
|
||||
}
|
||||
|
||||
if ((args.size()-1)/ 2 > MAX_NUMBER_OF_CONTRIBUTORS)
|
||||
const size_t max_contributors = hf_version >= 19 ? MAX_NUMBER_OF_CONTRIBUTORS_V2 : MAX_NUMBER_OF_CONTRIBUTORS_V1;
|
||||
if (args.size() > 1 + 2 * max_contributors)
|
||||
{
|
||||
result.err_msg = tr("Exceeds the maximum number of contributors, which is ") + std::to_string(MAX_NUMBER_OF_CONTRIBUTORS);
|
||||
result.err_msg = tr("Exceeds the maximum number of contributors, which is ") + std::to_string(max_contributors);
|
||||
return result;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
result.portions_for_operator = boost::lexical_cast<uint64_t>(args[0]);
|
||||
if (result.portions_for_operator > STAKING_PORTIONS)
|
||||
if (result.portions_for_operator > STAKING_PORTIONS_V1)
|
||||
{
|
||||
result.err_msg = tr("Invalid portion amount: ") + args[0] + tr(". Must be between 0 and ") + std::to_string(STAKING_PORTIONS);
|
||||
result.err_msg = tr("Invalid portion amount: ") + args[0] + tr(". Must be between 0 and ") + std::to_string(STAKING_PORTIONS_V1);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
result.err_msg = tr("Invalid portion amount: ") + args[0] + tr(". Must be between 0 and ") + std::to_string(STAKING_PORTIONS);
|
||||
result.err_msg = tr("Invalid portion amount: ") + args[0] + tr(". Must be between 0 and ") + std::to_string(STAKING_PORTIONS_V1);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -3729,13 +3734,14 @@ namespace service_nodes
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// FIXME(doyle): FIXME(oxen) !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
// This is temporary code to redistribute the insufficient portion dust
|
||||
// amounts between contributors. It should be removed in HF12.
|
||||
//
|
||||
std::array<uint64_t, MAX_NUMBER_OF_CONTRIBUTORS> excess_portions;
|
||||
std::array<uint64_t, MAX_NUMBER_OF_CONTRIBUTORS> min_contributions;
|
||||
std::array<uint64_t, MAX_NUMBER_OF_CONTRIBUTORS_V2> excess_portions;
|
||||
std::array<uint64_t, MAX_NUMBER_OF_CONTRIBUTORS_V2> min_contributions;
|
||||
{
|
||||
// NOTE: Calculate excess portions from each contributor
|
||||
uint64_t oxen_reserved = 0;
|
||||
|
@ -3755,7 +3761,7 @@ namespace service_nodes
|
|||
}
|
||||
}
|
||||
|
||||
uint64_t portions_left = STAKING_PORTIONS;
|
||||
uint64_t portions_left = STAKING_PORTIONS_V1;
|
||||
uint64_t total_reserved = 0;
|
||||
for (size_t i = 0; i < addr_to_portions.size(); ++i)
|
||||
{
|
||||
|
@ -3769,7 +3775,7 @@ namespace service_nodes
|
|||
// the minimum by a dust amount.
|
||||
uint64_t needed = min_portions - addr_to_portion.portions;
|
||||
const uint64_t FUDGE_FACTOR = 10;
|
||||
const uint64_t DUST_UNIT = (STAKING_PORTIONS / staking_requirement);
|
||||
const uint64_t DUST_UNIT = (STAKING_PORTIONS_V1 / staking_requirement);
|
||||
const uint64_t DUST = DUST_UNIT * FUDGE_FACTOR;
|
||||
if (needed > DUST)
|
||||
continue;
|
||||
|
@ -3793,7 +3799,7 @@ namespace service_nodes
|
|||
|
||||
// NOTE: Operator is sending in the minimum amount and it falls below
|
||||
// the minimum by dust, just increase the portions so it passes
|
||||
if (needed > 0 && addr_to_portions.size() < MAX_NUMBER_OF_CONTRIBUTORS)
|
||||
if (needed > 0 && addr_to_portions.size() < MAX_NUMBER_OF_CONTRIBUTORS_V2)
|
||||
addr_to_portion.portions += needed;
|
||||
}
|
||||
|
||||
|
@ -3805,7 +3811,7 @@ namespace service_nodes
|
|||
|
||||
if (min_portions == UINT64_MAX)
|
||||
{
|
||||
result.err_msg = tr("Too many contributors specified, you can only split a node with up to: ") + std::to_string(MAX_NUMBER_OF_CONTRIBUTORS) + tr(" people.");
|
||||
result.err_msg = tr("Too many contributors specified, you can only split a node with up to: ") + std::to_string(max_contributors) + tr(" people.");
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -3817,6 +3823,7 @@ namespace service_nodes
|
|||
total_reserved += oxen_amount;
|
||||
}
|
||||
|
||||
|
||||
result.success = true;
|
||||
return result;
|
||||
}
|
||||
|
@ -3840,6 +3847,7 @@ namespace service_nodes
|
|||
uint64_t exp_timestamp = time(nullptr) + STAKING_AUTHORIZATION_EXPIRATION_WINDOW;
|
||||
|
||||
crypto::hash hash;
|
||||
|
||||
bool hashed = cryptonote::get_registration_hash(contributor_args.addresses, contributor_args.portions_for_operator, contributor_args.portions, exp_timestamp, hash);
|
||||
if (!hashed)
|
||||
{
|
||||
|
@ -3959,7 +3967,7 @@ namespace service_nodes
|
|||
|
||||
// Add contributors and their portions to winners.
|
||||
result.payouts.reserve(info.contributors.size());
|
||||
const uint64_t remaining_portions = STAKING_PORTIONS - info.portions_for_operator;
|
||||
const uint64_t remaining_portions = STAKING_PORTIONS_V1 - info.portions_for_operator;
|
||||
for (const auto& contributor : info.contributors)
|
||||
{
|
||||
uint64_t hi, lo, resulthi, resultlo;
|
||||
|
|
|
@ -837,6 +837,6 @@ namespace service_nodes
|
|||
|
||||
payout service_node_info_to_payout(crypto::public_key const &key, service_node_info const &info);
|
||||
|
||||
const static payout_entry null_payout_entry = {cryptonote::null_address, STAKING_PORTIONS};
|
||||
const static payout_entry null_payout_entry = {cryptonote::null_address, STAKING_PORTIONS_V1};
|
||||
const static payout null_payout = {crypto::null_pkey, {null_payout_entry}};
|
||||
}
|
||||
|
|
|
@ -89,23 +89,32 @@ uint64_t portions_to_amount(uint64_t portions, uint64_t staking_requirement)
|
|||
{
|
||||
uint64_t hi, lo, resulthi, resultlo;
|
||||
lo = mul128(staking_requirement, portions, &hi);
|
||||
div128_64(hi, lo, STAKING_PORTIONS, &resulthi, &resultlo);
|
||||
div128_64(hi, lo, STAKING_PORTIONS_V1, &resulthi, &resultlo);
|
||||
return resultlo;
|
||||
}
|
||||
|
||||
bool check_service_node_portions(uint8_t hf_version, const std::vector<uint64_t>& portions)
|
||||
{
|
||||
if (portions.size() > MAX_NUMBER_OF_CONTRIBUTORS) return false;
|
||||
|
||||
const size_t max_contributors = hf_version >= 19 ? MAX_NUMBER_OF_CONTRIBUTORS_V2 : MAX_NUMBER_OF_CONTRIBUTORS_V1;
|
||||
if (portions.size() > max_contributors) return false;
|
||||
|
||||
if (portions[0] < get_min_node_operator_contribution(STAKING_PORTIONS_V1))
|
||||
{
|
||||
LOG_PRINT_L1("Register TX rejected: TX does not have sufficient operator stake");
|
||||
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);
|
||||
|
||||
const uint64_t min_portions = get_min_node_contribution(hf_version, STAKING_PORTIONS_V1, reserved, i);
|
||||
if (portions[i] < min_portions) return false;
|
||||
reserved += portions[i];
|
||||
}
|
||||
|
||||
return reserved <= STAKING_PORTIONS;
|
||||
return reserved <= STAKING_PORTIONS_V1;
|
||||
}
|
||||
|
||||
crypto::hash generate_request_stake_unlock_hash(uint32_t nonce)
|
||||
|
@ -127,7 +136,7 @@ uint64_t get_locked_key_image_unlock_height(cryptonote::network_type nettype, ui
|
|||
|
||||
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_V1);
|
||||
}
|
||||
|
||||
uint64_t get_max_node_contribution(uint8_t version, uint64_t staking_requirement, uint64_t total_reserved)
|
||||
|
@ -144,10 +153,12 @@ uint64_t get_min_node_contribution(uint8_t version, uint64_t staking_requirement
|
|||
return get_min_node_contribution_pre_v11(staking_requirement, total_reserved);
|
||||
|
||||
const uint64_t needed = staking_requirement - total_reserved;
|
||||
assert(MAX_NUMBER_OF_CONTRIBUTORS > num_contributions);
|
||||
if (MAX_NUMBER_OF_CONTRIBUTORS <= num_contributions) return UINT64_MAX;
|
||||
|
||||
const size_t num_contributions_remaining_avail = MAX_NUMBER_OF_CONTRIBUTORS - num_contributions;
|
||||
const size_t max_contributors = (version < cryptonote::network_version_19) ? MAX_NUMBER_OF_CONTRIBUTORS_V1: MAX_NUMBER_OF_CONTRIBUTORS_V2;
|
||||
assert(max_contributors > num_contributions);
|
||||
if (max_contributors <= num_contributions) return UINT64_MAX;
|
||||
|
||||
const size_t num_contributions_remaining_avail = max_contributors - num_contributions;
|
||||
return needed / num_contributions_remaining_avail;
|
||||
}
|
||||
|
||||
|
@ -158,6 +169,11 @@ uint64_t get_min_node_contribution_in_portions(uint8_t version, uint64_t staking
|
|||
return result;
|
||||
}
|
||||
|
||||
uint64_t get_min_node_operator_contribution(uint64_t staking_requirement)
|
||||
{
|
||||
return staking_requirement / MAX_NUMBER_OF_CONTRIBUTORS_V1;
|
||||
}
|
||||
|
||||
uint64_t get_portions_to_make_amount(uint64_t staking_requirement, uint64_t amount, uint64_t max_portions)
|
||||
{
|
||||
uint64_t lo, hi, resulthi, resultlo;
|
||||
|
@ -175,11 +191,11 @@ static bool get_portions_from_percent(double cur_percent, uint64_t& portions) {
|
|||
// Fix for truncation issue when operator cut = 100 for a pool Service Node.
|
||||
if (cur_percent == 100.0)
|
||||
{
|
||||
portions = STAKING_PORTIONS;
|
||||
portions = STAKING_PORTIONS_V1;
|
||||
}
|
||||
else
|
||||
{
|
||||
portions = (cur_percent / 100.0) * (double)STAKING_PORTIONS;
|
||||
portions = (cur_percent / 100.0) * (double)STAKING_PORTIONS_V1;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -241,10 +241,11 @@ namespace service_nodes {
|
|||
constexpr uint8_t MAXIMUM_EXTERNAL_OUT_OF_SYNC = 80;
|
||||
|
||||
|
||||
static_assert(STAKING_PORTIONS != UINT64_MAX, "UINT64_MAX is used as the invalid value for failing to calculate the min_node_contribution");
|
||||
static_assert(STAKING_PORTIONS_V1 != UINT64_MAX, "UINT64_MAX is used as the invalid value for failing to calculate the min_node_contribution");
|
||||
// return: UINT64_MAX if (num_contributions > the max number of contributions), otherwise the amount in oxen atomic units
|
||||
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_min_node_operator_contribution(uint64_t staking_requirement);
|
||||
|
||||
// Gets the maximum allowed stake amount. This is used to prevent significant overstaking. The
|
||||
// wallet tries to avoid this when submitting a stake, but it can still happen when competing stakes
|
||||
|
@ -267,7 +268,7 @@ 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);
|
||||
|
||||
// Returns lowest x such that (staking_requirement * x/STAKING_PORTIONS) >= amount
|
||||
uint64_t get_portions_to_make_amount(uint64_t staking_requirement, uint64_t amount, uint64_t max_portions = STAKING_PORTIONS);
|
||||
uint64_t get_portions_to_make_amount(uint64_t staking_requirement, uint64_t amount, uint64_t max_portions = STAKING_PORTIONS_V1);
|
||||
|
||||
bool get_portions_from_percent_str(std::string cut_str, uint64_t& portions);
|
||||
|
||||
|
|
|
@ -1582,7 +1582,7 @@ static void append_printable_service_node_list_entry(cryptonote::network_type ne
|
|||
|
||||
if (detailed_view) // Print operator information
|
||||
{
|
||||
stream << indent2 << "Operator Cut (\% Of Reward): " << to_string_rounded((entry.portions_for_operator / (double)STAKING_PORTIONS) * 100.0, 2) << "%\n";
|
||||
stream << indent2 << "Operator Cut (\% Of Reward): " << to_string_rounded((entry.portions_for_operator / (double)STAKING_PORTIONS_V1) * 100.0, 2) << "%\n";
|
||||
stream << indent2 << "Operator Address: " << entry.operator_address << "\n";
|
||||
}
|
||||
|
||||
|
@ -1891,7 +1891,7 @@ static uint64_t get_actual_amount(uint64_t amount, uint64_t portions)
|
|||
{
|
||||
uint64_t lo, hi, resulthi, resultlo;
|
||||
lo = mul128(amount, portions, &hi);
|
||||
div128_64(hi, lo, STAKING_PORTIONS, &resulthi, &resultlo);
|
||||
div128_64(hi, lo, STAKING_PORTIONS_V1, &resulthi, &resultlo);
|
||||
return resultlo;
|
||||
}
|
||||
|
||||
|
@ -2003,8 +2003,8 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
|
|||
};
|
||||
|
||||
// anything less than DUST will be added to operator stake
|
||||
const uint64_t DUST = MAX_NUMBER_OF_CONTRIBUTORS;
|
||||
std::cout << "Current staking requirement: " << highlight_money(staking_requirement) << std::endl;
|
||||
const uint64_t DUST = MAX_NUMBER_OF_CONTRIBUTORS_V2;
|
||||
std::cout << "Current staking requirement: " << cryptonote::print_money(staking_requirement) << " " << cryptonote::get_unit() << std::endl;
|
||||
|
||||
enum struct register_step
|
||||
{
|
||||
|
@ -2025,8 +2025,8 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
|
|||
{
|
||||
register_step prev_step = register_step::ask_address;
|
||||
size_t num_participants = 1;
|
||||
uint64_t operator_fee_portions = STAKING_PORTIONS;
|
||||
uint64_t portions_remaining = STAKING_PORTIONS;
|
||||
uint64_t operator_fee_portions = STAKING_PORTIONS_V1;
|
||||
uint64_t portions_remaining = STAKING_PORTIONS_V1;
|
||||
uint64_t total_reserved_contributions = 0;
|
||||
std::vector<std::string> addresses;
|
||||
std::vector<uint64_t> contributions;
|
||||
|
@ -2114,7 +2114,7 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
|
|||
if (result == input_line_result::yes)
|
||||
{
|
||||
std::cout << std::endl;
|
||||
state.contributions.push_back(STAKING_PORTIONS);
|
||||
state.contributions.push_back(STAKING_PORTIONS_V1);
|
||||
state.portions_remaining = 0;
|
||||
state.total_reserved_contributions += staking_requirement;
|
||||
|
||||
|
@ -2158,16 +2158,16 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
|
|||
case register_step::how_many_more_contributors:
|
||||
{
|
||||
auto [result, input] = input_line_value(
|
||||
"Number of additional contributors [1-" + std::to_string(MAX_NUMBER_OF_CONTRIBUTORS - 1) + "]");
|
||||
"Number of additional contributors [1-" + std::to_string(MAX_NUMBER_OF_CONTRIBUTORS_V2 - 1) + "]");
|
||||
|
||||
if (check_cancel_back(result))
|
||||
break;
|
||||
|
||||
size_t additional_contributors;
|
||||
if (!tools::parse_int(input, additional_contributors) ||
|
||||
additional_contributors < 1 || additional_contributors > (MAX_NUMBER_OF_CONTRIBUTORS - 1))
|
||||
additional_contributors < 1 || additional_contributors > (MAX_NUMBER_OF_CONTRIBUTORS_V2 - 1))
|
||||
{
|
||||
tools::fail_msg_writer() << "Invalid value; must be between 1 and " << (MAX_NUMBER_OF_CONTRIBUTORS - 1) << "." << std::endl;
|
||||
tools::fail_msg_writer() << "Invalid value; must be between 1 and " << (MAX_NUMBER_OF_CONTRIBUTORS_V2 - 1) << "." << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -2182,7 +2182,7 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
|
|||
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());
|
||||
uint64_t min_contribution = service_nodes::portions_to_amount(staking_requirement, min_contribution_portions);
|
||||
uint64_t min_contribution = is_operator ? service_nodes::get_min_node_operator_contribution(staking_requirement) : service_nodes::portions_to_amount(staking_requirement, min_contribution_portions);
|
||||
|
||||
auto [result, contribution_str] = input_line_value(fmt::format(
|
||||
"The {} contribution must be between {} and {} to meet the staking requirements.\n\n"
|
||||
|
@ -2236,7 +2236,7 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
|
|||
|
||||
case register_step::summary_info:
|
||||
{
|
||||
uint64_t open_spots = MAX_NUMBER_OF_CONTRIBUTORS - state.contributions.size();
|
||||
uint64_t open_spots = MAX_NUMBER_OF_CONTRIBUTORS_V2 - state.contributions.size();
|
||||
const uint64_t amount_left = staking_requirement - state.total_reserved_contributions;
|
||||
fmt::print("Total reserved contributions: {}\n", highlight_money(state.total_reserved_contributions));
|
||||
if (amount_left > DUST)
|
||||
|
@ -2276,7 +2276,7 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
|
|||
|
||||
if (amount_left > 0 || state.addresses.size() > 1)
|
||||
fmt::print("Operator fee (as % of Service Node rewards): \x1b[33;1m{}%\x1b[0m\n\n",
|
||||
state.operator_fee_portions * 100.0 / static_cast<double>(STAKING_PORTIONS));
|
||||
state.operator_fee_portions * 100.0 / static_cast<double>(STAKING_PORTIONS_V1));
|
||||
|
||||
constexpr auto row = "{:^14} {:^13} {:>17} {:>8}\n"sv;
|
||||
fmt::print(row, "Contributor", "Address", "Contribution", "Contr. %");
|
||||
|
@ -2293,12 +2293,12 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
|
|||
(i==0) ? "Operator" : "Contributor " + std::to_string(i),
|
||||
addr.substr(0, 9) + ".." + addr.substr(addr.size() - 2),
|
||||
cryptonote::print_money(amount),
|
||||
fmt::format("{:.2f}%", state.contributions[i] * 100.0 / STAKING_PORTIONS));
|
||||
fmt::format("{:.2f}%", state.contributions[i] * 100.0 / STAKING_PORTIONS_V1));
|
||||
}
|
||||
|
||||
if (amount_left > DUST)
|
||||
{
|
||||
size_t open_spots = MAX_NUMBER_OF_CONTRIBUTORS - state.contributions.size();
|
||||
size_t open_spots = MAX_NUMBER_OF_CONTRIBUTORS_V2 - state.contributions.size();
|
||||
for (size_t i = 0; i < open_spots; i++) {
|
||||
fmt::print(row,
|
||||
"(open)",
|
||||
|
|
|
@ -8137,6 +8137,7 @@ wallet2::stake_result wallet2::check_stake_allowed(const crypto::public_key& sn_
|
|||
|
||||
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_existing_contributions);
|
||||
|
||||
bool is_preexisting_contributor = false;
|
||||
|
@ -8165,7 +8166,7 @@ wallet2::stake_result wallet2::check_stake_allowed(const crypto::public_key& sn_
|
|||
return result;
|
||||
}
|
||||
|
||||
const bool full = snode_info.contributors.size() >= MAX_NUMBER_OF_CONTRIBUTORS;
|
||||
const bool full = snode_info.contributors.size() >= MAX_NUMBER_OF_CONTRIBUTORS_V2;
|
||||
if (full && !is_preexisting_contributor)
|
||||
{
|
||||
result.status = stake_result_status::service_node_contributors_maxed;
|
||||
|
@ -8175,7 +8176,7 @@ wallet2::stake_result wallet2::check_stake_allowed(const crypto::public_key& sn_
|
|||
|
||||
if (amount < min_contrib_total)
|
||||
{
|
||||
const uint64_t DUST = MAX_NUMBER_OF_CONTRIBUTORS;
|
||||
const uint64_t DUST = MAX_NUMBER_OF_CONTRIBUTORS_V2;
|
||||
if (min_contrib_total - amount <= DUST)
|
||||
{
|
||||
amount = min_contrib_total;
|
||||
|
@ -8225,7 +8226,9 @@ wallet2::stake_result wallet2::create_stake_tx(const crypto::public_key& service
|
|||
{
|
||||
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)
|
||||
{
|
||||
|
@ -8498,7 +8501,7 @@ wallet2::register_service_node_result wallet2::create_register_service_node_tx(c
|
|||
{
|
||||
uint64_t amount_payable_by_operator = 0;
|
||||
{
|
||||
const uint64_t DUST = MAX_NUMBER_OF_CONTRIBUTORS;
|
||||
const uint64_t DUST = MAX_NUMBER_OF_CONTRIBUTORS_V2;
|
||||
uint64_t amount_left = staking_requirement;
|
||||
for (size_t i = 0; i < contributor_args.portions.size(); i++)
|
||||
{
|
||||
|
|
|
@ -2981,6 +2981,7 @@ namespace {
|
|||
|
||||
// NOTE(oxen): Pre-emptively set subaddr_account to 0. We don't support onwards from Infinite Staking which is when this call was implemented.
|
||||
tools::wallet2::register_service_node_result register_result = m_wallet->create_register_service_node_tx(args, 0 /*subaddr_account*/);
|
||||
|
||||
if (register_result.status != tools::wallet2::register_service_node_result_status::success)
|
||||
throw wallet_rpc_error{error_code::TX_NOT_POSSIBLE, register_result.msg};
|
||||
|
||||
|
|
|
@ -1493,7 +1493,7 @@ struct oxen_chain_generator
|
|||
cryptonote::transaction create_tx(const cryptonote::account_base &src, const cryptonote::account_public_address &dest, uint64_t amount, uint64_t fee) const;
|
||||
cryptonote::transaction create_registration_tx(const cryptonote::account_base &src,
|
||||
const cryptonote::keypair &service_node_keys = cryptonote::keypair{hw::get_device("default")},
|
||||
uint64_t src_portions = STAKING_PORTIONS,
|
||||
uint64_t src_portions = STAKING_PORTIONS_V1,
|
||||
uint64_t src_operator_cut = 0,
|
||||
std::array<oxen_service_node_contribution, 3> const &contributors = {},
|
||||
int num_contributors = 0) const;
|
||||
|
|
|
@ -134,6 +134,9 @@ int main(int argc, char* argv[])
|
|||
GENERATE_AND_PLAY(oxen_service_nodes_checkpoint_quorum_size);
|
||||
GENERATE_AND_PLAY(oxen_service_nodes_gen_nodes);
|
||||
GENERATE_AND_PLAY(oxen_service_nodes_insufficient_contribution);
|
||||
GENERATE_AND_PLAY(oxen_service_nodes_insufficient_contribution_HF18);
|
||||
GENERATE_AND_PLAY(oxen_service_nodes_sufficient_contribution_HF19);
|
||||
GENERATE_AND_PLAY(oxen_service_nodes_insufficient_operator_contribution_HF19);
|
||||
GENERATE_AND_PLAY(oxen_service_nodes_test_rollback);
|
||||
GENERATE_AND_PLAY(oxen_service_nodes_test_swarms_basic);
|
||||
GENERATE_AND_PLAY(oxen_pulse_invalid_validator_bitset);
|
||||
|
|
|
@ -2980,14 +2980,20 @@ bool oxen_service_nodes_insufficient_contribution::generate(std::vector<test_eve
|
|||
gen.add_blocks_until_version(hard_forks.back().version);
|
||||
gen.add_mined_money_unlock_blocks();
|
||||
|
||||
uint64_t operator_portions = STAKING_PORTIONS / 2;
|
||||
uint64_t remaining_portions = STAKING_PORTIONS - operator_portions;
|
||||
const auto alice = gen.add_account();
|
||||
const auto tx0 = gen.create_and_add_tx(gen.first_miner_, alice.get_keys().m_account_address, MK_COINS(101));
|
||||
gen.create_and_add_next_block({tx0});
|
||||
gen.add_transfer_unlock_blocks();
|
||||
|
||||
uint64_t operator_portions = STAKING_PORTIONS_V1 / 2;
|
||||
uint64_t remaining_portions = STAKING_PORTIONS_V1 - operator_portions;
|
||||
cryptonote::keypair sn_keys{hw::get_device("default")};
|
||||
cryptonote::transaction register_tx = gen.create_registration_tx(gen.first_miner_, sn_keys, operator_portions);
|
||||
gen.add_tx(register_tx);
|
||||
gen.create_and_add_next_block({register_tx});
|
||||
gen.add_transfer_unlock_blocks();
|
||||
|
||||
cryptonote::transaction stake = gen.create_and_add_staking_tx(sn_keys.pub, gen.first_miner_, MK_COINS(1));
|
||||
cryptonote::transaction stake = gen.create_and_add_staking_tx(sn_keys.pub, alice, MK_COINS(1));
|
||||
gen.create_and_add_next_block({stake});
|
||||
|
||||
oxen_register_callback(events, "test_insufficient_stake_does_not_get_accepted", [sn_keys](cryptonote::core &c, size_t ev_index)
|
||||
|
@ -3004,6 +3010,118 @@ bool oxen_service_nodes_insufficient_contribution::generate(std::vector<test_eve
|
|||
return true;
|
||||
}
|
||||
|
||||
bool oxen_service_nodes_insufficient_contribution_HF18::generate(std::vector<test_event_entry> &events)
|
||||
{
|
||||
auto hard_forks = oxen_generate_hard_fork_table(cryptonote::network_version_19 - 1);
|
||||
oxen_chain_generator gen(events, hard_forks);
|
||||
|
||||
gen.add_blocks_until_version(hard_forks.back().version);
|
||||
gen.add_mined_money_unlock_blocks();
|
||||
|
||||
const auto alice = gen.add_account();
|
||||
const auto tx0 = gen.create_and_add_tx(gen.first_miner_, alice.get_keys().m_account_address, MK_COINS(101));
|
||||
gen.create_and_add_next_block({tx0});
|
||||
gen.add_transfer_unlock_blocks();
|
||||
|
||||
uint64_t operator_portions = STAKING_PORTIONS_V1 / MAX_NUMBER_OF_CONTRIBUTORS_V1;
|
||||
uint64_t operator_amount = service_nodes::get_staking_requirement(cryptonote::FAKECHAIN, hard_forks.back().height) / MAX_NUMBER_OF_CONTRIBUTORS_V1;
|
||||
uint64_t remaining_portions = STAKING_PORTIONS_V1 - operator_portions;
|
||||
uint64_t single_portion_illegal_HF18_legal_HF19 = remaining_portions / (MAX_NUMBER_OF_CONTRIBUTORS_V2 - 1);
|
||||
uint64_t single_contributed_amount = (service_nodes::get_staking_requirement(cryptonote::FAKECHAIN, hard_forks.back().height) - operator_amount) / (MAX_NUMBER_OF_CONTRIBUTORS_V2 - 1);
|
||||
cryptonote::keypair sn_keys{hw::get_device("default")};
|
||||
cryptonote::transaction register_tx = gen.create_registration_tx(gen.first_miner_, sn_keys, operator_portions);
|
||||
gen.add_tx(register_tx);
|
||||
gen.create_and_add_next_block({register_tx});
|
||||
gen.add_transfer_unlock_blocks();
|
||||
|
||||
assert(single_contributed_amount != 0);
|
||||
cryptonote::transaction stake = gen.create_and_add_staking_tx(sn_keys.pub, alice, single_contributed_amount);
|
||||
gen.create_and_add_next_block({stake});
|
||||
|
||||
oxen_register_callback(events, "test_insufficient_stake_does_not_get_accepted", [sn_keys, operator_amount](cryptonote::core &c, size_t ev_index)
|
||||
{
|
||||
DEFINE_TESTS_ERROR_CONTEXT("test_insufficient_stake_does_not_get_accepted");
|
||||
const auto sn_list = c.get_service_node_list_state({sn_keys.pub});
|
||||
CHECK_TEST_CONDITION(sn_list.size() == 1);
|
||||
CHECK_TEST_CONDITION(sn_list[0].info->contributors.size() == 1);
|
||||
|
||||
service_nodes::service_node_pubkey_info const &pubkey_info = sn_list[0];
|
||||
CHECK_EQ(pubkey_info.info->total_contributed, operator_amount);
|
||||
return true;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool oxen_service_nodes_sufficient_contribution_HF19::generate(std::vector<test_event_entry> &events)
|
||||
{
|
||||
auto hard_forks = oxen_generate_hard_fork_table(cryptonote::network_version_19);
|
||||
oxen_chain_generator gen(events, hard_forks);
|
||||
|
||||
gen.add_blocks_until_version(hard_forks.back().version);
|
||||
gen.add_mined_money_unlock_blocks();
|
||||
|
||||
const auto alice = gen.add_account();
|
||||
const auto tx0 = gen.create_and_add_tx(gen.first_miner_, alice.get_keys().m_account_address, MK_COINS(101));
|
||||
gen.create_and_add_next_block({tx0});
|
||||
gen.add_transfer_unlock_blocks();
|
||||
|
||||
uint64_t operator_portions = STAKING_PORTIONS_V1 / MAX_NUMBER_OF_CONTRIBUTORS_V1;
|
||||
uint64_t operator_amount = service_nodes::get_staking_requirement(cryptonote::FAKECHAIN, hard_forks.back().height) / MAX_NUMBER_OF_CONTRIBUTORS_V1;
|
||||
uint64_t remaining_portions = STAKING_PORTIONS_V1 - operator_portions;
|
||||
uint64_t single_portion_illegal_HF18_legal_HF19 = remaining_portions / (MAX_NUMBER_OF_CONTRIBUTORS_V2 - 1);
|
||||
uint64_t single_contributed_amount = (service_nodes::get_staking_requirement(cryptonote::FAKECHAIN, hard_forks.back().height) - operator_amount) / (MAX_NUMBER_OF_CONTRIBUTORS_V2 - 1);
|
||||
uint64_t total_amount = operator_amount + single_contributed_amount;
|
||||
cryptonote::keypair sn_keys{hw::get_device("default")};
|
||||
cryptonote::transaction register_tx = gen.create_registration_tx(gen.first_miner_, sn_keys, operator_portions);
|
||||
gen.add_tx(register_tx);
|
||||
gen.create_and_add_next_block({register_tx});
|
||||
|
||||
assert(single_contributed_amount == 0);
|
||||
cryptonote::transaction stake = gen.create_and_add_staking_tx(sn_keys.pub, alice, single_contributed_amount);
|
||||
gen.create_and_add_next_block({stake});
|
||||
|
||||
oxen_register_callback(events, "test_sufficient_stake_does_get_accepted", [sn_keys, total_amount](cryptonote::core &c, size_t ev_index)
|
||||
{
|
||||
DEFINE_TESTS_ERROR_CONTEXT("test_sufficient_stake_does_get_accepted");
|
||||
const auto sn_list = c.get_service_node_list_state({sn_keys.pub});
|
||||
CHECK_TEST_CONDITION(sn_list.size() == 1);
|
||||
CHECK_TEST_CONDITION(sn_list[0].info->contributors.size() == 2);
|
||||
|
||||
service_nodes::service_node_pubkey_info const &pubkey_info = sn_list[0];
|
||||
CHECK_EQ(pubkey_info.info->total_contributed, total_amount);
|
||||
return true;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool oxen_service_nodes_insufficient_operator_contribution_HF19::generate(std::vector<test_event_entry> &events)
|
||||
{
|
||||
auto hard_forks = oxen_generate_hard_fork_table(cryptonote::network_version_19);
|
||||
oxen_chain_generator gen(events, hard_forks);
|
||||
|
||||
gen.add_blocks_until_version(hard_forks.back().version);
|
||||
gen.add_mined_money_unlock_blocks();
|
||||
|
||||
uint64_t operator_portions = STAKING_PORTIONS_V1 / MAX_NUMBER_OF_CONTRIBUTORS_V2;
|
||||
uint64_t operator_amount = service_nodes::get_staking_requirement(cryptonote::FAKECHAIN, hard_forks.back().height) / MAX_NUMBER_OF_CONTRIBUTORS_V2;
|
||||
cryptonote::keypair sn_keys{hw::get_device("default")};
|
||||
cryptonote::transaction register_tx = gen.create_registration_tx(gen.first_miner_, sn_keys, operator_portions);
|
||||
gen.add_tx(register_tx);
|
||||
gen.create_and_add_next_block({register_tx});
|
||||
|
||||
oxen_register_callback(events, "test_insufficient_operator_stake_does_not_get_accepted", [sn_keys, operator_amount](cryptonote::core &c, size_t ev_index)
|
||||
{
|
||||
DEFINE_TESTS_ERROR_CONTEXT("test_insufficient_operator_stake_does_not_get_accepted");
|
||||
const auto sn_list = c.get_service_node_list_state({sn_keys.pub});
|
||||
CHECK_TEST_CONDITION(sn_list.size() == 0);
|
||||
return true;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static oxen_chain_generator setup_pulse_tests(std::vector<test_event_entry> &events)
|
||||
{
|
||||
auto hard_forks = oxen_generate_hard_fork_table();
|
||||
|
|
|
@ -77,6 +77,9 @@ struct oxen_service_nodes_alt_quorums
|
|||
struct oxen_service_nodes_checkpoint_quorum_size : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
struct oxen_service_nodes_gen_nodes : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
struct oxen_service_nodes_insufficient_contribution : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
struct oxen_service_nodes_insufficient_contribution_HF18 : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
struct oxen_service_nodes_sufficient_contribution_HF19 : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
struct oxen_service_nodes_insufficient_operator_contribution_HF19 : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
struct oxen_service_nodes_test_rollback : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
struct oxen_service_nodes_test_swarms_basic : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
struct oxen_pulse_invalid_validator_bitset : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
|
|
|
@ -335,50 +335,50 @@ TEST(service_nodes, min_portions)
|
|||
uint8_t hf_version = cryptonote::network_version_9_service_nodes;
|
||||
// Test new contributors can *NOT* stake to a registration with under 25% of the total stake if there is more than 25% available.
|
||||
{
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {0, STAKING_PORTIONS}));
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {0, STAKING_PORTIONS_V1}));
|
||||
}
|
||||
|
||||
{
|
||||
const auto small = MIN_PORTIONS - 1;
|
||||
const auto rest = STAKING_PORTIONS - small;
|
||||
const auto small = MIN_PORTIONS_V2 - 1;
|
||||
const auto rest = STAKING_PORTIONS_V1 - small;
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {small, rest}));
|
||||
}
|
||||
|
||||
{
|
||||
/// TODO: fix this test
|
||||
const auto small = MIN_PORTIONS - 1;
|
||||
const auto rest = STAKING_PORTIONS - small - STAKING_PORTIONS / 2;
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {STAKING_PORTIONS / 2, small, rest}));
|
||||
const auto small = MIN_PORTIONS_V2 - 1;
|
||||
const auto rest = STAKING_PORTIONS_V1 - small - STAKING_PORTIONS_V1 / 2;
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {STAKING_PORTIONS_V1 / 2, small, rest}));
|
||||
}
|
||||
|
||||
{
|
||||
const auto small = MIN_PORTIONS - 1;
|
||||
const auto rest = STAKING_PORTIONS - small - 2 * MIN_PORTIONS;
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {MIN_PORTIONS, MIN_PORTIONS, small, rest}));
|
||||
const auto small = MIN_PORTIONS_V2 - 1;
|
||||
const auto rest = STAKING_PORTIONS_V1 - small - 2 * MIN_PORTIONS_V2;
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {MIN_PORTIONS_V2, MIN_PORTIONS_V2, small, rest}));
|
||||
}
|
||||
|
||||
// Test new contributors *CAN* stake as the last person with under 25% if there is less than 25% available.
|
||||
|
||||
// Two contributers
|
||||
{
|
||||
const auto large = 4 * (STAKING_PORTIONS / 5);
|
||||
const auto rest = STAKING_PORTIONS - large;
|
||||
const auto large = 4 * (STAKING_PORTIONS_V1 / 5);
|
||||
const auto rest = STAKING_PORTIONS_V1 - large;
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, {large, rest});
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
// Three contributers
|
||||
{
|
||||
const auto half = STAKING_PORTIONS / 2 - 1;
|
||||
const auto rest = STAKING_PORTIONS - 2 * half;
|
||||
const auto half = STAKING_PORTIONS_V1 / 2 - 1;
|
||||
const auto rest = STAKING_PORTIONS_V1 - 2 * half;
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, {half, half, rest});
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
// Four contributers
|
||||
{
|
||||
const auto third = STAKING_PORTIONS / 3 - 1;
|
||||
const auto rest = STAKING_PORTIONS - 3 * third;
|
||||
const auto third = STAKING_PORTIONS_V1 / 3 - 1;
|
||||
const auto rest = STAKING_PORTIONS_V1 - 3 * third;
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, {third, third, third, rest});
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
|
@ -387,59 +387,59 @@ TEST(service_nodes, min_portions)
|
|||
hf_version = cryptonote::network_version_11_infinite_staking;
|
||||
|
||||
{
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {0, STAKING_PORTIONS}));
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {0, STAKING_PORTIONS_V1}));
|
||||
}
|
||||
|
||||
{
|
||||
const auto small = MIN_PORTIONS - 1;
|
||||
const auto rest = STAKING_PORTIONS - small;
|
||||
const auto small = MIN_PORTIONS_V2 - 1;
|
||||
const auto rest = STAKING_PORTIONS_V1 - small;
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {small, rest}));
|
||||
}
|
||||
|
||||
{
|
||||
const auto small = STAKING_PORTIONS / 8;
|
||||
const auto rest = STAKING_PORTIONS - small - STAKING_PORTIONS / 2;
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {STAKING_PORTIONS / 2, small, rest}));
|
||||
const auto small = STAKING_PORTIONS_V1 / 8;
|
||||
const auto rest = STAKING_PORTIONS_V1 - small - STAKING_PORTIONS_V1 / 2;
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {STAKING_PORTIONS_V1 / 2, small, rest}));
|
||||
}
|
||||
|
||||
{
|
||||
const auto small = MIN_PORTIONS - 1;
|
||||
const auto rest = STAKING_PORTIONS - small - 2 * MIN_PORTIONS;
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {MIN_PORTIONS, MIN_PORTIONS, small, rest}));
|
||||
const auto small = MIN_PORTIONS_V2 - 1;
|
||||
const auto rest = STAKING_PORTIONS_V1 - small - 2 * MIN_PORTIONS_V2;
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {MIN_PORTIONS_V2, MIN_PORTIONS_V2, small, rest}));
|
||||
}
|
||||
|
||||
// Test new contributors *CAN* stake as the last person with under 25% if there is less than 25% available.
|
||||
|
||||
// Two contributers
|
||||
{
|
||||
const auto large = 4 * (STAKING_PORTIONS / 5);
|
||||
const auto rest = STAKING_PORTIONS - large;
|
||||
const auto large = 4 * (STAKING_PORTIONS_V1 / 5);
|
||||
const auto rest = STAKING_PORTIONS_V1 - large;
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, {large, rest});
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
// Three contributers
|
||||
{
|
||||
const auto half = STAKING_PORTIONS / 2 - 1;
|
||||
const auto rest = STAKING_PORTIONS - 2 * half;
|
||||
const auto half = STAKING_PORTIONS_V1 / 2 - 1;
|
||||
const auto rest = STAKING_PORTIONS_V1 - 2 * half;
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, {half, half, rest});
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
// Four contributers
|
||||
{
|
||||
const auto third = STAKING_PORTIONS / 3 - 1;
|
||||
const auto rest = STAKING_PORTIONS - 3 * third;
|
||||
const auto third = STAKING_PORTIONS_V1 / 3 - 1;
|
||||
const auto rest = STAKING_PORTIONS_V1 - 3 * third;
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, {third, third, third, rest});
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
// New test for hf_v11: allow less than 25% stake in the presence of large existing contributions
|
||||
{
|
||||
const auto large = STAKING_PORTIONS / 2;
|
||||
const auto small_1 = STAKING_PORTIONS / 6;
|
||||
const auto small_2 = STAKING_PORTIONS / 6;
|
||||
const auto rest = STAKING_PORTIONS - large - small_1 - small_2;
|
||||
const auto large = STAKING_PORTIONS_V1 / 2;
|
||||
const auto small_1 = STAKING_PORTIONS_V1 / 6;
|
||||
const auto small_2 = STAKING_PORTIONS_V1 / 6;
|
||||
const auto rest = STAKING_PORTIONS_V1 - large - small_1 - small_2;
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, {large, small_1, small_2, rest});
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
|
@ -500,17 +500,10 @@ TEST(service_nodes, min_stake_amount)
|
|||
TEST(service_nodes, service_node_rewards_proportional_to_portions)
|
||||
{
|
||||
{
|
||||
const auto reward_a = cryptonote::get_portion_of_reward(MIN_PORTIONS, COIN);
|
||||
const auto reward_b = cryptonote::get_portion_of_reward(3 * MIN_PORTIONS, COIN);
|
||||
ASSERT_TRUE(3 * reward_a == reward_b);
|
||||
const auto reward_a = cryptonote::get_portion_of_reward(STAKING_PORTIONS_V1/2, COIN);
|
||||
const auto reward_b = cryptonote::get_portion_of_reward(STAKING_PORTIONS_V1, COIN);
|
||||
ASSERT_EQ(2 * reward_a, reward_b);
|
||||
}
|
||||
|
||||
{
|
||||
const auto reward_a = cryptonote::get_portion_of_reward(STAKING_PORTIONS/2, COIN);
|
||||
const auto reward_b = cryptonote::get_portion_of_reward(STAKING_PORTIONS, COIN);
|
||||
ASSERT_TRUE(2 * reward_a == reward_b);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST(service_nodes, service_node_get_locked_key_image_unlock_height)
|
||||
|
|
|
@ -126,18 +126,18 @@ TEST(SQLITE, CalculateRewards)
|
|||
EXPECT_TRUE(multiple_rewards[2].amount == 68);
|
||||
|
||||
// Check that 3 contributors receives their portion of the block reward when the operator takes a 10% fee
|
||||
multiple_contributors.portions_for_operator = STAKING_PORTIONS/10;
|
||||
multiple_contributors.portions_for_operator = STAKING_PORTIONS_V1/10;
|
||||
multiple_contributors.operator_address = first_address.address;
|
||||
block.reward = 1000;
|
||||
auto multiple_rewards_with_fee = sqliteDB.calculate_rewards(block.major_version, block.reward, multiple_contributors);
|
||||
// Operator gets 10%
|
||||
EXPECT_TRUE(multiple_rewards_with_fee[0].amount == 99);
|
||||
EXPECT_TRUE(tools::view_guts(multiple_rewards_with_fee[0].address_info.address) == tools::view_guts(first_address.address));
|
||||
EXPECT_EQ(multiple_rewards_with_fee[0].amount, 99);
|
||||
EXPECT_EQ(tools::view_guts(multiple_rewards_with_fee[0].address_info.address), tools::view_guts(first_address.address));
|
||||
// Contributors (including operator) receive the balance
|
||||
EXPECT_TRUE(multiple_rewards_with_fee[1].amount == 297);
|
||||
EXPECT_TRUE(tools::view_guts(multiple_rewards_with_fee[1].address_info.address) == tools::view_guts(first_address.address));
|
||||
EXPECT_TRUE(multiple_rewards_with_fee[2].amount == 297);
|
||||
EXPECT_TRUE(tools::view_guts(multiple_rewards_with_fee[2].address_info.address) == tools::view_guts(second_address.address));
|
||||
EXPECT_TRUE(multiple_rewards_with_fee[3].amount == 306);
|
||||
EXPECT_TRUE(tools::view_guts(multiple_rewards_with_fee[3].address_info.address) == tools::view_guts(third_address.address));
|
||||
EXPECT_EQ(multiple_rewards_with_fee[1].amount, 297);
|
||||
EXPECT_EQ(tools::view_guts(multiple_rewards_with_fee[1].address_info.address), tools::view_guts(first_address.address));
|
||||
EXPECT_EQ(multiple_rewards_with_fee[2].amount, 297);
|
||||
EXPECT_EQ(tools::view_guts(multiple_rewards_with_fee[2].address_info.address), tools::view_guts(second_address.address));
|
||||
EXPECT_EQ(multiple_rewards_with_fee[3].amount, 306);
|
||||
EXPECT_EQ(tools::view_guts(multiple_rewards_with_fee[3].address_info.address), tools::view_guts(third_address.address));
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ def instruct_daemon(method, params):
|
|||
|
||||
|
||||
parser = argparse.ArgumentParser(description='Get Block.')
|
||||
parser.add_argument("--hash", help="An integer for the height to be queried", type=int)
|
||||
parser.add_argument("--hash", help="An integer for the height to be queried", type=str)
|
||||
args = parser.parse_args()
|
||||
|
||||
if not (args.hash):
|
||||
|
|
|
@ -374,10 +374,10 @@ class Wallet(RPCDaemon):
|
|||
if 'error' in r:
|
||||
raise RuntimeError("Failed to submit service node registration tx: {}".format(r['error']['message']))
|
||||
|
||||
def register_sn_for_contributions(self, sn):
|
||||
def register_sn_for_contributions(self, sn, cut, amount):
|
||||
r = sn.json_rpc("get_service_node_registration_cmd", {
|
||||
"operator_cut": "10",
|
||||
"contributions": [{"address": self.address(), "amount": 50000000000}],
|
||||
"operator_cut": str(cut),
|
||||
"contributions": [{"address": self.address(), "amount": amount}],
|
||||
"staking_requirement": 100000000000
|
||||
}).json()
|
||||
if 'error' in r:
|
||||
|
@ -387,10 +387,10 @@ class Wallet(RPCDaemon):
|
|||
if 'error' in r:
|
||||
raise RuntimeError("Failed to submit service node registration tx: {}".format(r['error']['message']))
|
||||
|
||||
def contribute_to_sn(self, sn):
|
||||
def contribute_to_sn(self, sn, amount):
|
||||
r = self.json_rpc("stake", {
|
||||
"destination": self.address(),
|
||||
"amount": 50000000000,
|
||||
"amount": amount,
|
||||
"service_node_key": sn.sn_key(),
|
||||
}).json()
|
||||
if 'error' in r:
|
||||
|
|
|
@ -72,6 +72,14 @@ class SNNetwork:
|
|||
|
||||
self.alice, self.bob, self.mike = self.wallets
|
||||
|
||||
self.extrawallets = []
|
||||
for name in range(9):
|
||||
self.extrawallets.append(Wallet(
|
||||
node=self.nodes[len(self.extrawallets) % len(self.nodes)],
|
||||
name="extrawallet-"+str(name),
|
||||
rpc_wallet=self.binpath+'/oxen-wallet-rpc',
|
||||
datadir=datadir))
|
||||
|
||||
# Interconnections
|
||||
for i in range(len(self.all_nodes)):
|
||||
for j in (2, 3, 5, 7, 11):
|
||||
|
@ -105,8 +113,18 @@ class SNNetwork:
|
|||
w.refresh()
|
||||
vprint("Wallet {w.name} is ready: {a}".format(w=w, a=w.address()))
|
||||
|
||||
for w in self.extrawallets:
|
||||
vprint("Starting new RPC wallet {w.name} at {w.listen_ip}:{w.rpc_port}".format(w=w))
|
||||
w.start()
|
||||
for w in self.extrawallets:
|
||||
w.ready()
|
||||
w.refresh()
|
||||
vprint("Wallet {w.name} is ready: {a}".format(w=w, a=w.address()))
|
||||
|
||||
for w in self.wallets:
|
||||
w.wait_for_json_rpc("refresh")
|
||||
for w in self.extrawallets:
|
||||
w.wait_for_json_rpc("refresh")
|
||||
|
||||
configfile=self.datadir+'config.py'
|
||||
with open(configfile, 'w') as filetowrite:
|
||||
|
@ -121,7 +139,7 @@ class SNNetwork:
|
|||
# of 18.9, which means each registration requires 6 inputs. Thus we need a bare minimum of
|
||||
# 6(N-5) blocks, plus the 30 lock time on coinbase TXes = 6N more blocks (after the initial
|
||||
# 5 registrations).
|
||||
self.mine(100)
|
||||
self.mine(200)
|
||||
vprint("Submitting first round of service node registrations: ", end="", flush=True)
|
||||
for sn in self.sns[0:5]:
|
||||
self.mike.register_sn(sn)
|
||||
|
@ -145,7 +163,11 @@ class SNNetwork:
|
|||
self.print_wallet_balances()
|
||||
|
||||
vprint("Mining 40 blocks (registrations + blink quorum lag) and waiting for nodes to sync")
|
||||
self.sync_nodes(self.mine(40), timeout=120)
|
||||
self.sync_nodes(self.mine(39), timeout=120)
|
||||
for wallet in self.extrawallets:
|
||||
self.mike.transfer(wallet, 11000000000)
|
||||
self.sync_nodes(self.mine(1), timeout=120)
|
||||
|
||||
|
||||
self.print_wallet_balances()
|
||||
|
||||
|
@ -156,6 +178,7 @@ class SNNetwork:
|
|||
all_service_nodes_proofed = lambda sn: all(x['quorumnet_port'] > 0 for x in
|
||||
sn.json_rpc("get_n_service_nodes", {"fields":{"quorumnet_port":True}}).json()['result']['service_node_states'])
|
||||
|
||||
|
||||
vprint("Waiting for proofs to propagate: ", end="", flush=True)
|
||||
for sn in self.sns:
|
||||
wait_for(lambda: all_service_nodes_proofed(sn), timeout=120)
|
||||
|
@ -170,11 +193,13 @@ class SNNetwork:
|
|||
# self.bob.register_sn(self.sns[-1])
|
||||
|
||||
# This commented out code will register the last SN through Bobs wallet (Has not done any others)
|
||||
# and also get alice to contribute 50% of the node with a 10% operator fee
|
||||
self.bob.register_sn_for_contributions(self.sns[-1])
|
||||
self.sync_nodes(self.mine(5), timeout=120)
|
||||
self.alice.contribute_to_sn(self.sns[-1])
|
||||
self.sync_nodes(self.mine(2), timeout=120)
|
||||
# and also get 9 other wallets to contribute the rest of the node with a 10% operator fee
|
||||
self.bob.register_sn_for_contributions(self.sns[-1], 10, 28000000000)
|
||||
self.sync_nodes(self.mine(10), timeout=120)
|
||||
self.print_wallet_balances()
|
||||
for wallet in self.extrawallets:
|
||||
wallet.contribute_to_sn(self.sns[-1], 8000000000)
|
||||
self.sync_nodes(self.mine(1), timeout=120)
|
||||
time.sleep(10)
|
||||
for sn in self.sns:
|
||||
sn.send_uptime_proof()
|
||||
|
@ -264,6 +289,10 @@ class SNNetwork:
|
|||
b = w.balances(refresh=True)
|
||||
vprint(" {:5s}: {:.9f} (total) with {:.9f} (unlocked)".format(
|
||||
w.name, b[0] * 1e-9, b[1] * 1e-9))
|
||||
for w in self.extrawallets:
|
||||
b = w.balances(refresh=True)
|
||||
vprint(" {:5s}: {:.9f} (total) with {:.9f} (unlocked)".format(
|
||||
w.name, b[0] * 1e-9, b[1] * 1e-9))
|
||||
|
||||
|
||||
def __del__(self):
|
||||
|
|
Loading…
Reference in New Issue