Merge pull request #1530 from jagerman/prepare-reg-disp-fix

Prepare registration fixes and improvements
This commit is contained in:
Jason Rhinelander 2022-04-18 21:17:07 -03:00 committed by GitHub
commit 9a6c4a93a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 357 additions and 461 deletions

View File

@ -353,69 +353,54 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------
uint64_t power_integral(uint64_t a, uint64_t b)
{
if(b == 0)
return 1;
uint64_t total = a;
for(uint64_t i = 1; i != b; i++)
total *= a;
return total;
}
//---------------------------------------------------------------
bool parse_amount(uint64_t& amount, std::string_view str_amount)
std::optional<uint64_t> parse_amount(std::string_view str_amount)
{
uint64_t amount;
tools::trim(str_amount);
auto parts = tools::split(str_amount, "."sv);
if (parts.size() > 2)
return false; // 123.456.789 no thanks.
return std::nullopt; // 123.456.789 no thanks.
if (parts.size() == 2 && parts[1].empty())
parts.pop_back(); // allow "123." (treat it as as "123")
if (parts[0].find_first_not_of("0123456789"sv) != std::string::npos)
return false; // whole part contains non-digit
return std::nullopt; // whole part contains non-digit
if (parts[0].empty()) {
// Only allow an empty whole number part if there is a fractional part.
if (parts.size() == 1)
return false;
return std::nullopt;
amount = 0;
}
else
{
if (!tools::parse_int(parts[0], amount))
return false;
return std::nullopt;
// Scale up the number (e.g. 12 from "12.45") to atomic units.
//
// TODO: get rid of the user-configurable default_decimal_point nonsense and just multiply
// this value by the `COIN` constant.
for (size_t i = 0; i < CRYPTONOTE_DISPLAY_DECIMAL_POINT; i++)
{
if (amount > std::numeric_limits<uint64_t>::max() / 10)
return false; // would overflow
amount *= 10;
}
if (amount > std::numeric_limits<uint64_t>::max() / COIN)
return std::nullopt; // would overflow
amount *= COIN;
}
if (parts.size() == 1)
return true;
return amount;
if (parts[1].find_first_not_of("0123456789"sv) != std::string::npos)
return false; // fractional part contains non-digit
return std::nullopt; // fractional part contains non-digit
// If too long, but with insignificant 0's, trim them off
while (parts[1].size() > CRYPTONOTE_DISPLAY_DECIMAL_POINT && parts[1].back() == '0')
parts[1].remove_suffix(1);
if (parts[1].size() > CRYPTONOTE_DISPLAY_DECIMAL_POINT)
return false; // fractional part has too many significant digits
return std::nullopt; // fractional part has too many significant digits
uint64_t fractional;
if (!tools::parse_int(parts[1], fractional))
return false;
return std::nullopt;
// Scale up the value if it wasn't a full fractional value, e.g. if we have "10.45" then we
// need to convert the 45 we just parsed to 450'000'000.
@ -423,10 +408,10 @@ namespace cryptonote
fractional *= 10;
if (fractional > std::numeric_limits<uint64_t>::max() - amount)
return false; // would overflow
return std::nullopt; // would overflow
amount += fractional;
return true;
return amount;
}
//---------------------------------------------------------------
uint64_t get_transaction_weight(const transaction &tx, size_t blob_size)
@ -1047,39 +1032,33 @@ namespace cryptonote
cn_fast_hash(blob.data(), blob.size(), res);
}
//---------------------------------------------------------------
std::string get_unit(unsigned int decimal_point)
std::string print_money(uint64_t amount, bool strip_zeros)
{
if (decimal_point == (unsigned int)-1)
decimal_point = CRYPTONOTE_DISPLAY_DECIMAL_POINT;
switch (decimal_point)
{
case 9:
return "oxen";
case 6:
return "megarok";
case 3:
return "kilorok";
case 0:
return "rok";
default:
ASSERT_MES_AND_THROW("Invalid decimal point specification: " << decimal_point);
}
}
//---------------------------------------------------------------
std::string print_money(uint64_t amount, unsigned int decimal_point)
{
if (decimal_point == (unsigned int)-1)
decimal_point = CRYPTONOTE_DISPLAY_DECIMAL_POINT;
constexpr unsigned int decimal_point = CRYPTONOTE_DISPLAY_DECIMAL_POINT;
std::string s = std::to_string(amount);
if(s.size() < decimal_point+1)
{
s.insert(0, decimal_point+1 - s.size(), '0');
}
if (decimal_point > 0)
s.insert(s.size() - decimal_point, ".");
s.insert(s.size() - decimal_point, ".");
if (strip_zeros)
{
while (s.back() == '0')
s.pop_back();
if (s.back() == '.')
s.pop_back();
}
return s;
}
//---------------------------------------------------------------
std::string format_money(uint64_t amount, bool strip_zeros)
{
auto value = print_money(amount, strip_zeros);
value += ' ';
value += get_unit();
return value;
}
//---------------------------------------------------------------
std::string print_tx_verification_context(tx_verification_context const &tvc, transaction const *tx)
{
std::ostringstream os;

View File

@ -202,7 +202,7 @@ namespace cryptonote
uint64_t get_outs_money_amount(const transaction& tx);
bool check_inputs_types_supported(const transaction& tx);
bool check_outs_valid(const transaction& tx);
bool parse_amount(uint64_t& amount, std::string_view str_amount);
std::optional<uint64_t> parse_amount(std::string_view str_amount);
uint64_t get_transaction_weight(const transaction &tx);
uint64_t get_transaction_weight(const transaction &tx, size_t blob_size);
uint64_t get_pruned_transaction_weight(const transaction &tx);
@ -213,8 +213,12 @@ namespace cryptonote
uint64_t get_block_height(const block& b);
std::vector<uint64_t> relative_output_offsets_to_absolute(const std::vector<uint64_t>& off);
std::vector<uint64_t> absolute_output_offsets_to_relative(const std::vector<uint64_t>& off);
std::string get_unit(unsigned int decimal_point = -1);
std::string print_money(uint64_t amount, unsigned int decimal_point = -1);
constexpr std::string_view get_unit() { return "OXEN"sv; }
// Returns a monetary value with a decimal point; optionally strips insignificant trailing 0s.
std::string print_money(uint64_t amount, bool strip_zeros = false);
// Returns a formatted monetary value including the unit, e.g. "1.234567 OXEN"; strips
// insignificant trailing 0s by default (unlike the above) but can be overridden to not do that.
std::string format_money(uint64_t amount, bool strip_zeros = true);
std::string print_tx_verification_context (tx_verification_context const &tvc, transaction const *tx = nullptr);
std::string print_vote_verification_context(vote_verification_context const &vvc, service_nodes::quorum_vote_t const *vote = nullptr);

View File

@ -61,21 +61,20 @@ namespace daemonize {
namespace {
enum class input_line_result { yes, no, cancel, back, };
std::string input_line(std::string const &prompt)
template <typename... Args>
std::string input_line(Args&&... prompt)
{
std::cout << prompt << std::flush;
std::string result;
rdln::suspend_readline pause_readline;
(std::cout << ... << prompt) << std::flush;
std::string result;
std::cin >> result;
return result;
}
input_line_result input_line_yes_no_back_cancel(char const *msg)
input_line_result input_line_ask(std::string_view msg)
{
std::string prompt = std::string(msg);
prompt += " (Y/Yes/N/No/B/Back/C/Cancel): ";
std::string input = input_line(prompt);
auto input = input_line(msg, " (Y/Yes/N/No/B/Back/C/Cancel): ");
if (command_line::is_yes(input)) return input_line_result::yes;
if (command_line::is_no(input)) return input_line_result::no;
@ -83,27 +82,20 @@ namespace {
return input_line_result::cancel;
}
input_line_result input_line_yes_no_cancel(char const *msg)
std::pair<input_line_result, std::string> input_line_value(std::string_view msg, bool back = true)
{
std::string prompt = msg;
prompt += " (Y/Yes/N/No/C/Cancel): ";
std::string input = input_line(prompt);
std::string_view end = ""sv;
if (msg.back() == '\n') {
end = "\n"sv;
msg.remove_suffix(1);
}
auto input = input_line(msg, back ? " (B/Back/C/Cancel): " : " (C/Cancel): ", end);
if (command_line::is_yes(input)) return input_line_result::yes;
if (command_line::is_no(input)) return input_line_result::no;
return input_line_result::cancel;
}
input_line_result input_line_back_cancel_get_input(char const *msg, std::string &input)
{
std::string prompt = msg;
prompt += " (B/Back/C/Cancel): ";
input = input_line(prompt);
if (command_line::is_back(input)) return input_line_result::back;
if (command_line::is_cancel(input)) return input_line_result::cancel;
return input_line_result::yes;
return {
back && command_line::is_back(input) ? input_line_result::back :
command_line::is_cancel(input) ? input_line_result::cancel :
input_line_result::yes,
input};
}
void print_peer(std::string const & prefix, GET_PEER_LIST::peer const & peer, bool pruned_only, bool publicrpc_only)
@ -186,15 +178,11 @@ namespace {
std::string get_time_hms(time_t t)
{
unsigned int hours, minutes, seconds;
char buffer[24];
hours = t / 3600;
unsigned int hours = t / 3600;
t %= 3600;
minutes = t / 60;
t %= 60;
seconds = t;
snprintf(buffer, sizeof(buffer), "%02u:%02u:%02u", hours, minutes, seconds);
return std::string(buffer);
unsigned int minutes = t / 60;
unsigned int seconds = t % 60;
return fmt::format("{:02}:{:02}:{:02}", hours, minutes, seconds);
}
}
@ -605,7 +593,7 @@ bool rpc_command_executor::mining_status() {
if (!mining_busy && mres.active && mres.speed > 0 && mres.block_target > 0 && mres.difficulty > 0)
{
uint64_t daily = 86400 / (double)mres.difficulty * mres.speed * mres.block_reward;
tools::msg_writer() << "Expected: " << cryptonote::print_money(daily) << " OXEN daily, " << cryptonote::print_money(7*daily) << " weekly";
tools::msg_writer() << "Expected: " << cryptonote::format_money(daily) << " daily, " << cryptonote::format_money(7*daily) << " weekly";
}
return true;
@ -1899,18 +1887,6 @@ bool rpc_command_executor::print_sn_key()
return true;
}
// Returns lowest x such that (STAKING_PORTIONS * x/amount) >= portions
static uint64_t get_amount_to_make_portions(uint64_t amount, uint64_t portions)
{
uint64_t lo, hi, resulthi, resultlo;
lo = mul128(amount, portions, &hi);
if (lo > UINT64_MAX - (STAKING_PORTIONS - 1))
hi++;
lo += STAKING_PORTIONS-1;
div128_64(hi, lo, STAKING_PORTIONS, &resulthi, &resultlo);
return resultlo;
}
static uint64_t get_actual_amount(uint64_t amount, uint64_t portions)
{
uint64_t lo, hi, resulthi, resultlo;
@ -1919,6 +1895,21 @@ static uint64_t get_actual_amount(uint64_t amount, uint64_t portions)
return resultlo;
}
// Returns an error message on invalid, nullopt if good
static std::optional<std::string_view> is_invalid_staking_address(
std::string_view addr,
const cryptonote::network_type nettype) {
cryptonote::address_parse_info info;
bool valid = get_account_address_from_str(info, nettype, addr);
if (!valid)
return "Invalid OXEN address"sv;
if (info.is_subaddress)
return "Staking from subaddresses is not supported"sv;
if (info.has_payment_id)
return "Staking with a payment id/integrated address is not supported"sv;
return std::nullopt;
}
bool rpc_command_executor::prepare_registration(bool force_registration)
{
// RAII-style class to temporarily clear categories and restore upon destruction (i.e. upon returning).
@ -2003,31 +1994,32 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
std::max(service_nodes::get_staking_requirement(nettype, block_height),
service_nodes::get_staking_requirement(nettype, block_height + 30 * 24)); // allow 1 day
auto highlight_money = [](uint64_t amount) {
return fmt::format("\x1b[36;1m{}\x1b[0m", cryptonote::format_money(amount));
};
// anything less than DUST will be added to operator stake
const uint64_t DUST = MAX_NUMBER_OF_CONTRIBUTORS;
std::cout << "Current staking requirement: " << cryptonote::print_money(staking_requirement) << " " << cryptonote::get_unit() << std::endl;
std::cout << "Current staking requirement: " << highlight_money(staking_requirement) << std::endl;
enum struct register_step
{
ask_is_solo_stake = 0,
is_solo_stake__operator_address_to_reserve,
ask_address,
ask_is_solo_stake,
get_operator_fee,
do_you_want_to_reserve_other_contributors,
how_many_more_contributors,
staker_amount_to_reserve,
summary_info,
is_open_stake__get_operator_fee,
is_open_stake__do_you_want_to_reserve_other_contributors,
is_open_stake__how_many_more_contributors,
is_open_stake__operator_amount_to_reserve,
is_open_stake__operator_address_to_reserve,
is_open_stake__contributor_address_to_reserve,
is_open_stake__contributor_amount_to_reserve,
is_open_stake__summary_info,
final_summary,
cancelled_by_user,
};
struct prepare_registration_state
{
register_step prev_step = register_step::ask_is_solo_stake;
bool is_solo_stake;
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;
@ -2036,329 +2028,236 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
std::vector<uint64_t> contributions;
};
std::string operator_address;
prepare_registration_state state = {};
std::stack<prepare_registration_state> state_stack;
state_stack.push(state);
bool finished = false;
register_step step = register_step::ask_is_solo_stake;
for (input_line_result last_input_result = input_line_result::yes; !finished;)
bool go_back = false;
auto step = register_step::ask_address;
auto next_step = [&](register_step next)
{
if (last_input_result == input_line_result::back)
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;
}
};
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.addresses.empty();
auto [result, address_str] = input_line_value(fmt::format(
"Enter the OXEN address of {}\n",
is_operator ? "the Service Node operator" : fmt::format("contributor {}", state.contributions.size())),
/*back=*/ !is_operator);
if (check_cancel_back(result))
break;
if (auto bad = is_invalid_staking_address(address_str, nettype))
{
tools::fail_msg_writer() << *bad << std::endl;
break;
}
if (std::find(state.addresses.begin(), state.addresses.end(), address_str) != state.addresses.end())
{
tools::fail_msg_writer() << "Invalid OXEN address: you cannot provide the same address twice" << std::endl;
break;
}
state.addresses.push_back(std::move(address_str));
next_step(is_operator
? register_step::ask_is_solo_stake
: register_step::staker_amount_to_reserve);
break;
}
case register_step::ask_is_solo_stake:
{
last_input_result = input_line_yes_no_cancel("Will the operator contribute the entire stake?");
if(last_input_result == input_line_result::cancel)
{
step = register_step::cancelled_by_user;
continue;
}
auto result = input_line_ask("Will the operator contribute the entire stake?");
if (check_cancel_back(result))
break;
state.is_solo_stake = (last_input_result == input_line_result::yes);
if (state.is_solo_stake)
if (result == input_line_result::yes)
{
std::cout << std::endl;
step = register_step::is_solo_stake__operator_address_to_reserve;
state.contributions.push_back(STAKING_PORTIONS);
state.portions_remaining = 0;
state.total_reserved_contributions += staking_requirement;
next_step(register_step::final_summary);
}
else
{
step = register_step::is_open_stake__get_operator_fee;
}
state_stack.push(state);
continue;
next_step(register_step::staker_amount_to_reserve);
break;
}
case register_step::is_solo_stake__operator_address_to_reserve:
case register_step::get_operator_fee:
{
std::string address_str;
last_input_result = input_line_back_cancel_get_input("Enter the oxen address for the solo staker", address_str);
if (last_input_result == input_line_result::back)
continue;
auto [result, operator_fee_str] = input_line_value(
"Enter operator fee as a percentage of earned rewards [0-100]%");
if (last_input_result == input_line_result::cancel)
{
step = register_step::cancelled_by_user;
continue;
}
state.addresses.push_back(address_str); // the addresses will be validated later down the line
state.contributions.push_back(STAKING_PORTIONS);
state.portions_remaining = 0;
state.total_reserved_contributions += staking_requirement;
state.prev_step = step;
step = register_step::final_summary;
state_stack.push(state);
continue;
}
case register_step::is_open_stake__get_operator_fee:
{
std::string operator_fee_str;
last_input_result = input_line_back_cancel_get_input("Enter operator fee as a percentage of the total staking reward [0-100]%", operator_fee_str);
if (last_input_result == input_line_result::back)
continue;
if (last_input_result == input_line_result::cancel)
{
step = register_step::cancelled_by_user;
continue;
}
if (check_cancel_back(result))
break;
if (!service_nodes::get_portions_from_percent_str(operator_fee_str, state.operator_fee_portions))
{
std::cout << "Invalid value: " << operator_fee_str << ". Should be between [0-100]" << std::endl;
continue;
tools::fail_msg_writer() << "Invalid value: " << operator_fee_str << ". Should be between [0-100]" << std::endl;
break;
}
step = register_step::is_open_stake__do_you_want_to_reserve_other_contributors;
state_stack.push(state);
continue;
next_step(register_step::do_you_want_to_reserve_other_contributors);
break;
}
case register_step::is_open_stake__do_you_want_to_reserve_other_contributors:
case register_step::do_you_want_to_reserve_other_contributors:
{
last_input_result = input_line_yes_no_back_cancel("Do you want to reserve portions of the stake for other specific contributors?");
if (last_input_result == input_line_result::back)
continue;
auto result = input_line_ask("Do you want to reserve stakes for specific contributors?");
if (check_cancel_back(result))
break;
if (last_input_result == input_line_result::cancel)
{
step = register_step::cancelled_by_user;
continue;
}
state.prev_step = step;
if(last_input_result == input_line_result::yes)
{
step = register_step::is_open_stake__how_many_more_contributors;
}
else
{
std::cout << std::endl;
step = register_step::is_open_stake__operator_address_to_reserve;
}
state_stack.push(state);
continue;
next_step(result == input_line_result::yes
? register_step::how_many_more_contributors
: register_step::summary_info);
break;
}
case register_step::is_open_stake__how_many_more_contributors:
case register_step::how_many_more_contributors:
{
std::string prompt = "Number of additional contributors [1-" + std::to_string(MAX_NUMBER_OF_CONTRIBUTORS - 1) + "]";
std::string input;
last_input_result = input_line_back_cancel_get_input(prompt.c_str(), input);
auto [result, input] = input_line_value(
"Number of additional contributors [1-" + std::to_string(MAX_NUMBER_OF_CONTRIBUTORS - 1) + "]");
if (last_input_result == input_line_result::back)
continue;
if (check_cancel_back(result))
break;
if (last_input_result == input_line_result::cancel)
size_t additional_contributors;
if (!tools::parse_int(input, additional_contributors) ||
additional_contributors < 1 || additional_contributors > (MAX_NUMBER_OF_CONTRIBUTORS - 1))
{
step = register_step::cancelled_by_user;
continue;
tools::fail_msg_writer() << "Invalid value; must be between 1 and " << (MAX_NUMBER_OF_CONTRIBUTORS - 1) << "." << std::endl;
break;
}
long additional_contributors = strtol(input.c_str(), NULL, 10 /*base 10*/);
if(additional_contributors < 1 || additional_contributors > (MAX_NUMBER_OF_CONTRIBUTORS - 1))
{
std::cout << "Invalid value. Should be between [1-" << (MAX_NUMBER_OF_CONTRIBUTORS - 1) << "]" << std::endl;
continue;
}
std::cout << std::endl;
state.num_participants += static_cast<size_t>(additional_contributors);
state.prev_step = step;
step = register_step::is_open_stake__operator_address_to_reserve;
state_stack.push(state);
continue;
state.num_participants += additional_contributors;
next_step(register_step::ask_address);
break;
}
case register_step::is_open_stake__operator_address_to_reserve:
case register_step::staker_amount_to_reserve:
{
std::string address_str;
last_input_result = input_line_back_cancel_get_input("Enter the oxen address for the operator", address_str);
if (last_input_result == input_line_result::back)
continue;
bool is_operator = state.total_reserved_contributions == 0;
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);
if (last_input_result == input_line_result::cancel)
{
step = register_step::cancelled_by_user;
continue;
}
auto [result, contribution_str] = input_line_value(fmt::format(
"The {} contribution must be between {} and {} to meet the staking requirements.\n\n"
"How much OXEN does {} want to stake?",
is_operator ? "operator" : "next",
highlight_money(min_contribution),
highlight_money(amount_left),
is_operator ? "the operator" : fmt::format("contributor {}", state.contributions.size())));
state.addresses.push_back(address_str); // the addresses will be validated later down the line
state.prev_step = step;
step = register_step::is_open_stake__operator_amount_to_reserve;
state_stack.push(state);
continue;
}
case register_step::is_open_stake__operator_amount_to_reserve:
{
uint64_t min_contribution_portions = service_nodes::get_min_node_contribution_in_portions(hf_version, staking_requirement, 0, 0);
const uint64_t min_contribution = get_amount_to_make_portions(staking_requirement, min_contribution_portions);
std::cout << "Minimum amount that can be reserved: " << cryptonote::print_money(min_contribution) << " " << cryptonote::get_unit() << std::endl;
std::string contribution_str;
last_input_result = input_line_back_cancel_get_input("How much oxen does the operator want to reserve in the stake?", contribution_str);
if (last_input_result == input_line_result::back)
continue;
if (last_input_result == input_line_result::cancel)
{
step = register_step::cancelled_by_user;
continue;
}
if (check_cancel_back(result))
break;
uint64_t contribution;
if(!cryptonote::parse_amount(contribution, contribution_str))
{
std::cout << "Invalid amount." << std::endl;
continue;
}
uint64_t portions = service_nodes::get_portions_to_make_amount(staking_requirement, contribution);
if(portions < min_contribution_portions)
{
std::cout << "The operator needs to contribute at least 25% of the stake requirement (" << cryptonote::print_money(min_contribution) << " " << cryptonote::get_unit() << "). Aborted." << std::endl;
continue;
}
if(portions > state.portions_remaining)
{
std::cout << "The operator contribution is higher than the staking requirement. Any excess contribution will be locked for the staking duration, but won't yield any additional reward." << std::endl;
portions = state.portions_remaining;
}
state.contributions.push_back(portions);
state.portions_remaining -= portions;
state.total_reserved_contributions += get_actual_amount(staking_requirement, portions);
state.prev_step = step;
if (state.num_participants > 1)
{
step = register_step::is_open_stake__contributor_address_to_reserve;
}
if (auto c = cryptonote::parse_amount(contribution_str))
contribution = *c;
else
{
step = register_step::is_open_stake__summary_info;
tools::fail_msg_writer() << "Invalid amount." << std::endl;
break;
}
std::cout << std::endl;
state_stack.push(state);
continue;
}
case register_step::is_open_stake__contributor_address_to_reserve:
{
std::string const prompt = "Enter the oxen address for contributor " + std::to_string(state.contributions.size() + 1);
std::string address_str;
last_input_result = input_line_back_cancel_get_input(prompt.c_str(), address_str);
if (last_input_result == input_line_result::back)
continue;
if (last_input_result == input_line_result::cancel)
if (contribution > amount_left)
{
step = register_step::cancelled_by_user;
continue;
}
// the addresses will be validated later down the line
state.addresses.push_back(address_str);
state.prev_step = step;
step = register_step::is_open_stake__contributor_amount_to_reserve;
state_stack.push(state);
continue;
}
case register_step::is_open_stake__contributor_amount_to_reserve:
{
const uint64_t amount_left = staking_requirement - state.total_reserved_contributions;
uint64_t min_contribution_portions = service_nodes::get_min_node_contribution_in_portions(hf_version, staking_requirement, state.total_reserved_contributions, state.contributions.size());
const uint64_t min_contribution = service_nodes::portions_to_amount(staking_requirement, min_contribution_portions);
std::cout << "The minimum amount possible to contribute is " << cryptonote::print_money(min_contribution) << " " << cryptonote::get_unit() << std::endl;
std::cout << "There is " << cryptonote::print_money(amount_left) << " " << cryptonote::get_unit() << " left to meet the staking requirement." << std::endl;
std::string contribution_str;
std::string const prompt = "How much oxen does contributor " + std::to_string(state.contributions.size() + 1) + " want to reserve in the stake?";
last_input_result = input_line_back_cancel_get_input(prompt.c_str(), contribution_str);
if (last_input_result == input_line_result::back)
continue;
if (last_input_result == input_line_result::cancel)
{
step = register_step::cancelled_by_user;
continue;
}
uint64_t contribution;
if (!cryptonote::parse_amount(contribution, contribution_str))
{
std::cout << "Invalid amount." << std::endl;
continue;
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)
{
std::cout << "The amount is too small." << std::endl;
continue;
tools::fail_msg_writer() << "Invalid amount\n";
break;
}
if (portions > state.portions_remaining)
portions = state.portions_remaining;
state.contributions.push_back(portions);
state.portions_remaining -= portions;
state.total_reserved_contributions += get_actual_amount(staking_requirement, portions);
state.prev_step = step;
if (state.contributions.size() == state.num_participants)
step = register_step::is_open_stake__summary_info;
else
step = register_step::is_open_stake__contributor_address_to_reserve;
std::cout << std::endl;
state_stack.push(state);
continue;
next_step(
is_operator ? register_step::get_operator_fee :
state.num_participants > state.contributions.size()
? register_step::ask_address
: register_step::summary_info);
break;
}
case register_step::is_open_stake__summary_info:
case register_step::summary_info:
{
uint64_t open_spots = MAX_NUMBER_OF_CONTRIBUTORS - state.contributions.size();
const uint64_t amount_left = staking_requirement - state.total_reserved_contributions;
std::cout << "Total staking contributions reserved: " << cryptonote::print_money(state.total_reserved_contributions) << " " << cryptonote::get_unit() << std::endl;
fmt::print("Total reserved contributions: {}\n", highlight_money(state.total_reserved_contributions));
if (amount_left > DUST)
{
std::cout << "Your total reservations do not equal the staking requirement." << std::endl;
std::cout << "You will leave the remaining portion of " << cryptonote::print_money(amount_left) << " " << cryptonote::get_unit() << " open to contributions from anyone, and the Service Node will not activate until the full staking requirement is filled." << std::endl;
fmt::print(
"\nThe total reserved amount ({}) is less than the required full stake ({}).\n"
"The remaining stake ({}) will be open to contribution from {}.\n"
"The Service Node will not activate until the full contribution has been filled.\n\n",
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"
);
last_input_result = input_line_yes_no_back_cancel("Is this ok?\n");
if(last_input_result == input_line_result::no || last_input_result == input_line_result::cancel)
{
step = register_step::cancelled_by_user;
continue;
}
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;
if(last_input_result == input_line_result::back)
continue;
state_stack.push(state);
state.prev_step = step;
next_step(register_step::final_summary);
break;
}
// Not calling next_step here because we have no state change to push
step = register_step::final_summary;
continue;
std::cout << std::endl;
break;
}
case register_step::final_summary:
@ -2366,51 +2265,65 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
assert(state.addresses.size() == state.contributions.size());
const uint64_t amount_left = staking_requirement - state.total_reserved_contributions;
std::cout << "Summary:" << std::endl;
std::cout << "Operating costs as % of reward: " << (state.operator_fee_portions * 100.0 / static_cast<double>(STAKING_PORTIONS)) << "%" << std::endl;
printf("%-16s%-9s%-19s%-s\n", "Contributor", "Address", "Contribution", "Contribution(%)");
printf("%-16s%-9s%-19s%-s\n", "___________", "_______", "____________", "_______________");
std::cout << "\nRegistration Summary:\n" << std::endl;
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));
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.num_participants; ++i)
{
const std::string participant_name = (i==0) ? "Operator" : "Contributor " + std::to_string(i);
uint64_t amount = get_actual_amount(staking_requirement, state.contributions[i]);
if (amount_left <= DUST && i == 0)
amount += amount_left; // add dust to the operator.
printf("%-16s%-9s%-19s%-.9f\n", participant_name.c_str(), state.addresses[i].substr(0,6).c_str(), cryptonote::print_money(amount).c_str(), (double)state.contributions[i] * 100 / (double)STAKING_PORTIONS);
const auto& addr = state.addresses[i];
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}%", state.contributions[i] * 100.0 / STAKING_PORTIONS));
}
if (amount_left > DUST)
{
printf("%-16s%-9s%-19s%-.2f\n", "(open)", "", cryptonote::print_money(amount_left).c_str(), amount_left * 100.0 / staking_requirement);
size_t open_spots = MAX_NUMBER_OF_CONTRIBUTORS - 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 << "\nActual amounts may differ slightly from specification. This is due to\n" << std::endl;
std::cout << "limitations on the way fractions are represented internally.\n" << std::endl;
std::cout <<
"\nActual amounts may differ slightly from specification because of limitations on"
"\nthe way fractions are represented on the blockchain\n\n";
}
std::cout << "\nBecause the actual requirement will depend on the time that you register, the\n";
std::cout << "amounts shown here are used as a guide only, and the percentages will remain\n";
std::cout << "the same." << std::endl << std::endl;
last_input_result = input_line_yes_no_back_cancel("Do you confirm the information above is correct?");
if(last_input_result == input_line_result::no || last_input_result == input_line_result::cancel)
{
step = register_step::cancelled_by_user;
continue;
}
if(last_input_result == input_line_result::back)
continue;
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;
continue;
break;
}
case register_step::cancelled_by_user:
{
std::cout << "Cancel requested in prepare registration. Aborting." << std::endl;
tools::fail_msg_writer() << "Registration preparation cancelled." << std::endl;
return true;
}
}
@ -2425,18 +2338,6 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
args.push_back(std::to_string(state.contributions[i]));
}
for (size_t i = 0; i < state.addresses.size(); i++)
{
for (size_t j = 0; j < i; j++)
{
if (state.addresses[i] == state.addresses[j])
{
std::cout << "Must not provide the same address twice" << std::endl;
return true;
}
}
}
scoped_log_cats.reset();
{
@ -2447,8 +2348,9 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
req.make_friendly = true;
req.staking_requirement = staking_requirement;
if (!invoke<GET_SERVICE_NODE_REGISTRATION_CMD_RAW>(std::move(req), res, "Failed to validate registration arguments; "
"check the addresses and registration parameters and that the Daemon is running with the '--service-node' flag"))
if (!invoke<GET_SERVICE_NODE_REGISTRATION_CMD_RAW>(std::move(req), res,
"Failed to validate registration arguments; check the addresses and registration parameters "
"and make sure oxend is running as a service node."))
return false;
tools::success_msg_writer() << res.registration_cmd;

View File

@ -2302,8 +2302,8 @@ bool simple_wallet::set_min_output_count(const std::vector<std::string> &args/*
bool simple_wallet::set_min_output_value(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
uint64_t value;
if (!cryptonote::parse_amount(value, args[1]))
auto value = cryptonote::parse_amount(args[1]);
if (!value)
{
fail_msg_writer() << tr("invalid value");
return true;
@ -2312,7 +2312,7 @@ bool simple_wallet::set_min_output_value(const std::vector<std::string> &args/*
const auto pwd_container = get_and_verify_password();
if (pwd_container)
{
m_wallet->set_min_output_value(value);
m_wallet->set_min_output_value(*value);
m_wallet->rewrite(m_wallet_file, pwd_container->password());
}
return true;
@ -2424,15 +2424,15 @@ bool simple_wallet::set_ignore_outputs_above(const std::vector<std::string> &arg
const auto pwd_container = get_and_verify_password();
if (pwd_container)
{
uint64_t amount;
if (!cryptonote::parse_amount(amount, args[1]))
auto amount = cryptonote::parse_amount(args[1]);
if (!amount)
{
fail_msg_writer() << tr("Invalid amount");
return true;
}
if (amount == 0)
if (*amount == 0)
amount = MONEY_SUPPLY;
m_wallet->ignore_outputs_above(amount);
m_wallet->ignore_outputs_above(*amount);
m_wallet->rewrite(m_wallet_file, pwd_container->password());
}
return true;
@ -2443,13 +2443,13 @@ bool simple_wallet::set_ignore_outputs_below(const std::vector<std::string> &arg
const auto pwd_container = get_and_verify_password();
if (pwd_container)
{
uint64_t amount;
if (!cryptonote::parse_amount(amount, args[1]))
auto amount = cryptonote::parse_amount(args[1]);
if (!amount)
{
fail_msg_writer() << tr("Invalid amount");
return true;
}
m_wallet->ignore_outputs_below(amount);
m_wallet->ignore_outputs_below(*amount);
m_wallet->rewrite(m_wallet_file, pwd_container->password());
}
return true;
@ -5806,9 +5806,13 @@ bool simple_wallet::transfer_main(Transfer transfer_type, const std::vector<std:
static constexpr auto BURN_PREFIX = "burn="sv;
uint64_t burn_amount = 0;
std::string burn_amount_str = eat_named_argument(local_args, BURN_PREFIX);
if (!burn_amount_str.empty() && !cryptonote::parse_amount(burn_amount, burn_amount_str)) {
fail_msg_writer() << tr("Invalid amount");
return true;
if (!burn_amount_str.empty()) {
if (auto b = cryptonote::parse_amount(burn_amount_str))
burn_amount = *b;
else {
fail_msg_writer() << tr("Invalid amount");
return true;
}
}
uint32_t priority = 0;
@ -5885,8 +5889,9 @@ bool simple_wallet::transfer_main(Transfer transfer_type, const std::vector<std:
return false;
}
bool ok = cryptonote::parse_amount(de.amount, local_args[i + 1]);
if(!ok || 0 == de.amount)
if (auto a = cryptonote::parse_amount(local_args[i + 1]); a && *a > 0)
de.amount = *a;
else
{
fail_msg_writer() << tr("amount is wrong: ") << local_args[i] << ' ' << local_args[i + 1] <<
", " << tr("expected number from 0 to ") << print_money(std::numeric_limits<uint64_t>::max());
@ -6112,7 +6117,9 @@ bool simple_wallet::stake(const std::vector<std::string> &args_)
}
else
{
if (!cryptonote::parse_amount(amount, local_args[1]) || amount == 0)
if (auto a = cryptonote::parse_amount(local_args[1]); a && *a > 0)
amount = *a;
else
{
fail_msg_writer() << tr("amount is wrong: ") << local_args[1] <<
", " << tr("expected number from ") << print_money(1) << " to " << print_money(std::numeric_limits<uint64_t>::max());
@ -7557,18 +7564,18 @@ bool simple_wallet::sweep_account(const std::vector<std::string> &args_)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::sweep_below(const std::vector<std::string> &args_)
{
uint64_t below = 0;
if (args_.size() < 1)
{
fail_msg_writer() << tr("missing threshold amount");
return true;
}
if (!cryptonote::parse_amount(below, args_[0]))
auto below = cryptonote::parse_amount(args_[0]);
if (!below)
{
fail_msg_writer() << tr("invalid amount threshold");
return true;
}
return sweep_main(m_current_subaddress_account, below, Transfer::Normal, std::vector<std::string>(++args_.begin(), args_.end()));
return sweep_main(m_current_subaddress_account, *below, Transfer::Normal, std::vector<std::string>(++args_.begin(), args_.end()));
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, const std::function<const wallet::tx_construction_data&(size_t)> &get_tx, const std::string &extra_message)
@ -8250,7 +8257,9 @@ bool simple_wallet::get_reserve_proof(const std::vector<std::string> &args)
{
account_minreserve = std::pair<uint32_t, uint64_t>();
account_minreserve->first = m_current_subaddress_account;
if (!cryptonote::parse_amount(account_minreserve->second, args[0]))
if (auto r = cryptonote::parse_amount(args[0]))
account_minreserve->second = *r;
else
{
fail_msg_writer() << tr("amount is wrong: ") << args[0];
return true;
@ -8587,7 +8596,9 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_)
uint64_t max_amount = std::numeric_limits<uint64_t>::max();
if (local_args.size() > 0)
{
if (!cryptonote::parse_amount(min_amount, local_args[0]))
if (auto a = cryptonote::parse_amount(local_args[0]))
min_amount = *a;
else
{
fail_msg_writer() << tr("amount is wrong: ") << local_args[0];
return true;
@ -8595,7 +8606,9 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_)
local_args.erase(local_args.begin());
if (local_args.size() > 0)
{
if (!cryptonote::parse_amount(max_amount, local_args[0]))
if (auto a = cryptonote::parse_amount(local_args[0]))
max_amount = *a;
else
{
fail_msg_writer() << tr("amount is wrong: ") << local_args[0];
return true;

View File

@ -316,9 +316,7 @@ std::string Wallet::displayAmount(uint64_t amount)
EXPORT
uint64_t Wallet::amountFromString(const std::string &amount)
{
uint64_t result = 0;
cryptonote::parse_amount(result, amount);
return result;
return cryptonote::parse_amount(amount).value_or(0);
}
EXPORT

View File

@ -14457,7 +14457,9 @@ bool wallet2::parse_uri(std::string_view uri, std::string &address, std::string
if (key == "tx_amount"sv)
{
amount = 0;
if (!cryptonote::parse_amount(amount, value))
if (auto a = cryptonote::parse_amount(value))
amount = *a;
else
{
error = "URI has invalid amount: " + value;
return false;

View File

@ -38,21 +38,20 @@ namespace
{
void do_pos_test(uint64_t expected, const std::string& str)
{
uint64_t val;
std::string number_str = str;
std::replace(number_str.begin(), number_str.end(), '_', '.');
number_str.erase(std::remove(number_str.begin(), number_str.end(), '~'), number_str.end());
ASSERT_TRUE(parse_amount(val, number_str));
ASSERT_EQ(expected, val);
auto val = parse_amount(number_str);
ASSERT_TRUE(val);
ASSERT_EQ(expected, *val);
}
void do_neg_test(const std::string& str)
{
uint64_t val;
std::string number_str = str;
std::replace(number_str.begin(), number_str.end(), '_', '.');
number_str.erase(std::remove(number_str.begin(), number_str.end(), '~'), number_str.end());
ASSERT_FALSE(parse_amount(val, number_str));
ASSERT_FALSE(parse_amount(number_str));
}
}

View File

@ -164,46 +164,45 @@ TEST(parse_and_validate_tx_extra, fails_on_wrong_size_in_extra_nonce)
}
TEST(validate_parse_amount_case, validate_parse_amount)
{
uint64_t res = 0;
bool r = cryptonote::parse_amount(res, "0.0001");
ASSERT_TRUE(r);
ASSERT_EQ(res, 100000);
auto a = cryptonote::parse_amount("0.0001");
ASSERT_TRUE(a);
ASSERT_EQ(*a, 100000);
r = cryptonote::parse_amount(res, "100.0001");
ASSERT_TRUE(r);
ASSERT_EQ(res, 100000100000);
a = cryptonote::parse_amount("100.0001");
ASSERT_TRUE(a);
ASSERT_EQ(*a, 100000100000);
r = cryptonote::parse_amount(res, "000.0000");
ASSERT_TRUE(r);
ASSERT_EQ(res, 0);
a = cryptonote::parse_amount("000.0000");
ASSERT_TRUE(a);
ASSERT_EQ(*a, 0);
r = cryptonote::parse_amount(res, "0");
ASSERT_TRUE(r);
ASSERT_EQ(res, 0);
a = cryptonote::parse_amount("0");
ASSERT_TRUE(a);
ASSERT_EQ(*a, 0);
r = cryptonote::parse_amount(res, " 100.0001 ");
ASSERT_TRUE(r);
ASSERT_EQ(res, 100000100000);
a = cryptonote::parse_amount(" 100.0001 ");
ASSERT_TRUE(a);
ASSERT_EQ(*a, 100000100000);
r = cryptonote::parse_amount(res, " 100.0000 ");
ASSERT_TRUE(r);
ASSERT_EQ(res, 100000000000);
a = cryptonote::parse_amount(" 100.0000 ");
ASSERT_TRUE(a);
ASSERT_EQ(*a, 100000000000);
r = cryptonote::parse_amount(res, " 100. 0000 ");
ASSERT_FALSE(r);
a = cryptonote::parse_amount(" 100. 0000 ");
ASSERT_FALSE(a);
r = cryptonote::parse_amount(res, "100. 0000");
ASSERT_FALSE(r);
a = cryptonote::parse_amount("100. 0000");
ASSERT_FALSE(a);
r = cryptonote::parse_amount(res, "100 . 0000");
ASSERT_FALSE(r);
a = cryptonote::parse_amount("100 . 0000");
ASSERT_FALSE(a);
r = cryptonote::parse_amount(res, "100.00 00");
ASSERT_FALSE(r);
a = cryptonote::parse_amount("100.00 00");
ASSERT_FALSE(a);
r = cryptonote::parse_amount(res, "1 00.00 00");
ASSERT_FALSE(r);
a = cryptonote::parse_amount("1 00.00 00");
ASSERT_FALSE(a);
}
TEST(sort_tx_extra, empty)