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

Implement simple state witness distribution, chunk validation, and chunk approval distribution skeletons. #10287

Merged
merged 7 commits into from
Dec 15, 2023

Conversation

robin-near
Copy link
Contributor

This is a pretty rough PR, but I'm making an attempt to concretely implement the flow from distributing the state witness to sending the chunk endorsement. The following is left out: (1) production of the state witness; (2) actual validation logic; (3) receiving the chunk endorsement. However, the skeleton of the flow is there, including multithreading of the validation logic.

Very open to comments.

@robin-near robin-near requested a review from a team as a code owner December 4, 2023 05:04
@dsuggs-near
Copy link
Contributor

I've turned off automatic merging altogether until I understand this problem.

@dsuggs-near dsuggs-near closed this Dec 4, 2023
@dsuggs-near dsuggs-near reopened this Dec 4, 2023
Copy link
Contributor

@pugachAG pugachAG left a comment

Choose a reason for hiding this comment

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

Added a couple of suggestions, but overall LGTM!

/// A module that handles chunk validation logic. Chunk validation refers to a
/// critical process of stateless validation, where chunk validators (certain
/// validators selected to validate the chunk) verify that the chunk's state
/// witness is correct, and then sends chunk endorsements to the block producer
Copy link
Contributor

Choose a reason for hiding this comment

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

grammar nit: should "send" be used here instead of "sends"? 🤔

/// endorsement message to the block producer. The actual validation logic
/// happens in a separate thread.
pub fn start_validating_chunk(&self, state_witness: ChunkStateWitness) -> Result<(), Error> {
if self.my_signer.is_none() {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I suggest to avoid unwrapping this later:

let Some(my_signer) = self.my_signer else {
    return Err(Error::NotAValidator);
}

and then use my_signer instead of self.my_signer.unwrap()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

whoaaaaaaaaaa, I never knew let-else is a thing in Rust!! I had always been doing

let my_signer = if let Some(my_signer) = self.my_signer.as_ref() { my_signer } else { return ... }

THANK YOU!

chain/client/src/chunk_validation.rs Outdated Show resolved Hide resolved
let runtime_adapter = self.runtime_adapter.clone();
rayon::spawn(move || match validate_chunk(&state_witness, &*runtime_adapter) {
Ok(()) => {
tracing::debug!(
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Having chunk hash and block producer not as a part of the message should make logs easier to work with. Also that seems to be a bit more common pattern in our code base. So maybe consider moving those outside of the message.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you mean moving them as fields?

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah, having separate fields instead of string interpolation into the message

/// witness is correct, and then sends chunk endorsements to the block producer
/// so that the chunk can be included in the block.
pub struct ChunkValidator {
my_signer: Option<Arc<dyn ValidatorSigner>>,
Copy link
Contributor

Choose a reason for hiding this comment

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

what does 'my_signer' mean? :o

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I'm a validator, this is my signer. It's the signer of the node itself. I'll make a comment.

/// Performs the chunk validation logic. When done, it will send the chunk
/// endorsement message to the block producer. The actual validation logic
/// happens in a separate thread.
pub fn start_validating_chunk(&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.

out of curiosity, how is the word 'start' used in rust? Does it indicate async nature of the function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think Rust functions commonly spawn an asynchronous task to do things. Those that do are called "spawn(...)" but that specifically spawns a generic function or task into some thread. So I'm following the naming used in Chain::start_processing_block etc.


/// Distributes the chunk state witness to chunk validators that are
/// selected to validate this chunk.
pub fn send_chunk_state_witness_to_chunk_validators(
Copy link
Contributor

Choose a reason for hiding this comment

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

This is little confusing. Is this client used by both chunk proposer AND chunk validator?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, Client is used by everyone so this is just an additional function on the Client. I didn't want to put it in client.rs, so that I don't further increase the size of that file.

let num_requests = self.requests.read().unwrap().len();
let mut handled = false;
for _ in 0..num_requests {
let request = self.requests.write().unwrap().pop_front().unwrap();
Copy link
Member

@Longarithm Longarithm Dec 7, 2023

Choose a reason for hiding this comment

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

Is pop_front().unwrap() actually fine here? If someone else called handle_filtered against it, it is possible that there will be no requests in the queue in the end and it will panic. Looks scary...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I mean, this mock object is supposed to be drained by a single thread - the testing thread. Other threads can only push messages to it. Having two threads calling handle_filtered can't possibly lead to a good test even if this implementation was correct.

Copy link
Member

Choose a reason for hiding this comment

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

Oh that's a test environment, I've missed that. Anyway worth fixing

Copy link
Member

Choose a reason for hiding this comment

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

I mean, this mock object is supposed to be drained by a single thread - the testing thread. Other threads can only push messages to it. Having two threads calling handle_filtered can't possibly lead to a good test even if this implementation was correct.

Just got your message. Let's have it documented then - by saying that MockPeerManagerAdapter is owned only by one thread?

Comment on lines +67 to +78
let mut records = Vec::new();
for (i, account) in accounts.iter().enumerate() {
// The staked amount must be consistent with validators from genesis.
let staked = if i < 8 { validator_stake } else { 0 };
records.push(StateRecord::Account {
account_id: account.clone(),
account: Account::new(0, staked, CryptoHash::default(), 0),
});
// The total supply must be correct to pass validation.
genesis_config.total_supply += staked;
}
let genesis = Genesis::new(genesis_config, GenesisRecords(records)).unwrap();
Copy link
Member

Choose a reason for hiding this comment

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

nit: looks generic enough to make it a method Genesis::from_accounts(genesis_config, accounts). I'd guess other tests should do the same. Not even relevant to this PR, just makes me think that logic of creating genesis is convoluted.

@robin-near
Copy link
Contributor Author

So this apparently breaks tens of tests :( because of the additional network message that integration tests don't expect.

@robin-near robin-near added this pull request to the merge queue Dec 15, 2023
Copy link

codecov bot commented Dec 15, 2023

Codecov Report

Attention: 35 lines in your changes are missing coverage. Please review.

Comparison is base (2b975fc) 71.84% compared to head (2a9f7ba) 71.90%.

Files Patch % Lines
chain/client/src/chunk_validation.rs 91.00% 5 Missing and 4 partials ⚠️
core/primitives/src/chunk_validation.rs 42.85% 1 Missing and 3 partials ⚠️
chain/client/src/test_utils/setup.rs 50.00% 3 Missing ⚠️
chain/network/src/testonly/fake_client.rs 0.00% 3 Missing ⚠️
core/primitives/src/validator_signer.rs 50.00% 3 Missing ⚠️
chain/chain-primitives/src/error.rs 0.00% 2 Missing ⚠️
chain/client/src/adapter.rs 66.66% 2 Missing ⚠️
core/primitives/src/errors.rs 50.00% 2 Missing ⚠️
chain/client/src/client.rs 92.30% 0 Missing and 1 partial ⚠️
chain/client/src/client_actor.rs 50.00% 1 Missing ⚠️
... and 5 more
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #10287      +/-   ##
==========================================
+ Coverage   71.84%   71.90%   +0.05%     
==========================================
  Files         712      714       +2     
  Lines      143447   143704     +257     
  Branches   143447   143704     +257     
==========================================
+ Hits       103061   103324     +263     
+ Misses      35658    35640      -18     
- Partials     4728     4740      +12     
Flag Coverage Δ
backward-compatibility 0.08% <0.00%> (-0.01%) ⬇️
db-migration 0.08% <0.00%> (-0.01%) ⬇️
genesis-check 1.26% <0.00%> (-0.01%) ⬇️
integration-tests 36.35% <88.01%> (+0.16%) ⬆️
linux 71.49% <30.13%> (-0.10%) ⬇️
linux-nightly 71.47% <88.01%> (+0.03%) ⬆️
macos 55.69% <11.76%> (+0.72%) ⬆️
pytests 1.48% <0.00%> (-0.01%) ⬇️
sanity-checks 1.28% <0.00%> (-0.01%) ⬇️
unittests 68.20% <30.13%> (-0.03%) ⬇️
upgradability 0.13% <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.

Merged via the queue into near:master with commit 52e015c Dec 15, 2023
18 of 19 checks passed
@robin-near robin-near deleted the chunk2 branch December 15, 2023 06:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants