Compare commits
8 Commits
f7b1b75d5a
...
da271a6f22
Author | SHA1 | Date | |
---|---|---|---|
da271a6f22 | |||
b9b7d84466 | |||
8ff588cce9 | |||
298a332681 | |||
1c4c517728 | |||
b969081cbf | |||
9bdb7b5d5c | |||
c4b16805f7 |
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -1186,7 +1186,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "casper-runtime"
|
name = "casper-runtime"
|
||||||
version = "3.5.29"
|
version = "3.5.30"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"casper-runtime-constants",
|
"casper-runtime-constants",
|
||||||
"frame-benchmarking",
|
"frame-benchmarking",
|
||||||
@ -3529,7 +3529,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ghost-cli"
|
name = "ghost-cli"
|
||||||
version = "0.8.0"
|
version = "0.8.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"clap 4.5.4",
|
"clap 4.5.4",
|
||||||
@ -3585,7 +3585,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ghost-machine-primitives"
|
name = "ghost-machine-primitives"
|
||||||
version = "0.8.0"
|
version = "0.8.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"sc-sysinfo",
|
"sc-sysinfo",
|
||||||
@ -3594,7 +3594,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ghost-metrics"
|
name = "ghost-metrics"
|
||||||
version = "0.8.0"
|
version = "0.8.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
"bs58 0.5.1",
|
"bs58 0.5.1",
|
||||||
@ -3668,7 +3668,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ghost-node"
|
name = "ghost-node"
|
||||||
version = "0.8.0"
|
version = "0.8.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
@ -3699,7 +3699,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ghost-rpc"
|
name = "ghost-rpc"
|
||||||
version = "0.8.0"
|
version = "0.8.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ghost-core-primitives",
|
"ghost-core-primitives",
|
||||||
"jsonrpsee",
|
"jsonrpsee",
|
||||||
@ -3751,7 +3751,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ghost-service"
|
name = "ghost-service"
|
||||||
version = "0.8.0"
|
version = "0.8.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assert_matches",
|
"assert_matches",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -3835,7 +3835,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ghost-slow-clap"
|
name = "ghost-slow-clap"
|
||||||
version = "0.3.35"
|
version = "0.3.39"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"frame-benchmarking",
|
"frame-benchmarking",
|
||||||
"frame-support",
|
"frame-support",
|
||||||
|
@ -17,7 +17,7 @@ homepage.workspace = true
|
|||||||
[workspace.package]
|
[workspace.package]
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
authors = ["571nky", "57r37ch", "f4750"]
|
authors = ["571nky", "57r37ch", "f4750"]
|
||||||
version = "0.8.0"
|
version = "0.8.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
homepage = "https://ghostchain.io"
|
homepage = "https://ghostchain.io"
|
||||||
repository = "https://git.ghostchain.io/ghostchain/ghost-node"
|
repository = "https://git.ghostchain.io/ghostchain/ghost-node"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ghost-slow-clap"
|
name = "ghost-slow-clap"
|
||||||
version = "0.3.35"
|
version = "0.3.39"
|
||||||
description = "Applause protocol for the EVM bridge"
|
description = "Applause protocol for the EVM bridge"
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
|
@ -90,7 +90,7 @@ pub struct Clap<AccountId, NetworkId, Balance> {
|
|||||||
pub amount: Balance,
|
pub amount: Balance,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
|
#[derive(Default, Clone, Copy, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
|
||||||
pub struct SessionAuthorityInfo {
|
pub struct SessionAuthorityInfo {
|
||||||
pub claps: u32,
|
pub claps: u32,
|
||||||
pub disabled: bool,
|
pub disabled: bool,
|
||||||
@ -465,6 +465,13 @@ impl<T: Config> Pallet<T> {
|
|||||||
authorities.get(clap.authority_index as usize).is_some(),
|
authorities.get(clap.authority_index as usize).is_some(),
|
||||||
Error::<T>::NotAnAuthority
|
Error::<T>::NotAnAuthority
|
||||||
);
|
);
|
||||||
|
ensure!(
|
||||||
|
ClapsInSession::<T>::get(&clap.session_index)
|
||||||
|
.get(&clap.authority_index)
|
||||||
|
.map(|info| !info.disabled)
|
||||||
|
.unwrap_or(true),
|
||||||
|
Error::<T>::CurrentValidatorIsDisabled
|
||||||
|
);
|
||||||
|
|
||||||
let clap_unique_hash =
|
let clap_unique_hash =
|
||||||
Self::generate_unique_hash(&clap.receiver, &clap.amount, &clap.network_id);
|
Self::generate_unique_hash(&clap.receiver, &clap.amount, &clap.network_id);
|
||||||
@ -491,15 +498,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
ClapsInSession::<T>::try_mutate(&clap.session_index, |claps_details| {
|
ClapsInSession::<T>::mutate(&clap.session_index, |claps_details| {
|
||||||
if claps_details
|
|
||||||
.get(&clap.authority_index)
|
|
||||||
.map(|x| x.disabled)
|
|
||||||
.unwrap_or_default()
|
|
||||||
{
|
|
||||||
return Err(Error::<T>::CurrentValidatorIsDisabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
(*claps_details)
|
(*claps_details)
|
||||||
.entry(clap.authority_index)
|
.entry(clap.authority_index)
|
||||||
.and_modify(|individual| (*individual).claps.saturating_inc())
|
.and_modify(|individual| (*individual).claps.saturating_inc())
|
||||||
@ -507,9 +506,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
claps: 1u32,
|
claps: 1u32,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
});
|
});
|
||||||
|
});
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Self::deposit_event(Event::<T>::Clapped {
|
Self::deposit_event(Event::<T>::Clapped {
|
||||||
authority_id: clap.authority_index,
|
authority_id: clap.authority_index,
|
||||||
@ -766,23 +763,20 @@ impl<T: Config> Pallet<T> {
|
|||||||
|
|
||||||
Ok(match maybe_block_range {
|
Ok(match maybe_block_range {
|
||||||
Some((from_block, to_block)) => match maybe_new_evm_block {
|
Some((from_block, to_block)) => match maybe_new_evm_block {
|
||||||
Some(_) => {
|
Some(_) if from_block.le(&to_block) => {
|
||||||
match estimated_block.checked_sub(from_block) {
|
let adjusted_to_block = estimated_block
|
||||||
Some(current_distance)
|
.checked_sub(from_block)
|
||||||
if current_distance
|
.map(|current_distance| current_distance
|
||||||
< max_block_distance =>
|
.le(&max_block_distance)
|
||||||
{
|
.then(|| estimated_block)
|
||||||
(from_block, estimated_block)
|
)
|
||||||
}
|
.flatten()
|
||||||
_ => (
|
.unwrap_or(from_block
|
||||||
from_block,
|
|
||||||
from_block
|
|
||||||
.saturating_add(max_block_distance)
|
.saturating_add(max_block_distance)
|
||||||
.min(estimated_block),
|
.min(estimated_block));
|
||||||
),
|
(from_block, adjusted_to_block)
|
||||||
}
|
}
|
||||||
}
|
_ => (to_block, to_block),
|
||||||
None => (to_block, to_block),
|
|
||||||
},
|
},
|
||||||
None => (estimated_block, estimated_block),
|
None => (estimated_block, estimated_block),
|
||||||
})
|
})
|
||||||
@ -967,10 +961,18 @@ impl<T: Config> Pallet<T> {
|
|||||||
.ok_or(OffchainErr::ErrorInEvmResponse)?)
|
.ok_or(OffchainErr::ErrorInEvmResponse)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_median_claps(session_index: &SessionIndex) -> u32 {
|
fn calculate_median_claps(
|
||||||
let mut claps_in_session = ClapsInSession::<T>::get(session_index)
|
actual_claps_in_session: &BTreeMap<AuthIndex, SessionAuthorityInfo>,
|
||||||
.values()
|
authorities_len: usize,
|
||||||
.filter_map(|value| (!value.disabled).then(|| value.claps))
|
) -> u32 {
|
||||||
|
let mut claps_in_session = (0..authorities_len)
|
||||||
|
.filter_map(|authority_index| {
|
||||||
|
let clap_info = actual_claps_in_session
|
||||||
|
.get(&(authority_index as AuthIndex))
|
||||||
|
.copied()
|
||||||
|
.unwrap_or_default();
|
||||||
|
(!clap_info.disabled).then(|| clap_info.claps)
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if claps_in_session.is_empty() {
|
if claps_in_session.is_empty() {
|
||||||
@ -991,17 +993,21 @@ impl<T: Config> Pallet<T> {
|
|||||||
|
|
||||||
fn is_good_actor(
|
fn is_good_actor(
|
||||||
authority_index: usize,
|
authority_index: usize,
|
||||||
session_index: SessionIndex,
|
|
||||||
median_claps: u32,
|
median_claps: u32,
|
||||||
|
claps_in_session: &BTreeMap<AuthIndex, SessionAuthorityInfo>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if median_claps == 0 {
|
if median_claps == 0 {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let number_of_claps = ClapsInSession::<T>::get(session_index)
|
let number_of_claps = claps_in_session
|
||||||
.entry(authority_index as AuthIndex)
|
.get(&(authority_index as AuthIndex))
|
||||||
.or_default()
|
.copied()
|
||||||
.claps;
|
.map(|info| match info.disabled {
|
||||||
|
true => median_claps,
|
||||||
|
false => info.claps,
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let authority_deviation = if number_of_claps < median_claps {
|
let authority_deviation = if number_of_claps < median_claps {
|
||||||
Perbill::from_rational(median_claps - number_of_claps, median_claps)
|
Perbill::from_rational(median_claps - number_of_claps, median_claps)
|
||||||
@ -1083,14 +1089,15 @@ impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
|
|||||||
fn on_before_session_ending() {
|
fn on_before_session_ending() {
|
||||||
let session_index = T::ValidatorSet::session_index();
|
let session_index = T::ValidatorSet::session_index();
|
||||||
let validators = T::ValidatorSet::validators();
|
let validators = T::ValidatorSet::validators();
|
||||||
let authorities = Authorities::<T>::get(&session_index);
|
let authorities_len = Authorities::<T>::get(&session_index).len();
|
||||||
|
let claps_in_session = ClapsInSession::<T>::get(&session_index);
|
||||||
|
|
||||||
let median_claps = Self::calculate_median_claps(&session_index);
|
let median_claps = Self::calculate_median_claps(&claps_in_session, authorities_len);
|
||||||
|
|
||||||
let offenders = validators
|
let offenders = validators
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(index, _)| !Self::is_good_actor(*index, session_index, median_claps))
|
.filter(|(index, _)| !Self::is_good_actor(*index, median_claps, &claps_in_session))
|
||||||
.filter_map(|(_, id)| {
|
.filter_map(|(_, id)| {
|
||||||
<T::ValidatorSet as ValidatorSetWithIdentification<T::AccountId>>::IdentificationOf::convert(
|
<T::ValidatorSet as ValidatorSetWithIdentification<T::AccountId>>::IdentificationOf::convert(
|
||||||
id.clone(),
|
id.clone(),
|
||||||
@ -1105,7 +1112,7 @@ impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
|
|||||||
throttling: offenders.clone(),
|
throttling: offenders.clone(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let validator_set_count = authorities.len() as u32;
|
let validator_set_count = authorities_len as u32;
|
||||||
let offence = ThrottlingOffence {
|
let offence = ThrottlingOffence {
|
||||||
session_index,
|
session_index,
|
||||||
validator_set_count,
|
validator_set_count,
|
||||||
|
@ -119,6 +119,157 @@ fn test_throttling_slash_function() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_median_calculations_are_correct() {
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
let data = BTreeMap::from([
|
||||||
|
(
|
||||||
|
0u32,
|
||||||
|
SessionAuthorityInfo {
|
||||||
|
claps: 0,
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
3u32,
|
||||||
|
SessionAuthorityInfo {
|
||||||
|
claps: 1,
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
assert_eq!(SlowClap::calculate_median_claps(&data, 4), 0);
|
||||||
|
|
||||||
|
let data = BTreeMap::from([
|
||||||
|
(
|
||||||
|
0u32,
|
||||||
|
SessionAuthorityInfo {
|
||||||
|
claps: 0,
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1u32,
|
||||||
|
SessionAuthorityInfo {
|
||||||
|
claps: 69,
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
2u32,
|
||||||
|
SessionAuthorityInfo {
|
||||||
|
claps: 69,
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
3u32,
|
||||||
|
SessionAuthorityInfo {
|
||||||
|
claps: 420,
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
assert_eq!(SlowClap::calculate_median_claps(&data, 4), 69);
|
||||||
|
|
||||||
|
let data = BTreeMap::from([
|
||||||
|
(
|
||||||
|
0u32,
|
||||||
|
SessionAuthorityInfo {
|
||||||
|
claps: 31,
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1u32,
|
||||||
|
SessionAuthorityInfo {
|
||||||
|
claps: 420,
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
2u32,
|
||||||
|
SessionAuthorityInfo {
|
||||||
|
claps: 69,
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
3u32,
|
||||||
|
SessionAuthorityInfo {
|
||||||
|
claps: 156,
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
assert_eq!(SlowClap::calculate_median_claps(&data, 4), 50);
|
||||||
|
|
||||||
|
let data = BTreeMap::from([
|
||||||
|
(
|
||||||
|
0u32,
|
||||||
|
SessionAuthorityInfo {
|
||||||
|
claps: 0,
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1u32,
|
||||||
|
SessionAuthorityInfo {
|
||||||
|
claps: 420,
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
2u32,
|
||||||
|
SessionAuthorityInfo {
|
||||||
|
claps: 0,
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
3u32,
|
||||||
|
SessionAuthorityInfo {
|
||||||
|
claps: 0,
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
assert_eq!(SlowClap::calculate_median_claps(&data, 4), 210);
|
||||||
|
|
||||||
|
let data = BTreeMap::from([
|
||||||
|
(
|
||||||
|
0u32,
|
||||||
|
SessionAuthorityInfo {
|
||||||
|
claps: 0,
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1u32,
|
||||||
|
SessionAuthorityInfo {
|
||||||
|
claps: 420,
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
2u32,
|
||||||
|
SessionAuthorityInfo {
|
||||||
|
claps: 69,
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
3u32,
|
||||||
|
SessionAuthorityInfo {
|
||||||
|
claps: 0,
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
assert_eq!(SlowClap::calculate_median_claps(&data, 4), 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn request_body_is_correct_for_get_block_number() {
|
fn request_body_is_correct_for_get_block_number() {
|
||||||
let (offchain, _) = TestOffchainExt::new();
|
let (offchain, _) = TestOffchainExt::new();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "casper-runtime"
|
name = "casper-runtime"
|
||||||
version = "3.5.29"
|
version = "3.5.30"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
description = "Runtime of the Casper Network"
|
description = "Runtime of the Casper Network"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
|
@ -505,7 +505,7 @@ impl pallet_staking::Config for Runtime {
|
|||||||
type SlashDeferDuration = SlashDeferDuration;
|
type SlashDeferDuration = SlashDeferDuration;
|
||||||
type AdminOrigin = EitherOf<EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>, Skeletons>;
|
type AdminOrigin = EitherOf<EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>, Skeletons>;
|
||||||
type SessionInterface = Self;
|
type SessionInterface = Self;
|
||||||
type EraPayout = pallet_staking::ConvertCurve<RewardCurve>;
|
type EraPayout = ghost_networks::BridgedInflationCurve<RewardCurve, Self>;
|
||||||
type MaxExposurePageSize = MaxExposurePageSize;
|
type MaxExposurePageSize = MaxExposurePageSize;
|
||||||
type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy;
|
type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy;
|
||||||
type NextNewSession = Session;
|
type NextNewSession = Session;
|
||||||
|
@ -98,6 +98,9 @@ downgrade_compiler_if_needed() {
|
|||||||
toolchain_name=$(rustup show | grep default | head -n 1 | cut -d' ' -f1)
|
toolchain_name=$(rustup show | grep default | head -n 1 | cut -d' ' -f1)
|
||||||
rustup target add wasm32-unknown-unknown --toolchain $toolchain_name
|
rustup target add wasm32-unknown-unknown --toolchain $toolchain_name
|
||||||
rustup component add rust-src --toolchain $toolchain_name
|
rustup component add rust-src --toolchain $toolchain_name
|
||||||
|
cd $PROJECT_FOLDER
|
||||||
|
echo "[+] clean build cache..."
|
||||||
|
cargo clean
|
||||||
else
|
else
|
||||||
echo "[+] rustc compiler version is compatible"
|
echo "[+] rustc compiler version is compatible"
|
||||||
fi
|
fi
|
||||||
@ -226,8 +229,6 @@ if [[ $HARD_RESET = true ]]; then
|
|||||||
sudo rm -rf "$BASE_PATH/chains/casper_staging_testnet"
|
sudo rm -rf "$BASE_PATH/chains/casper_staging_testnet"
|
||||||
|
|
||||||
cd $PROJECT_FOLDER
|
cd $PROJECT_FOLDER
|
||||||
echo "[+] clean build cache..."
|
|
||||||
cargo clean
|
|
||||||
echo "[+] starting build in 3 seconds..."
|
echo "[+] starting build in 3 seconds..."
|
||||||
sleep 3
|
sleep 3
|
||||||
cargo build --release
|
cargo build --release
|
||||||
|
File diff suppressed because one or more lines are too long
@ -198,7 +198,6 @@ fn casper_testnet_evm_networks() -> Vec<(u32, Vec<u8>)> {
|
|||||||
"https://api.zan.top/eth-sepolia".into(),
|
"https://api.zan.top/eth-sepolia".into(),
|
||||||
"https://rpc.sepolia.ethpandaops.io".into(),
|
"https://rpc.sepolia.ethpandaops.io".into(),
|
||||||
"https://ethereum-sepolia-rpc.publicnode.com".into(),
|
"https://ethereum-sepolia-rpc.publicnode.com".into(),
|
||||||
"https://rpc-sepolia.rockx.com".into(),
|
|
||||||
"https://1rpc.io/sepolia".into(),
|
"https://1rpc.io/sepolia".into(),
|
||||||
"https://0xrpc.io/sep".into(),
|
"https://0xrpc.io/sep".into(),
|
||||||
"https://eth-sepolia.api.onfinality.io/public".into(),
|
"https://eth-sepolia.api.onfinality.io/public".into(),
|
||||||
|
Loading…
Reference in New Issue
Block a user