mirror of
https://github.com/oxen-io/oxen-core.git
synced 2023-12-14 02:22:56 +01:00
Remove pre-HF19 registration commands from oxend
This commit is contained in:
parent
a9c83bd0b2
commit
3662cb21fb
|
@ -238,22 +238,6 @@ uint64_t get_portions_to_make_amount(uint64_t staking_requirement, uint64_t amou
|
|||
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 = cryptonote::old::STAKING_PORTIONS;
|
||||
}
|
||||
else
|
||||
{
|
||||
portions = (cur_percent / 100.0) * (double)cryptonote::old::STAKING_PORTIONS;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<double> parse_fee_percent(std::string_view fee)
|
||||
{
|
||||
if (tools::ends_with(fee, "%"))
|
||||
|
@ -272,14 +256,6 @@ std::optional<double> parse_fee_percent(std::string_view fee)
|
|||
return percent;
|
||||
}
|
||||
|
||||
bool get_portions_from_percent_str(std::string cut_str, uint64_t& portions) {
|
||||
|
||||
if (auto pct = parse_fee_percent(cut_str))
|
||||
return get_portions_from_percent(*pct, portions);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t percent_to_basis_points(std::string percent_string) {
|
||||
const auto percent = parse_fee_percent(percent_string);
|
||||
if (!percent)
|
||||
|
|
|
@ -301,7 +301,6 @@ uint64_t get_portions_to_make_amount(uint64_t staking_requirement, uint64_t amou
|
|||
|
||||
std::optional<double> parse_fee_percent(std::string_view fee);
|
||||
|
||||
bool get_portions_from_percent_str(std::string cut_str, uint64_t& portions);
|
||||
uint64_t percent_to_basis_points(std::string percent_string);
|
||||
|
||||
}
|
||||
|
|
|
@ -1901,11 +1901,6 @@ bool rpc_command_executor::print_sn_key()
|
|||
|
||||
namespace {
|
||||
|
||||
uint64_t get_actual_amount(uint64_t amount, uint64_t portions)
|
||||
{
|
||||
return mul128_div64(amount, portions, cryptonote::old::STAKING_PORTIONS);
|
||||
}
|
||||
|
||||
// Returns an error message on invalid, nullopt if good
|
||||
std::optional<std::string_view> is_invalid_staking_address(
|
||||
std::string_view addr,
|
||||
|
@ -1984,388 +1979,6 @@ std::string highlight_money(uint64_t amount) {
|
|||
|
||||
} // anon. namespace
|
||||
|
||||
|
||||
// Legacy pre-HF19 registrations (with 4 contributions and portions instead of amounts).
|
||||
//
|
||||
// TODO XXX TODO: This code is vastly duplicated from the post-HF19 version (except that it
|
||||
// calculates everything in portions rather than amounts). This is very un-DRY, but it is highly
|
||||
// temporary: it is *only* needed for oxen 10.x clients to be able to produce valid registrations
|
||||
// prior to the hard fork: it can be removed entirely as soon as HF19 happens.
|
||||
bool rpc_command_executor::prepare_registration_hf18(hf hf_version, bool force_registration) {
|
||||
|
||||
auto scoped_log_cats = std::make_unique<clear_log_categories>();
|
||||
|
||||
// Check if the daemon was started in Service Node or not
|
||||
GET_INFO::response res{};
|
||||
GET_SERVICE_KEYS::response kres{};
|
||||
if (!invoke<GET_INFO>({}, res, "Failed to get node info") ||
|
||||
!invoke<GET_SERVICE_KEYS>({}, kres, "Failed to retrieve service node keys"))
|
||||
return false;
|
||||
|
||||
cryptonote::network_type const nettype =
|
||||
res.mainnet ? cryptonote::network_type::MAINNET :
|
||||
res.devnet ? cryptonote::network_type::DEVNET :
|
||||
res.testnet ? cryptonote::network_type::TESTNET :
|
||||
res.nettype == "fakechain" ? cryptonote::network_type::FAKECHAIN :
|
||||
cryptonote::network_type::UNDEFINED;
|
||||
|
||||
if (!check_service_node_running(res, force_registration))
|
||||
return false;
|
||||
|
||||
uint64_t block_height = std::max(res.height, res.target_height);
|
||||
|
||||
// Query the latest block we've synced and check that the timestamp is sensible, issue a warning if not
|
||||
if (!check_blockchain_synced(*this, block_height))
|
||||
return false;
|
||||
|
||||
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
|
||||
|
||||
fmt::print("\n\n\x1b[33;1m"
|
||||
"Oxen Service Node Registration\n"
|
||||
"------------------------------\n"
|
||||
"Service Node Pubkey: \x1b[32;1m{}\x1b[33;1m\n"
|
||||
"Staking requirement: {} from up to {} contributors\n\n",
|
||||
kres.service_node_pubkey,
|
||||
highlight_money(staking_requirement),
|
||||
oxen::MAX_CONTRIBUTORS_V1);
|
||||
|
||||
enum struct register_step
|
||||
{
|
||||
ask_address,
|
||||
ask_amount,
|
||||
get_operator_fee,
|
||||
summary_info,
|
||||
final_summary,
|
||||
cancelled_by_user,
|
||||
};
|
||||
|
||||
struct prepare_registration_state
|
||||
{
|
||||
register_step prev_step = register_step::ask_address;
|
||||
uint64_t operator_fee_portions = cryptonote::old::STAKING_PORTIONS;
|
||||
uint64_t portions_remaining = cryptonote::old::STAKING_PORTIONS;
|
||||
uint64_t total_reserved_contributions = 0;
|
||||
std::vector<std::pair<std::string, uint64_t>> contributions;
|
||||
};
|
||||
|
||||
prepare_registration_state state{};
|
||||
std::stack<prepare_registration_state> state_stack;
|
||||
state_stack.push(state);
|
||||
|
||||
|
||||
bool finished = false;
|
||||
bool go_back = false;
|
||||
auto step = register_step::ask_address;
|
||||
|
||||
auto next_step = [&](register_step next)
|
||||
{
|
||||
state.prev_step = step;
|
||||
step = next;
|
||||
state_stack.push(state);
|
||||
std::cout << std::endl;
|
||||
};
|
||||
auto check_cancel_back = [&](input_line_result result) -> bool {
|
||||
switch (result) {
|
||||
case input_line_result::cancel:
|
||||
step = register_step::cancelled_by_user;
|
||||
return true;
|
||||
case input_line_result::back:
|
||||
go_back = true;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// anything less than DUST will be added to operator stake
|
||||
constexpr uint64_t DUST = oxen::MAX_CONTRIBUTORS_V1;
|
||||
|
||||
while (!finished)
|
||||
{
|
||||
if (go_back)
|
||||
{
|
||||
step = state.prev_step;
|
||||
state_stack.pop();
|
||||
state = state_stack.top();
|
||||
go_back = false;
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
switch(step)
|
||||
{
|
||||
case register_step::ask_address:
|
||||
{
|
||||
bool is_operator = state.contributions.empty();
|
||||
std::string prompt;
|
||||
if (is_operator)
|
||||
prompt = "\n\nEnter the OXEN address of the the Service Node operator\n";
|
||||
else
|
||||
prompt = fmt::format("\n\nThis service node requires an additional stake of {}.\n\n"
|
||||
"To add a reserved contribution spot enter the contributor's OXEN address now.\n"
|
||||
"Leave this blank to leave the remaining stake open to public contributors.\n",
|
||||
highlight_money(staking_requirement - state.total_reserved_contributions));
|
||||
auto [result, address_str] = input_line_value(prompt, /*back=*/ !is_operator);
|
||||
|
||||
if (check_cancel_back(result))
|
||||
break;
|
||||
|
||||
if (!is_operator && address_str.empty())
|
||||
next_step(register_step::get_operator_fee);
|
||||
else if (auto bad = is_invalid_staking_address(address_str, nettype))
|
||||
tools::fail_msg_writer() << *bad << std::endl;
|
||||
else if (std::any_of(state.contributions.begin(), state.contributions.end(), [a=address_str](auto& b) { return b.first == a; }))
|
||||
tools::fail_msg_writer() << "Invalid OXEN address: you cannot provide the same address twice" << std::endl;
|
||||
else
|
||||
{
|
||||
state.contributions.emplace_back(std::move(address_str), 0);
|
||||
next_step(register_step::ask_amount);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case register_step::ask_amount:
|
||||
{
|
||||
bool is_operator = state.total_reserved_contributions == 0;
|
||||
uint64_t amount_left = staking_requirement - state.total_reserved_contributions;
|
||||
uint64_t min_contribution_portions = is_operator
|
||||
? service_nodes::MINIMUM_OPERATOR_PORTION
|
||||
: service_nodes::get_min_node_contribution_in_portions(
|
||||
hf_version, staking_requirement, state.total_reserved_contributions, state.contributions.size() - 1);
|
||||
uint64_t min_contribution = service_nodes::portions_to_amount(staking_requirement, min_contribution_portions);
|
||||
|
||||
auto [result, contribution_str] = input_line_value(fmt::format(
|
||||
"\n\nThe {} must stake between {} and {}.\n\n"
|
||||
"How much OXEN does {} want to stake?",
|
||||
is_operator ? "operator" : "next contributor",
|
||||
highlight_money(min_contribution),
|
||||
highlight_money(amount_left),
|
||||
is_operator ? "the operator" : fmt::format("contributor {}", state.contributions.size() - 1)),
|
||||
true,
|
||||
"/\x1b[36;1mmax\x1b[0m/\x1b[36;1mmin\x1b[0m",
|
||||
"max"
|
||||
);
|
||||
|
||||
if (check_cancel_back(result))
|
||||
break;
|
||||
|
||||
uint64_t contribution;
|
||||
if (contribution_str == "max")
|
||||
{
|
||||
fmt::print("Using maximum contribution ({})\n", highlight_money(amount_left));
|
||||
contribution = amount_left;
|
||||
}
|
||||
else if (contribution_str == "min")
|
||||
{
|
||||
fmt::print("Using minimum contribution ({})\n", highlight_money(min_contribution));
|
||||
contribution = min_contribution;
|
||||
}
|
||||
else if (auto c = cryptonote::parse_amount(contribution_str))
|
||||
contribution = *c;
|
||||
else
|
||||
{
|
||||
tools::fail_msg_writer() << "Invalid amount." << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
if (contribution > amount_left)
|
||||
{
|
||||
tools::fail_msg_writer() << fmt::format(
|
||||
"Invalid amount: The contribution exceeds the remaining staking requirement ({}).\n",
|
||||
highlight_money(amount_left));
|
||||
break;
|
||||
}
|
||||
|
||||
uint64_t portions = service_nodes::get_portions_to_make_amount(staking_requirement, contribution);
|
||||
if (portions < min_contribution_portions)
|
||||
{
|
||||
tools::fail_msg_writer() << "Invalid amount: that contribution does not meet the minimum staking requirement.\n";
|
||||
break;
|
||||
}
|
||||
|
||||
if (portions > state.portions_remaining)
|
||||
portions = state.portions_remaining;
|
||||
|
||||
state.contributions.back().second = portions;
|
||||
state.portions_remaining -= portions;
|
||||
state.total_reserved_contributions += get_actual_amount(staking_requirement, portions);
|
||||
|
||||
next_step(
|
||||
state.portions_remaining > 0 ? register_step::ask_address :
|
||||
register_step::get_operator_fee);
|
||||
break;
|
||||
}
|
||||
|
||||
case register_step::get_operator_fee:
|
||||
{
|
||||
if (state.contributions.size() == 1 && state.portions_remaining == 0)
|
||||
{
|
||||
// Solo node, don't need to ask the fee
|
||||
state.operator_fee_portions = cryptonote::old::STAKING_PORTIONS;
|
||||
step = register_step::summary_info; // Not next_step() because we are skipping this step
|
||||
}
|
||||
else
|
||||
{
|
||||
auto [result, operator_fee_str] = input_line_value(R"(
|
||||
|
||||
|
||||
This service node has multiple contributors and thus requires an operator fee
|
||||
percentage. This percentage is removed from the block reward and assigned to
|
||||
the operator, then the remaining reward is split among contributors (including
|
||||
the operator) proportionally to their contribution.
|
||||
|
||||
Enter the operator fee as a percentage [0.00-100.00])");
|
||||
|
||||
if (check_cancel_back(result))
|
||||
break;
|
||||
|
||||
if (!service_nodes::get_portions_from_percent_str(operator_fee_str, state.operator_fee_portions))
|
||||
{
|
||||
tools::fail_msg_writer() << "Invalid value: " << operator_fee_str << ". Should be between [0-100]" << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
next_step(register_step::summary_info);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case register_step::summary_info:
|
||||
{
|
||||
uint64_t open_spots = oxen::MAX_CONTRIBUTORS_V1 - 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)
|
||||
{
|
||||
// Not calling next_step here because we have no state change to push
|
||||
step = register_step::final_summary;
|
||||
std::cout << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
fmt::print(R"(
|
||||
The total reserved amount ({}) is less than the required full stake ({}).
|
||||
The remaining stake ({}) will be open to contribution from {}.
|
||||
The Service Node will not activate until the entire stake has been contributed.
|
||||
|
||||
)",
|
||||
highlight_money(state.total_reserved_contributions),
|
||||
highlight_money(staking_requirement),
|
||||
highlight_money(amount_left),
|
||||
open_spots > 1 ? fmt::format("1-{} public contributors", open_spots) : "1 public contributor"
|
||||
);
|
||||
|
||||
auto result = input_line_ask("Is this acceptable?");
|
||||
if (result == input_line_result::no)
|
||||
result = input_line_result::cancel;
|
||||
if (check_cancel_back(result))
|
||||
break;
|
||||
|
||||
next_step(register_step::final_summary);
|
||||
break;
|
||||
}
|
||||
|
||||
case register_step::final_summary:
|
||||
{
|
||||
const uint64_t amount_left = staking_requirement - state.total_reserved_contributions;
|
||||
|
||||
std::cout << "\nRegistration Summary:\n\n";
|
||||
|
||||
std::cout << "Service Node Pubkey: \x1b[32;1m" << kres.service_node_pubkey << "\x1b[0m\n" << std::endl;
|
||||
|
||||
if (amount_left > 0 || state.contributions.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>(cryptonote::old::STAKING_PORTIONS));
|
||||
|
||||
constexpr auto row = "{:^14} {:^13} {:>17} {:>8}\n"sv;
|
||||
fmt::print(row, "Contributor", "Address", "Contribution", "Contr. %");
|
||||
fmt::print(row, "_____________", "_____________", "_________________", "________");
|
||||
fmt::print("\n");
|
||||
|
||||
for (size_t i = 0; i < state.contributions.size(); ++i)
|
||||
{
|
||||
const auto& [addr, portion] = state.contributions[i];
|
||||
uint64_t amount = get_actual_amount(staking_requirement, portion);
|
||||
if (amount_left <= DUST && i == 0)
|
||||
amount += amount_left; // add dust to the operator.
|
||||
fmt::print(row,
|
||||
(i==0) ? "Operator" : "Contributor " + std::to_string(i),
|
||||
addr.substr(0, 9) + ".." + addr.substr(addr.size() - 2),
|
||||
cryptonote::print_money(amount),
|
||||
fmt::format("{:.2f}%", portion * 100.0 / (double)cryptonote::old::STAKING_PORTIONS));
|
||||
}
|
||||
|
||||
if (amount_left > DUST)
|
||||
{
|
||||
size_t open_spots = oxen::MAX_CONTRIBUTORS_V1 - state.contributions.size();
|
||||
for (size_t i = 0; i < open_spots; i++) {
|
||||
fmt::print(row,
|
||||
"(open)",
|
||||
"(any)",
|
||||
i == 0 && open_spots == 1 ? cryptonote::print_money(amount_left) :
|
||||
i == 0 ? ">=" + cryptonote::print_money((amount_left + open_spots - 1) / open_spots) :
|
||||
"",
|
||||
i == 0 && open_spots == 1 ? fmt::format("{:.2f}%", amount_left * 100.0 / staking_requirement) :
|
||||
i == 0 ? fmt::format(">={:.2f}%", amount_left * 100.0 / staking_requirement / open_spots) :
|
||||
"");
|
||||
}
|
||||
}
|
||||
else if (amount_left > 0)
|
||||
std::cout << R"(
|
||||
Actual amounts may differ slightly from specification because of limitations on
|
||||
the way fractions are represented on the blockchain.
|
||||
|
||||
)";
|
||||
|
||||
auto result = input_line_ask("\nIs the staking information above correct?");
|
||||
if (result == input_line_result::no)
|
||||
result = input_line_result::cancel;
|
||||
if (check_cancel_back(result))
|
||||
break;
|
||||
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case register_step::cancelled_by_user:
|
||||
{
|
||||
tools::fail_msg_writer() << "Registration preparation cancelled." << std::endl;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// <operator cut> <address> <fraction> [<address> <fraction> [...]]]
|
||||
std::vector<std::string> args;
|
||||
args.push_back(std::to_string(state.operator_fee_portions));
|
||||
for (const auto& [addr, portion] : state.contributions)
|
||||
{
|
||||
args.push_back(addr);
|
||||
args.push_back(std::to_string(portion));
|
||||
}
|
||||
|
||||
scoped_log_cats.reset();
|
||||
|
||||
GET_SERVICE_NODE_REGISTRATION_CMD_RAW::request req{};
|
||||
req.args = args;
|
||||
req.make_friendly = true;
|
||||
req.staking_requirement = staking_requirement;
|
||||
|
||||
if (GET_SERVICE_NODE_REGISTRATION_CMD_RAW::response res{};
|
||||
invoke<GET_SERVICE_NODE_REGISTRATION_CMD_RAW>(std::move(req), res,
|
||||
"Failed to validate registration arguments; check the addresses and registration parameters,\n"
|
||||
"make sure oxend is running as a service node, and check oxend's error log for more details."))
|
||||
{
|
||||
std::cout << "\n\n";
|
||||
tools::success_msg_writer() << res.registration_cmd;
|
||||
std::cout << "\n\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool rpc_command_executor::prepare_registration(bool force_registration)
|
||||
{
|
||||
|
||||
|
@ -2374,8 +1987,10 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
|
|||
return false;
|
||||
|
||||
auto hf_version = hf_res.version;
|
||||
if (hf_version < hf::hf19_reward_batching)
|
||||
return prepare_registration_hf18(hf_version, force_registration);
|
||||
if (hf_version < hf::hf19_reward_batching) {
|
||||
tools::fail_msg_writer() << "Error: this command only supports HF19+";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto scoped_log_cats = std::make_unique<clear_log_categories>();
|
||||
|
||||
|
|
Loading…
Reference in a new issue