Skip to content

Commit a3e21f2

Browse files
authored
Merge pull request #185 from eosnetworkfoundation/gh_184
Add support for storing public keys on-chain that will be used to validate a network peer's identity.
2 parents c66e447 + 0365e12 commit a3e21f2

File tree

5 files changed

+624
-12
lines changed

5 files changed

+624
-12
lines changed

contracts/eosio.system/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ add_contract(
77
${CMAKE_CURRENT_SOURCE_DIR}/src/finalizer_key.cpp
88
${CMAKE_CURRENT_SOURCE_DIR}/src/name_bidding.cpp
99
${CMAKE_CURRENT_SOURCE_DIR}/src/native.cpp
10+
${CMAKE_CURRENT_SOURCE_DIR}/src/peer_keys.cpp
1011
${CMAKE_CURRENT_SOURCE_DIR}/src/producer_pay.cpp
1112
${CMAKE_CURRENT_SOURCE_DIR}/src/powerup.cpp
1213
${CMAKE_CURRENT_SOURCE_DIR}/src/rex.cpp
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#pragma once
2+
3+
#include <eosio/contract.hpp>
4+
#include <eosio/crypto.hpp>
5+
#include <eosio/name.hpp>
6+
7+
#include <string>
8+
#include <optional>
9+
10+
namespace eosiosystem {
11+
12+
using eosio::name;
13+
using eosio::public_key;
14+
15+
// -------------------------------------------------------------------------------------------------
16+
// -------------------------------------------------------------------------------------------------
17+
struct [[eosio::table("peerkeys"), eosio::contract("eosio.system")]] peer_key {
18+
struct v0_data {
19+
std::optional<public_key> pubkey; // peer key for network message authentication
20+
EOSLIB_SERIALIZE(v0_data, (pubkey))
21+
};
22+
23+
name account;
24+
std::variant<v0_data> data;
25+
26+
uint64_t primary_key() const { return account.value; }
27+
28+
void set_public_key(const public_key& key) { data = v0_data{key}; }
29+
const std::optional<eosio::public_key>& get_public_key() const {
30+
return std::visit([](auto& v) -> const std::optional<eosio::public_key>& { return v.pubkey; }, data);
31+
}
32+
void update_row() {}
33+
void init_row(name n) { *this = peer_key{n, v0_data{}}; }
34+
};
35+
36+
// -------------------------------------------------------------------------------------------------
37+
// -------------------------------------------------------------------------------------------------
38+
typedef eosio::multi_index<"peerkeys"_n, peer_key> peer_keys_table;
39+
40+
// -------------------------------------------------------------------------------------------------
41+
// -------------------------------------------------------------------------------------------------
42+
struct [[eosio::contract("eosio.system")]] peer_keys : public eosio::contract {
43+
44+
peer_keys(name s, name code, eosio::datastream<const char*> ds)
45+
: eosio::contract(s, code, ds) {}
46+
47+
struct peerkeys_t {
48+
name producer_name;
49+
std::optional<public_key> peer_key;
50+
51+
EOSLIB_SERIALIZE(peerkeys_t, (producer_name)(peer_key))
52+
};
53+
54+
using getpeerkeys_res_t = std::vector<peerkeys_t>;
55+
56+
/**
57+
* Action to register a public key for a proposer or finalizer name.
58+
* This key will be used to validate a network peer's identity.
59+
* A proposer or finalizer can only have have one public key registered at a time.
60+
* If a key is already registered for `proposer_finalizer_name`, and `regpeerkey` is
61+
* called with a different key, the new key replaces the previous one in `peer_keys_table`
62+
*/
63+
[[eosio::action]]
64+
void regpeerkey(const name& proposer_finalizer_name, const public_key& key);
65+
66+
/**
67+
* Action to delete a public key for a proposer or finalizer name.
68+
*
69+
* An existing public key for a given account can be changed by calling `regpeerkey` again.
70+
*/
71+
[[eosio::action]]
72+
void delpeerkey(const name& proposer_finalizer_name, const public_key& key);
73+
74+
/**
75+
* Returns a list of up to 50 top producers (active *and* non-active, in votes rank order),
76+
* along with their peer public key if it was registered via the regpeerkey action.
77+
*/
78+
[[eosio::action]]
79+
getpeerkeys_res_t getpeerkeys();
80+
81+
};
82+
83+
} // namespace eosiosystem
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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

Comments
 (0)