oxen-core/src/cryptonote_core/service_node_rules.cpp

225 lines
7.0 KiB
C++

#include "cryptonote_config.h"
#include "cryptonote_basic/hardfork.h"
#include "common/oxen.h"
#include "epee/int-util.h"
#include <boost/endian/conversion.hpp>
#include <limits>
#include <vector>
#include <boost/lexical_cast.hpp>
#include <cfenv>
#include "service_node_rules.h"
namespace service_nodes {
// TODO(oxen): Move to oxen_economy, this will also need access to oxen::exp2
uint64_t get_staking_requirement(cryptonote::network_type nettype, uint64_t height)
{
if (nettype == cryptonote::TESTNET || nettype == cryptonote::FAKECHAIN || nettype == cryptonote::DEVNET)
return COIN * 100;
if (is_hard_fork_at_least(nettype, cryptonote::network_version_16_pulse, height))
return 15000'000000000;
if (is_hard_fork_at_least(nettype, cryptonote::network_version_13_enforce_checkpoints, height))
{
constexpr int64_t heights[] = {
385824,
429024,
472224,
515424,
558624,
601824,
645024,
};
constexpr int64_t lsr[] = {
20458'380815527,
19332'319724305,
18438'564443912,
17729'190407764,
17166'159862153,
16719'282221956,
16364'595203882,
};
assert(static_cast<int64_t>(height) >= heights[0]);
constexpr uint64_t LAST_HEIGHT = heights[oxen::array_count(heights) - 1];
constexpr uint64_t LAST_REQUIREMENT = lsr [oxen::array_count(lsr) - 1];
if (height >= LAST_HEIGHT)
return LAST_REQUIREMENT;
size_t i = 0;
for (size_t index = 1; index < oxen::array_count(heights); index++)
{
if (heights[index] > static_cast<int64_t>(height))
{
i = (index - 1);
break;
}
}
int64_t H = height;
int64_t result = lsr[i] + (H - heights[i]) * ((lsr[i + 1] - lsr[i]) / (heights[i + 1] - heights[i]));
return static_cast<uint64_t>(result);
}
uint64_t hardfork_height = 101250;
if (height < hardfork_height) height = hardfork_height;
uint64_t height_adjusted = height - hardfork_height;
uint64_t base = 0, variable = 0;
std::fesetround(FE_TONEAREST);
if (is_hard_fork_at_least(nettype, cryptonote::network_version_11_infinite_staking, height))
{
base = 15000 * COIN;
variable = (25007.0 * COIN) / oxen::exp2(height_adjusted/129600.0);
}
else
{
base = 10000 * COIN;
variable = (35000.0 * COIN) / oxen::exp2(height_adjusted/129600.0);
}
uint64_t result = base + variable;
return result;
}
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_V1, &resulthi, &resultlo);
return resultlo;
}
bool check_service_node_portions(uint8_t hf_version, const std::vector<uint64_t>& portions)
{
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_V1, reserved, i);
if (portions[i] < min_portions) return false;
reserved += portions[i];
}
return reserved <= STAKING_PORTIONS_V1;
}
crypto::hash generate_request_stake_unlock_hash(uint32_t nonce)
{
static_assert(sizeof(crypto::hash) == 8 * sizeof(uint32_t) && alignof(crypto::hash) >= alignof(uint32_t));
crypto::hash result;
boost::endian::native_to_little_inplace(nonce);
for (size_t i = 0; i < 8; i++)
reinterpret_cast<uint32_t*>(result.data)[i] = nonce;
return result;
}
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_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_V1);
}
uint64_t get_max_node_contribution(uint8_t version, uint64_t staking_requirement, uint64_t total_reserved)
{
if (version >= cryptonote::network_version_16_pulse)
return (staking_requirement - total_reserved) * config::MAXIMUM_ACCEPTABLE_STAKE::num
/ config::MAXIMUM_ACCEPTABLE_STAKE::den;
return std::numeric_limits<uint64_t>::max();
}
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_infinite_staking)
return get_min_node_contribution_pre_v11(staking_requirement, total_reserved);
const uint64_t needed = staking_requirement - total_reserved;
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;
}
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 = (atomic_amount == UINT64_MAX) ? UINT64_MAX : (get_portions_to_make_amount(staking_requirement, atomic_amount));
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;
lo = mul128(amount, max_portions, &hi);
if (lo > UINT64_MAX - (staking_requirement - 1))
hi++;
lo += staking_requirement-1;
div128_64(hi, lo, staking_requirement, &resulthi, &resultlo);
return resultlo;
}
static bool get_portions_from_percent(double cur_percent, uint64_t& portions) {
if(cur_percent < 0.0 || cur_percent > 100.0) return false;
// Fix for truncation issue when operator cut = 100 for a pool Service Node.
if (cur_percent == 100.0)
{
portions = STAKING_PORTIONS_V1;
}
else
{
portions = (cur_percent / 100.0) * (double)STAKING_PORTIONS_V1;
}
return true;
}
bool get_portions_from_percent_str(std::string cut_str, uint64_t& portions) {
if(!cut_str.empty() && cut_str.back() == '%')
{
cut_str.pop_back();
}
double cut_percent;
try
{
cut_percent = boost::lexical_cast<double>(cut_str);
}
catch(...)
{
return false;
}
return get_portions_from_percent(cut_percent, portions);
}
} // namespace service_nodes