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: avoid processing state witness multiple times #11111

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

pugachAG
Copy link
Contributor

@pugachAG pugachAG commented Apr 18, 2024

This PR introduces tracking of processes state witnesses as a protection against wasting stateless validator resources.
Note that we track state witnesses for chunks built only on top of the blocks that are known to us (orphan pool takes care of the rest) and not yet final (covered by #11081). So we only need a pretty limited capacity for the LRU cache keeping track of received instances.

Part of #10565.

@pugachAG pugachAG added the A-stateless-validation Area: stateless validation label Apr 18, 2024
@pugachAG pugachAG marked this pull request as ready for review April 18, 2024 18:35
@pugachAG pugachAG requested a review from a team as a code owner April 18, 2024 18:35
@pugachAG pugachAG linked an issue Apr 18, 2024 that may be closed by this pull request
Copy link

codecov bot commented Apr 18, 2024

Codecov Report

Attention: Patch coverage is 66.66667% with 8 lines in your changes are missing coverage. Please review.

Project coverage is 71.06%. Comparing base (5403d04) to head (4127a9f).
Report is 8 commits behind head on master.

Files Patch % Lines
...nt/src/stateless_validation/chunk_validator/mod.rs 56.25% 6 Missing and 1 partial ⚠️
chain/chain-primitives/src/error.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##           master   #11111   +/-   ##
=======================================
  Coverage   71.06%   71.06%           
=======================================
  Files         767      767           
  Lines      153390   153362   -28     
  Branches   153390   153362   -28     
=======================================
- Hits       109003   108990   -13     
+ Misses      39943    39928   -15     
  Partials     4444     4444           
Flag Coverage Δ
backward-compatibility 0.24% <0.00%> (+<0.01%) ⬆️
db-migration 0.24% <0.00%> (+<0.01%) ⬆️
genesis-check 1.43% <0.00%> (+<0.01%) ⬆️
integration-tests 36.77% <66.66%> (-0.08%) ⬇️
linux 69.46% <4.16%> (-0.02%) ⬇️
linux-nightly 70.53% <66.66%> (-0.02%) ⬇️
macos 54.23% <4.16%> (+0.65%) ⬆️
pytests 1.66% <0.00%> (+<0.01%) ⬆️
sanity-checks 1.45% <0.00%> (+<0.01%) ⬆️
unittests 66.74% <4.16%> (-0.01%) ⬇️
upgradability 0.29% <0.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

/// (shard_id, epoch_id, height_created). This protects against malicious chunk
/// producers wasting stateless validator resources by making it apply chunk multiple
/// times.
fn check_duplicate_witness(&mut self, state_witness: &ChunkStateWitness) -> Result<(), Error> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we store the chunk hash as "value" for LRU cache and add a check to see if we have received the same chunk hash? If the chunk hash of the witness is the same it can probably be deemed as a non-malicious behavior?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Technically submitting the witness for the same chunk (hence with the same chunk hash) could also be an attack vector. For example another stateless validator could send a witness it received to us multiple times and in this case it is not coming from the chunk producer.
On top of that currently we don't really distinguish between "definitely malicious behaviours" and "potentially malicious behaviours", in both cases we just ignore the witness. So I suggest to keep it simple for now and extend it later if we ever need it.

@@ -103,6 +107,8 @@ impl ChunkValidator {
)));
}

self.check_duplicate_witness(&state_witness)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm IMO this should be placed after pre_validate_chunk_state_witness().

Otherwise a malicious chunk producer can do something like:

  1. Submit valid ChunkStateWitness
  2. Submit 100 junk ChunkStateWitnesses to fill the cache with junk (possibly from other random nodes to avoid getting banned)
  3. Go to (1), the cache doesn't contain the valid ChunkStateWitness so it'll be processed again.

This way a malicious chunk producer could force the validator to process the valid ChunkStateWitness an arbitrary number of times.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This should not be possible because of the checks as part of partially_validate_state_witness. In particular chunk producer could only submit one witness for every height it suppose to produce a chunk on. On top of that it should be on top of a not-yet-final block which is known to us (otherwise it goes to orphan pool). So chunk producer could only submit junk for a very narrow set of heights hence the cache size of 100.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh I didn't notice that apart from pre_validate_chunk_state_witness we also do partially_validate_state_witness at the beginning of process_chunk_state_witness . That makes the situation better 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

But could a malicious chunk producer submit 100 fake witnesses (with existing prev_block_hash) for the next 100 heights for which it's a chunk producer? I think we don't limit the distance from the tip for non-orphan witnesses?

Copy link
Contributor Author

@pugachAG pugachAG Apr 19, 2024

Choose a reason for hiding this comment

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

Only if we don't have a final block for those 100 heights. If that happens then the blockchain is in a very degraded state and we have bigger problems to worry about 🙃 Keeping Flat Storage deltas in memory was built on the same assumptions by the way.

Copy link
Contributor

Choose a reason for hiding this comment

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

The scenario that I'm imagining would look something like this:
There's a final block F at height 100
There's a chunk producer P, responsible for producing chunks at heights 102, 104, 106, 108, 110, ...

P is malicious and constructs a fake witness for each of those heights:

  • Witness {height: 102, prev_block_hash: F, receipts: lotsofwork}
  • Witness {height: 104, prev_block_hash: F, receipts: lotsofwork}
  • Witness {height: 106, prev_block_hash: F, receipts: lotsofwork}
  • ...

Then P sends all of those witnesses to validator V.

Wouldn't V process all of those witnesses? P could fill the whole cache with them, or just make V do a lot of useless work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I see, that is indeed a valid scenario, good catch!
Moving this PR to draft until I have a solution that addresses that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-stateless-validation Area: stateless validation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[stateless_validation][tracking issue] Security concerns Round-1
3 participants