SwiftHTML & CSSSolidityDesenvolvimento de JogosSolana/Rust
19.11.2024

Aula 077: Lidando com Concurrency em Programas Solana

A concorrência é um aspecto crucial ao projetar e desenvolver aplicações na blockchain Solana. Devido ao design único da arquitetura da Solana, os desenvolvedores precisam entender como lidar com operações concorrentes de maneira adequada para garantir a integridade e o desempenho de seus programas. Nesta aula, iremos explorar a concorrência em programas Solana e apresentar algumas melhores práticas com exemplos de código.

Entendendo a Concorrência na Solana

A Solana opera em uma arquitetura única que permite a execução paralela de transações. No entanto, isso significa que os desenvolvedores precisam gerenciar o acesso concorrente a recursos compartilhados dentro de seus programas. Se múltiplas transações modificarem o mesmo estado de conta simultaneamente, isso pode levar a "corridas de dados" ou inconsistências.

Conceitos Chave

  • Contas: Na Solana, todos os dados são armazenados em contas. Cada transação afeta uma ou várias contas.
  • Bloqueios: A Solana utiliza um sistema de bloqueio em nível de conta para evitar condições de corrida.
  • Transações: Uma transação na Solana pode consistir em múltiplas instruções, e a execução ocorre de maneira atômica entre as contas envolvidas.

Melhores Práticas para Lidar com Concurrência

  1. Use a Propriedade das Contas de Forma Inteligente: Quando múltiplas instruções em uma transação acessam a mesma conta, certifique-se de que elas sejam de propriedade do mesmo programa. Isso ajuda a evitar problemas decorrentes de modificações concorrentes.

  2. Separe Estado Mutável e Dados Imutáveis: Sempre que possível, mantenha o estado mutável separado dos dados imutáveis. Dados imutáveis podem ser compartilhados livremente, enquanto o estado mutável deve ser acessado com cautela.

  3. Atualizações em Lote nas Transações: Agrupe atualizações relacionadas às contas dentro de uma única transação para reduzir o risco de modificações concorrentes.

  4. Evite Dependências Cíclicas: Esteja atento a como as diferentes contas interagem umas com as outras. Dependências cíclicas podem levar a deadlocks se não forem gerenciadas corretamente.

Exemplo: Uma Transferência Simples de Tokens

Vamos criar um programa simples de transferência de tokens que ilustra como lidar com concorrência de forma eficaz. O programa permitirá a transferência de tokens entre duas contas, garantindo um manuseio adequado do acesso concorrente.

Estrutura do Programa

use anchor_lang::prelude::*;

declare_id!("SeuIdDoProgramaAqui");

#[program]
pub mod transferencia_tokens {
    use super::*;

    pub fn transferir(ctx: Context<Transferir>, quantidade: u64) -> Result<()> {
        let remetente = &mut ctx.accounts.remetente;
        let receptor = &mut ctx.accounts.receptor;

        // Garantir que o remetente tem tokens suficientes
        if remetente.saldo < quantidade {
            return Err(ErrorCode::FundosInsuficientes.into());
        }

        // Realizar a transferência
        remetente.saldo -= quantidade;
        receptor.saldo += quantidade;

        Ok(())
    }
}

#[derive(Accounts)]
pub struct Transferir<'info> {
    #[account(mut)]
    pub remetente: Account<'info, ContaToken>,
    #[account(mut)]
    pub receptor: Account<'info, ContaToken>,
}

#[account]
pub struct ContaToken {
    pub saldo: u64,
}

#[error_code]
pub enum ErrorCode {
    #[msg("Fundos insuficientes na conta do remetente.")]
    FundosInsuficientes,
}

Nesse exemplo:

  • Contas: Definimos ContaToken para acompanhar os saldos para a transferência.
  • Função de Transferência: A função transferir verifica se o remetente possui saldo suficiente antes de realizar a transferência.
  • Manuseio de Concorrência: O uso de #[account(mut)] garante que tanto o remetente quanto o receptor sejam contas mutáveis. O runtime da Solana gerenciará a concorrência bloqueando essas contas durante a transação.

Testando o Programa

Você deve implementar testes para lidar com atualizações concorrentes. Aqui está como você poderia configurar testes de transferência concorrente.

#[cfg(test)]
mod tests {
    use super::*;
    use anchor_lang::prelude::*;
    use anchor_lang::solana_program::program_test::*;
    use solana_program_test::*;
    use std::sync::Arc;

    #[tokio::test]
    async fn test_transferencias_concorrentes() {
        let teste_programa = ProgramTest::new(
            "transferencia_tokens",
            id(),
            processor!(process_instruction)
        );

        let (mut client_banco, pagador, recent_blockhash) = teste_programa.start().await;

        // Crie contas de tokens iniciais para remetente e receptor aqui (omitido para brevidade)

        let pubkey_remetente = Keypair::new().pubkey();
        let pubkey_receptor = Keypair::new().pubkey();

        // Função para executar a transferência
        async fn executar_transferencia(
            client: &mut BanksClient,
            remetente: Pubkey,
            receptor: Pubkey,
            quantidade: u64,
        ) {
            let instruction = transferir(
                ... // Defina sua instrução aqui
            );
            // Execute a transação
            client.process_transaction(&transaction).await.unwrap();
        }

        let quantidade_transferencia = 10;

        // Inicie duas tarefas de transferência concorrentes
        let client_clone1 = Arc::new(client_banco.clone());
        let client_clone2 = Arc::clone(&client_clone1);

        let handle1 = tokio::spawn(async move {
            executar_transferencia(&mut *client_clone1.lock().unwrap(), pubkey_remetente, pubkey_receptor, quantidade_transferencia).await;
        });

        let handle2 = tokio::spawn(async move {
            executar_transferencia(&mut *client_clone2.lock().unwrap(), pubkey_remetente, pubkey_receptor, quantidade_transferencia).await;
        });

        // Aguarde ambas as tarefas completarem
        let _ = tokio::try_join!(handle1, handle2).unwrap();
    }
}

Neste setup de teste, demonstramos como lidar com transações concorrentes usando funções assíncronas. A função executar_transferencia executa duas transferências concorrentes para simular potenciais condições de corrida.

Conclusão

Lidar com concorrência de maneira eficaz em programas da Solana é fundamental para manter a integridade dos dados e o desempenho da aplicação. Ao entender a arquitetura da Solana e empregar melhores práticas, como um gerenciamento adequado do acesso às contas e uma estrutura cuidadosa das transações, os desenvolvedores podem criar aplicações descentralizadas robustas.

Nesta aula, cobrimos pontos chave sobre concorrência na Solana, juntamente com um exemplo tangível de um programa de transferência de tokens. Consolidar esses conceitos ajudará você a escrever programas Solana mais seguros e eficientes no futuro.

Did you like this article? Rate it from 1 to 5:

Thank you for voting!