-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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
Secure sysvars under hash by freezing all strictly #7892
Conversation
|
||
pub fn freeze(&self) { | ||
if self.set_hash() { | ||
self.update_slot_hashes(); |
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.
Updating slot_hashes
here results in mutating bank_hash of frozen slot
, which complicates things. So just delay updating.
runtime/src/bank.rs
Outdated
@@ -442,6 +442,7 @@ impl Bank { | |||
new.ancestors.insert(p.slot(), i + 1); | |||
}); | |||
|
|||
new.update_slot_hashes(parent.slot(), parent.hash()); |
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.
Strictly speaking, this parent bank's book keeping is duplicated across all the child (= forked) banks; but I think this is a justifiable trade-off to avoid complicated mutable bank hash management otherwise.
Codecov Report
@@ Coverage Diff @@
## master #7892 +/- ##
========================================
+ Coverage 81.9% 81.9% +<.1%
========================================
Files 243 243
Lines 52568 52621 +53
========================================
+ Hits 43075 43120 +45
- Misses 9493 9501 +8 |
Hmm, CI passed albeit new errors are observed from
|
CC: @rob-solana (I'm guessing you're the sysvar expert.) |
nice work! with moving slothash updates to the child, you can remove the code that skips sysvars in hashing, then this PR gets nice and small, and we're removing code (yay!) |
@rob-solana Thanks for confirming! Sadly, I can't remove the code that skips sysvars in hashing as long as recent_blockhashes isn't deterministic. It changes the contained set of slots differing with older slots. Originally, I thought this is because each validator can have possibly different vote history, so this is plausible. But this occurs even by running the I think this is very odd or moreover a bug? I think recent blockhashes must be deterministic otherwise old TXes are rejected sometimes and not othertimes First run:
Next run:
Really odd... I can get a different set of slots for the recent block hashes for the same slot 264 everytime I ran |
|
3e8810a
to
a2d2413
Compare
@sakridge Every remaining work is done; I'm pretty sure this is having the prime time for the review! :) |
runtime/src/bank.rs
Outdated
|
||
// Normally, just use the hash from parent slot. However, use the | ||
// existing stored hash if any for the sake of bank hash's idempotent. | ||
if let Some((hot_account, _)) = self.get_account_modified_since_parent(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.
IMO, I just camed up this naming and like it. new_account <=> (old_account == {hot -> cold -> frozen}_account)
. So, if this is agreeable, I'll rename get_account_modified_since_parent
to get_hot_account()
.
@mvines @garious Any opnitions? Mention based on https://github.com/solana-labs/solana/pull/2806/files#diff-7145777648f8c12caeb15b4e1755215cR560 #2806
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 find the hot/cold labeling here intuitive. If I didn't have the additional context that a hot account is an account_modified_since_parent I don't think I would have guessed that on my own.
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.
Thanks for your opinion! Then, I just reverted the naming thing: 4f1fe0a
Sorting thing is now reverted; and as far as I can tell from the quoted log of an idling validator, hashing of this size per a slot should not be a problem at all. :)
|
@@ -38,7 +38,6 @@ use solana_sdk::bank_hash::BankHash; | |||
use solana_sdk::clock::{Epoch, Slot}; | |||
use solana_sdk::hash::{Hash, Hasher}; | |||
use solana_sdk::pubkey::Pubkey; | |||
use solana_sdk::sysvar; |
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.
Bye bye, leaky abstraction; Feels so good. :)
#[should_panic] | ||
fn test_accounts_empty_account_bank_hash() { | ||
let accounts = Accounts::new(Vec::new()); | ||
accounts.store_slow(0, &Pubkey::default(), &Account::new(1, 0, &sysvar::id())); |
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.
sysvar
s are now the first citizen in the AccountsDB land. :)
@@ -759,6 +758,14 @@ impl AccountsDB { | |||
let hash_info = bank_hashes | |||
.get(&parent_slot) | |||
.expect("accounts_db::set_hash::no parent slot"); | |||
if bank_hashes.get(&slot).is_some() { |
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.
is this additional line in scope for this PR?
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.
@rob-solana Yes!
First, this PR added additional tests for the lazy initialization of slot hashes sysvar around here. That exact line internally hits here and bails out with this if
condition. So, this if
is needed to pass the test correctly. Otherwise, this following assertion for idempotent bank hash would fail because set_file
without this if
would incorrectly reset the bank_hash for the tested slot, which is shared by bank2
and bank3
.
I know such sharing of a slot between child banks doesn't occur in the real validator behavior. Thus, I alternatively could replace this error!
with panic!
and adjust all tests. But it takes extra effort and introduces an artifical limitation to AccountsDB: Currently, all of other AccountsDB code doesn't forbid such slot sharing, it just works. Only this part didn't work; So I wanted to preserve that flexible property and managed to do that with this tiny additional if
clause.
In other words, AccountsDB isn't aware of anything called forks and free of leaky abstraction now and forever. I think that's nice and beautiful design and separation of concern. :)
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
sysvars' store(create_account)
pattern was expedient when bank hashes were actually hashes.
Now that bank hashes are xors (and/or changing to RSA inclusion proofs), we might consider changing sysvars' pattern to update a mutable account, just to alleviate confusion for people visiting this code in the future. sysvars really jump through some hoops to get updated now...
@rob-solana Thanks for reviewing and approvals! FYI: @sakridge I'm going to merge this, although I originally added you as a reviewer. :)
I see! Thanks for explaining! I'll keep that in mind. :) |
original pr: solana-labs#5573 pr that added bank3: solana-labs#7892
original pr: solana-labs#5573 pr that added bank3: solana-labs#7892
Problem
sysvar
account data inside snapshots can be changed without affecting the bank hash (meaning being unable to detect malicious snapshots) because it's excluded from the bank hash calculation.it's a security risk.
Also, it's desirable to save
sysvar
s in the snapshot instead of not doing so because we can't always dynamically create all ofsysvar
s when starting a validator. Not hashing some of them would relax us from creating deterministically serialized sysvar data; However I've opted not to do so because otherwise we can't guarantee the exact sysvar state when replaying the ledger when debugging ledger inconsistency. And general consistency with normal accounts.I'm not completely sure the exact reason for the unconditional special handling of all
sysvar
's exclusion from the bank hash, but I think this is due to some outdated implementation restriction for the historical reasons as far as I glanced the history of git/discord. So, just remove the special handling altogether! :)Summary of changes
Remove the special handling by adjusting sysvar updates and include sysvar accounts into the bank hash calcuration like any other accounts, resulting in beautifier design. :)
To make it possible:
sysvar
's update from insideBank::freeze()
toBank::new_from_parent()
like other sysvar updates.sysvar
's account hash by definingBank::update_sysvar_account()
.set_hash
andfreeze
Todo/Notes
part of #7167