Skip to content

Commit 6a713ed

Browse files
committed
polkadot: pin inclusion blocks until slashed
1 parent 84183ac commit 6a713ed

File tree

12 files changed

+187
-65
lines changed

12 files changed

+187
-65
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

polkadot/node/core/chain-api/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ polkadot-node-metrics = { path = "../../metrics" }
1414
polkadot-node-subsystem = { path = "../../subsystem" }
1515
sc-client-api = { path = "../../../../substrate/client/api" }
1616
sc-consensus-babe = { path = "../../../../substrate/client/consensus/babe" }
17+
schnellru = "0.2.1"
1718

1819
[dev-dependencies]
1920
futures = { version = "0.3.21", features = ["thread-pool"] }

polkadot/node/core/chain-api/src/lib.rs

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,15 @@
3434
use std::sync::Arc;
3535

3636
use futures::prelude::*;
37-
use sc_client_api::AuxStore;
37+
use sc_client_api::{AuxStore, BlockPinning};
38+
use schnellru::{ByLength, LruMap};
3839
use sp_blockchain::HeaderBackend;
3940

4041
use polkadot_node_subsystem::{
4142
messages::ChainApiMessage, overseer, FromOrchestra, OverseerSignal, SpawnedSubsystem,
4243
SubsystemError, SubsystemResult,
4344
};
44-
use polkadot_primitives::Block;
45+
use polkadot_primitives::{Block, Hash};
4546

4647
mod metrics;
4748
use self::metrics::Metrics;
@@ -50,24 +51,32 @@ use self::metrics::Metrics;
5051
mod tests;
5152

5253
const LOG_TARGET: &str = "parachain::chain-api";
54+
// Should be lower than the upper limit in Substrate,
55+
// but high enough to allow the slashing to succeed.
56+
const MAX_PINNED_BLOCKS: u32 = 64;
5357

5458
/// The Chain API Subsystem implementation.
5559
pub struct ChainApiSubsystem<Client> {
5660
client: Arc<Client>,
61+
// Maps the block hash to the number of times it was pinned.
62+
// The mapping is used to limit the number of pinned blocks
63+
// and enforce unpinning of blocks that were never unpinned explicitly.
64+
pinned_blocks: LruMap<Hash, usize>,
5765
metrics: Metrics,
5866
}
5967

6068
impl<Client> ChainApiSubsystem<Client> {
6169
/// Create a new Chain API subsystem with the given client.
6270
pub fn new(client: Arc<Client>, metrics: Metrics) -> Self {
63-
ChainApiSubsystem { client, metrics }
71+
let pinned_blocks = LruMap::new(ByLength::new(MAX_PINNED_BLOCKS));
72+
ChainApiSubsystem { client, metrics, pinned_blocks }
6473
}
6574
}
6675

6776
#[overseer::subsystem(ChainApi, error = SubsystemError, prefix = self::overseer)]
6877
impl<Client, Context> ChainApiSubsystem<Client>
6978
where
70-
Client: HeaderBackend<Block> + AuxStore + 'static,
79+
Client: HeaderBackend<Block> + BlockPinning<Block> + AuxStore + 'static,
7180
{
7281
fn start(self, ctx: Context) -> SpawnedSubsystem {
7382
let future = run::<Client, Context>(ctx, self)
@@ -80,10 +89,10 @@ where
8089
#[overseer::contextbounds(ChainApi, prefix = self::overseer)]
8190
async fn run<Client, Context>(
8291
mut ctx: Context,
83-
subsystem: ChainApiSubsystem<Client>,
92+
mut subsystem: ChainApiSubsystem<Client>,
8493
) -> SubsystemResult<()>
8594
where
86-
Client: HeaderBackend<Block> + AuxStore,
95+
Client: HeaderBackend<Block> + BlockPinning<Block> + AuxStore,
8796
{
8897
loop {
8998
match ctx.recv().await? {
@@ -156,6 +165,40 @@ where
156165
subsystem.metrics.on_request(result.is_ok());
157166
let _ = response_channel.send(result);
158167
},
168+
ChainApiMessage::PinBlock(hash) => {
169+
let _timer = subsystem.metrics.time_pin_block();
170+
171+
// check if the map is full
172+
if subsystem.pinned_blocks.len() == MAX_PINNED_BLOCKS as usize {
173+
// unpin the least recently pinned block
174+
let (hash, count) = subsystem
175+
.pinned_blocks
176+
.pop_oldest()
177+
.expect("len is checked above; qed");
178+
for _ in 0..count {
179+
subsystem.client.unpin_block(hash);
180+
}
181+
}
182+
if let Some(count) = subsystem.pinned_blocks.get_or_insert(hash, || 0) {
183+
*count += 1;
184+
}
185+
// don't propagate the result
186+
// the caller can not do anything about it
187+
let result = subsystem.client.pin_block(hash);
188+
subsystem.metrics.on_request(result.is_ok());
189+
},
190+
ChainApiMessage::UnpinBlock(hash) => {
191+
let _timer = subsystem.metrics.time_unpin_block();
192+
if let Some(count) = subsystem.pinned_blocks.get(&hash) {
193+
*count = count.saturating_sub(1);
194+
if *count == 0 {
195+
let _ = subsystem.pinned_blocks.remove(&hash);
196+
}
197+
}
198+
subsystem.client.unpin_block(hash);
199+
// always succeeds
200+
subsystem.metrics.on_request(true);
201+
},
159202
},
160203
}
161204
}

polkadot/node/core/chain-api/src/metrics.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ pub(crate) struct MetricsInner {
2525
pub(crate) finalized_block_hash: prometheus::Histogram,
2626
pub(crate) finalized_block_number: prometheus::Histogram,
2727
pub(crate) ancestors: prometheus::Histogram,
28+
pub(crate) pin_block: prometheus::Histogram,
29+
pub(crate) unpin_block: prometheus::Histogram,
2830
}
2931

3032
/// Chain API metrics.
@@ -75,6 +77,16 @@ impl Metrics {
7577
pub fn time_ancestors(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
7678
self.0.as_ref().map(|metrics| metrics.ancestors.start_timer())
7779
}
80+
81+
/// Provide a timer for `pin_block` which observes on drop.
82+
pub fn time_pin_block(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
83+
self.0.as_ref().map(|metrics| metrics.pin_block.start_timer())
84+
}
85+
86+
/// Provide a timer for `unpin_block` which observes on drop.
87+
pub fn time_unpin_block(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
88+
self.0.as_ref().map(|metrics| metrics.unpin_block.start_timer())
89+
}
7890
}
7991

8092
impl metrics::Metrics for Metrics {
@@ -132,6 +144,20 @@ impl metrics::Metrics for Metrics {
132144
))?,
133145
registry,
134146
)?,
147+
pin_block: prometheus::register(
148+
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
149+
"polkadot_parachain_chain_api_pin_block",
150+
"Time spent within `chain_api::pin_block`",
151+
))?,
152+
registry,
153+
)?,
154+
unpin_block: prometheus::register(
155+
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
156+
"polkadot_parachain_chain_api_unpin_block",
157+
"Time spent within `chain_api::unpin_block`",
158+
))?,
159+
registry,
160+
)?,
135161
};
136162
Ok(Metrics(Some(metrics)))
137163
}

polkadot/node/core/chain-api/src/tests.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,14 @@ impl AuxStore for TestClient {
185185
}
186186
}
187187

188+
impl BlockPinning<Block> for TestClient {
189+
fn pin_block(&self, _hash: Hash) -> sp_blockchain::Result<()> {
190+
Ok(())
191+
}
192+
193+
fn unpin_block(&self, _hash: Hash) {}
194+
}
195+
188196
#[test]
189197
fn request_block_number() {
190198
test_harness(|client, mut sender| {

polkadot/node/core/dispute-coordinator/src/initialized.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ use polkadot_node_primitives::{
3434
};
3535
use polkadot_node_subsystem::{
3636
messages::{
37-
ApprovalVotingMessage, BlockDescription, ChainSelectionMessage, DisputeCoordinatorMessage,
38-
DisputeDistributionMessage, ImportStatementsResult,
37+
ApprovalVotingMessage, BlockDescription, ChainApiMessage, ChainSelectionMessage,
38+
DisputeCoordinatorMessage, DisputeDistributionMessage, ImportStatementsResult,
3939
},
4040
overseer, ActivatedLeaf, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, RuntimeApiError,
4141
};
@@ -402,7 +402,7 @@ impl Initialized {
402402
let mut key_ownership_proofs = Vec::new();
403403
let mut dispute_proofs = Vec::new();
404404

405-
for (_height, inclusion_parent) in inclusions {
405+
for (_height, inclusion_parent) in inclusions.iter().cloned() {
406406
for (validator_index, validator_id) in pending.keys.iter() {
407407
let res =
408408
key_ownership_proof(ctx.sender(), inclusion_parent, validator_id.clone())
@@ -466,6 +466,16 @@ impl Initialized {
466466
"Could not generate key ownership proofs for {} keys",
467467
expected_keys - resolved_keys,
468468
);
469+
} else {
470+
gum::debug!(
471+
target: LOG_TARGET,
472+
?session_index,
473+
?candidate_hash,
474+
"All good. Unpinning the inclusion blocks",
475+
);
476+
for (_number, hash) in inclusions {
477+
ctx.send_message(ChainApiMessage::UnpinBlock(hash)).await;
478+
}
469479
}
470480
debug_assert_eq!(resolved_keys, dispute_proofs.len());
471481

@@ -1267,6 +1277,7 @@ impl Initialized {
12671277

12681278
// Notify ChainSelection if a dispute has concluded against a candidate. ChainSelection
12691279
// will need to mark the candidate's relay parent as reverted.
1280+
// Also pin the inclusion blocks until we slash the validators.
12701281
if import_result.has_fresh_byzantine_threshold_against() {
12711282
let blocks_including = self.scraper.get_blocks_including_candidate(&candidate_hash);
12721283
for (parent_block_number, parent_block_hash) in &blocks_including {
@@ -1279,6 +1290,9 @@ impl Initialized {
12791290
);
12801291
}
12811292
if blocks_including.len() > 0 {
1293+
for (_number, inclusion_block_hash) in &blocks_including {
1294+
ctx.send_message(ChainApiMessage::PinBlock(*inclusion_block_hash)).await;
1295+
}
12821296
ctx.send_message(ChainSelectionMessage::RevertBlocks(blocks_including)).await;
12831297
} else {
12841298
gum::debug!(

polkadot/node/malus/src/variants/back_garbage_candidate.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
use polkadot_cli::{
2222
prepared_overseer_builder,
2323
service::{
24-
AuthorityDiscoveryApi, AuxStore, BabeApi, Block, Error, HeaderBackend, Overseer,
25-
OverseerConnector, OverseerGen, OverseerGenArgs, OverseerHandle, ParachainHost,
24+
AuthorityDiscoveryApi, AuxStore, BabeApi, Block, BlockPinning, Error, HeaderBackend,
25+
Overseer, OverseerConnector, OverseerGen, OverseerGenArgs, OverseerHandle, ParachainHost,
2626
ProvideRuntimeApi,
2727
},
2828
Cli,
@@ -59,17 +59,21 @@ pub(crate) struct BackGarbageCandidates {
5959
}
6060

6161
impl OverseerGen for BackGarbageCandidates {
62-
fn generate<Spawner, RuntimeClient>(
62+
fn generate<Spawner, Client>(
6363
&self,
6464
connector: OverseerConnector,
65-
args: OverseerGenArgs<'_, Spawner, RuntimeClient>,
65+
args: OverseerGenArgs<'_, Spawner, Client>,
6666
) -> Result<
67-
(Overseer<SpawnGlue<Spawner>, Arc<DefaultSubsystemClient<RuntimeClient>>>, OverseerHandle),
67+
(Overseer<SpawnGlue<Spawner>, Arc<DefaultSubsystemClient<Client>>>, OverseerHandle),
6868
Error,
6969
>
7070
where
71-
RuntimeClient: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block> + AuxStore,
72-
RuntimeClient::Api: ParachainHost<Block> + BabeApi<Block> + AuthorityDiscoveryApi<Block>,
71+
Client: 'static
72+
+ ProvideRuntimeApi<Block>
73+
+ HeaderBackend<Block>
74+
+ BlockPinning<Block>
75+
+ AuxStore,
76+
Client::Api: ParachainHost<Block> + BabeApi<Block> + AuthorityDiscoveryApi<Block>,
7377
Spawner: 'static + SpawnNamed + Clone + Unpin,
7478
{
7579
let spawner = args.spawner.clone();

polkadot/node/malus/src/variants/dispute_valid_candidates.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
use polkadot_cli::{
2626
prepared_overseer_builder,
2727
service::{
28-
AuthorityDiscoveryApi, AuxStore, BabeApi, Block, Error, HeaderBackend, Overseer,
29-
OverseerConnector, OverseerGen, OverseerGenArgs, OverseerHandle, ParachainHost,
28+
AuthorityDiscoveryApi, AuxStore, BabeApi, Block, BlockPinning, Error, HeaderBackend,
29+
Overseer, OverseerConnector, OverseerGen, OverseerGenArgs, OverseerHandle, ParachainHost,
3030
ProvideRuntimeApi,
3131
},
3232
Cli,
@@ -76,17 +76,21 @@ pub(crate) struct DisputeValidCandidates {
7676
}
7777

7878
impl OverseerGen for DisputeValidCandidates {
79-
fn generate<Spawner, RuntimeClient>(
79+
fn generate<Spawner, Client>(
8080
&self,
8181
connector: OverseerConnector,
82-
args: OverseerGenArgs<'_, Spawner, RuntimeClient>,
82+
args: OverseerGenArgs<'_, Spawner, Client>,
8383
) -> Result<
84-
(Overseer<SpawnGlue<Spawner>, Arc<DefaultSubsystemClient<RuntimeClient>>>, OverseerHandle),
84+
(Overseer<SpawnGlue<Spawner>, Arc<DefaultSubsystemClient<Client>>>, OverseerHandle),
8585
Error,
8686
>
8787
where
88-
RuntimeClient: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block> + AuxStore,
89-
RuntimeClient::Api: ParachainHost<Block> + BabeApi<Block> + AuthorityDiscoveryApi<Block>,
88+
Client: 'static
89+
+ ProvideRuntimeApi<Block>
90+
+ HeaderBackend<Block>
91+
+ BlockPinning<Block>
92+
+ AuxStore,
93+
Client::Api: ParachainHost<Block> + BabeApi<Block> + AuthorityDiscoveryApi<Block>,
9094
Spawner: 'static + SpawnNamed + Clone + Unpin,
9195
{
9296
let spawner = args.spawner.clone();

polkadot/node/malus/src/variants/suggest_garbage_candidate.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
use polkadot_cli::{
2626
prepared_overseer_builder,
2727
service::{
28-
AuthorityDiscoveryApi, AuxStore, BabeApi, Block, Error, HeaderBackend, Overseer,
29-
OverseerConnector, OverseerGen, OverseerGenArgs, OverseerHandle, ParachainHost,
28+
AuthorityDiscoveryApi, AuxStore, BabeApi, Block, BlockPinning, Error, HeaderBackend,
29+
Overseer, OverseerConnector, OverseerGen, OverseerGenArgs, OverseerHandle, ParachainHost,
3030
ProvideRuntimeApi,
3131
},
3232
Cli,
@@ -262,17 +262,21 @@ pub(crate) struct SuggestGarbageCandidates {
262262
}
263263

264264
impl OverseerGen for SuggestGarbageCandidates {
265-
fn generate<Spawner, RuntimeClient>(
265+
fn generate<Spawner, Client>(
266266
&self,
267267
connector: OverseerConnector,
268-
args: OverseerGenArgs<'_, Spawner, RuntimeClient>,
268+
args: OverseerGenArgs<'_, Spawner, Client>,
269269
) -> Result<
270-
(Overseer<SpawnGlue<Spawner>, Arc<DefaultSubsystemClient<RuntimeClient>>>, OverseerHandle),
270+
(Overseer<SpawnGlue<Spawner>, Arc<DefaultSubsystemClient<Client>>>, OverseerHandle),
271271
Error,
272272
>
273273
where
274-
RuntimeClient: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block> + AuxStore,
275-
RuntimeClient::Api: ParachainHost<Block> + BabeApi<Block> + AuthorityDiscoveryApi<Block>,
274+
Client: 'static
275+
+ ProvideRuntimeApi<Block>
276+
+ HeaderBackend<Block>
277+
+ BlockPinning<Block>
278+
+ AuxStore,
279+
Client::Api: ParachainHost<Block> + BabeApi<Block> + AuthorityDiscoveryApi<Block>,
276280
Spawner: 'static + SpawnNamed + Clone + Unpin,
277281
{
278282
gum::info!(

polkadot/node/service/src/lib.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ pub use consensus_common::{Proposal, SelectChain};
8989
use frame_benchmarking_cli::SUBSTRATE_REFERENCE_HARDWARE;
9090
use mmr_gadget::MmrGadget;
9191
pub use polkadot_primitives::{Block, BlockId, BlockNumber, CollatorPair, Hash, Id as ParaId};
92-
pub use sc_client_api::{Backend, CallExecutor};
92+
pub use sc_client_api::{Backend, BlockPinning, CallExecutor};
9393
pub use sc_consensus::{BlockImport, LongestChain};
9494
pub use sc_executor::NativeExecutionDispatch;
9595
use sc_executor::{HeapAllocStrategy, WasmExecutor, DEFAULT_HEAP_ALLOC_STRATEGY};
@@ -753,9 +753,13 @@ pub fn new_full<OverseerGenerator: OverseerGen>(
753753
Some(backoff)
754754
};
755755

756-
// Warn the user that BEEFY is still experimental for Polkadot.
757-
if enable_beefy && config.chain_spec.is_polkadot() {
758-
gum::warn!("BEEFY is still experimental, usage on Polkadot network is discouraged.");
756+
// If not on a known test network, warn the user that BEEFY is still experimental.
757+
if enable_beefy &&
758+
!config.chain_spec.is_rococo() &&
759+
!config.chain_spec.is_wococo() &&
760+
!config.chain_spec.is_versi()
761+
{
762+
gum::warn!("BEEFY is still experimental, usage on a production network is discouraged.");
759763
}
760764

761765
let disable_grandpa = config.disable_grandpa;
@@ -1046,7 +1050,7 @@ pub fn new_full<OverseerGenerator: OverseerGen>(
10461050
overseer_connector,
10471051
OverseerGenArgs {
10481052
keystore,
1049-
runtime_client: overseer_client.clone(),
1053+
client: overseer_client.clone(),
10501054
parachains_db,
10511055
network_service: network.clone(),
10521056
sync_service: sync_service.clone(),

0 commit comments

Comments
 (0)