Allow changing the behavior for imported blocks#5236
Conversation
|
It looks like @cecton signed our Contributor License Agreement. 👍 Many thanks, Parity Technologies CLA Bot |
NikVolf
left a comment
There was a problem hiding this comment.
Add an on_block_imported method on NetworkService that will allow the user to call the method manually
I think we need example (test) how this on_block_imported can be used, this will also ensure that api is actually consumable
client/network/src/service.rs
Outdated
| } | ||
|
|
||
| /// You must call this when a new block is imported by the client. | ||
| pub fn on_block_imported(&self, header: B::Header, data: Vec<u8>, is_best: bool) { |
There was a problem hiding this comment.
It's not just about announcing the block. It also updates the state of sync so that it knows what's in the database.
There was a problem hiding this comment.
There is already a method announce block there:
substrate/client/network/src/service.rs
Lines 552 to 558 in 61c5d4b
There was a problem hiding this comment.
Yeah I know. For whatever we decide, default_announce_block should be named accordingly.
(ignore)
There was a problem hiding this comment.
There is already a method announce block there:
substrate/client/network/src/service.rs
Lines 552 to 558 in 61c5d4b
I just realized this. This makes the whole story here even more confusing. We just want to announce a block later, but that would not update sync and would break everything... If we continue with this new method here, I would propose to remove this announce_block function.
@tomaka any ideas?
There was a problem hiding this comment.
Removing that function without replacing the functionality exactly will break GRANDPA.
|
I don't think there's a better way to document That, however, makes it a bit worrisome to make it possible to disable the fact that it's being called automatically. |
Maybe it could be something like: "This method is normally called by substrate unless default_announce_block is set on false in the Configuration, in which case you must call this when a block is now in the database, otherwise things will break" |
|
I'm starting to wonder if it wouldn't be better to make a Configuration parameter "override_on_block_imported" that would be an |
|
Maybe a good example is better to explain my idea: let mut imported_blocks_stream = client.import_notification_stream().fuse();
let mut finality_notification_stream = client.finality_notification_stream().fuse();
futures::future::poll_fn(move |cx| {
let before_polling = Instant::now();
// We poll `imported_blocks_stream`.
while let Poll::Ready(Some(notification)) = Pin::new(&mut imported_blocks_stream).poll_next(cx) {
let maybe_import =
if let Some(override_func) = maybe_override {
override_func(notification)
} else {
Some(notification)
};
if let Some(notification) = maybe_import {
network.on_block_imported(notification.header, Vec::new(), notification.is_new_best);
}
}(Note that there is no need to expose the internal on_block_imported anymore) |
|
If the reason for this PR is to be able to announce a block later, then IMO this is a very wrong approach and we should instead configure the network to not announce a block by default when As mentioned, there is already a function that lets you announce a block. |
client/network/src/protocol.rs
Outdated
| // blocks are announced by default | ||
| if !self.config.default_block_announce { | ||
| return; | ||
| } | ||
|
|
client/network/src/protocol.rs
Outdated
| /// Maximum number of peers to ask the same blocks in parallel. | ||
| pub max_parallel_downloads: u32, | ||
| /// Use default block announcement | ||
| pub default_announce_block: bool, |
There was a problem hiding this comment.
I agree with Pierre that this feels very hacky and fragile. The documentation is not good either.
Right. We should definitely be using that, but come up with a cleaner way to disable the normal "announce new best" logic. Usually this would be done via a trait. Could something like this fit? trait ImportedBlockAnnounceFilter {
// Whether to announce a block that has just been imported to peers.
fn announce_block(&self, some_block_info) -> bool;
}
struct AnnounceNewBestBlocks;
impl ImportedBlockAnnounceFilter for AnnounceNewBestBlocks {
// ...
}
struct AnnounceNothing;
impl ImportBlockAnnounceFilter for AnnounceNothing {
// ...
}I want to revisit the higher-level context that is driving this PR as well, so we can make a more informed decision. I'd imagine for Cumulus we will want more customization here anyway. At the moment, we assume a couple of things in Cumulus that will not hold true for all or most cases once we start refining APIs:
In confluence with each other, this leads to a situation where we only want to broadcast blocks which have been seconded by a validator. However, if we remove assumption 1 (e.g. a PoW parachain), we could just as easily announce blocks that fulfill a PoW requirement. If we alternatively remove assumption 2 (e.g. a PoS parachain), we could announce blocks that are signed by the correct slot author. It's only in the case where 1&2 are both assumed (most minimal case - pushing collator selection entirely to the emergent market) that we ever need this behavior. And it's much better IMO to be explicit about which behaviors are activated in the network service at any given time. |
|
Giving more thoughts, to me the best solution would simply be to never announce blocks automatically. In other words, not even give option to disable it like this PR does, but always disable it. Instead have this code here also call As an alternative we could also change the signature of This is the solution that has the less surprise factor to me. You know exactly what happens. This adds a bit more burden to the |
|
Sounds good to me! |
|
@tomaka I think I understand theoretically but not how it translates in practice 😅 can you check this? |
client/service/src/lib.rs
Outdated
| network.on_block_imported(notification.header, Vec::new(), notification.is_new_best, false); | ||
| network.service().announce_block(notification.hash, Vec::new()); |
There was a problem hiding this comment.
Well, you can pass true here.
The entire point of passing an extra boolean parameter is that you don't have to call announce_block separately.
| network.on_block_imported(notification.header, Vec::new(), notification.is_new_best, false); | |
| network.service().announce_block(notification.hash, Vec::new()); | |
| network.on_block_imported(notification.header, Vec::new(), notification.is_new_best, true); |
There was a problem hiding this comment.
Although maybe not having a boolean is better?
If you're reading the code of sc_service trying to figure out when blocks are announced, this little extra true is easy to miss.
There was a problem hiding this comment.
enum Announce { Yes, No }
There was a problem hiding this comment.
I finally get it 😅 calling the code explicitly is more visible than the boolean
| /// the network. | ||
| pub fn on_block_imported(&mut self, header: &B::Header, data: Vec<u8>, is_best: bool, also_announce: bool) { | ||
| /// Call this when a block has been imported in the import queue | ||
| pub fn on_block_imported(&mut self, header: &B::Header, is_best: bool) { |
There was a problem hiding this comment.
TBH, seems that this function now does nothing unless the new block is best. Maybe it should be fn on_best_block_imported(&mut self, header: &B::Header)
There was a problem hiding this comment.
But then we would need to reintroduce it if later we want to do something for non-best blocks.
I quite dislike that function to be honest, but its API seems to be "please call me when a block has been added to the client", and adding an "only if it is the new best block" clause seems even weirder and more error-prone to me.
@NikVolf I thought about it and I have no idea how this can be tested or how to give a meaningful example at this point because I don't fully understand the product. I'm open to suggestions but I propose to merge as it for now because it's technically tested (tests fail if the blocks are not announced right after being imported). When the feature in cumulus will be fully developed (this might take time) maybe I will find some idea. |
|
Please merge master. |
|
And also requires a polkadot pr. |
|
@bkchr done. Could you click the merge button please? |
* Adapt code to API changes * Update sp-io
This change is needed for cumulus to be able to implement paritytech/cumulus#7
The goal is to allow the user to override the default behavior of substrate when a new block is imported. By default the original behavior is kept unchanged.
The changes are:
polkadot companion: paritytech/polkadot#955