-
Notifications
You must be signed in to change notification settings - Fork 42
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
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
... and 1 file with indirect coverage changes 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
79d8ac1
to
a8f7261
Compare
There was a problem hiding this 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 :)
src/runtime/program/vote/error.zig
Outdated
/// 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{ |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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)
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; | ||
} |
There was a problem hiding this comment.
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
const authorize_pubkey = switch (vote_authorize) { | ||
.voter => VoteAuthorize.voter, | ||
.withdrawer => VoteAuthorize.withdrawer, | ||
}; | ||
|
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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
/// 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 { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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()
?
77610c1
to
cbf8ea4
Compare
There was a problem hiding this 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 }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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), |
There was a problem hiding this comment.
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_| |
There was a problem hiding this comment.
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_| { |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 } { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
.
Implements the authorize related instructions.
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.