Conversation
9c65372 to
e0471c2
Compare
e0471c2 to
3b7d1b1
Compare
| } | ||
| } | ||
|
|
||
| pub fn from_bytes(bytes: &[u8]) -> Result<Self, StakeStateError> { |
There was a problem hiding this comment.
This could be zero-copy if you do a transmute instead of StakeStateV2Tag::deserialize, but not sure if there is any benefit in doing that.
There was a problem hiding this comment.
Would we lose some safety though? What do you think about this instead?
pub fn from_bytes(bytes: &[u8]) -> Result<Self, StakeStateError> {
if bytes.len() < 4 {
return Err(StakeStateError::UnexpectedEof);
}
let raw = u32::from_le_bytes(bytes[..4].try_into().unwrap());
Self::from_u32(raw)
}| #[repr(transparent)] | ||
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Default, SchemaWrite, SchemaRead)] | ||
| #[wincode(assert_zero_copy)] | ||
| pub struct PodAddress(pub [u8; 32]); |
There was a problem hiding this comment.
We should add wincode support to Address, then this type is not needed.
There was a problem hiding this comment.
| }; | ||
|
|
||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| pub enum StakeStateV2View<'a> { |
There was a problem hiding this comment.
Since this is a breaking change, could we stop using the enum? Seems like all we need is already in StakeStateV2Layout, so that one could become the state "account". One benefit is that we won't need to have the *View and *Write type. The fields not applicable for a variant can be optional.
There was a problem hiding this comment.
(Discussed offline)
Going to revise the API, but ensure a private-field pattern to ensure only methods can make state transitions to prevent invalidate state.
joncinque
left a comment
There was a problem hiding this comment.
Looks great overall! Mostly small things on my side
| miri: | ||
| needs: set_env | ||
| runs-on: ubuntu-latest | ||
| env: | ||
| RUST_TOOLCHAIN: ${{ needs.set_env.outputs.RUST_TOOLCHAIN_NIGHTLY }} | ||
| steps: | ||
| - name: Git Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Install Rust | ||
| uses: dtolnay/rust-toolchain@nightly | ||
| with: | ||
| toolchain: ${{ env.RUST_TOOLCHAIN }} | ||
| components: miri | ||
|
|
||
| - name: Setup Miri | ||
| run: cargo +"$RUST_TOOLCHAIN" miri setup | ||
|
|
||
| - name: Run Miri (pinocchio/interface) | ||
| run: make miri-pinocchio-interface |
There was a problem hiding this comment.
Let's integrate this directly as a new variable in the reusable action, ie https://github.com/solana-program/actions/blob/287c6068cc3b672ec4ce9dc6acb75757d477fd2b/.github/workflows/main.yml#L39
Then it's easy for any repo to add a miri check
| /// Wincode deserialization error. | ||
| Read(wincode::ReadError), | ||
| /// Input buffer is shorter than 200 bytes. | ||
| /// Buffer shorter than 200 bytes. | ||
| UnexpectedEof, | ||
| /// Pass-through for wincode write errors when serializing layout structs. | ||
| /// Wincode serialization error. | ||
| Write(wincode::WriteError), |
There was a problem hiding this comment.
nit: I missed the PR that introduced these, but unless we have a really good reason, it's more annoying than helpful to expose external types in a public API. Let's not have wincode::ReadError or wincode::WriteError there directly, and instead use our own variants
| #[repr(transparent)] | ||
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Default, SchemaWrite, SchemaRead)] | ||
| #[wincode(assert_zero_copy)] | ||
| pub struct PodAddress(pub [u8; 32]); |
There was a problem hiding this comment.
There was a problem hiding this comment.
@febo discussed updating spl-pod to contain these types, which would probably simplify things.
On the other hand, there's no harm in redefining them if needed, since they're just used internally to convert to normal types.
| #[inline] | ||
| pub(crate) fn from_u32(v: u32) -> Result<Self, StakeStateError> { | ||
| match v { | ||
| 0 => Ok(Self::Uninitialized), | ||
| 1 => Ok(Self::Initialized), | ||
| 2 => Ok(Self::Stake), | ||
| 3 => Ok(Self::RewardsPool), | ||
| other => Err(StakeStateError::InvalidTag(other)), | ||
| } | ||
| } |
There was a problem hiding this comment.
nit: up to you, but it would be more idiomatic rust to impl TryFrom<u32> instead of this function
| /// Returns a reference to `Meta` if in the `Initialized` or `Stake` state. | ||
| #[inline] | ||
| pub fn meta(&self) -> Option<&Meta> { | ||
| match self.tag() { |
There was a problem hiding this comment.
nit: Since this function is called a lot, I wonder if we'd get some performance gains from something like StakeStateV2Tag::from_u32_unchecked to be used from inside the function
| /// Returns a mutable reference to `Meta` if in the `Initialized` or `Stake` state. | ||
| #[inline] | ||
| pub fn meta_mut(&mut self) -> Result<&mut Meta, StakeStateError> { | ||
| match self.tag() { | ||
| StakeStateV2Tag::Initialized | StakeStateV2Tag::Stake => Ok(&mut self.meta), | ||
| tag => Err(StakeStateError::InvalidStateAccess(tag)), | ||
| } | ||
| } |
There was a problem hiding this comment.
nit: is there a reason for the mutable ones to return a Result and the immutable ones to return an Option? Probably better to be consistent
| if from == StakeStateV2Tag::Initialized { | ||
| self.stake_flags = 0; | ||
| self.padding.fill(0); | ||
| } |
There was a problem hiding this comment.
Sorry if I'm being dense, but why is this done only for Initialized?
| [dependencies] | ||
| # TODO: Waiting on 0.3.0 to be released | ||
| wincode = { git = "https://github.com/anza-xyz/wincode.git", rev = "fa70c7c7c13885085f743e4f01deb1a4de0b64fb", default-features = false, features = ["derive"] } | ||
| wincode = { git = "https://github.com/anza-xyz/wincode.git", rev = "6feb75ff4c5b26275b0ee8bc4a12ea906d996a02", default-features = false, features = ["derive"] } |
There was a problem hiding this comment.
Since we're now on v0.4, let's use that published version!
| pub const META_OFF: usize = TAG_LEN; | ||
| pub const STAKE_OFF: usize = TAG_LEN + size_of::<Meta>(); | ||
| pub const FLAGS_OFF: usize = TAG_LEN + size_of::<Meta>() + size_of::<Stake>(); | ||
| pub const PADDING_OFF: usize = FLAGS_OFF + 1; |
There was a problem hiding this comment.
nit: doesn't need to be done here, but let's spell out OFFSET fully
Adds a new zero‑copy state API backed by wincode.
Key Changes
p-stake-interfacecrate (#![no_std]compatible)PodU32,PodU64,PodI64,PodAddress) for safe zero-copy fromunaligned slices
StakeStateV2:StakeStateV2::from_bytes(&[u8]) -> Result<&StakeStateV2, StakeStateError>for read-only accessStakeStateV2::from_bytes_mut(&mut [u8]) -> Result<&mut StakeStateV2, StakeStateError>formutations
meta(),stake(),meta_mut(),stake_mut()initialize(),delegate()Motivation
Deserialization via bincode/borsh is computationally expensive for programs that only need to access specific fields or verify the state. This zero-copy approach allows for significantly lower compute unit (CU) usage when interacting with stake accounts.