Módulos de Framework de Rede¶
Introdução¶
O framework Substrate fornece implementações completas e prontas para uso das funções principais que uma rede Tanssi precisa para funcionar corretamente, incluindo criptografia, consenso, governança e assim por diante. Estas implementações são totalmente personalizáveis e podem ser substituídas por lógica personalizada, se necessário.
Ao criar o Tempo de Execução, que define as regras de transição de estado entre dois blocos aplicados a um conjunto de transações, o comportamento e as funcionalidades pretendidas da blockchain precisam de ser definidos ao determinar as regras da transição de estado.
Para construir o Tempo de Execução, o Substrate fornece muitos módulos integrados (também conhecidos como pallets) que podem ser livremente usados como blocos de construção para compor e interagir com quaisquer outros módulos feitos sob medida, permitindo que as equipas criem comportamentos únicos de acordo com os requisitos específicos da sua rede Tanssi.
Módulos Integrados¶
Ao projetar e escrever as regras de uma rede Tanssi, o conjunto disponível de módulos funcionais traz uma solução para muitos dos requisitos de codificação que, caso contrário, precisariam ser construídos do zero.
Aqui está uma lista de alguns dos módulos mais populares:
- Balances - fornece funções para lidar com contas e saldos para a moeda nativa da rede Tanssi
- Assets - fornece funções para lidar com qualquer tipo de token fungível
- NFTs - fornece funções para lidar com tokens não fungíveis
- Democracy - fornece funções para gerir e administrar a votação geral dos stakeholders
- Multisig - fornece funções para envio de multi-assinaturas
- Recovery - fornece funções para permitir que os utilizadores recuperem o acesso às suas contas quando a chave privada é perdida. Isto funciona ao conceder a outras contas o direito de assinar transações em nome da conta perdida (observe que é necessário ter escolhido previamente as contas autorizadas)
- Staking - fornece funções para administrar tokens apostados, apoiar recompensas, cortes, depósito, levantamento, e assim por diante
Além daqueles já listados, outros módulos como identity, smart contracts, vesting, e muitos outros que estão livremente disponíveis podem acelerar o desenvolvimento da rede Tanssi e, consequentemente, o tempo de lançamento.
Nota
O framework também inclui outros módulos que fornecem funcionalidades essenciais de protocolo, como consenso e codificação de dados de baixo nível.
Módulos Feitos sob Medida¶
Os desenvolvedores que criam novos módulos desfrutam de total liberdade para expressar qualquer comportamento desejado na lógica principal da blockchain, como expor novas transações, armazenar informações sensíveis e validar e impor lógica de negócios.
Como explicado no artigo Arquitetura, um módulo precisa ser capaz de se comunicar com o cliente principal, expondo e integrando com uma API muito específica que permite que o tempo de execução exponha transações, acesse o armazenamento e code e decodifique informações armazenadas na cadeia. Também precisa incluir muitos outros códigos de ligação necessários que fazem o módulo funcionar no nó.
Para melhorar a experiência do desenvolvedor ao escrever módulos, o Substrate depende muito de macros Rust. As macros são instruções especiais que se expandem automaticamente para o código Rust pouco antes do tempo de compilação, permitindo que os módulos mantenham até sete vezes a quantidade de código fora da vista dos desenvolvedores. Isto permite que os desenvolvedores se concentrem nos requisitos funcionais específicos ao escrever módulos, em vez de lidar com tecnicidades e o código de suporte necessário.
Todos os módulos no Substrate, incluindo os feitos sob medida, implementam estas macros de atributo, das quais as três primeiras são obrigatórias:
#[frame_support::pallet]- atributo de entrada que marca o módulo como utilizável no runtime#[pallet::pallet]- aplicado a uma estrutura usada para recuperar informações do módulo com facilidade#[pallet::config]- atributo obrigatório para definir a configuração dos tipos de dados do módulo#[pallet::call]- macro usada para definir funções expostas como transações, permitindo que sejam despachadas para o runtime; aqui os desenvolvedores adicionam transações e lógica personalizadas#[pallet::error]- como transações podem falhar (por exemplo, fundos insuficientes) e, por segurança, um módulo não deve gerar exceções, todos os possíveis erros devem ser identificados e listados em um enum para serem retornados em uma execução malsucedida#[pallet::event]- eventos podem ser definidos e usados para fornecer mais informações ao usuário#[pallet::storage]- macro usada para definir elementos que serão persistidos em storage; como recursos são escassos em uma blockchain, deve ser usada com parcimônia para armazenar apenas informações essenciais
Todas estas macros atuam como atributos que devem ser aplicados ao código logo acima dos módulos, funções, estruturas, enums, tipos, etc., Rust, permitindo que o módulo seja construído e adicionado ao tempo de execução, que, com o tempo, irá expor a lógica personalizada ao mundo exterior, conforme exposto na seção seguinte.
Exemplo de Módulo Personalizado¶
Como exemplo de um módulo personalizado, o seguinte código (não destinado a uso em produção) demonstra o uso das macros mencionadas anteriormente, apresentando uma loteria simples com funcionalidade mínima, expondo duas transações:
-
buy_ticket - esta transação verifica se o utilizador que está a assinar o pedido ainda não comprou um bilhete e tem fundos suficientes para pagar. Se tudo estiver bem, o módulo transfere o preço do bilhete para uma conta especial e regista o utilizador como participante do prémio
-
award_prize - esta transação gera um número aleatório para escolher o vencedor da lista de participantes. O vencedor recebe a quantia total dos fundos transferidos para a conta especial do módulo
#![cfg_attr(not(feature = "std"), no_std)]
/// Learn more about FRAME and the core library of Substrate FRAME pallets:
/// <https://docs.substrate.io/reference/frame-pallets/>
pub use pallet::*;
#[frame_support::pallet(dev_mode)]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::{*, ValueQuery, OptionQuery};
use frame_system::pallet_prelude::*;
use scale_info::prelude::vec::Vec;
use frame_support::
{
sp_runtime::traits::AccountIdConversion,
traits:: {
Currency, ExistenceRequirement, Randomness
},
PalletId,
};
type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
#[pallet::pallet]
pub struct Pallet<T>(_);
/// Configure the module by specifying the parameters and types on which it depends.
#[pallet::config]
pub trait Config: frame_system::Config {
// Event definition
type RuntimeEvent: From<Event<Self>>
+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
// Currency
type Currency: Currency<Self::AccountId>;
// Randomness
type MyRandomness: Randomness<Self::Hash, BlockNumberFor<Self>>;
// Ticket cost
#[pallet::constant]
type TicketCost: Get<BalanceOf<Self>>;
// Maximum number of participants
#[pallet::constant]
type MaxParticipants: Get<u32>;
// Module Id
#[pallet::constant]
type PalletId: Get<PalletId>;
}
// The pallet's runtime storage items.
#[pallet::storage]
#[pallet::getter(fn get_participants)]
pub(super) type Participants<T: Config> = StorageValue<
_,
BoundedVec<T::AccountId, T::MaxParticipants>,
OptionQuery
>;
#[pallet::storage]
#[pallet::getter(fn get_nonce)]
pub(super) type Nonce<T: Config> = StorageValue<
_,
u64,
ValueQuery
>;
// Pallets use events to inform users when important changes are made.
// https://docs.substrate.io/main-docs/build/events-errors/
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Event emitted when a ticket is bought
TicketBought { who: T::AccountId },
/// Event emitted when the prize is awarded
PrizeAwarded { winner: T::AccountId },
/// Event emitted when the prize is to be awarded, but there are no participants
ThereAreNoParticipants,
}
// Errors inform users that something went wrong
#[pallet::error]
pub enum Error<T> {
NotEnoughCurrency,
AccountAlreadyParticipating,
CanNotAddParticipant,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(0)]
pub fn buy_ticket(origin: OriginFor<T>) -> DispatchResult {
// 1. Validates the origin signature
let buyer = ensure_signed(origin)?;
// 2. Checks that the user has enough balance to afford the ticket price
ensure!(
T::Currency::free_balance(&buyer) >= T::TicketCost::get(),
Error::<T>::NotEnoughCurrency
);
// 3. Checks that the user is not already participating
if let Some(participants) = Self::get_participants() {
ensure!(
!participants.contains(&buyer),
Error::<T>::AccountAlreadyParticipating
);
}
// 4. Adds the user as a new participant for the prize
match Self::get_participants() {
Some(mut participants) => {
ensure!(
participants.try_push(buyer.clone()).is_ok(),
Error::<T>::CanNotAddParticipant
);
Participants::<T>::set(Some(participants));
},
None => {
let mut participants = BoundedVec::new();
ensure!(
participants.try_push(buyer.clone()).is_ok(),
Error::<T>::CanNotAddParticipant
);
Participants::<T>::set(Some(participants));
}
};
// 5. Transfers the ticket cost to the module's account
// to be hold until transferred to the winner
T::Currency::transfer(
&buyer,
&Self::get_pallet_account(),
T::TicketCost::get(),
ExistenceRequirement::KeepAlive)?;
// 6. Notify the event
Self::deposit_event(Event::TicketBought { who: buyer });
Ok(())
}
#[pallet::call_index(1)]
#[pallet::weight(0)]
pub fn award_prize(origin: OriginFor<T>) -> DispatchResult {
// 1. Validates the origin signature
let _who = ensure_root(origin)?;
match Self::get_participants() {
Some(participants) => {
// 2. Gets a random number from the randomness module
let nonce = Self::get_and_increment_nonce();
let (random_seed, _) = T::MyRandomness::random(&nonce);
let random_number = <u32>::decode(&mut random_seed.as_ref())
.expect("secure hashes should always be bigger than u32; qed");
// 3. Selects the winner from the participants lit
let winner_index = random_number as usize % participants.len();
let winner = participants.as_slice().get(winner_index).unwrap();
// 4. Transfers the total prize to the winner's account
let prize = T::Currency::free_balance(&Self::get_pallet_account());
T::Currency::transfer(
&Self::get_pallet_account(),
&winner,
prize,
ExistenceRequirement::AllowDeath)?;
// 5. Resets the participants list, and gets ready for another lottery round
Participants::<T>::kill();
// 6. Notify the event
Self::deposit_event(Event::PrizeAwarded { winner: winner.clone() } );
},
None => {
// Notify the event (No participants)
Self::deposit_event(Event::ThereAreNoParticipants);
}
};
Ok(())
}
}
impl<T: Config> Pallet<T> {
fn get_pallet_account() -> T::AccountId {
T::PalletId::get().into_account_truncating()
}
fn get_and_increment_nonce() -> Vec<u8> {
let nonce = Nonce::<T>::get();
Nonce::<T>::put(nonce.wrapping_add(1));
nonce.encode()
}
}
}
Para mais informações sobre o processo passo a passo de criação de um módulo feito sob medida para o tempo de execução, consulte Adicionar um módulo personalizado na secção de Construtor.
| Criada: 9 de dezembro de 2025
