mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-28 02:48:03 +00:00
New genesis (#327)
* Update Substrate & Polkadot * Update chainspecs * Update again to fix test
This commit is contained in:
Generated
+529
-486
File diff suppressed because it is too large
Load Diff
+17
-17
@@ -35,7 +35,7 @@ use sp_runtime::{
|
||||
};
|
||||
use sp_state_machine::InspectState;
|
||||
|
||||
use polkadot_node_primitives::{Collation, CollationGenerationConfig};
|
||||
use polkadot_node_primitives::{Collation, CollationGenerationConfig, CollationResult};
|
||||
use polkadot_node_subsystem::messages::{CollationGenerationMessage, CollatorProtocolMessage};
|
||||
use polkadot_overseer::OverseerHandler;
|
||||
use polkadot_primitives::v1::{
|
||||
@@ -48,7 +48,7 @@ use codec::{Decode, Encode};
|
||||
|
||||
use log::{debug, error, info, trace};
|
||||
|
||||
use futures::prelude::*;
|
||||
use futures::{channel::oneshot, prelude::*};
|
||||
|
||||
use std::{marker::PhantomData, sync::Arc, time::Duration};
|
||||
|
||||
@@ -100,10 +100,10 @@ where
|
||||
PF: Environment<Block> + 'static + Send,
|
||||
PF::Proposer: Send,
|
||||
BI: BlockImport<
|
||||
Block,
|
||||
Error = ConsensusError,
|
||||
Transaction = <PF::Proposer as Proposer<Block>>::Transaction,
|
||||
> + Send
|
||||
Block,
|
||||
Error = ConsensusError,
|
||||
Transaction = <PF::Proposer as Proposer<Block>>::Transaction,
|
||||
> + Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
BS: BlockBackend<Block>,
|
||||
@@ -120,7 +120,6 @@ where
|
||||
para_id: ParaId,
|
||||
proposer_factory: PF,
|
||||
inherent_data_providers: InherentDataProviders,
|
||||
overseer_handler: OverseerHandler,
|
||||
block_import: BI,
|
||||
block_status: Arc<BS>,
|
||||
spawner: Arc<dyn SpawnNamed + Send + Sync>,
|
||||
@@ -129,11 +128,7 @@ where
|
||||
polkadot_client: Arc<PClient>,
|
||||
polkadot_backend: Arc<PBackend2>,
|
||||
) -> Self {
|
||||
let wait_to_announce = Arc::new(Mutex::new(WaitToAnnounce::new(
|
||||
spawner,
|
||||
announce_block,
|
||||
overseer_handler,
|
||||
)));
|
||||
let wait_to_announce = Arc::new(Mutex::new(WaitToAnnounce::new(spawner, announce_block)));
|
||||
|
||||
Self {
|
||||
para_id,
|
||||
@@ -347,7 +342,7 @@ where
|
||||
mut self,
|
||||
relay_parent: PHash,
|
||||
validation_data: PersistedValidationData,
|
||||
) -> Option<Collation> {
|
||||
) -> Option<CollationResult> {
|
||||
trace!(target: LOG_TARGET, "Producing candidate");
|
||||
|
||||
let last_head = match Block::Header::decode(&mut &validation_data.parent_head.0[..]) {
|
||||
@@ -448,16 +443,21 @@ where
|
||||
let collation = self.build_collation(b, block_hash, validation_data.relay_parent_number)?;
|
||||
let pov_hash = collation.proof_of_validity.hash();
|
||||
|
||||
let (result_sender, signed_stmt_recv) = oneshot::channel();
|
||||
|
||||
self.wait_to_announce
|
||||
.lock()
|
||||
.wait_to_announce(block_hash, pov_hash);
|
||||
.wait_to_announce(block_hash, pov_hash, signed_stmt_recv);
|
||||
|
||||
info!(
|
||||
target: LOG_TARGET,
|
||||
"Produced proof-of-validity candidate {:?} from block {:?}.", pov_hash, block_hash,
|
||||
);
|
||||
|
||||
Some(collation)
|
||||
Some(CollationResult {
|
||||
collation,
|
||||
result_sender: Some(result_sender),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,7 +524,6 @@ where
|
||||
para_id,
|
||||
proposer_factory,
|
||||
inherent_data_providers,
|
||||
overseer_handler.clone(),
|
||||
block_import,
|
||||
block_status,
|
||||
Arc::new(spawner),
|
||||
@@ -707,7 +706,8 @@ mod tests {
|
||||
validation_data.parent_head = header.encode().into();
|
||||
|
||||
let collation = block_on((config.collator)(relay_parent, &validation_data))
|
||||
.expect("Collation is build");
|
||||
.expect("Collation is build")
|
||||
.collation;
|
||||
|
||||
let block_data = collation.proof_of_validity.block_data;
|
||||
|
||||
|
||||
@@ -17,9 +17,7 @@ sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "mas
|
||||
# Polkadot deps
|
||||
polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "master" }
|
||||
polkadot-statement-table = { git = "https://github.com/paritytech/polkadot", branch = "master" }
|
||||
polkadot-overseer = { git = "https://github.com/paritytech/polkadot", branch = "master" }
|
||||
polkadot-node-primitives = { git = "https://github.com/paritytech/polkadot", branch = "master" }
|
||||
polkadot-node-subsystem = { git = "https://github.com/paritytech/polkadot", branch = "master" }
|
||||
polkadot-service = { git = "https://github.com/paritytech/polkadot", branch = "master" }
|
||||
polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "master" }
|
||||
|
||||
@@ -27,7 +25,7 @@ polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch =
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0", features = [ "derive" ] }
|
||||
futures = { version = "0.3.1", features = ["compat"] }
|
||||
futures-timer = "3.0.2"
|
||||
log = "0.4.8"
|
||||
tracing = "0.1.22"
|
||||
parking_lot = "0.10.2"
|
||||
derive_more = "0.99.2"
|
||||
|
||||
|
||||
+53
-44
@@ -20,10 +20,6 @@
|
||||
//! that use the relay chain provided consensus. See [`BlockAnnounceValidator`]
|
||||
//! and [`WaitToAnnounce`] for more information about this implementation.
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod wait_on_relay_chain_block;
|
||||
|
||||
use sc_client_api::{Backend, BlockchainEvents};
|
||||
use sp_api::ProvideRuntimeApi;
|
||||
use sp_blockchain::HeaderBackend;
|
||||
@@ -38,8 +34,6 @@ use sp_runtime::{
|
||||
};
|
||||
|
||||
use polkadot_node_primitives::{SignedFullStatement, Statement};
|
||||
use polkadot_node_subsystem::messages::StatementDistributionMessage;
|
||||
use polkadot_overseer::OverseerHandler;
|
||||
use polkadot_primitives::v1::{
|
||||
Block as PBlock, CandidateReceipt, CompactStatement, Hash as PHash, Id as ParaId,
|
||||
OccupiedCoreAssumption, ParachainHost, SignedStatement, SigningContext,
|
||||
@@ -48,16 +42,19 @@ use polkadot_service::ClientHandle;
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
channel::oneshot,
|
||||
future::{ready, FutureExt},
|
||||
pin_mut, select, Future, StreamExt,
|
||||
pin_mut, select, Future,
|
||||
};
|
||||
use log::trace;
|
||||
|
||||
use std::{convert::TryFrom, fmt, marker::PhantomData, pin::Pin, sync::Arc};
|
||||
|
||||
use wait_on_relay_chain_block::WaitOnRelayChainBlock;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod wait_on_relay_chain_block;
|
||||
|
||||
const LOG_TARGET: &str = "cumulus-network";
|
||||
|
||||
type BoxedError = Box<dyn std::error::Error + Send>;
|
||||
@@ -90,7 +87,7 @@ impl BlockAnnounceData {
|
||||
let candidate_hash = if let CompactStatement::Candidate(h) = self.statement.payload() {
|
||||
h
|
||||
} else {
|
||||
log::debug!(
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
"`CompactStatement` isn't the candidate variant!",
|
||||
);
|
||||
@@ -98,7 +95,7 @@ impl BlockAnnounceData {
|
||||
};
|
||||
|
||||
if *candidate_hash != self.receipt.hash() {
|
||||
log::debug!(
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Receipt candidate hash doesn't match candidate hash in statement",
|
||||
);
|
||||
@@ -108,7 +105,7 @@ impl BlockAnnounceData {
|
||||
if polkadot_parachain::primitives::HeadData(encoded_header).hash()
|
||||
!= self.receipt.descriptor.para_head
|
||||
{
|
||||
log::debug!(
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Receipt para head hash doesn't match the hash of the header in the block announcement",
|
||||
);
|
||||
@@ -121,7 +118,10 @@ impl BlockAnnounceData {
|
||||
/// Check the signature of the statement.
|
||||
///
|
||||
/// Returns an `Err(_)` if it failed.
|
||||
fn check_signature<P>(&self, relay_chain_client: &Arc<P>) -> Result<Validation, BlockAnnounceError>
|
||||
fn check_signature<P>(
|
||||
&self,
|
||||
relay_chain_client: &Arc<P>,
|
||||
) -> Result<Validation, BlockAnnounceError>
|
||||
where
|
||||
P: ProvideRuntimeApi<PBlock> + Send + Sync + 'static,
|
||||
P::Api: ParachainHost<PBlock>,
|
||||
@@ -152,12 +152,12 @@ impl BlockAnnounceData {
|
||||
let signer = match authorities.get(validator_index as usize) {
|
||||
Some(r) => r,
|
||||
None => {
|
||||
log::debug!(
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Block announcement justification signer is a validator index out of bound",
|
||||
);
|
||||
|
||||
return Ok(Validation::Failure { disconnect: true })
|
||||
return Ok(Validation::Failure { disconnect: true });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -167,7 +167,7 @@ impl BlockAnnounceData {
|
||||
.check_signature(&signing_context, &signer)
|
||||
.is_err()
|
||||
{
|
||||
log::debug!(
|
||||
tracing::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Block announcement justification signature is invalid.",
|
||||
);
|
||||
@@ -301,7 +301,7 @@ where
|
||||
let known_best_number = parent_head.number();
|
||||
|
||||
if block_number >= known_best_number {
|
||||
trace!(
|
||||
tracing::trace!(
|
||||
target: "cumulus-network",
|
||||
"validation failed because a justification is needed if the block at the top of the chain."
|
||||
);
|
||||
@@ -474,7 +474,6 @@ where
|
||||
pub struct WaitToAnnounce<Block: BlockT> {
|
||||
spawner: Arc<dyn SpawnNamed + Send + Sync>,
|
||||
announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>,
|
||||
overseer_handler: OverseerHandler,
|
||||
current_trigger: oneshot::Sender<()>,
|
||||
}
|
||||
|
||||
@@ -483,24 +482,26 @@ impl<Block: BlockT> WaitToAnnounce<Block> {
|
||||
pub fn new(
|
||||
spawner: Arc<dyn SpawnNamed + Send + Sync>,
|
||||
announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>,
|
||||
overseer_handler: OverseerHandler,
|
||||
) -> WaitToAnnounce<Block> {
|
||||
let (tx, _rx) = oneshot::channel();
|
||||
|
||||
WaitToAnnounce {
|
||||
spawner,
|
||||
announce_block,
|
||||
overseer_handler,
|
||||
current_trigger: tx,
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait for a candidate message for the block, then announce the block. The candidate
|
||||
/// message will be added as justification to the block announcement.
|
||||
pub fn wait_to_announce(&mut self, block_hash: <Block as BlockT>::Hash, pov_hash: PHash) {
|
||||
pub fn wait_to_announce(
|
||||
&mut self,
|
||||
block_hash: <Block as BlockT>::Hash,
|
||||
pov_hash: PHash,
|
||||
signed_stmt_recv: oneshot::Receiver<SignedFullStatement>,
|
||||
) {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let announce_block = self.announce_block.clone();
|
||||
let overseer_handler = self.overseer_handler.clone();
|
||||
|
||||
self.current_trigger = tx;
|
||||
|
||||
@@ -511,27 +512,27 @@ impl<Block: BlockT> WaitToAnnounce<Block> {
|
||||
block_hash,
|
||||
pov_hash,
|
||||
announce_block,
|
||||
overseer_handler,
|
||||
signed_stmt_recv,
|
||||
)
|
||||
.fuse();
|
||||
let t2 = rx.fuse();
|
||||
|
||||
pin_mut!(t1, t2);
|
||||
|
||||
trace!(
|
||||
tracing::trace!(
|
||||
target: "cumulus-network",
|
||||
"waiting for announce block in a background task...",
|
||||
);
|
||||
|
||||
select! {
|
||||
_ = t1 => {
|
||||
trace!(
|
||||
tracing::trace!(
|
||||
target: "cumulus-network",
|
||||
"block announcement finished",
|
||||
);
|
||||
},
|
||||
_ = t2 => {
|
||||
trace!(
|
||||
tracing::trace!(
|
||||
target: "cumulus-network",
|
||||
"previous task that waits for announce block has been canceled",
|
||||
);
|
||||
@@ -547,25 +548,33 @@ async fn wait_to_announce<Block: BlockT>(
|
||||
block_hash: <Block as BlockT>::Hash,
|
||||
pov_hash: PHash,
|
||||
announce_block: Arc<dyn Fn(Block::Hash, Vec<u8>) + Send + Sync>,
|
||||
mut overseer_handler: OverseerHandler,
|
||||
signed_stmt_recv: oneshot::Receiver<SignedFullStatement>,
|
||||
) {
|
||||
let (sender, mut receiver) = mpsc::channel(5);
|
||||
overseer_handler
|
||||
.send_msg(StatementDistributionMessage::RegisterStatementListener(
|
||||
sender,
|
||||
))
|
||||
.await;
|
||||
|
||||
while let Some(statement) = receiver.next().await {
|
||||
match statement.payload() {
|
||||
Statement::Seconded(c) if &c.descriptor.pov_hash == &pov_hash => {
|
||||
if let Ok(data) = BlockAnnounceData::try_from(statement) {
|
||||
announce_block(block_hash, data.encode());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
let statement = match signed_stmt_recv.await {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
tracing::debug!(
|
||||
target: "cumulus-network",
|
||||
pov_hash = ?pov_hash,
|
||||
block = ?block_hash,
|
||||
"Wait to announce stopped, because sender was dropped.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match statement.payload() {
|
||||
Statement::Seconded(c) if &c.descriptor.pov_hash == &pov_hash => {
|
||||
if let Ok(data) = BlockAnnounceData::try_from(statement) {
|
||||
announce_block(block_hash, data.encode());
|
||||
}
|
||||
}
|
||||
_ => tracing::debug!(
|
||||
target: "cumulus-network",
|
||||
statement = ?statement,
|
||||
block = ?block_hash,
|
||||
expected_pov_hash = ?pov_hash,
|
||||
"Received invalid statement while waiting to announce block.",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ use polkadot_test_client::{
|
||||
};
|
||||
use sp_api::{ApiRef, ProvideRuntimeApi};
|
||||
use sp_blockchain::HeaderBackend;
|
||||
use sp_consensus::{block_validation::BlockAnnounceValidator as _, BlockOrigin};
|
||||
use sp_consensus::BlockOrigin;
|
||||
use sp_core::H256;
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
use sp_keystore::{testing::KeyStore, SyncCryptoStore, SyncCryptoStorePtr};
|
||||
|
||||
@@ -16,7 +16,7 @@ polkadot-parachain = { git = "https://github.com/paritytech/polkadot", default-f
|
||||
# Substrate dependencies
|
||||
frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
|
||||
pallet-balances = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
|
||||
sp-core = { git = "https://github.com/paritytech/substrate", version = "2.0.0-rc5", default-features = false, branch = "master" }
|
||||
sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
|
||||
sp-inherents = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
|
||||
sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
|
||||
sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
|
||||
|
||||
@@ -866,7 +866,7 @@ mod tests {
|
||||
use frame_support::{
|
||||
assert_ok,
|
||||
dispatch::UnfilteredDispatchable,
|
||||
impl_outer_event, impl_outer_origin, parameter_types,
|
||||
parameter_types,
|
||||
traits::{OnFinalize, OnInitialize},
|
||||
};
|
||||
use frame_system::{InitKind, RawOrigin};
|
||||
@@ -877,26 +877,22 @@ mod tests {
|
||||
use sp_version::RuntimeVersion;
|
||||
use std::cell::RefCell;
|
||||
|
||||
impl_outer_origin! {
|
||||
pub enum Origin for Test where system = frame_system {}
|
||||
}
|
||||
use crate as parachain_system;
|
||||
|
||||
mod parachain_system {
|
||||
pub use crate::Event;
|
||||
}
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
|
||||
impl_outer_event! {
|
||||
pub enum TestEvent for Test {
|
||||
frame_system<T>,
|
||||
parachain_system,
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Test where
|
||||
Block = Block,
|
||||
NodeBlock = Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic,
|
||||
{
|
||||
System: frame_system::{Module, Call, Config, Storage, Event<T>},
|
||||
ParachainSystem: parachain_system::{Module, Call, Storage, Event},
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// For testing the pallet, we construct most of a mock runtime. This means
|
||||
// first constructing a configuration type (`Test`) which `impl`s each of the
|
||||
// configuration traits of modules we want to use.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct Test;
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub Version: RuntimeVersion = RuntimeVersion {
|
||||
@@ -912,7 +908,7 @@ mod tests {
|
||||
}
|
||||
impl frame_system::Config for Test {
|
||||
type Origin = Origin;
|
||||
type Call = ();
|
||||
type Call = Call;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
@@ -920,12 +916,12 @@ mod tests {
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = TestEvent;
|
||||
type Event = Event;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type BlockLength = ();
|
||||
type BlockWeights = ();
|
||||
type Version = Version;
|
||||
type PalletInfo = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
@@ -935,16 +931,13 @@ mod tests {
|
||||
type SS58Prefix = ();
|
||||
}
|
||||
impl Config for Test {
|
||||
type Event = TestEvent;
|
||||
type Event = Event;
|
||||
type OnValidationData = ();
|
||||
type SelfParaId = ParachainId;
|
||||
type DownwardMessageHandlers = SaveIntoThreadLocal;
|
||||
type HrmpMessageHandlers = SaveIntoThreadLocal;
|
||||
}
|
||||
|
||||
type ParachainSystem = Module<Test>;
|
||||
type System = frame_system::Module<Test>;
|
||||
|
||||
pub struct SaveIntoThreadLocal;
|
||||
|
||||
std::thread_local! {
|
||||
@@ -1241,7 +1234,7 @@ mod tests {
|
||||
let events = System::events();
|
||||
assert_eq!(
|
||||
events[0].event,
|
||||
TestEvent::parachain_system(Event::ValidationFunctionStored(1123))
|
||||
Event::parachain_system(crate::Event::ValidationFunctionStored(1123))
|
||||
);
|
||||
},
|
||||
)
|
||||
@@ -1252,7 +1245,7 @@ mod tests {
|
||||
let events = System::events();
|
||||
assert_eq!(
|
||||
events[0].event,
|
||||
TestEvent::parachain_system(Event::ValidationFunctionApplied(1234))
|
||||
Event::parachain_system(crate::Event::ValidationFunctionApplied(1234))
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -57,6 +57,7 @@ fn call_validate_block(
|
||||
Some(1024),
|
||||
sp_io::SubstrateHostFunctions::host_functions(),
|
||||
1,
|
||||
None,
|
||||
);
|
||||
|
||||
executor
|
||||
|
||||
@@ -41,7 +41,7 @@ sc-telemetry = { git = "https://github.com/paritytech/substrate", branch = "mast
|
||||
sc-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sc-basic-authorship = { git = "https://github.com/paritytech/substrate", version = "0.8.0-rc5", branch = "master" }
|
||||
sc-basic-authorship = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-timestamp = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -10,7 +10,7 @@ rand = "0.7.3"
|
||||
serde = { version = "1.0.101", features = ["derive"] }
|
||||
|
||||
# Substrate
|
||||
sc-basic-authorship = { git = "https://github.com/paritytech/substrate", version = "0.8.0-rc5", branch = "master" }
|
||||
sc-basic-authorship = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sc-block-builder = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sc-chain-spec = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||
|
||||
Reference in New Issue
Block a user