Skip to content

Commit 4771709

Browse files
authored
change: ETCM-12419 decouple sidechain-rpc from slots (#1095)
1 parent 0ab7194 commit 4771709

File tree

15 files changed

+250
-115
lines changed

15 files changed

+250
-115
lines changed

Cargo.lock

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

changelog.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,18 @@ supported by the toolkit, this change is backwards compatible for existing chain
3434
and instead uses `CommitteeMember` and `AuthoritySelectionInputs` types directly, which were moved from
3535
`authority_selection_inherents` crate to `sp_session_validator_management`.
3636
As these types were the only ones supported by the toolkit, this change is backwards compatible for existing chains.
37+
* `pallet-sidechain-rpc` by default no longer provides information about sidechain slots and uses the newly added
38+
runtime APIs `sp_sidechain::GetEpochDurationApi` instead of deprecated `sp_sidechain::SlotApi`. Chain builders should
39+
implement the new API in their runtime. Backward compatibility option can be switched on by enabling `legacy-slotapi-compat`
40+
feature in the crate, which will cause legacy chain nodes to still use `SlotApi` where present and include the
41+
`sidechain.slot` as an optional field.
3742

3843
## Removed
3944

4045
* `pallet-partner-chains-session` has been removed. Partner Chains should use only the stock Substrate session pallet
4146
* `PalletSessionSupport` type provided by `pallet-session-validator-management`. The `SessionManager` and `ShouldEndSession`
4247
implementations were moved directly to the `Pallet` type instead.
48+
* `sidechain.slots` field in the response of `sidechain_getStatus` RPC method
4349

4450
## Fixed
4551

@@ -53,6 +59,7 @@ types using Substrate's `app_crypto` macro. This should be a drop-in replacement
5359
for the analogous cross-chain key types that previously had to be defined by each
5460
Partner Chain separately for use in `pallet_session_validator_management` and
5561
other pallets that use cross-chain keys.
62+
* `sp_sidechain::GetEpochDurationApi` runtime API
5663

5764
# v1.8.0
5865

demo/node/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ sp-block-production-log = { workspace = true }
5656
sidechain-domain = { workspace = true }
5757
sidechain-slots = { workspace = true }
5858
sp-sidechain = { workspace = true }
59-
pallet-sidechain-rpc = { workspace = true }
59+
pallet-sidechain-rpc = { workspace = true, features = ["legacy-slotapi-compat"] }
6060
pallet-session-validator-management = { workspace = true }
6161
sp-session-validator-management = { workspace = true }
6262
sp-session-validator-management-query = { workspace = true }

demo/node/src/rpc.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ use sc_rpc::SubscriptionTaskExecutor;
2525
use sc_transaction_pool_api::TransactionPool;
2626
use sidechain_domain::ScEpochNumber;
2727
use sidechain_domain::mainchain_epoch::MainchainEpochConfig;
28-
use sp_api::ProvideRuntimeApi;
28+
use sp_api::{CallApiAt, ProvideRuntimeApi};
2929
use sp_block_builder::BlockBuilder;
3030
use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata};
3131
use sp_session_validator_management_query::SessionValidatorManagementQuery;
32+
use sp_sidechain::GetEpochDurationApi;
3233
use std::sync::Arc;
3334
use time_source::TimeSource;
3435

@@ -66,6 +67,7 @@ pub fn create_full<C, P, B, T>(
6667
) -> Result<RpcModule<()>, Box<dyn std::error::Error + Send + Sync>>
6768
where
6869
C: ProvideRuntimeApi<Block>,
70+
C: CallApiAt<Block>,
6971
C: HeaderBackend<Block> + HeaderMetadata<Block, Error = BlockChainError> + 'static,
7072
C: Send + Sync + 'static,
7173
C::Api: substrate_frame_rpc_system::AccountNonceApi<Block, AccountId, Nonce>,
@@ -84,6 +86,7 @@ where
8486
ScEpochNumber,
8587
>,
8688
C::Api: CandidateValidationApi<Block>,
89+
C::Api: GetEpochDurationApi<Block>,
8790
P: TransactionPool + 'static,
8891
B: sc_client_api::Backend<Block> + Send + Sync + 'static,
8992
B::State: sc_client_api::backend::StateBackend<sp_runtime::traits::HashingFor<Block>>,

demo/runtime/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,6 +1063,12 @@ impl_runtime_apis! {
10631063
}
10641064
}
10651065

1066+
impl sp_sidechain::GetEpochDurationApi<Block> for Runtime {
1067+
fn get_epoch_duration_millis() -> u64 {
1068+
u64::from(Sidechain::slots_per_epoch().0) * MILLISECS_PER_BLOCK
1069+
}
1070+
}
1071+
10661072
impl sidechain_slots::SlotApi<Block> for Runtime {
10671073
fn slot_config() -> sidechain_slots::ScSlotConfig {
10681074
sidechain_slots::ScSlotConfig {

e2e-tests/tests/test_rpc.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ def test_get_status(self, api: BlockchainApi):
3232
assert partner_chain_status['mainchain']['nextEpochTimestamp']
3333
assert partner_chain_status['sidechain']['nextEpochTimestamp']
3434
assert partner_chain_status['sidechain']['epoch']
35-
assert partner_chain_status['sidechain']['slot']
3635

3736
@mark.test_key('ETCM-7442')
3837
def test_get_params(self, api: BlockchainApi):

toolkit/sidechain/primitives/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ mod api_declarations {
6464
fn genesis_utxo() -> UtxoId;
6565
}
6666

67+
/// Runtime API for retrieving the Partner Chain's epoch duration
68+
pub trait GetEpochDurationApi {
69+
/// Returns Partner Chain epoch duration in milliseconds
70+
fn get_epoch_duration_millis() -> u64;
71+
}
72+
6773
/// Runtime API for getting information about current Partner Chain slot and epoch
6874
#[deprecated(since = "1.7.0", note = "Code that needs this data should define its own runtime API instead.")]
6975
pub trait GetSidechainStatus {

toolkit/sidechain/rpc/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ sp-api = { workspace = true, features = ['std'] }
2323
sp-blockchain = { workspace = true }
2424
sp-sidechain = { workspace = true, features = ['std'] }
2525
sidechain-domain = { workspace = true, features = ['std'] }
26-
sidechain-slots = { workspace = true, features = ['std', 'serde'] }
2726
serde_json = { workspace = true, features = ['std'] }
2827
sp-timestamp = { workspace = true, features = ['std'] }
2928
hex = { workspace = true, features = ['std'] }
@@ -35,7 +34,7 @@ derive-new = { workspace = true }
3534
serde_json = { workspace = true }
3635
pretty_assertions = { workspace = true }
3736
time-source = { workspace = true, features = ['mock'] }
38-
sp-consensus-slots = { workspace = true, features = ["std"] }
3937

4038
[features]
4139
default = []
40+
legacy-slotapi-compat = []
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//! Backward compatibility support for legacy features and APIs
2+
3+
/// Support for legacy chains that used to implement `sidechain_slots::SlotApi`
4+
pub(crate) mod slots {
5+
use parity_scale_codec::Decode;
6+
use sp_api::{CallApiAt, CallApiAtParams};
7+
use sp_runtime::traits::Block as BlockT;
8+
9+
/// Raw method name of `sidechain_slots::SlotApi::slot_config`
10+
const RAW_GET_SLOT_RUNTIME_METHOD: &str = "SlotApi_slot_config";
11+
12+
/// Slot configuration type
13+
#[derive(sp_core::Decode, Debug)]
14+
pub(crate) struct ScSlotConfig {
15+
pub(crate) slots_per_epoch: u32,
16+
pub(crate) slot_duration_millis: u64,
17+
}
18+
19+
/// Trait to read slot config using `sidechain_slots::SlotApi`, failing gracefully if it's not available
20+
pub(crate) trait GetScSlotConfig<Block: BlockT> {
21+
fn get_sc_slot_config(&self, best_block: Block::Hash) -> Option<ScSlotConfig>;
22+
}
23+
24+
impl<Client, Block> GetScSlotConfig<Block> for Client
25+
where
26+
Block: BlockT,
27+
Client: CallApiAt<Block> + Send + Sync + 'static,
28+
{
29+
fn get_sc_slot_config(&self, best_block: Block::Hash) -> Option<ScSlotConfig> {
30+
let call_params = CallApiAtParams {
31+
at: best_block,
32+
function: RAW_GET_SLOT_RUNTIME_METHOD,
33+
arguments: vec![],
34+
overlayed_changes: &Default::default(),
35+
call_context: sp_api::CallContext::Offchain,
36+
recorder: &None,
37+
extensions: &Default::default(),
38+
};
39+
let raw_result = self.call_api_at(call_params).ok()?;
40+
let slot_config = ScSlotConfig::decode(&mut &raw_result[..]).ok()?;
41+
Some(slot_config)
42+
}
43+
}
44+
}

toolkit/sidechain/rpc/src/lib.rs

Lines changed: 98 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,20 @@
44
//!
55
//! ## Implementing runtime APIs
66
//!
7-
//! Your runtime should implement the [SlotApi] and [GetGenesisUtxo] runtime APIs. For example, if
8-
//! your chain uses Aura for consensus, they may be implemented similar to this:
7+
//! Your runtime should implement the [GetGenesisUtxo] and [GetEpochDurationApi] runtime APIs.
8+
//! For example, if your chain uses Aura for consensus, they may be implemented similar to this:
99
//! ```rust,ignore
10-
//! impl sidechain_slots::SlotApi<Block> for Runtime {
11-
//! fn slot_config() -> sidechain_slots::ScSlotConfig {
12-
//! sidechain_slots::ScSlotConfig {
13-
//! slots_per_epoch: Sidechain::slots_per_epoch(),
14-
//! slot_duration: SlotDuration::from(Aura::slot_duration())
15-
//! }
16-
//! }
17-
//! }
1810
//! impl sp_sidechain::GetGenesisUtxo<Block> for Runtime {
1911
//! fn genesis_utxo() -> UtxoId {
2012
//! Sidechain::genesis_utxo()
2113
//! }
2214
//! }
15+
//!
16+
//! impl sp_sidechain::GetEpochDurationApi<Block> for Runtime {
17+
//! fn get_epoch_duration_millis() -> u64 {
18+
//! BLOCKS_PER_EPOCH * MILLISECS_PER_BLOCK
19+
//! }
20+
//! }
2321
//! ```
2422
//!
2523
//! ## Adding to the RPC stack
@@ -30,10 +28,9 @@
3028
//! # use jsonrpsee::RpcModule;
3129
//! # use pallet_sidechain_rpc::{*, types::GetBestHash};
3230
//! # use sidechain_domain::mainchain_epoch::MainchainEpochConfig;
33-
//! # use sidechain_slots::SlotApi;
34-
//! # use sp_api::ProvideRuntimeApi;
31+
//! # use sp_api::{ CallApiAt, ProvideRuntimeApi };
3532
//! # use sp_runtime::traits::Block as BlockT;
36-
//! # use sp_sidechain::GetGenesisUtxo;
33+
//! # use sp_sidechain::{ GetEpochDurationApi, GetGenesisUtxo };
3734
//! # use std::sync::Arc;
3835
//! # use time_source::TimeSource;
3936
//! fn create_rpc<B: BlockT, C: Send + Sync + 'static>(
@@ -42,8 +39,8 @@
4239
//! data_source: Arc<dyn SidechainRpcDataSource + Send + Sync>,
4340
//! ) -> Result<RpcModule<()>, Box<dyn std::error::Error + Send + Sync>>
4441
//! where
45-
//! C: ProvideRuntimeApi<B> + GetBestHash<B>,
46-
//! C::Api: SlotApi<B> + GetGenesisUtxo<B>
42+
//! C: ProvideRuntimeApi<B> + GetBestHash<B> + CallApiAt<B>,
43+
//! C::Api: GetGenesisUtxo<B> + GetEpochDurationApi<B>
4744
//! {
4845
//!
4946
//! let mut module = RpcModule::new(());
@@ -66,29 +63,42 @@
6663
//! data source is provided by the Partner Chain toolkit in the `partner_chains_db_sync_data_sources`
6764
//! crate.
6865
//!
66+
//! ## Legacy compatibility mode
67+
//!
68+
//! In previous versions of the Partner Chains toolkit, the RPC services in this crate relied on now
69+
//! deprecated runtime api `sp_sidechain::SlotApi` to compute Partner Chain epochs and current slot.
70+
//! For Partner Chains that started before this dependency was removed, a compatibility mode is
71+
//! provided behind the `legacy-slotapi-compat` feature flag. Enabling this flag will cause the nodes to
72+
//! use this runtime API when present and optionally include information about current Patner Chain
73+
//! slot in `sidechain.slot` field of `sidechain_getStatus` response.
74+
//!
6975
//! [GetGenesisUtxo]: sp_sidechain::GetGenesisUtxo
70-
//! [SlotApi]: sidechain_slots::SlotApi
76+
//! [GetEpochDurationApi]: sp_sidechain::GetEpochDurationApi
7177
#![deny(missing_docs)]
7278
use derive_new::new;
7379
use jsonrpsee::{
7480
core::{RpcResult, async_trait},
7581
proc_macros::rpc,
7682
types::{ErrorObject, ErrorObjectOwned, error::ErrorCode},
7783
};
84+
#[cfg(feature = "legacy-slotapi-compat")]
85+
use legacy_compat::slots::*;
7886
use sidechain_domain::mainchain_epoch::{MainchainEpochConfig, MainchainEpochDerivation};
7987
use sidechain_domain::{MainchainBlock, UtxoId};
80-
use sidechain_slots::SlotApi;
8188
use sp_api::ProvideRuntimeApi;
82-
use sp_core::offchain::Timestamp;
89+
use sp_core::offchain::{Duration, Timestamp};
8390
use sp_runtime::traits::Block as BlockT;
84-
use sp_sidechain::GetGenesisUtxo;
91+
use sp_sidechain::{GetEpochDurationApi, GetGenesisUtxo};
8592
use std::sync::Arc;
8693
use time_source::*;
8794
use types::*;
8895

8996
/// Response types returned by RPC endpoints for Sidechain pallet
9097
pub mod types;
9198

99+
#[cfg(feature = "legacy-slotapi-compat")]
100+
mod legacy_compat;
101+
92102
#[cfg(test)]
93103
mod tests;
94104

@@ -142,37 +152,89 @@ impl<C, B> SidechainRpc<C, B> {
142152
}
143153
}
144154

155+
/// Runtime client that can serve data to sidechain RPC
156+
trait SidechainRpcClient<Block: BlockT> {
157+
/// Returns Partner Chain epoch duration
158+
fn get_epoch_duration(
159+
&self,
160+
best_block: Block::Hash,
161+
) -> Result<Duration, Box<dyn std::error::Error + Send + Sync>>;
162+
163+
/// Returns the Partner Chain's genesis UTXO
164+
fn get_genesis_utxo(
165+
&self,
166+
best_block: Block::Hash,
167+
) -> Result<UtxoId, Box<dyn std::error::Error + Send + Sync>>;
168+
169+
#[cfg(feature = "legacy-slotapi-compat")]
170+
/// Returns slot duration
171+
fn get_maybe_slot_duration(&self, best_block: Block::Hash) -> Option<u64>;
172+
}
173+
174+
impl<Block, Client> SidechainRpcClient<Block> for Client
175+
where
176+
Block: BlockT,
177+
Client: sp_api::CallApiAt<Block> + Send + Sync + 'static,
178+
Client: ProvideRuntimeApi<Block>,
179+
Client::Api: GetEpochDurationApi<Block>,
180+
Client::Api: GetGenesisUtxo<Block>,
181+
{
182+
fn get_epoch_duration(
183+
&self,
184+
best_block: Block::Hash,
185+
) -> Result<Duration, Box<dyn std::error::Error + Send + Sync>> {
186+
#[cfg(feature = "legacy-slotapi-compat")]
187+
if let Some(slot_config) = self.get_sc_slot_config(best_block) {
188+
return Ok(Duration::from_millis(
189+
u64::from(slot_config.slots_per_epoch) * slot_config.slot_duration_millis,
190+
));
191+
}
192+
193+
Ok(Duration::from_millis(self.runtime_api().get_epoch_duration_millis(best_block)?))
194+
}
195+
196+
fn get_genesis_utxo(
197+
&self,
198+
best_block: Block::Hash,
199+
) -> Result<UtxoId, Box<dyn std::error::Error + Send + Sync>> {
200+
Ok(self.runtime_api().genesis_utxo(best_block)?)
201+
}
202+
203+
#[cfg(feature = "legacy-slotapi-compat")]
204+
fn get_maybe_slot_duration(&self, best_block: Block::Hash) -> Option<u64> {
205+
let slot_config = self.get_sc_slot_config(best_block)?;
206+
Some(slot_config.slot_duration_millis)
207+
}
208+
}
209+
145210
#[async_trait]
146211
impl<C, Block> SidechainRpcApiServer for SidechainRpc<C, Block>
147212
where
148213
Block: BlockT,
149214
C: Send + Sync + 'static,
150-
C: ProvideRuntimeApi<Block>,
215+
C: SidechainRpcClient<Block>,
151216
C: GetBestHash<Block>,
152-
C::Api: SlotApi<Block> + GetGenesisUtxo<Block>,
153217
{
154218
fn get_params(&self) -> RpcResult<GetParamsOutput> {
155-
let api = self.client.runtime_api();
156219
let best_block = self.client.best_hash();
157220

158-
let genesis_utxo = api.genesis_utxo(best_block).map_err(error_object_from)?;
221+
let genesis_utxo = self.client.get_genesis_utxo(best_block).map_err(error_object_from)?;
159222

160223
Ok(GetParamsOutput { genesis_utxo })
161224
}
162225

163226
async fn get_status(&self) -> RpcResult<GetStatusResponse> {
164-
let api = self.client.runtime_api();
165227
let best_block = self.client.best_hash();
166228

167-
let slot_config = api.slot_config(best_block).map_err(error_object_from)?;
168-
169229
let current_timestamp = self.get_current_timestamp();
170-
let current_sidechain_slot =
171-
slot_config.slot_from_timestamp(current_timestamp.unix_millis());
172-
let current_sidechain_epoch = slot_config.epoch_number(current_sidechain_slot);
173-
let next_sidechain_epoch_timestamp = slot_config
174-
.epoch_start_time(current_sidechain_epoch.next())
175-
.ok_or(GetStatusRpcError::CannotConvertSidechainSlotToTimestamp)?;
230+
231+
let sc_epoch_duration: Duration =
232+
self.client.get_epoch_duration(best_block).map_err(error_object_from)?;
233+
234+
let current_sidechain_epoch = current_timestamp.unix_millis() / sc_epoch_duration.millis();
235+
236+
let next_sidechain_epoch_timestamp =
237+
Timestamp::from((current_sidechain_epoch + 1) * sc_epoch_duration.millis());
176238

177239
let latest_mainchain_block =
178240
self.data_source.get_latest_block_info().await.map_err(|err| {
@@ -188,8 +250,10 @@ where
188250

189251
Ok(GetStatusResponse {
190252
sidechain: SidechainData {
191-
epoch: current_sidechain_epoch.0,
192-
slot: current_sidechain_slot.into(),
253+
epoch: current_sidechain_epoch,
254+
#[cfg(feature = "legacy-slotapi-compat")]
255+
slot: (self.client.get_maybe_slot_duration(best_block))
256+
.map(|duration| current_timestamp.unix_millis() / duration),
193257
next_epoch_timestamp: next_sidechain_epoch_timestamp,
194258
},
195259
mainchain: MainchainData {

0 commit comments

Comments
 (0)