-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
slashing: add proof account functionality #7394
Conversation
@@ -0,0 +1 @@ | |||
8sT74BE7sanh4iT84EyVUL8b77cVruLHXGjvTyJ4GwCe |
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 used the default key when from when I deployed to testnet, can update once we decide how this is going to be deployed.
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.
No problem. Since this will be an "enshrined" program we could even add it a non-random address with a feature-gate, like S1ashing1111111111111....
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.
Got it, let me know if I have this correct for the deployment scheme:
- We upload the program to some address A
- On epoch boundary where the feature flag is active we create a program account owned by the system program
S1ashing1111...
- We them move the contents of A to this vanity address
- Further upgrades to the program will require another feature flag
I did see that some of the other spl programs were deployed with a squads multi sig as the upgrade authority. Curious about your thoughts about that deployment schema.
It seems that would allow us more freedom in upgrading or disabling the slashing program should there be any vulnerabilities. But optically is it worse if the keys are held by anza & FD?
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.
Yeah, that would be the process. Anza / FD really can't hold the upgrade keys to something so sensitive -- we'll need to leave it up to the validators. We can start off with feature gates, and then eventually move to a validator DAO, if that ever materializes.
return Err(ProgramError::InvalidAccountData); | ||
} | ||
|
||
msg!("Creating proof account with size {}", account_length); |
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 provided the functionality for the user to create and initialize the account here, but if you prefer we could have them create the account and write it themselves using the record program, only interacting with slashing for the verification.
Not sure what the preferred style is for these things.
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.
That's a good question. If these proofs might be really big, we could go with the same model as zk proofs, where the proofs can be read from instruction data or account data (doesn't matter which program).
This way, the slashing program gets way simpler -- it just needs to take in data, check it, and then create a valid slash "context state account" from that, containing the minimum required data, ie:
- validator that committed the slashable offense
- pubkey that submitted the proof
- kind of slashable offense
- epoch in which it happened
These would look similar to the "context state accounts" in the zk elgamal proof program. Here's how the program looks: https://github.com/anza-xyz/agave/blob/master/programs/zk-elgamal-proof/src/lib.rs
And the ZkProofData
trait: https://github.com/anza-xyz/agave/blob/master/zk-sdk/src/zk_elgamal_proof_program/proof_data/mod.rs#L65
And one example separating the proof data from context: https://github.com/anza-xyz/agave/blob/c88e6df566c5c17d71e9574785755683a8fb033a/zk-sdk/src/zk_elgamal_proof_program/proof_data/pubkey_validity.rs#L35
The only issue with using account data owned by any program is that the data could get changed afterwards, which can make auditability more difficult. The slashing program could get around that by using the same trick as account-compression, where the program CPIs into a no-op program with all of the proof data, which forces the runtime to log the instruction data. This way it's stored longterm.
Or, keep it simple, and make the "context state account" contain all the data.
Does this make sense? What do you think?
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.
That's fair, I am not opposed to having the user create and write the proof data themselves.
To answer your questions:
- The proof sizes will be fairly large. I expect the duplicate block proof to be the smallest with ~2400 bytes
- For now I expect the proofs to be of fixed size known up front for each proof type, although this might change if we decide to add more slashing conditions in the future
- There also should be no reason to CPI into the slashing program, or create the proof on chain. All slashable events occur (at least partially) offchain by nature.
- I haven't decided yet on how the results will be collected but the context state account makes sense. At most we'd need 1 account per staked validator.
We could also just maintain a more compact structure instead of creating a separate account:HashMap<Epoch, (Pubkey, Vec<ProofType, usize>>>
essentially a list of # of infractions by type per pubkey.
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.
Gotcha, that's all good context. A few followups:
There also should be no reason to CPI into the slashing program, or create the proof on chain. All slashable events occur (at least partially) offchain by nature.
You never know what people will do! Just so I'm sure I'm not totally misunderstanding, when you say "no reason to create the proof on chain", you mean specifically "from another program", correct? Because the concept is that someone will upload the whole proof on-chain, right?
I haven't decided yet on how the results will be collected but the context state account makes sense. At most we'd need 1 account per staked validator.
Ah right, yeah that makes sense. I was thinking (naively) that we wouldn't even need accounts per validator, since you can just submit proofs as individual accounts, but we probably want to avoid duplicate submissions. For example, if a validator creates a duplicate block, and they created more than one pair of offending shreds, then there will be two distinct valid slash proofs, but for the same event.
Ok, so then we'll need two account types: proofs (can be created on any address) and validator accounts (some PDA that can be instantiated by anyone). Let's avoid the hashmap -- that can become an easy attack vector.
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.
you mean specifically "from another program", correct
Yeah exactly, the proof will still be uploaded on-chain.
For example, if a validator creates a duplicate block, and they created more than one pair of offending shreds, then there will be two distinct valid slash proofs, but for the same event.
This is a good point, avoiding duplicates definitely makes it worth tracking per validator. I was also thinking that since the runtime code will eventually have to traverse these accounts in order to debit delegations so it's a good idea to keep it bounded.
Let's avoid the hashmap -- that can become an easy attack vector.
Sounds good.
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.
Looks like a good start! Let's figure out how this program should operate, and then we can dig down more into the implementation.
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.
Nit: this file hasn't been needed for a long time, so we can remove it
@@ -0,0 +1 @@ | |||
8sT74BE7sanh4iT84EyVUL8b77cVruLHXGjvTyJ4GwCe |
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.
No problem. Since this will be an "enshrined" program we could even add it a non-random address with a feature-gate, like S1ashing1111111111111....
/// Incorrect authority provided on write or close | ||
#[error("Incorrect authority provided on write or close")] | ||
IncorrectAuthority, |
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.
nit: we could use ProgramError::IncorrectAuthority
instead, but I don't feel too strongly about it
/// Calculation overflow | ||
#[error("Calculation overflow")] | ||
Overflow, |
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.
nit: we could use ProgramError::ArithmeticOverflow
instead
return Err(ProgramError::InvalidAccountData); | ||
} | ||
|
||
msg!("Creating proof account with size {}", account_length); |
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.
That's a good question. If these proofs might be really big, we could go with the same model as zk proofs, where the proofs can be read from instruction data or account data (doesn't matter which program).
This way, the slashing program gets way simpler -- it just needs to take in data, check it, and then create a valid slash "context state account" from that, containing the minimum required data, ie:
- validator that committed the slashable offense
- pubkey that submitted the proof
- kind of slashable offense
- epoch in which it happened
These would look similar to the "context state accounts" in the zk elgamal proof program. Here's how the program looks: https://github.com/anza-xyz/agave/blob/master/programs/zk-elgamal-proof/src/lib.rs
And the ZkProofData
trait: https://github.com/anza-xyz/agave/blob/master/zk-sdk/src/zk_elgamal_proof_program/proof_data/mod.rs#L65
And one example separating the proof data from context: https://github.com/anza-xyz/agave/blob/c88e6df566c5c17d71e9574785755683a8fb033a/zk-sdk/src/zk_elgamal_proof_program/proof_data/pubkey_validity.rs#L35
The only issue with using account data owned by any program is that the data could get changed afterwards, which can make auditability more difficult. The slashing program could get around that by using the same trick as account-compression, where the program CPIs into a no-op program with all of the proof data, which forces the runtime to log the instruction data. This way it's stored longterm.
Or, keep it simple, and make the "context state account" contain all the data.
Does this make sense? What do you think?
/// Types of slashing proofs | ||
#[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
pub enum ProofType { | ||
/// Proof consisting of 2 shreds signed by the leader indicating the leader | ||
/// submitted a duplicate block. | ||
DuplicateBlockProof, | ||
/// Invalid proof type | ||
InvalidType, | ||
} |
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 would start with 0
as invalid, since accounts are initialized with all zeroes.
/// Struct version, allows for upgrades to the program | ||
pub version: u8, |
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.
We probably don't need versioning for this -- we would likely create new proof types and deprecate the old ones as needed
per the comments above, closing this in favor of #7418 |
Initial skeleton of slashing program.
Adds proof account creation, write and close, mostly plagiarized from the record program.
Since proof sizes are known upfront there is no need to realloc.
Future PR will add verification for this proof.