|
| 1 | +#include <cstdint> |
| 2 | +#include <eosio.system/eosio.system.hpp> |
| 3 | +#include <eosio.system/peer_keys.hpp> |
| 4 | + |
| 5 | +#include <eosio/eosio.hpp> |
| 6 | + |
| 7 | +namespace eosiosystem { |
| 8 | + |
| 9 | +void peer_keys::regpeerkey(const name& proposer_finalizer_name, const public_key& key) { |
| 10 | + require_auth(proposer_finalizer_name); |
| 11 | + peer_keys_table peer_keys_table(get_self(), get_self().value); |
| 12 | + check(!std::holds_alternative<eosio::webauthn_public_key>(key), "webauthn keys not allowed in regpeerkey action"); |
| 13 | + |
| 14 | + auto peers_itr = peer_keys_table.find(proposer_finalizer_name.value); |
| 15 | + if (peers_itr == peer_keys_table.end()) { |
| 16 | + peer_keys_table.emplace(proposer_finalizer_name, [&](auto& row) { |
| 17 | + row.init_row(proposer_finalizer_name); |
| 18 | + row.set_public_key(key); |
| 19 | + }); |
| 20 | + } else { |
| 21 | + const auto& prev_key = peers_itr->get_public_key(); |
| 22 | + check(!prev_key || *prev_key != key, "Provided key is the same as currently stored one"); |
| 23 | + peer_keys_table.modify(peers_itr, same_payer, [&](auto& row) { |
| 24 | + row.update_row(); |
| 25 | + row.set_public_key(key); |
| 26 | + }); |
| 27 | + } |
| 28 | +} |
| 29 | + |
| 30 | +void peer_keys::delpeerkey(const name& proposer_finalizer_name, const public_key& key) { |
| 31 | + require_auth(proposer_finalizer_name); |
| 32 | + peer_keys_table peer_keys_table(get_self(), get_self().value); |
| 33 | + |
| 34 | + auto peers_itr = peer_keys_table.find(proposer_finalizer_name.value); |
| 35 | + check(peers_itr != peer_keys_table.end(), "Key not present for name: " + proposer_finalizer_name.to_string()); |
| 36 | + const auto& prev_key = peers_itr->get_public_key(); |
| 37 | + check(prev_key && *prev_key == key, "Current key does not match the provided one"); |
| 38 | + peer_keys_table.erase(peers_itr); |
| 39 | +} |
| 40 | + |
| 41 | +peer_keys::getpeerkeys_res_t peer_keys::getpeerkeys() { |
| 42 | + peer_keys_table peer_keys_table(get_self(), get_self().value); |
| 43 | + producers_table producers(get_self(), get_self().value); |
| 44 | + constexpr size_t max_return = 50; |
| 45 | + |
| 46 | + getpeerkeys_res_t resp; |
| 47 | + resp.reserve(max_return); |
| 48 | + |
| 49 | + double vote_threshold = 0; // vote_threshold will always be >= 0 |
| 50 | + |
| 51 | + auto add_peer = [&](auto it) { |
| 52 | + auto peers_itr = peer_keys_table.find(it->owner.value); |
| 53 | + if (peers_itr == peer_keys_table.end()) |
| 54 | + resp.push_back(peerkeys_t{it->owner, {}}); |
| 55 | + else |
| 56 | + resp.push_back(peerkeys_t{it->owner, peers_itr->get_public_key()}); |
| 57 | + |
| 58 | + // once 21 producers have been selected, we will only consider producers |
| 59 | + // that have more than 50% of the votes of the 21st selected producer. |
| 60 | + // --------------------------------------------------------------------- |
| 61 | + if (resp.size() == 21) |
| 62 | + vote_threshold = it->total_votes * 0.5; |
| 63 | + }; |
| 64 | + |
| 65 | + auto idx = producers.get_index<"prototalvote"_n>(); |
| 66 | + |
| 67 | + auto it = idx.cbegin(); |
| 68 | + auto rit = idx.cend(); |
| 69 | + if (it == rit) |
| 70 | + return resp; |
| 71 | + else |
| 72 | + --rit; |
| 73 | + |
| 74 | + // 1. Consider both active and non-active producers. as a non-active producer can be |
| 75 | + // reactivated at any time. |
| 76 | + // 2. Once we have selected 21 producers, the threshold of votes required to be selected |
| 77 | + // increases from `> 0` to `> 50% of the votes that the 21st selected producer has`. |
| 78 | + // 3. We iterate from both ends, as non-active producers are indexed at the end (their |
| 79 | + // vote total is negated for the index computation). As a consequence, the highest |
| 80 | + // voted non-active producer will be the last entry of our index. |
| 81 | + // -------------------------------------------------------------------------------------- |
| 82 | + bool last_one = false; |
| 83 | + do { |
| 84 | + // at this point, `it` and `rit` both point to a valid `producer_info` (possibly the same) |
| 85 | + assert(it <= rit); |
| 86 | + assert(vote_threshold >= 0); |
| 87 | + assert(it->total_votes >= 0 && rit->total_votes >= 0); |
| 88 | + last_one = (it == rit); |
| 89 | + if (rit->total_votes > std::max(vote_threshold, it->total_votes)) { |
| 90 | + add_peer(rit); |
| 91 | + assert(it != rit); // Should always be satisfied since `rit->total_votes > it->total_votes` |
| 92 | + --rit; // safe because `rit` cannot point to the first entry of the index. |
| 93 | + } else if (it->total_votes > vote_threshold) { |
| 94 | + add_peer(it); |
| 95 | + ++it; |
| 96 | + } else { |
| 97 | + // `total_votes <= threshold` on both ends of the index, exit the loop. |
| 98 | + break; |
| 99 | + } |
| 100 | + } while (!last_one && resp.size() < max_return); |
| 101 | + |
| 102 | + return resp; |
| 103 | +} |
| 104 | + |
| 105 | +} // namespace eosiosystem |
0 commit comments