Remove pre-HF19 registration commands from oxend

This commit is contained in:
Jason Rhinelander 2022-08-30 19:26:31 -03:00 committed by Sean Darcy
parent a9c83bd0b2
commit 3662cb21fb
3 changed files with 4 additions and 414 deletions

View file

@ -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)

View file

@ -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);
}

View file

@ -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>();