Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vote-program): Authorize instructions #576

Open
wants to merge 32 commits into
base: main
Choose a base branch
from

Conversation

dadepo
Copy link
Contributor

@dadepo dadepo commented Feb 24, 2025

Implements the authorize related instructions.

  • authorize
  • authorize_with_seed
  • authorize_checked
  • authorize_checked_with_seed

This also includes support for versioned state and ability to convert from previous states to the current vote state.

A bulk of the methods on the VoteState where also implemented as part of implementing the authorize instructions.

Copy link

codecov bot commented Feb 27, 2025

Codecov Report

Attention: Patch coverage is 90.43321% with 53 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/runtime/program/vote/state.zig 86.40% 42 Missing ⚠️
src/runtime/program/vote/execute.zig 94.95% 11 Missing ⚠️
Files with missing lines Coverage Δ
src/utils/collections.zig 94.28% <100.00%> (+0.29%) ⬆️
src/runtime/program/vote/execute.zig 93.98% <94.95%> (+3.78%) ⬆️
src/runtime/program/vote/state.zig 86.68% <86.40%> (-1.55%) ⬇️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Base automatically changed from dade/vote-program to main March 3, 2025 15:45
@dadepo dadepo marked this pull request as ready for review March 7, 2025 14:39
@dadepo dadepo requested review from yewman and 0xNineteen March 7, 2025 14:40
0xNineteen
0xNineteen previously approved these changes Mar 14, 2025
Copy link
Contributor

@0xNineteen 0xNineteen left a comment

Choose a reason for hiding this comment

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

lgtm

@dadepo dadepo force-pushed the dade/vote-program-authorize branch from 79d8ac1 to a8f7261 Compare March 17, 2025 19:25
@dadepo dadepo requested a review from 0xNineteen March 17, 2025 19:56
Copy link
Contributor

@yewman yewman left a comment

Choose a reason for hiding this comment

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

A couple of comments from an initial pass :)

/// Agave https://github.com/anza-xyz/solana-sdk/blob/4e30766b8d327f0191df6490e48d9ef521956495/vote-interface/src/error.rs#L11
///
/// Reasons the vote might have had an error
pub const VoteError = error{
Copy link
Contributor

Choose a reason for hiding this comment

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

Native program errors should be enums if possible, since when we set the custom error it need to match Agave's value which comes from an error enum. This is my bad as originally I had written system program like this, but have just fixed it in a new PR here #618

Copy link
Contributor Author

Choose a reason for hiding this comment

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

cbf8ea4 Although the return type of setNewAuthorizedVoter now looks a bit awkward. Not sure how that can be improved.

switch (vote_authorize) {
.voter => {
const current_epoch = clock.epoch;
const target_epoch = std.math.add(u64, current_epoch, 1) catch {
Copy link
Contributor

Choose a reason for hiding this comment

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

Even though this should never really fail, it is out of sequence with respect to Agave and theoretically could cause an incorrect error return

authorized: Pubkey,
vote_authorize: VoteAuthorize,
clock: Clock,
signers: ?std.AutoHashMap(Pubkey, void),
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 passing a slice would be simpler here, it would also make the subsequent control flow easier to understand and compare with agave. Then signature verification can just linearly search the slice. We would probably need a helper on the instruction context to pull out a slice of signers for of the call scenarios

Copy link
Contributor

Choose a reason for hiding this comment

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

Depends if searching through the signers map is time-critical. Moving to a slice would make it O(N). If that doesn't matter, I agree with Harold and would prefer a slice. Either way, it shouldn't be a managed type, the allocator is passed in just above.


// current authorized withdrawer or epoch authorized voter must say "yay"
if (!authorized_withdrawer_signer) {
const epoch_authorized_voter = try vote_state.getAndUpdateAuthorizedVoter(
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't this is called regardless of the value of authorized_withdrawer_signer?

allocator,
current_epoch,
);
if (!ic.info.isPubkeySigner(epoch_authorized_voter)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This should check signers if it is non null right? (Would be redundant if we make signers a non-optional slice)

Comment on lines +392 to +393
var new_authority = try ic.borrowInstructionAccount(
@intFromEnum(vote_instruction.VoteAuthorize.AccountIndex.new_authority),
);
defer new_authority.release();

if (!ic.info.isPubkeySigner(new_authority.pubkey)) {
return InstructionError.MissingRequiredSignature;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

For context, Agave stores pubkeys in the transaction context accounts. Sig, and Firedancer keep account pubkeys within the instruction context account meta so we don't need to re-index into the transaction context accounts every time we need a key. Instead of borrowing an account from the transaction context, just use ic.info.getAccountMetaAtIndex which gives you the pubkey along with some other meta data

Comment on lines +406 to +404
const authorize_pubkey = switch (vote_authorize) {
.voter => VoteAuthorize.voter,
.withdrawer => VoteAuthorize.withdrawer,
};

Copy link
Contributor

Choose a reason for hiding this comment

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

What is the purpose of this?

};

pub const VoteAuthorizeWithSeedArgs = struct {
authorization_type: sig.runtime.program.vote_program.state.VoteAuthorize,
Copy link
Contributor

Choose a reason for hiding this comment

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

Could probaby use an alias for these

Comment on lines +254 to +255
/// Agave https://github.com/anza-xyz/solana-sdk/blob/4e30766b8d327f0191df6490e48d9ef521956495/vote-interface/src/state/vote_state_0_23_5.rs#L11
pub const VoteState0_23_5 = struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't mind how we format these permalinks, but I think it would be nice to be consistent. I went with /// [agave] ... arbitrarily and am happy to conform to any other suggestion but I do love consistency 😛

return self.authorized_voters.contains(epoch);
}

// TODO Add method that returns iterator over authorized_voters
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need an iterator other than authorized_voters.iterator()?

@dadepo dadepo force-pushed the dade/vote-program-authorize branch from 77610c1 to cbf8ea4 Compare March 18, 2025 09:14
Copy link
Contributor

@Rexicon226 Rexicon226 left a comment

Choose a reason for hiding this comment

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

Just gave it a quick read through, haven't compared to Agave yet.

@@ -129,10 +163,268 @@ fn intializeAccount(
clock,
);
defer vote_state.deinit();
try vote_account.serializeIntoAccountData(vote_state);
try vote_account.serializeIntoAccountData(VoteStateVersions{ .current = vote_state });
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
try vote_account.serializeIntoAccountData(VoteStateVersions{ .current = vote_state });
try vote_account.serializeIntoAccountData(.{ .current = vote_state });

authorized: Pubkey,
vote_authorize: VoteAuthorize,
clock: Clock,
signers: ?std.AutoHashMap(Pubkey, void),
Copy link
Contributor

Choose a reason for hiding this comment

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

Depends if searching through the signers map is time-critical. Moving to a slice would make it O(N). If that doesn't matter, I agree with Harold and would prefer a slice. Either way, it shouldn't be a managed type, the allocator is passed in just above.

// https://github.com/anza-xyz/agave/blob/49fb51295c1062b6b09e585b2fe0a4676c33d3d4/programs/vote/src/vote_state/mod.rs#L701-L707
// https://github.com/anza-xyz/solana-sdk/blob/4e30766b8d327f0191df6490e48d9ef521956495/vote-interface/src/state/mod.rs#L873
{
const authorized_withdrawer_signer = if (signers) |signers_|
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't prepend or append identifiers with _. Call it maybe_signers and signers if it does need to be optional.

target_epoch,
);

if (err) |err_| {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, I'd like us to not use _ like this.


fn validateIsSigner(
authorized: Pubkey,
signers: std.AutoHashMap(Pubkey, void),
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't be managed.

const ids = sig.runtime.ids;
const testing = sig.runtime.program.testing;
const PriorVote = sig.runtime.program.vote_program.state.PriorVote;
_ = &PriorVote;
Copy link
Contributor

Choose a reason for hiding this comment

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

Useless discard.


/// Upper limit on the size of the Vote State
/// when votes.len() is MAX_LOCKOUT_HISTORY.
pub fn sizeOf() usize {
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be a constant.

self: *VoteState,
new_authorized_voter: Pubkey,
target_epoch: Epoch,
) (error{OutOfMemory} || InstructionError)!struct { void, ?VoteError } {
Copy link
Contributor

Choose a reason for hiding this comment

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

I do not understand why this is returning a void tuple member.

return .{
.node_pubkey = node_pubkey,
.authorized_voters = if (authorized_voter) |authorized_voter_|
AuthorizedVoters.init(allocator, 0, authorized_voter_) catch unreachable
Copy link
Contributor

Choose a reason for hiding this comment

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

I see no reason not to use try here instead of catch unreachable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: 👀 In review
Development

Successfully merging this pull request may close these issues.

4 participants