// 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 = <::Currency as Currency<::AccountId>>::Balance; pub type NetworkIdOf = <::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId; type WeavingResult = Result>>; type WeaversBitmap = BoundedBitmap::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(_); #[pallet::config] pub trait Config: SendTransactionTypes> + frame_system::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; type AuthorityId: Member + Parameter + RuntimeAppPublic + Ord + MaybeSerializeDeserialize + MaxEncodedLen; type Currency: Currency; type NetworkDataHandler: NetworkDataBasicHandler + NetworkDataInspectHandler + NetworkDataMutateHandler>; type BlockNumberProvider: BlockNumberProvider>; #[pallet::constant] type MaxAuthorities: Get; #[pallet::constant] type MaxAuthoritiesChunks: Get; #[pallet::constant] type WeavingDelay: Get; #[pallet::constant] type AttestationDelay: Get; #[pallet::constant] type UnsignedPriority: Get; type WeightInfo: WeightInfo; } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { BlockAttestated { network_id: NetworkIdOf, authority_index: AuthIndex, external_block: ExternalBlockNumber, }, HashAttestated { next_session: WeavingSession, authority_index: AuthIndex, network_id: NetworkIdOf, root_hash: H256, }, ThreadPulled { pulled_thread_key: H256, network_id: NetworkIdOf, receiver: T::AccountId, amount: BalanceOf, }, } #[pallet::error] pub enum Error { 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 = StorageMap<_, Twox64Concat, NetworkIdOf, WeavingSession, ValueQuery>; #[pallet::storage] #[pallet::getter(fn pulled_threads)] pub(super) type PulledThreads = StorageMap< _, Twox64Concat, H256, (), OptionQuery, >; #[pallet::storage] #[pallet::getter(fn loom_states)] pub(super) type LoomStates = StorageDoubleMap< _, Twox64Concat, NetworkIdOf, Twox64Concat, WeavingSession, H256, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn tapestry_strands)] pub(super) type TapestryStrands = StorageNMap< _, ( NMapKey>, NMapKey, NMapKey, NMapKey, ), WeaversBitmap, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn network_attestations)] pub(super) type NetworkAttestations = StorageMap< _, Twox64Concat, NetworkIdOf, BoundedVec, T::MaxAuthorities>, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn next_consensus_attempt_blocks)] pub(super) type NextConsensusAttemptBlocks = StorageMap< _, Twox64Concat, NetworkIdOf, BlockNumberFor, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn weaving_states)] pub(super) type WeavingStates = StorageMap< _, Twox64Concat, NetworkIdOf, WeavingState, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn tapestry_drafts)] pub(super) type TapestryDrafts = StorageMap< _, Twox64Concat, NetworkIdOf, TapestryDraft>, OptionQuery, >; #[pallet::storage] #[pallet::getter(fn authorities)] pub(super) type Authorities = StorageValue<_, WeakBoundedVec, ValueQuery>; #[pallet::storage] #[pallet::getter(fn disabled_authorities)] pub(super) type DisabledAuthorities = StorageValue<_, WeaversBitmap, ValueQuery>; #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { pub authorities: Vec<(T::AccountId, T::AuthorityId)>, pub loom_states: Vec<(NetworkIdOf, WeavingSession, H256)>, } #[pallet::genesis_build] impl BuildGenesisConfig for GenesisConfig { fn build(&self) { validate_bitmap_sizes::>(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::::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::::initialize_authorities(authorities_vec); } } } #[pallet::call] impl Pallet { #[pallet::call_index(0)] #[pallet::weight(( T::WeightInfo::extend_warp(), DispatchClass::Normal, Pays::No, ))] pub fn extend_warp( origin: OriginFor, block_attestation: BlockAttestation, NetworkIdOf, AuthIndex>, signature: ::Signature, ) -> DispatchResult { ensure_none(origin)?; let (authority_index, network_id) = Self::validate_attestation_signature(&block_attestation, &signature) .map_err(|_| Error::::InvalidParticipantAttestation)?; ensure!( !TapestryDrafts::::contains_key(&network_id), Error::::WeavingIsActive, ); let external_block = block_attestation.external_block; let current_authorities_count = Authorities::::decode_len(). unwrap_or_default(); NetworkAttestations::::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::::TooManyAttestations, ); attestations[authority_index as usize] = Some(external_block); Ok(()) })?; Self::deposit_event(Event::::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, hash_attestation: HashAttestation, NetworkIdOf, AuthIndex>, signature: ::Signature, ) -> DispatchResult { ensure_none(origin)?; let (authority_index, network_id) = Self::validate_attestation_signature(&hash_attestation, &signature) .map_err(|_| Error::::InvalidParticipantAttestation)?; ensure!( TapestryDrafts::::contains_key(&network_id), Error::::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::::try_mutate( &(network_id, weaving_session, next_session, root_hash), |tapestry_strand| -> Result { ensure!( !tapestry_strand.contains(authority_index), Error::::AuthorityPartOfTrapestry, ); tapestry_strand.insert(authority_index); Ok(tapestry_strand.count_ones::()) }, )?; WeavingStates::::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::::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, network_id: NetworkIdOf, session: WeavingSession, thread_proof: ThreadProof>, ) -> DispatchResult { ensure_none(origin)?; let network_data = T::NetworkDataHandler::get(&network_id) .ok_or(Error::::NonExistentNetworkId)?; let root_hash = LoomStates::::get(&network_id, &session); let pulled_thread_key = thread_proof.get_unique_key(session); ensure!( !PulledThreads::::contains_key(&pulled_thread_key), Error::::ThreadAlreadyPulled, ); let receiver_account = thread_proof .verify_proof(root_hash, &network_data.gatekeeper) .ok_or(Error::::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::::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::::CouldNotAccumulateIncomingImbalance) .and_then(|_| { T::NetworkDataHandler::increase_gatekeeper_amount(&network_id, &amount) .map_err(|_| Error::::CouldNotIncreaseGatekeeperAmount) }) .and_then(|_| { T::NetworkDataHandler::accumulate_commission(&commission) .map_err(|_| Error::::CouldNotAccumulateCommission) })?; let _ = T::Currency::deposit_creating(&receiver, pure_amount); PulledThreads::::insert(pulled_thread_key, ()); Self::deposit_event(Event::::ThreadPulled { pulled_thread_key, network_id, receiver, amount, }); Ok(()) } } #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(current_block: BlockNumberFor) -> 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::::decode_len().unwrap_or_default(); let weaving_threshold = get_byzantium_threshold(len); let maybe_tapestry_drafts = TapestryDrafts::::get(&network_id); match maybe_tapestry_drafts { Some(tapestry_draft) => { weight.saturating_accrue(T::DbWeight::get().reads(1)); let weaving_state = WeavingStates::::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::::get(&network_id); let loom_state_changed = LoomStates::::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::::insert(&network_id, next_session); } weight.saturating_accrue(T::DbWeight::get().writes(1)); TapestryDrafts::::remove(&network_id); } } None => { weight.saturating_accrue(T::DbWeight::get().reads(1)); let next_attempt_block = NextConsensusAttemptBlocks::::get(&network_id); if current_block < next_attempt_block { return weight; } weight.saturating_accrue(T::DbWeight::get().reads(1)); let attestation_len = NetworkAttestations::::decode_len(&network_id); if attestation_len.unwrap_or(0) > len { weight.saturating_accrue(T::DbWeight::get().writes(1)); NetworkAttestations::::remove(&network_id); return weight; } let attestation_delay_converted: BlockNumberFor = T::AttestationDelay::get().unique_saturated_into(); weight.saturating_accrue(T::DbWeight::get().writes(1)); let next_release_block = current_block + attestation_delay_converted; NextConsensusAttemptBlocks::::insert(network_id, next_release_block); let mut block_numbers = NetworkAttestations::::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::::insert(&network_id, tapestry_draft); weight.saturating_accrue(T::DbWeight::get().writes(1)); } } } weight } fn offchain_worker(now: BlockNumberFor) { 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 ValidateUnsigned for Pallet { type Call = Call; 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 Pallet { fn validate_attestation_signature( attestation: &A, signature: &::Signature, ) -> Result<(AuthIndex, NetworkIdOf), InvalidTransaction> where A: AttestationMetadata, NetworkIdOf, AuthIndex>, A: Encode, { let authority_index = attestation.authority_index(); let attestation_block = attestation.block(); let network_id = attestation.network_id(); if DisabledAuthorities::::get().contains(authority_index) { return Err(InvalidTransaction::BadSigner); } if T::BlockNumberProvider::current_block_number() <= attestation_block { return Err(InvalidTransaction::BadProof); } Authorities::::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, ) -> WeavingResult>> { 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::