-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Conversation
Not sure it is such a great idea to bundle extra data with block announcement. Does the protocol guarantee that announcement for the same block will always come with the same extra data? |
To be honest, that is a decent approach as well. Perhaps we should rather have a method to use a custom block announcement message rather than bundle the extra data. The desired use-case is for Cumulus: before a block is included on the relay chain but after voting on it has begun, the block should be synced by peers only when bundled with a submission attestation. We would need two changes:
|
How is it different from e.g block justifications, that come not as part of the announcement, but as block data? |
Justifications are meant to prove finality and be permanently valid. It's exactly what you pinpointed - that this is for a use-case where the justification-like thing is temporary and doesn't need to be persisted, at least not for long. We want this data to come along with the announcement, because it is crucial for our decision as to whether to download the head or not. Having to do an extra round-trip for all announced blocks would be pretty ridiculous. |
So, I personally would go with an extra field for the block announcement message and move the default validation completely out of the sync code. I could imagine that people want to bring in their own validation of the block announcement, for whatever reasons. But yeah, for the sake of bringing Cumulus to a point that it works, we can also go the way of adding an extra message for our own block announcements. |
I'm fine with this |
@twittner OK - looks like the current approach of the PR with slight modification to move default validation out of the sync code will work. |
It would be helpful to know what you consider "default validation" from here: substrate/core/network/src/protocol/sync.rs Lines 893 to 1002 in d2e7d66
|
The peer management does not seems to be used after line 933? As the updating of the peer information is not validating, it should stay in the sync layer. |
It is, e.g.
|
Can we not make this available to the announce validator using a trait? The validator would probably return the following:
Then in the
|
Could you be more specific? Do you mean we should run |
The goal of the PR is to give us the option to do additional, customized validation on whether a block announcement is valid based on possibly time-sensitive circumstances. We'd do so by bundling a bit of optional extra data with the announcement to be checked by some user code, along with the announcement. Most of the checks that the
With that in mind I'd suggest that "default" verification could be an empty or nearly-empty trait implementation - this trait implementation could live outside of the sync code, although I'd say it's reasonable for it to live in the network crate. I assume that Basti is referring to replacing these lines if !is_required_data_available {
return OnBlockAnnounce::Request(who, request)
} with match res {
ValidationResult::Request(req) => if requires_additional_data {
return OnBlockAnnounce::Request(who, request)
} else {
return OnBlockAnnounce::ImportHeader
},
ValidationResult::Nothing => OnBlockAnnounce::Nothing
} I'm not 100% sure what the implications of having the request come from the announce-validator are. @bkchr, it doesn't seem to be a problem to me if we have the request generated by |
Then there's also the other side of the coin to discuss: how the block announcement should actually be bundled with this extra-data. I'm not particularly opinionated on this, but ideally the two methods can fall within the same trait to prevent user-protocol logic from becoming spread out. |
Yeah, sorry for the delay! I think what @rphmeier writes, sounds reasonable. Regarding the extra-data, as this is probably just some opaque field, we could probably check this in the same function we are giving the general "yes/no" to the block request. |
a5e2946
to
dabb6d8
Compare
Ok, turning it into a non-WIP PR now. Please review when time permits it. Regarding
This is not addressed in this PR. The various places where interfaces require associated data use empty vectors for now. |
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.
Nits
core/network/src/test/sync.rs
Outdated
@@ -499,7 +499,7 @@ fn light_peer_imports_header_from_announce() { | |||
let mut runtime = current_thread::Runtime::new().unwrap(); | |||
|
|||
fn import_with_announce(net: &mut TestNet, runtime: &mut current_thread::Runtime, hash: H256) { | |||
net.peer(0).announce_block(hash); | |||
net.peer(0).announce_block(hash, Vec::new()); // TODO |
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.
Needs an issue filed.
core/client/src/client.rs
Outdated
@@ -1015,6 +1002,7 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where | |||
is_new_best, | |||
storage_changes, | |||
retracted, | |||
associated_data: Vec::new(), // TODO |
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.
Any TODOs or FIXMEs need to contain links to open issues.
&self, | ||
notify_import: ImportSummary<Block>, | ||
) -> error::Result<()> { | ||
fn notify_imported(&self, notify_import: ImportSummary<Block>) -> error::Result<()> { | ||
if let Some(storage_changes) = notify_import.storage_changes { | ||
// TODO [ToDr] How to handle re-orgs? Should we re-emit all storage changes? |
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.
@tomusdrw solved or needs an issue?
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.
It seems that we are notifying about all blocks (not only the new best ones), so I think we don't really need any special handling here. CC @jacogr what would you expect in case of re-orgs and storage changes subscription?
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.
If there is a re-org, would expect the subscriptions to pass through the most-recent-actual-state values.
Effectively the lasted values from the subscription and state_getStorage
should match - both should reflect the current chain state.
core/network/src/protocol/sync.rs
Outdated
use crate::{ | ||
config::{Roles, BoxFinalityProofRequestBuilder}, | ||
message::{self, generic::FinalityProofRequest, BlockAttributes, BlockRequest, BlockResponse, FinalityProofResponse}, | ||
message::{ | ||
BlockAnnounce, |
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.
better to wrap when the line limit exceeds.
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.
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.
Why?
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.
consistency with the rest of substrate -- though this (afaik) is not strictly in our style guide. Usually what I do (and I think it is sort of the implicit styleguide):
use crate::{
top_level_stuff_1, top_level_stuff_2, top_level_stuff_3, top_level_stuff_wrap_if_needed,
inner_module_1::{stuff, bar, Bazinga},
inner_module_2::{foo, kaboom, blah},
}
basically break once per sub-module but other than that only when line-width is reached.
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.
Some nitpicks, in general looking good.
core/network/src/protocol/sync.rs
Outdated
use crate::{ | ||
config::{Roles, BoxFinalityProofRequestBuilder}, | ||
message::{self, generic::FinalityProofRequest, BlockAttributes, BlockRequest, BlockResponse, FinalityProofResponse}, | ||
message::{ | ||
BlockAnnounce, |
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.
#[derive(Debug, PartialEq, Eq)] | ||
pub enum Validation { | ||
/// Valid block announcement. | ||
Success, |
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.
Why not call them Valid
and Invalid
directly? And the enum ValidationResult
or something similar?
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.
Result
has a special meaning in Rust - as a nitpick, we shouldn't call anything except for variants of the Result<T, E>
enum Result
.
Typical Rust style would have this be enum Valid { Valid, NotValid }
or enum Valid { Yes, No }
.
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 think naming the type "Valid" can be misleading because "valid" is already biased towards "yes". Saying something valid is not valid feels a little inconsistent to me, so at a minimum I would like to see a neutral name. I agree that the "Result" suffix should better be avoided, especially since the type name will often be embedded in another Result
. I would rather read Result<Validation, ...>
instead of Result<ValidationResult, ...>
.
core/client/src/client.rs
Outdated
@@ -201,6 +186,8 @@ pub struct BlockImportNotification<Block: BlockT> { | |||
pub is_new_best: bool, | |||
/// List of retracted blocks ordered by block number. | |||
pub retracted: Vec<Block::Hash>, | |||
/// Any data associated with this block import. | |||
pub associated_data: Vec<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.
Why do we need the data as part of the import notification?
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.
Hmm, now I understand, but I don't think that this is required. In Cumulus we will have to delay sending the notification anyway and will fetch the associated data from a different source. So, yeah, just remove it.
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.
If we remove this, how do we send block announcements with associated data to network peers?
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.
https://github.com/paritytech/substrate/pull/3346/files#diff-16b4c34d50bdfcbf732c09c7be1fdcf9R684 here do we send the block announcement. The import notification is just used to know that we need to send a block announcement. But as I already said, we will send the block announcements differently in Cumulus.
Co-Authored-By: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>
Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>
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 one last master merge.
@arkpar do we need to update the protocol version? I assume yes?
Merging was actually quite involved. I think #3602 has introduced some changes in the vicinity. Maybe @andresilva could check that things still make sense? |
Indeed the protocol should be backwards compatible. It should not send any additional data to nodes running older version. Furthermore message encoding for the current version should match standard SCALE encoding. |
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 good to me! Thanks Toralf.
edit: logic/API is fine, Arkady's point about backwards compatibility would be the last thing to address
core/network/src/protocol/message.rs
Outdated
@@ -284,16 +286,21 @@ pub mod generic { | |||
if let Some(state) = &self.state { | |||
state.encode_to(dest); | |||
} | |||
if !self.data.is_empty() { |
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.
This will interfere with encoding any additional fields. I.e. this new protocol version should always encode data
when communicating with the same protocol version and do not encode when communicating with an older version. I'd suggest using Option<Vec<u8>>
in BlockAnnounce
for that. Later we can remove backwards compatibility and Option
without changing the actual encoding for the current version.
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 normally have called encode_to
unconditionally. Wrapping data
in an Option
is not bulletproof either, as one can easily just put it in a Some
. Also, changing the type ripples through the whole system which is not great. As I mentioned elsewhere, we should reconsider our encoding, otherwise minor changes such as this will continue to hurt us.
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.
Sure, we should get proper versioning support at some point, but this is out of scope of this PR I believe.
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.
2 last nitpicks, then it is good to be merged.
@@ -273,6 +273,8 @@ pub mod generic { | |||
pub header: H, | |||
/// Block state. TODO: Remove `Option` and custom encoding when v4 becomes common. | |||
pub state: Option<BlockState>, | |||
/// Data associated with this block announcement, e.g. a candidate message. |
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.
The Option
should be removed here as well, when 4
becomes common.
} | ||
} | ||
|
||
impl<H: Decode> Decode for BlockAnnounce<H> { | ||
fn decode<I: Input>(input: &mut I) -> Result<Self, codec::Error> { | ||
let header = H::decode(input)?; | ||
let state = BlockState::decode(input).ok(); | ||
let data = Vec::decode(input).ok(); |
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'm not happy with this. Conceptually it should be Option<(BlockState, Vec<u8>)>
. As both should be present with version 4. But yeah, for the sake of merging this and don't over-complicating it more, we probably can go with this.
Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>
Adds a
BlockAnnounceValidator
trait which is used to checkBlockAnnounce
ments. It also adds an associated data field toBlockAnnounce
which is used for things like candidate messages.