Skip to content

Commit

Permalink
CLI compare_rep_weights to compare ledger and hardcoded weights (#2719)
Browse files Browse the repository at this point in the history
* CLI compare_rep_weights to compare ledger and hardcoded rep weights

* Clarify the basis of comparison are the hardcoded weights not the other way around (Serg comment)

* Add const-qualifier to uint128_union::format_balance

* Add standard deviation (sigma) to the output

* Wrap sum in a lambda (Wes review)

* Log each individual mismatch sample

* Refactor and output outliers

* Eat one line (Wes)

* Wes review

* Change threshold to 1-sigma, also present new representatives (not present in hardcoded)

* Filter ledger weights to 99% cummulative weight (same as hardcoded)

* Reserve known and use alias (Wes comments)
  • Loading branch information
guilhermelawless authored Apr 24, 2020
1 parent 793dd71 commit 3ee498b
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 29 deletions.
4 changes: 2 additions & 2 deletions nano/lib/numbers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -749,15 +749,15 @@ std::string format_balance (nano::uint128_t balance, nano::uint128_t scale, int
return stream.str ();
}

std::string nano::uint128_union::format_balance (nano::uint128_t scale, int precision, bool group_digits)
std::string nano::uint128_union::format_balance (nano::uint128_t scale, int precision, bool group_digits) const
{
auto thousands_sep = std::use_facet<std::numpunct<char>> (std::locale ()).thousands_sep ();
auto decimal_point = std::use_facet<std::numpunct<char>> (std::locale ()).decimal_point ();
std::string grouping = "\3";
return ::format_balance (number (), scale, precision, group_digits, thousands_sep, decimal_point, grouping);
}

std::string nano::uint128_union::format_balance (nano::uint128_t scale, int precision, bool group_digits, const std::locale & locale)
std::string nano::uint128_union::format_balance (nano::uint128_t scale, int precision, bool group_digits, const std::locale & locale) const
{
auto thousands_sep = std::use_facet<std::moneypunct<char>> (locale).thousands_sep ();
auto decimal_point = std::use_facet<std::moneypunct<char>> (locale).decimal_point ();
Expand Down
4 changes: 2 additions & 2 deletions nano/lib/numbers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ class uint128_union
void encode_dec (std::string &) const;
bool decode_dec (std::string const &, bool = false);
bool decode_dec (std::string const &, nano::uint128_t);
std::string format_balance (nano::uint128_t scale, int precision, bool group_digits);
std::string format_balance (nano::uint128_t scale, int precision, bool group_digits, const std::locale & locale);
std::string format_balance (nano::uint128_t scale, int precision, bool group_digits) const;
std::string format_balance (nano::uint128_t scale, int precision, bool group_digits, const std::locale & locale) const;
nano::uint128_t number () const;
void clear ();
bool is_zero () const;
Expand Down
147 changes: 147 additions & 0 deletions nano/nano_node/entry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <boost/program_options.hpp>
#include <boost/range/adaptor/reversed.hpp>

#include <numeric>
#include <sstream>

#include <argon2.h>
Expand Down Expand Up @@ -69,6 +70,7 @@ int main (int argc, char * const * argv)
("version", "Prints out version")
("config", boost::program_options::value<std::vector<std::string>>()->multitoken(), "Pass node configuration values. This takes precedence over any values in the configuration file. This option can be repeated multiple times.")
("daemon", "Start node daemon")
("compare_rep_weights", "Display a summarized comparison between the hardcoded bootstrap weights and representative weights from the ledger. Full comparison is output to logs")
("debug_block_count", "Display the number of block")
("debug_bootstrap_generate", "Generate bootstrap sequence of blocks")
("debug_dump_frontier_unchecked_dependents", "Dump frontiers which have matching unchecked keys")
Expand Down Expand Up @@ -158,6 +160,151 @@ int main (int argc, char * const * argv)
}
daemon.run (data_path, flags);
}
else if (vm.count ("compare_rep_weights"))
{
if (!nano::network_constants ().is_test_network ())
{
auto node_flags = nano::inactive_node_flag_defaults ();
nano::update_flags (node_flags, vm);
node_flags.generate_cache.reps = true;
auto inactive_node = nano::default_inactive_node (data_path, vm);
auto node = inactive_node->node;

auto const hardcoded = node->get_bootstrap_weights ().second;
auto const ledger_unfiltered = node->ledger.cache.rep_weights.get_rep_amounts ();

auto get_total = [](decltype (hardcoded) const & reps) -> nano::uint128_union {
return std::accumulate (reps.begin (), reps.end (), nano::uint128_t{ 0 }, [](auto sum, auto const & rep) { return sum + rep.second; });
};

// Hardcoded weights are filtered to a cummulative weight of 99%, need to do the same for ledger weights
std::remove_const_t<decltype (ledger_unfiltered)> ledger;
{
std::vector<std::pair<nano::account, nano::uint128_t>> sorted;
sorted.reserve (ledger_unfiltered.size ());
std::copy (ledger_unfiltered.begin (), ledger_unfiltered.end (), std::back_inserter (sorted));
std::sort (sorted.begin (), sorted.end (), [](auto const & left, auto const & right) { return left.second > right.second; });
auto const total_unfiltered = get_total (ledger_unfiltered);
nano::uint128_t sum{ 0 };
auto target = (total_unfiltered.number () / 100) * 99;
for (auto i (sorted.begin ()), n (sorted.end ()); i != n && sum <= target; sum += i->second, ++i)
{
ledger.insert (*i);
}
}

auto const total_ledger = get_total (ledger);
auto const total_hardcoded = get_total (hardcoded);

struct mismatched_t
{
nano::account rep;
nano::uint128_union hardcoded;
nano::uint128_union ledger;
nano::uint128_union diff;
std::string get_entry () const
{
return boost::str (boost::format ("representative %1% hardcoded %2% ledger %3% mismatch %4%")
% rep.to_account () % hardcoded.format_balance (nano::Mxrb_ratio, 0, true) % ledger.format_balance (nano::Mxrb_ratio, 0, true) % diff.format_balance (nano::Mxrb_ratio, 0, true));
}
};

std::vector<mismatched_t> mismatched;
mismatched.reserve (hardcoded.size ());
std::transform (hardcoded.begin (), hardcoded.end (), std::back_inserter (mismatched), [&ledger, &node](auto const & rep) {
auto ledger_rep (ledger.find (rep.first));
nano::uint128_t ledger_weight = (ledger_rep == ledger.end () ? 0 : ledger_rep->second);
auto absolute = ledger_weight > rep.second ? ledger_weight - rep.second : rep.second - ledger_weight;
return mismatched_t{ rep.first, rep.second, ledger_weight, absolute };
});

// Sort by descending difference
std::sort (mismatched.begin (), mismatched.end (), [](mismatched_t const & left, mismatched_t const & right) { return left.diff > right.diff; });

nano::uint128_union const mismatch_total = std::accumulate (mismatched.begin (), mismatched.end (), nano::uint128_t{ 0 }, [](auto sum, mismatched_t const & sample) { return sum + sample.diff.number (); });
nano::uint128_union const mismatch_mean = mismatch_total.number () / mismatched.size ();

nano::uint512_union mismatch_variance = std::accumulate (mismatched.begin (), mismatched.end (), nano::uint512_t (0), [M = mismatch_mean.number (), N = mismatched.size ()](nano::uint512_t sum, mismatched_t const & sample) {
auto x = sample.diff.number ();
nano::uint512_t const mean_diff = x > M ? x - M : M - x;
nano::uint512_t const sqr = mean_diff * mean_diff;
return sum + sqr;
})
/ mismatched.size ();

nano::uint128_union const mismatch_stddev = nano::narrow_cast<nano::uint128_t> (boost::multiprecision::sqrt (mismatch_variance.number ()));

auto const outlier_threshold = std::max (nano::Gxrb_ratio, mismatch_mean.number () + 1 * mismatch_stddev.number ());
decltype (mismatched) outliers;
std::copy_if (mismatched.begin (), mismatched.end (), std::back_inserter (outliers), [outlier_threshold](mismatched_t const & sample) {
return sample.diff > outlier_threshold;
});

auto const newcomer_threshold = std::max (nano::Gxrb_ratio, mismatch_mean.number ());
std::vector<std::pair<nano::account, nano::uint128_t>> newcomers;
std::copy_if (ledger.begin (), ledger.end (), std::back_inserter (newcomers), [&hardcoded](auto const & rep) {
return !hardcoded.count (rep.first) && rep.second;
});

// Sort by descending weight
std::sort (newcomers.begin (), newcomers.end (), [](auto const & left, auto const & right) { return left.second > right.second; });

auto newcomer_entry = [](auto const & rep) {
return boost::str (boost::format ("representative %1% hardcoded --- ledger %2%") % rep.first.to_account () % nano::uint128_union (rep.second).format_balance (nano::Mxrb_ratio, 0, true));
};

std::cout << boost::str (boost::format ("hardcoded weight %1% Mnano\nledger weight %2% Mnano\nmismatched\n\tsamples %3%\n\ttotal %4% Mnano\n\tmean %5% Mnano\n\tsigma %6% Mnano\n")
% total_hardcoded.format_balance (nano::Mxrb_ratio, 0, true)
% total_ledger.format_balance (nano::Mxrb_ratio, 0, true)
% mismatched.size ()
% mismatch_total.format_balance (nano::Mxrb_ratio, 0, true)
% mismatch_mean.format_balance (nano::Mxrb_ratio, 0, true)
% mismatch_stddev.format_balance (nano::Mxrb_ratio, 0, true));

if (!outliers.empty ())
{
std::cout << "outliers\n";
for (auto const & outlier : outliers)
{
std::cout << '\t' << outlier.get_entry () << '\n';
}
}

if (!newcomers.empty ())
{
std::cout << "newcomers\n";
for (auto const & newcomer : newcomers)
{
if (newcomer.second > newcomer_threshold)
{
std::cout << '\t' << newcomer_entry (newcomer) << '\n';
}
}
}

// Log more data
auto const log_threshold = nano::Gxrb_ratio;
for (auto const & sample : mismatched)
{
if (sample.diff > log_threshold)
{
node->logger.always_log (sample.get_entry ());
}
}
for (auto const & newcomer : newcomers)
{
if (newcomer.second > log_threshold)
{
node->logger.always_log (newcomer_entry (newcomer));
}
}
}
else
{
std::cout << "Not available for the test network" << std::endl;
result = -1;
}
}
else if (vm.count ("debug_block_count"))
{
auto inactive_node = nano::default_inactive_node (data_path, vm);
Expand Down
62 changes: 37 additions & 25 deletions nano/node/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -407,36 +407,19 @@ node_seq (seq)

if ((network_params.network.is_live_network () || network_params.network.is_beta_network ()) && !flags.inactive_node)
{
auto bootstrap_weights = get_bootstrap_weights ();
// Use bootstrap weights if initial bootstrap is not completed
bool use_bootstrap_weight (false);
const uint8_t * weight_buffer = network_params.network.is_live_network () ? nano_bootstrap_weights_live : nano_bootstrap_weights_beta;
size_t weight_size = network_params.network.is_live_network () ? nano_bootstrap_weights_live_size : nano_bootstrap_weights_beta_size;
nano::bufferstream weight_stream ((const uint8_t *)weight_buffer, weight_size);
nano::uint128_union block_height;
if (!nano::try_read (weight_stream, block_height))
bool use_bootstrap_weight = ledger.cache.block_count < bootstrap_weights.first;
if (use_bootstrap_weight)
{
auto max_blocks = (uint64_t)block_height.number ();
use_bootstrap_weight = ledger.cache.block_count < max_blocks;
if (use_bootstrap_weight)
ledger.bootstrap_weight_max_blocks = bootstrap_weights.first;
ledger.bootstrap_weights = bootstrap_weights.second;
for (auto const & rep : ledger.bootstrap_weights)
{
ledger.bootstrap_weight_max_blocks = max_blocks;
while (true)
{
nano::account account;
if (nano::try_read (weight_stream, account.bytes))
{
break;
}
nano::amount weight;
if (nano::try_read (weight_stream, weight.bytes))
{
break;
}
logger.always_log ("Using bootstrap rep weight: ", account.to_account (), " -> ", weight.format_balance (Mxrb_ratio, 0, true), " XRB");
ledger.bootstrap_weights[account] = weight.number ();
}
logger.always_log ("Using bootstrap rep weight: ", rep.first.to_account (), " -> ", nano::uint128_union (rep.second).format_balance (Mxrb_ratio, 0, true), " XRB");
}
}

// Drop unchecked blocks if initial bootstrap is completed
if (!flags.disable_unchecked_drop && !use_bootstrap_weight && !flags.read_only)
{
Expand Down Expand Up @@ -1616,6 +1599,35 @@ void nano::node::epoch_upgrader_impl (nano::private_key const & prv_a, nano::epo
logger.always_log ("Epoch upgrade is completed");
}

std::pair<uint64_t, decltype (nano::ledger::bootstrap_weights)> nano::node::get_bootstrap_weights () const
{
std::unordered_map<nano::account, nano::uint128_t> weights;
const uint8_t * weight_buffer = network_params.network.is_live_network () ? nano_bootstrap_weights_live : nano_bootstrap_weights_beta;
size_t weight_size = network_params.network.is_live_network () ? nano_bootstrap_weights_live_size : nano_bootstrap_weights_beta_size;
nano::bufferstream weight_stream ((const uint8_t *)weight_buffer, weight_size);
nano::uint128_union block_height;
uint64_t max_blocks = 0;
if (!nano::try_read (weight_stream, block_height))
{
max_blocks = nano::narrow_cast<uint64_t> (block_height.number ());
while (true)
{
nano::account account;
if (nano::try_read (weight_stream, account.bytes))
{
break;
}
nano::amount weight;
if (nano::try_read (weight_stream, weight.bytes))
{
break;
}
weights[account] = weight.number ();
}
}
return { max_blocks, weights };
}

nano::inactive_node::inactive_node (boost::filesystem::path const & path_a, nano::node_flags const & node_flags_a) :
io_context (std::make_shared<boost::asio::io_context> ()),
alarm (*io_context),
Expand Down
1 change: 1 addition & 0 deletions nano/node/node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ class node final : public std::enable_shared_from_this<nano::node>
bool online () const;
bool init_error () const;
bool epoch_upgrader (nano::private_key const &, nano::epoch, uint64_t, uint64_t);
std::pair<uint64_t, decltype (nano::ledger::bootstrap_weights)> get_bootstrap_weights () const;
nano::worker worker;
nano::write_database_queue write_database_queue;
boost::asio::io_context & io_ctx;
Expand Down

0 comments on commit 3ee498b

Please sign in to comment.