Skip to content

Conversation

@greg7mdp
Copy link
Contributor

@greg7mdp greg7mdp commented Feb 14, 2025

Resolves #184.

Two new user-facing eosio actions are added to the system contract: regpeerkey and delpeerkey.

The intent of these is to allows block producers to register a public key (associated to the producer's account_name), which will be used to authenticate gossip messages sent by nodes managed by this producer. Once this is done, these nodes, provided they belong to one of the top 50 active producers, will be allowed into a gossip network managed by nodeos which disseminates the network addresses of these active producer nodes.

This allows block producers to maintain a connections to all other active block producers, which is desirable since the Savannah consensus mechanism requires signatures from a quorum of block producers for each block.

Note: The Savanna protocol differentiates block proposers and block finalizers, and nodes can take either (or both) of these roles. It is really block finalizers whose connectivity is critical, as, when a new block is produced, it will include the votes received on previous blocks from block finalizers,
As a result, a block producer benefits from being connected to as many block finalizers as possible (and also to the surrounding block proposers in the schedule).
In the current implementation of the Savanna protocol on EOS, the top 21 block producers must provide both the block proposer and block finalizer roles.

  • regpeerkey: allows a block producer to register a public key for a producer account name. A block producer can only have have one public key registered at a time. If a key is already registered for an account_name, and regpeerkey is called with a different key, the new key replaces the previous one in the system contract table.

  • delpeerkey: allows to remove a public key from the system contract table. The main purpose is to reclaim the ram used, as the key may persist in nodeos memory. An existing public key for a given account can be changed by calling regpeerkey again.

One new action (to be used internally by Spring as a readonly action) is added to the system contract: getpeerkeys

  • getpeerkeys: Returns a list of top-50 producers (in rank order), along with their peer public key if it was added via the regpeerkey action.

@greg7mdp greg7mdp requested a review from arhag February 18, 2025 21:19
@greg7mdp
Copy link
Contributor Author

greg7mdp commented Mar 4, 2025

@arhag @ericpassmore It would be very nice if this could make it into release 3.8 of eos-system-contracts. The change is very straightforward and I don't see any risk in retargeting the PR to release/3.8.

It is needed for AntelopeIO/spring#1169.

Resolved: this will be in the next version (April or later)

struct [[eosio::table("peerkeysver"), eosio::contract("eosio.system")]] peer_keys_version {
uint64_t version; // version incremented every time peer_keys_table is modified

uint64_t primary_key() const { return 0; } // table has just one row
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was a little worried about singleton since you wrote that:

find()s are one of the most expensive host functions a contract can call, and cdt's singleton impl is woof in this regard. eosio system contract: 12 find()s on every single action to deal with singletons

But if you say singleton is better I'm happy to change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

singleton really seems like the right pattern here

The performance woof can be resolved in cdt fairly easily some day. Actually, we can probably resolve the problem in nodeos too by short circuiting redundant lookups in the iterator cache.

};

struct [[eosio::table("peerkeysver"), eosio::contract("eosio.system")]] peer_keys_version {
uint64_t version; // version incremented every time peer_keys_table is modified
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can get around the need for tracking this (and the need for the table as a whole) by instead storing the block num a row is modified on. It's not a change I really feel strongly about, but it would be more efficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, but I thought that, in addition to the version, I would also track the number of deleted keys.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yes that is true, deletion is more tricky that way

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by instead storing the block num a row is modified on

Is there actually a way to get the current block number in a contract? It would be nice to have it to resolve the fork switch issue, but from this discussion I wonder if it is available?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep it was added in the GET_BLOCK_NUM protocol feature

struct [[eosio::table("peerkeys"), eosio::contract("eosio.system")]] peer_key {
name proposer_finalizer_name;
uint32_t block_num; // block number where this row was emplaced or modified
uint8_t version; // version 0 and above must have the `key` optional
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment doesn't match my understanding of the prior discussion. The reason we have a version is to do the opposite of the comment: it's here to give us the option to remove the optional<key> (and any other future binary extensions). Bumping the version allows a complete 'reset' of the serialization contents.

Also from an ABI standpoint, it's probably best practice to go ahead and make it a variant even though there is just a 'v0' now. The reason is once the version needs to be bumped in the future the only way the ABI can represent both simultaneously is via a variant. Transitioning to a variant in the future would break consumers who can only handle v0 since the version field would disappear from the ABI. This situation is likely academic considering the use case for this table; I suggest it mainly because I think system contracts should follow best practices.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully this is what you had in mind.

@ericpassmore
Copy link
Contributor

Note:start
category: Other
component: P2P
summary: On chain public keys to validate a peer's identity.
Note:end

@greg7mdp greg7mdp merged commit a3e21f2 into main Apr 16, 2025
1 check passed
@greg7mdp greg7mdp deleted the gh_184 branch April 16, 2025 17:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for storing public keys on-chain that will be used to validate a network peer's identity.

6 participants