1112 lines
38 KiB
Rust
1112 lines
38 KiB
Rust
// Ensure we're `no_std` when compiling for Wasm.
|
|
#![cfg_attr(not(feature = "std"), no_std)]
|
|
|
|
use frame_support::{
|
|
pallet_prelude::*,
|
|
traits::{Currency, Get, OneSessionHandler},
|
|
};
|
|
use frame_system::{
|
|
pallet_prelude::*,
|
|
offchain::{SendTransactionTypes, SubmitTransaction},
|
|
};
|
|
|
|
use sp_std::prelude::*;
|
|
use sp_core::H256;
|
|
use sp_runtime::{
|
|
offchain::{
|
|
self as rt_offchain,
|
|
http::{PendingRequest, Request},
|
|
storage::StorageValueRef,
|
|
storage_lock::{StorageLock, Time},
|
|
},
|
|
traits::{BlockNumberProvider, Saturating, UniqueSaturatedInto},
|
|
Perbill, RuntimeAppPublic,
|
|
};
|
|
|
|
use ghost_helpers::{
|
|
bounded_bitmap::{validate_bitmap_sizes, BoundedBitmap},
|
|
get_byzantium_threshold,
|
|
networks::{NetworkData, NetworkType},
|
|
};
|
|
use ghost_traits::{
|
|
bounded_bitmap::{BoundedBitmapReader, BoundedBitmapWriter},
|
|
networks::{
|
|
NetworkDataBasicHandler, NetworkDataInspectHandler, NetworkDataMutateHandler,
|
|
NetworkRpcResolver,
|
|
},
|
|
};
|
|
|
|
mod errors;
|
|
mod impls;
|
|
mod types;
|
|
mod weights;
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
#[cfg(feature = "runtime-benchmarks")]
|
|
mod benchmarking;
|
|
|
|
#[cfg(any(test, feature = "runtime-benchmarks"))]
|
|
mod mock;
|
|
|
|
use crate::errors::*;
|
|
use crate::types::*;
|
|
pub use crate::weights::WeightInfo;
|
|
|
|
pub use pallet::*;
|
|
|
|
pub mod sr25519 {
|
|
mod app_sr25519 {
|
|
use sp_application_crypto::{app_crypto, sr25519, KeyTypeId};
|
|
const WEAVER: KeyTypeId = KeyTypeId(*b"weav");
|
|
app_crypto!(sr25519, WEAVER);
|
|
}
|
|
|
|
sp_application_crypto::with_pair! {
|
|
pub type AuthorityPair = app_sr25519::Pair;
|
|
}
|
|
|
|
pub type AuthoritySignature = app_sr25519::Signature;
|
|
pub type AuthorityId = app_sr25519::Public;
|
|
}
|
|
|
|
const LOG_TARGET: &str = "runtime::ghost-weaver";
|
|
const DB_PREFIX: &[u8] = b"ghost-weaver::";
|
|
|
|
const LOCK_BLOCK_EXPIRATION: u64 = 20;
|
|
const MIN_LOCK_GUARD_PERIOD: u64 = 15_000;
|
|
const FETCH_TIMEOUT_PERIOD: u64 = 3_000;
|
|
|
|
type AuthIndex = ghost_helpers::AuthIndexU16;
|
|
type BitmapChunk = ghost_helpers::BitmapChunkU32;
|
|
|
|
type RequestId = u64;
|
|
type ThreadId = u64;
|
|
type WeavingSession = u64;
|
|
type ExternalBlockNumber = u64;
|
|
|
|
type BalanceOf<T> =
|
|
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
|
|
|
pub type NetworkIdOf<T> = <<T as Config>::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId;
|
|
|
|
type WeavingResult<T, O> = Result<O, WeavingError<NetworkIdOf<T>>>;
|
|
|
|
type WeaversBitmap<T> = BoundedBitmap<BitmapChunk, <T as Config>::MaxAuthoritiesChunks>;
|
|
|
|
#[frame_support::pallet]
|
|
pub mod pallet {
|
|
use super::*;
|
|
|
|
const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
|
|
|
|
#[pallet::pallet]
|
|
#[pallet::storage_version(STORAGE_VERSION)]
|
|
#[pallet::without_storage_info]
|
|
pub struct Pallet<T>(_);
|
|
|
|
#[pallet::config]
|
|
pub trait Config: SendTransactionTypes<Call<Self>> + frame_system::Config {
|
|
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
|
|
|
type AuthorityId: Member
|
|
+ Parameter
|
|
+ RuntimeAppPublic
|
|
+ Ord
|
|
+ MaybeSerializeDeserialize
|
|
+ MaxEncodedLen;
|
|
|
|
type Currency: Currency<Self::AccountId>;
|
|
|
|
type NetworkDataHandler: NetworkDataBasicHandler
|
|
+ NetworkDataInspectHandler<NetworkData>
|
|
+ NetworkDataMutateHandler<NetworkData, BalanceOf<Self>>;
|
|
|
|
type BlockNumberProvider: BlockNumberProvider<BlockNumber = BlockNumberFor<Self>>;
|
|
|
|
#[pallet::constant]
|
|
type MaxAuthorities: Get<u32>;
|
|
|
|
#[pallet::constant]
|
|
type MaxAuthoritiesChunks: Get<u32>;
|
|
|
|
#[pallet::constant]
|
|
type WeavingDelay: Get<u64>;
|
|
|
|
#[pallet::constant]
|
|
type AttestationDelay: Get<u64>;
|
|
|
|
#[pallet::constant]
|
|
type UnsignedPriority: Get<TransactionPriority>;
|
|
|
|
type WeightInfo: WeightInfo;
|
|
}
|
|
|
|
#[pallet::event]
|
|
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
|
pub enum Event<T: Config> {
|
|
BlockAttestated {
|
|
network_id: NetworkIdOf<T>,
|
|
authority_index: AuthIndex,
|
|
external_block: ExternalBlockNumber,
|
|
},
|
|
HashAttestated {
|
|
next_session: WeavingSession,
|
|
authority_index: AuthIndex,
|
|
network_id: NetworkIdOf<T>,
|
|
root_hash: H256,
|
|
},
|
|
ThreadPulled {
|
|
pulled_thread_key: H256,
|
|
network_id: NetworkIdOf<T>,
|
|
receiver: T::AccountId,
|
|
amount: BalanceOf<T>,
|
|
},
|
|
}
|
|
|
|
#[pallet::error]
|
|
pub enum Error<T> {
|
|
InvalidParticipantAttestation,
|
|
NonExistentNetworkId,
|
|
TooManyAttestations,
|
|
TimeWentBackwards,
|
|
WeavingIsActive,
|
|
WeavingIsInactive,
|
|
WeaverAlreadyExists,
|
|
|
|
AuthorityPartOfTrapestry,
|
|
ThreadAlreadyPulled,
|
|
InvalidMerkleProof,
|
|
InvalidReceiverAddress,
|
|
CouldNotAccumulateIncomingImbalance,
|
|
CouldNotIncreaseGatekeeperAmount,
|
|
CouldNotAccumulateCommission,
|
|
}
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn current_weaving_session)]
|
|
pub(super) type CurrentWeavingSession<T: Config> =
|
|
StorageMap<_, Twox64Concat, NetworkIdOf<T>, WeavingSession, ValueQuery>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn pulled_threads)]
|
|
pub(super) type PulledThreads<T: Config> = StorageMap<
|
|
_,
|
|
Twox64Concat, H256,
|
|
(),
|
|
OptionQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn loom_states)]
|
|
pub(super) type LoomStates<T: Config> = StorageDoubleMap<
|
|
_,
|
|
Twox64Concat, NetworkIdOf<T>,
|
|
Twox64Concat, WeavingSession,
|
|
H256,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn tapestry_strands)]
|
|
pub(super) type TapestryStrands<T: Config> = StorageNMap<
|
|
_,
|
|
(
|
|
NMapKey<Twox64Concat, NetworkIdOf<T>>,
|
|
NMapKey<Twox64Concat, WeavingSession>,
|
|
NMapKey<Twox64Concat, WeavingSession>,
|
|
NMapKey<Twox64Concat, H256>,
|
|
),
|
|
WeaversBitmap<T>,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn network_attestations)]
|
|
pub(super) type NetworkAttestations<T: Config> = StorageMap<
|
|
_,
|
|
Twox64Concat, NetworkIdOf<T>,
|
|
BoundedVec<Option<ExternalBlockNumber>, T::MaxAuthorities>,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn next_consensus_attempt_blocks)]
|
|
pub(super) type NextConsensusAttemptBlocks<T: Config> = StorageMap<
|
|
_,
|
|
Twox64Concat, NetworkIdOf<T>,
|
|
BlockNumberFor<T>,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn weaving_states)]
|
|
pub(super) type WeavingStates<T: Config> = StorageMap<
|
|
_,
|
|
Twox64Concat, NetworkIdOf<T>,
|
|
WeavingState<AuthIndex>,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn tapestry_drafts)]
|
|
pub(super) type TapestryDrafts<T: Config> = StorageMap<
|
|
_,
|
|
Twox64Concat, NetworkIdOf<T>,
|
|
TapestryDraft<BlockNumberFor<T>>,
|
|
OptionQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn authorities)]
|
|
pub(super) type Authorities<T: Config> =
|
|
StorageValue<_, WeakBoundedVec<T::AuthorityId, T::MaxAuthorities>, ValueQuery>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn disabled_authorities)]
|
|
pub(super) type DisabledAuthorities<T: Config> = StorageValue<_, WeaversBitmap<T>, ValueQuery>;
|
|
|
|
#[pallet::genesis_config]
|
|
#[derive(frame_support::DefaultNoBound)]
|
|
pub struct GenesisConfig<T: Config> {
|
|
pub authorities: Vec<(T::AccountId, T::AuthorityId)>,
|
|
pub loom_states: Vec<(NetworkIdOf<T>, WeavingSession, H256)>,
|
|
}
|
|
|
|
#[pallet::genesis_build]
|
|
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
|
fn build(&self) {
|
|
validate_bitmap_sizes::<WeaversBitmap<T>>(T::MaxAuthorities::get());
|
|
|
|
self.loom_states.iter().for_each(|data| {
|
|
if !T::NetworkDataHandler::contains_key(&data.0) {
|
|
log::info!(
|
|
target: LOG_TARGET,
|
|
"🕸️ ATTENTION! Network #{:?} not registered yet, could not insert root {:?} for weaving session #{:?}!",
|
|
data.0,
|
|
data.2,
|
|
data.1,
|
|
);
|
|
return;
|
|
}
|
|
LoomStates::<T>::insert(&data.0, data.1, data.2);
|
|
});
|
|
|
|
if !self.authorities.is_empty() {
|
|
let authorities_vec = self
|
|
.authorities
|
|
.iter()
|
|
.map(|(account_id, authority)| (account_id, authority.clone()));
|
|
|
|
Pallet::<T>::initialize_authorities(authorities_vec);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[pallet::call]
|
|
impl<T: Config> Pallet<T> {
|
|
#[pallet::call_index(0)]
|
|
#[pallet::weight((
|
|
T::WeightInfo::extend_warp(),
|
|
DispatchClass::Normal,
|
|
Pays::No,
|
|
))]
|
|
pub fn extend_warp(
|
|
origin: OriginFor<T>,
|
|
block_attestation: BlockAttestation<BlockNumberFor<T>, NetworkIdOf<T>, AuthIndex>,
|
|
signature: <T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> DispatchResult {
|
|
ensure_none(origin)?;
|
|
|
|
let (authority_index, network_id) =
|
|
Self::validate_attestation_signature(&block_attestation, &signature)
|
|
.map_err(|_| Error::<T>::InvalidParticipantAttestation)?;
|
|
|
|
ensure!(
|
|
!TapestryDrafts::<T>::contains_key(&network_id),
|
|
Error::<T>::WeavingIsActive,
|
|
);
|
|
|
|
let external_block = block_attestation.external_block;
|
|
|
|
let current_authorities_count = Authorities::<T>::decode_len().
|
|
unwrap_or_default();
|
|
|
|
NetworkAttestations::<T>::try_mutate(
|
|
&network_id,
|
|
|attestations| -> DispatchResult {
|
|
if attestations.is_empty() {
|
|
attestations.bounded_resize(current_authorities_count, None);
|
|
}
|
|
|
|
ensure!(
|
|
(authority_index as usize) < attestations.len(),
|
|
Error::<T>::TooManyAttestations,
|
|
);
|
|
|
|
attestations[authority_index as usize] = Some(external_block);
|
|
|
|
Ok(())
|
|
})?;
|
|
|
|
Self::deposit_event(Event::<T>::BlockAttestated {
|
|
network_id,
|
|
authority_index,
|
|
external_block,
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[pallet::call_index(1)]
|
|
#[pallet::weight((
|
|
T::WeightInfo::weave_weft(),
|
|
DispatchClass::Normal,
|
|
Pays::No,
|
|
))]
|
|
pub fn weave_weft(
|
|
origin: OriginFor<T>,
|
|
hash_attestation: HashAttestation<BlockNumberFor<T>, NetworkIdOf<T>, AuthIndex>,
|
|
signature: <T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> DispatchResult {
|
|
ensure_none(origin)?;
|
|
|
|
let (authority_index, network_id) =
|
|
Self::validate_attestation_signature(&hash_attestation, &signature)
|
|
.map_err(|_| Error::<T>::InvalidParticipantAttestation)?;
|
|
|
|
ensure!(
|
|
TapestryDrafts::<T>::contains_key(&network_id),
|
|
Error::<T>::WeavingIsInactive,
|
|
);
|
|
|
|
let root_hash = hash_attestation.root_hash;
|
|
let next_session = hash_attestation.next_session;
|
|
let weaving_session = hash_attestation.weaving_session();
|
|
|
|
let weavers_count = TapestryStrands::<T>::try_mutate(
|
|
&(network_id, weaving_session, next_session, root_hash),
|
|
|tapestry_strand| -> Result<AuthIndex, DispatchError> {
|
|
ensure!(
|
|
!tapestry_strand.contains(authority_index),
|
|
Error::<T>::AuthorityPartOfTrapestry,
|
|
);
|
|
|
|
tapestry_strand.insert(authority_index);
|
|
Ok(tapestry_strand.count_ones::<AuthIndex>())
|
|
},
|
|
)?;
|
|
|
|
WeavingStates::<T>::mutate(&network_id, |weaving_state| {
|
|
if weaving_state.count < weavers_count {
|
|
*weaving_state = WeavingState::new(root_hash, next_session, weavers_count);
|
|
}
|
|
});
|
|
|
|
Self::deposit_event(Event::<T>::HashAttestated {
|
|
authority_index,
|
|
next_session,
|
|
network_id,
|
|
root_hash,
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[pallet::call_index(2)]
|
|
#[pallet::weight((
|
|
match &thread_proof {
|
|
ThreadProof::UtxoThreadProof(_) => T::WeightInfo::pull_utxo_thread(),
|
|
ThreadProof::EvmThreadProof(_) => T::WeightInfo::pull_evm_thread(),
|
|
},
|
|
DispatchClass::Normal,
|
|
Pays::No,
|
|
))]
|
|
pub fn pull_thread(
|
|
origin: OriginFor<T>,
|
|
network_id: NetworkIdOf<T>,
|
|
session: WeavingSession,
|
|
thread_proof: ThreadProof<BalanceOf<T>>,
|
|
) -> DispatchResult {
|
|
ensure_none(origin)?;
|
|
|
|
let network_data = T::NetworkDataHandler::get(&network_id)
|
|
.ok_or(Error::<T>::NonExistentNetworkId)?;
|
|
|
|
let root_hash = LoomStates::<T>::get(&network_id, &session);
|
|
let pulled_thread_key = thread_proof.get_unique_key(session);
|
|
|
|
ensure!(
|
|
!PulledThreads::<T>::contains_key(&pulled_thread_key),
|
|
Error::<T>::ThreadAlreadyPulled,
|
|
);
|
|
|
|
let receiver_account = thread_proof
|
|
.verify_proof(root_hash, &network_data.gatekeeper)
|
|
.ok_or(Error::<T>::InvalidMerkleProof)?;
|
|
|
|
let amount = thread_proof.amount().clone();
|
|
let receiver_bytes: &[u8; 32] = receiver_account.as_ref();
|
|
let receiver = T::AccountId::decode(&mut &receiver_bytes[..])
|
|
.map_err(|_| Error::<T>::InvalidReceiverAddress)?;
|
|
|
|
let commission = Perbill::from_parts(network_data.incoming_fee).mul_ceil(amount);
|
|
let pure_amount = amount.saturating_sub(commission);
|
|
|
|
let _ = T::NetworkDataHandler::accumulate_incoming_imbalance(&pure_amount)
|
|
.map_err(|_| Error::<T>::CouldNotAccumulateIncomingImbalance)
|
|
.and_then(|_| {
|
|
T::NetworkDataHandler::increase_gatekeeper_amount(&network_id, &amount)
|
|
.map_err(|_| Error::<T>::CouldNotIncreaseGatekeeperAmount)
|
|
})
|
|
.and_then(|_| {
|
|
T::NetworkDataHandler::accumulate_commission(&commission)
|
|
.map_err(|_| Error::<T>::CouldNotAccumulateCommission)
|
|
})?;
|
|
|
|
let _ = T::Currency::deposit_creating(&receiver, pure_amount);
|
|
PulledThreads::<T>::insert(pulled_thread_key, ());
|
|
|
|
Self::deposit_event(Event::<T>::ThreadPulled {
|
|
pulled_thread_key,
|
|
network_id,
|
|
receiver,
|
|
amount,
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[pallet::hooks]
|
|
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
|
fn on_initialize(current_block: BlockNumberFor<T>) -> Weight {
|
|
let mut weight = T::DbWeight::get().reads(1);
|
|
let converted_block: usize = current_block.unique_saturated_into();
|
|
|
|
let (network_id, data) = match T::NetworkDataHandler::network_for_block(converted_block)
|
|
{
|
|
Some((network_id, data)) => (network_id, data),
|
|
None => return weight,
|
|
};
|
|
|
|
weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 1));
|
|
let len = Authorities::<T>::decode_len().unwrap_or_default();
|
|
let weaving_threshold = get_byzantium_threshold(len);
|
|
|
|
let maybe_tapestry_drafts = TapestryDrafts::<T>::get(&network_id);
|
|
|
|
match maybe_tapestry_drafts {
|
|
Some(tapestry_draft) => {
|
|
weight.saturating_accrue(T::DbWeight::get().reads(1));
|
|
let weaving_state = WeavingStates::<T>::get(&network_id);
|
|
|
|
let time_is_out = tapestry_draft.until <= current_block;
|
|
let threshold_reached = weaving_threshold <= weaving_state.count as usize;
|
|
|
|
if time_is_out || threshold_reached {
|
|
weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 1));
|
|
|
|
let current_session = CurrentWeavingSession::<T>::get(&network_id);
|
|
let loom_state_changed =
|
|
LoomStates::<T>::mutate(&network_id, ¤t_session, |loom_state| {
|
|
if *loom_state != weaving_state.hash && threshold_reached {
|
|
*loom_state = weaving_state.hash;
|
|
return true;
|
|
}
|
|
false
|
|
});
|
|
|
|
if loom_state_changed && weaving_state.next_session != current_session {
|
|
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
|
|
|
let next_session = current_session.saturating_add(1);
|
|
CurrentWeavingSession::<T>::insert(&network_id, next_session);
|
|
}
|
|
|
|
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
|
TapestryDrafts::<T>::remove(&network_id);
|
|
}
|
|
}
|
|
None => {
|
|
weight.saturating_accrue(T::DbWeight::get().reads(1));
|
|
let next_attempt_block =
|
|
NextConsensusAttemptBlocks::<T>::get(&network_id);
|
|
|
|
if current_block < next_attempt_block { return weight; }
|
|
|
|
weight.saturating_accrue(T::DbWeight::get().reads(1));
|
|
let attestation_len = NetworkAttestations::<T>::decode_len(&network_id);
|
|
|
|
if attestation_len.unwrap_or(0) > len {
|
|
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
|
NetworkAttestations::<T>::remove(&network_id);
|
|
return weight;
|
|
}
|
|
|
|
let attestation_delay_converted: BlockNumberFor<T> =
|
|
T::AttestationDelay::get().unique_saturated_into();
|
|
|
|
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
|
let next_release_block = current_block + attestation_delay_converted;
|
|
NextConsensusAttemptBlocks::<T>::insert(network_id, next_release_block);
|
|
|
|
let mut block_numbers = NetworkAttestations::<T>::get(&network_id)
|
|
.iter()
|
|
.flatten()
|
|
.copied()
|
|
.collect();
|
|
|
|
if let Some(median_block) = Self::validate_block_numbers_consensus(
|
|
&mut block_numbers,
|
|
data.block_deviation,
|
|
weaving_threshold,
|
|
) {
|
|
let tapestry_draft = TapestryDraftBuilder::default()
|
|
.with_block_delay(T::WeavingDelay::get())
|
|
.with_current_block(current_block)
|
|
.with_median_external(median_block)
|
|
.build();
|
|
|
|
TapestryDrafts::<T>::insert(&network_id, tapestry_draft);
|
|
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
|
}
|
|
}
|
|
}
|
|
|
|
weight
|
|
}
|
|
|
|
fn offchain_worker(now: BlockNumberFor<T>) {
|
|
if sp_io::offchain::is_validator() {
|
|
log::warn!(target: LOG_TARGET, "🕸️ Weaver started at block #{:?}", now);
|
|
match Self::start_weaving(now) {
|
|
Ok(_) => log::warn!(target: LOG_TARGET, "🕸️ Weaver finished gracefully at block #{:?}", now),
|
|
Err(e) => log::warn!(target: LOG_TARGET, "🕸️ Weaver failed at block #{:?}: {:?}", now, e),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[pallet::validate_unsigned]
|
|
impl<T: Config> ValidateUnsigned for Pallet<T> {
|
|
type Call = Call<T>;
|
|
|
|
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
|
|
match call {
|
|
Call::extend_warp {
|
|
block_attestation,
|
|
signature,
|
|
} => {
|
|
let weaver_context =
|
|
Self::validate_attestation_signature(block_attestation, signature)?;
|
|
|
|
ValidTransaction::with_tag_prefix("WeaverBlock")
|
|
.priority(T::UnsignedPriority::get())
|
|
.and_provides(weaver_context.encode())
|
|
.longevity(LOCK_BLOCK_EXPIRATION)
|
|
.propagate(true)
|
|
.build()
|
|
}
|
|
Call::weave_weft {
|
|
hash_attestation,
|
|
signature,
|
|
} => {
|
|
let weaver_context =
|
|
Self::validate_attestation_signature(hash_attestation, signature)?;
|
|
|
|
ValidTransaction::with_tag_prefix("WeaverHash")
|
|
.priority(T::UnsignedPriority::get())
|
|
.and_provides(weaver_context.encode())
|
|
.longevity(LOCK_BLOCK_EXPIRATION)
|
|
.propagate(true)
|
|
.build()
|
|
}
|
|
_ => InvalidTransaction::Call.into(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Config> Pallet<T> {
|
|
fn validate_attestation_signature<A>(
|
|
attestation: &A,
|
|
signature: &<T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> Result<(AuthIndex, NetworkIdOf<T>), InvalidTransaction>
|
|
where
|
|
A: AttestationMetadata<BlockNumberFor<T>, NetworkIdOf<T>, AuthIndex>,
|
|
A: Encode,
|
|
{
|
|
let authority_index = attestation.authority_index();
|
|
let attestation_block = attestation.block();
|
|
let network_id = attestation.network_id();
|
|
|
|
if DisabledAuthorities::<T>::get().contains(authority_index) {
|
|
return Err(InvalidTransaction::BadSigner);
|
|
}
|
|
|
|
if T::BlockNumberProvider::current_block_number() <= attestation_block {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
Authorities::<T>::get()
|
|
.get(authority_index as usize)
|
|
.ok_or(InvalidTransaction::BadProof)
|
|
.and_then(|authority| {
|
|
attestation
|
|
.using_encoded(|encoded| authority.verify(&encoded, signature))
|
|
.then(|| ())
|
|
.ok_or(InvalidTransaction::BadSigner)
|
|
})?;
|
|
|
|
if !T::NetworkDataHandler::contains_key(&network_id) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
Ok((authority_index, network_id))
|
|
}
|
|
|
|
fn start_weaving(
|
|
block_number: BlockNumberFor<T>,
|
|
) -> WeavingResult<T, WeavingOk<NetworkIdOf<T>>> {
|
|
let converted_block: usize = block_number.unique_saturated_into();
|
|
let network_in_use = T::NetworkDataHandler::network_for_block(converted_block)
|
|
.ok_or(WeavingError::NoStoredNetworks)?;
|
|
|
|
let network_id_encoded = network_in_use.0.encode();
|
|
|
|
let lock_period_key = Self::create_storage_key(b"network-period", &network_id_encoded);
|
|
let lock_period = Self::read_offchain_storage(&lock_period_key, &MIN_LOCK_GUARD_PERIOD);
|
|
let lock_until = rt_offchain::Duration::from_millis(lock_period);
|
|
|
|
let network_lock_key = Self::create_storage_key(b"network-lock-", &network_id_encoded);
|
|
let mut network_lock = StorageLock::<Time>::with_deadline(&network_lock_key, lock_until);
|
|
|
|
let _lock_guard = network_lock
|
|
.try_lock()
|
|
.map_err(|_| WeavingError::OffchainLockPeriod(network_in_use.0))?;
|
|
|
|
Self::do_weaving(block_number, network_in_use.0, &network_in_use.1)
|
|
}
|
|
|
|
fn do_weaving(
|
|
current_block: BlockNumberFor<T>,
|
|
network_id: NetworkIdOf<T>,
|
|
network_data: &NetworkData,
|
|
) -> WeavingResult<T, WeavingOk<NetworkIdOf<T>>> {
|
|
let network_id_encoded = network_id.encode();
|
|
let endpoint_key = Self::create_storage_key(b"endpoint-", &network_id_encoded);
|
|
let timeout_key = Self::create_storage_key(b"timeout-", &network_id_encoded);
|
|
|
|
let rpc_endpoints = Self::read_offchain_storage(
|
|
&endpoint_key,
|
|
&network_data
|
|
.default_endpoints
|
|
.iter()
|
|
.map(|endpoint| endpoint.clone().into_inner())
|
|
.collect::<Vec<Vec<u8>>>(),
|
|
);
|
|
|
|
let timeout = Self::read_offchain_storage(&timeout_key, &FETCH_TIMEOUT_PERIOD);
|
|
let deadline =
|
|
sp_io::offchain::timestamp().add(rt_offchain::Duration::from_millis(timeout));
|
|
|
|
if rpc_endpoints.len() == 0 {
|
|
return Err(WeavingError::NoEndpointsAvailable(network_id));
|
|
}
|
|
|
|
match TapestryDrafts::<T>::get(&network_id) {
|
|
Some(tapestry_draft) => Self::try_request_and_submit_hash(
|
|
network_id,
|
|
current_block,
|
|
tapestry_draft.median,
|
|
deadline,
|
|
&network_data,
|
|
&rpc_endpoints,
|
|
),
|
|
None => Self::try_request_and_submit_block(
|
|
network_id,
|
|
current_block,
|
|
deadline,
|
|
&network_data,
|
|
&rpc_endpoints,
|
|
),
|
|
}
|
|
}
|
|
|
|
fn try_request_and_submit_hash(
|
|
network_id: NetworkIdOf<T>,
|
|
current_block: BlockNumberFor<T>,
|
|
target_block: ExternalBlockNumber,
|
|
deadline: rt_offchain::Timestamp,
|
|
network_data: &NetworkData,
|
|
rpc_endpoints: &Vec<Vec<u8>>,
|
|
) -> WeavingResult<T, WeavingOk<NetworkIdOf<T>>> {
|
|
let weaving_session = CurrentWeavingSession::<T>::get(&network_id);
|
|
|
|
let request_id = current_block.unique_saturated_into();
|
|
|
|
let request_body = network_data.r#type.get_hash_request_body(
|
|
request_id,
|
|
target_block,
|
|
weaving_session,
|
|
&network_data.gatekeeper,
|
|
&network_data.selector,
|
|
);
|
|
|
|
let pending_requests =
|
|
Self::prepare_pending_requests(&rpc_endpoints, deadline, &request_body)?;
|
|
|
|
let hashes_and_next_sessions =
|
|
network_data
|
|
.r#type
|
|
.parse_hash_requests(pending_requests, deadline, request_id);
|
|
|
|
let (first_hash, first_next_session) = hashes_and_next_sessions
|
|
.first()
|
|
.and_then(|(first_hash, first_next_session)| {
|
|
hashes_and_next_sessions
|
|
.iter()
|
|
.all(|(hash, next_session)| {
|
|
hash == first_hash && next_session == first_next_session
|
|
})
|
|
.then(|| (*first_hash, *first_next_session))
|
|
})
|
|
.ok_or(WeavingError::<NetworkIdOf<T>>::ContradictoryHashes(
|
|
network_id,
|
|
))?;
|
|
|
|
let tapestry_key = (network_id, weaving_session, first_next_session, first_hash);
|
|
let tapestry_strand = TapestryStrands::<T>::get(&tapestry_key);
|
|
|
|
let context = AttestationContext::default()
|
|
.with_weaving_session(weaving_session)
|
|
.with_current_block(current_block)
|
|
.with_network_id(network_id);
|
|
|
|
Self::local_authorities().for_each(|(authority_index, authority_key)| {
|
|
if tapestry_strand.contains(authority_index) {
|
|
log::error!(
|
|
target: LOG_TARGET,
|
|
"🕸️ Weft already weaved from authority #{:?} for network #{:?}.",
|
|
authority_index,
|
|
network_id,
|
|
);
|
|
return;
|
|
}
|
|
|
|
let hash_attestation = context
|
|
.with_authority_index(authority_index)
|
|
.build_hash_attestation(first_hash, first_next_session);
|
|
|
|
let Some(signature) = authority_key.sign(&hash_attestation.encode()) else {
|
|
log::error!(
|
|
target: LOG_TARGET,
|
|
"🕸️ Signing hash attestation failed from authority #{:?} for network #{:?}.",
|
|
authority_index,
|
|
network_id,
|
|
);
|
|
return;
|
|
};
|
|
|
|
let call = Call::weave_weft { hash_attestation, signature };
|
|
|
|
if let Err(err) =
|
|
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into()) {
|
|
log::error!(
|
|
target: LOG_TARGET,
|
|
"🕸️ Failed to submit hash attestation from authority #{:?} for network {:?}: {:?}.",
|
|
authority_index,
|
|
network_id,
|
|
err,
|
|
);
|
|
}
|
|
});
|
|
|
|
Ok(WeavingOk::Hash(
|
|
target_block,
|
|
first_hash,
|
|
first_next_session,
|
|
network_id,
|
|
))
|
|
}
|
|
|
|
fn try_request_and_submit_block(
|
|
network_id: NetworkIdOf<T>,
|
|
current_block: BlockNumberFor<T>,
|
|
deadline: rt_offchain::Timestamp,
|
|
network_data: &NetworkData,
|
|
rpc_endpoints: &Vec<Vec<u8>>,
|
|
) -> WeavingResult<T, WeavingOk<NetworkIdOf<T>>> {
|
|
let network_id_encoded = network_id.encode();
|
|
let distance_key = Self::create_storage_key(b"distance-", &network_id_encoded);
|
|
let block_deviation =
|
|
Self::read_offchain_storage(&distance_key, &network_data.block_deviation);
|
|
|
|
let request_id = current_block.unique_saturated_into();
|
|
let request_body = network_data.r#type.get_block_request_body(request_id);
|
|
|
|
let pending_requests =
|
|
Self::prepare_pending_requests(&rpc_endpoints, deadline, &request_body)?;
|
|
|
|
let mut block_numbers =
|
|
network_data
|
|
.r#type
|
|
.parse_block_requests(pending_requests, deadline, request_id);
|
|
|
|
let total_blocks = block_numbers.len();
|
|
let weaving_threshold = get_byzantium_threshold(total_blocks);
|
|
|
|
let median_block = Self::validate_block_numbers_consensus(
|
|
&mut block_numbers,
|
|
block_deviation,
|
|
weaving_threshold,
|
|
)
|
|
.ok_or(WeavingError::ContradictoryBlocks(
|
|
total_blocks as u32,
|
|
block_deviation,
|
|
network_id,
|
|
))?;
|
|
|
|
let finality_delay = network_data.finality_delay as ExternalBlockNumber;
|
|
let finalized_block = median_block.saturating_sub(finality_delay);
|
|
|
|
let context = AttestationContext::default()
|
|
.with_current_block(current_block)
|
|
.with_network_id(network_id);
|
|
|
|
Self::local_authorities().for_each(|(authority_index, authority_key)| {
|
|
let block_attestation = context
|
|
.with_authority_index(authority_index)
|
|
.build_block_attestation(finalized_block);
|
|
|
|
let Some(signature) = authority_key.sign(&block_attestation.encode()) else {
|
|
log::error!(
|
|
target: LOG_TARGET,
|
|
"🕸️ Signing block attestation failed from authority #{:?} for network {:?}.",
|
|
authority_index,
|
|
network_id,
|
|
);
|
|
return;
|
|
};
|
|
|
|
let call = Call::extend_warp { block_attestation, signature };
|
|
|
|
if let Err(err) =
|
|
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into()) {
|
|
log::error!(
|
|
target: LOG_TARGET,
|
|
"🕸️ Failed to submit block attestation from authority #{:?} for network {:?}: {:?}.",
|
|
authority_index,
|
|
network_id,
|
|
err,
|
|
);
|
|
}
|
|
});
|
|
|
|
Ok(WeavingOk::Block(finalized_block, network_id))
|
|
}
|
|
|
|
fn prepare_pending_requests(
|
|
rpc_endpoints: &[Vec<u8>],
|
|
deadline: rt_offchain::Timestamp,
|
|
request_body: &[u8],
|
|
) -> WeavingResult<T, Vec<PendingRequest>> {
|
|
let default_headers: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
|
|
let request_body_str =
|
|
core::str::from_utf8(request_body).map_err(|_| WeavingError::UnparsableRequestBody)?;
|
|
|
|
let pending_requests = rpc_endpoints
|
|
.iter()
|
|
.filter_map(|rpc_endpoint| {
|
|
let headers = Self::read_offchain_storage(&rpc_endpoint, &default_headers);
|
|
|
|
let rpc_endpoint_str = core::str::from_utf8(rpc_endpoint)
|
|
.inspect_err(|err| log::error!(target: LOG_TARGET, "🕸️ Could not convert to UTF-8: {:?}", err))
|
|
.ok()?;
|
|
|
|
let body_bytes: &[u8] = request_body_str.as_bytes();
|
|
let body_slice: &[&[u8]] = &[body_bytes];
|
|
|
|
let mut request = Request::post(&rpc_endpoint_str, body_slice)
|
|
.add_header("Accept", "application/json")
|
|
.add_header("Content-Type", "application/json");
|
|
|
|
for (key, header) in headers.iter() {
|
|
let maybe_key_str = core::str::from_utf8(key);
|
|
let maybe_header_str = core::str::from_utf8(header);
|
|
|
|
match (maybe_key_str, maybe_header_str) {
|
|
(Ok(key_str), Ok(header_str)) => {
|
|
request = request.add_header(key_str, header_str);
|
|
}
|
|
_ => continue,
|
|
}
|
|
}
|
|
|
|
request
|
|
.deadline(deadline)
|
|
.send()
|
|
.inspect_err(|err| {
|
|
log::error!(
|
|
target: LOG_TARGET,
|
|
"🕸️ Failed request on {}: {:?}",
|
|
rpc_endpoint_str,
|
|
err,
|
|
);
|
|
})
|
|
.ok()
|
|
})
|
|
.collect();
|
|
|
|
Ok(pending_requests)
|
|
}
|
|
|
|
fn validate_block_numbers_consensus<I>(
|
|
block_numbers: &mut Vec<ExternalBlockNumber>,
|
|
max_block_deviation: I,
|
|
threshold: usize,
|
|
) -> Option<ExternalBlockNumber>
|
|
where
|
|
I: UniqueSaturatedInto<ExternalBlockNumber>,
|
|
{
|
|
let total_blocks = block_numbers.len();
|
|
|
|
if block_numbers.is_empty() || total_blocks < threshold {
|
|
return None;
|
|
}
|
|
|
|
block_numbers.sort();
|
|
let max_deviation: ExternalBlockNumber = max_block_deviation
|
|
.unique_saturated_into();
|
|
|
|
let mut left = total_blocks / 2;
|
|
let mut right = left;
|
|
|
|
loop {
|
|
let can_expand_left =
|
|
left > 0 && (block_numbers[right] - block_numbers[left - 1] <= max_deviation);
|
|
let can_expand_right = right < total_blocks - 1
|
|
&& (block_numbers[right + 1] - block_numbers[left] <= max_deviation);
|
|
|
|
match (can_expand_left, can_expand_right) {
|
|
(true, true) => {
|
|
let dist_in_left = block_numbers[right] - block_numbers[left - 1];
|
|
let dist_in_right = block_numbers[right + 1] - block_numbers[left];
|
|
|
|
if dist_in_left > dist_in_right {
|
|
right += 1;
|
|
} else {
|
|
left -= 1;
|
|
}
|
|
}
|
|
(true, false) => left -= 1,
|
|
(false, true) => right += 1,
|
|
(false, false) => break,
|
|
}
|
|
}
|
|
|
|
let window_len = right + 1 - left;
|
|
let mid_idx = (right + left) / 2;
|
|
|
|
let mid_value = if window_len % 2 == 0 {
|
|
let a = block_numbers[mid_idx];
|
|
let b = block_numbers[mid_idx + 1];
|
|
|
|
a + (b - a) / 2
|
|
} else {
|
|
block_numbers[mid_idx]
|
|
};
|
|
|
|
window_len.ge(&threshold).then(|| mid_value)
|
|
}
|
|
|
|
fn create_storage_key(first: &[u8], second: &[u8]) -> Vec<u8> {
|
|
let mut key = DB_PREFIX.to_vec();
|
|
key.extend(first);
|
|
key.extend(second);
|
|
key
|
|
}
|
|
|
|
fn read_offchain_storage<R: codec::Decode + Clone>(storage_key: &[u8], default_value: &R) -> R {
|
|
StorageValueRef::persistent(&storage_key)
|
|
.get::<R>()
|
|
.ok()
|
|
.flatten()
|
|
.unwrap_or(default_value.clone())
|
|
}
|
|
|
|
fn local_authorities() -> impl Iterator<Item = (AuthIndex, T::AuthorityId)> {
|
|
let authorities = Authorities::<T>::get();
|
|
let mut local_authorities = T::AuthorityId::all();
|
|
local_authorities.sort();
|
|
|
|
authorities
|
|
.into_iter()
|
|
.enumerate()
|
|
.filter_map(move |(index, authority)| {
|
|
local_authorities
|
|
.binary_search(&authority)
|
|
.ok()
|
|
.map(|location| {
|
|
(
|
|
index.unique_saturated_into(),
|
|
local_authorities[location].clone(),
|
|
)
|
|
})
|
|
})
|
|
}
|
|
|
|
fn initialize_authorities<'a, I: 'a>(validators: I)
|
|
where
|
|
I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
|
|
{
|
|
let authorities = validators.map(|x| x.1).collect::<Vec<_>>();
|
|
let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::force_from(
|
|
authorities,
|
|
Some("ghost-weaver pallet reached maximum number of authorities."),
|
|
);
|
|
|
|
Authorities::<T>::set(bounded_authorities);
|
|
DisabledAuthorities::<T>::kill();
|
|
}
|
|
}
|
|
|
|
impl<T: Config> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
|
|
type Public = T::AuthorityId;
|
|
}
|
|
|
|
impl<T: Config> BlockNumberProvider for Pallet<T> {
|
|
type BlockNumber = BlockNumberFor<T>;
|
|
fn current_block_number() -> Self::BlockNumber {
|
|
T::BlockNumberProvider::current_block_number()
|
|
}
|
|
}
|
|
|
|
impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
|
|
type Key = T::AuthorityId;
|
|
|
|
fn on_genesis_session<'a, I: 'a>(validators: I)
|
|
where
|
|
I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
|
|
{
|
|
Self::initialize_authorities(validators);
|
|
}
|
|
|
|
fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, _queued_validators: I)
|
|
where
|
|
I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
|
|
{
|
|
Self::initialize_authorities(validators);
|
|
}
|
|
|
|
fn on_before_session_ending() {}
|
|
|
|
fn on_disabled(validator_index: u32) {
|
|
DisabledAuthorities::<T>::mutate(|bitmap| {
|
|
bitmap.insert(validator_index);
|
|
});
|
|
}
|
|
}
|