WEB3DEV

Yan Luiz
Yan Luiz

Posted on • Atualizado em

Gerenciamento de memória de alto desempenho para contratos inteligentes

Esse artigo é uma tradução do Solana Labs feita por Fatima Lima. Você pode encontrar o original aqui.

Como contratos funcionam na blockchain com Prova de Histórico da Solana.

Principais Características

  • Bytecode de alto desempenho projetado para verificação e compilação rápida para código nativo
  • Gerenciamento de memória projetado para análise rápida de dependências de dados
  • Execução de contratos inteligentes paralelizáveis em tantos núcleos quantos o sistema possa fornecer

Nossa abordagem para a execução de contratos inteligentes é baseada em como os sistemas operacionais carregam e executam código dinâmico no kernel.

Na Figura 1. um cliente não confiável, ou Espaço do usuário nos termos dos Sistemas Operacionais, cria um programa na linguagem front-end de sua escolha, (como C/C++/Rust/Lua), e o compila com LLVM para o objeto Solana Bytecode. Este arquivo objeto é um ELF padrão.

O trabalho computacionalmente caro de converter linguagens frontend em programas é feito localmente pelo cliente.

Image description

  • Frontend para LLVM recebe um programa fornecido pelo usuário em uma linguagem de nível superior, como C/C++/Rust/Lua, ou é chamado como uma biblioteca de Javascript ou qualquer outra linguagem
  • A cadeia de ferramentas LLVM faz o trabalho real de converter o programa para um ELF.

A saída é um ELF com um bytecode específico, projetado para verificação rápida e conversão para o conjunto de instruções da máquina local em que a Solana está rodando.

Do lado do kernel, o ELF é verificado, carregado e executado.

  • O verificador examina se o bytecode é válido e seguro para execução e o converte no conjunto de instruções da máquina local.
  • O carregador prepara a memória necessária para carregar o código e marcar o segmento como executável.
  • O tempo de execução na verdade chama o programa com argumentos e gerencia as mudanças na máquina virtual.

Bytecode Solana

A maior parte do foco no desempenho de contratos inteligentes tem sido a mudança para WASM. O bytecode não importa! Embora nosso bytecode seja baseado no Berkley Packet Filter, podemos usar qualquer coisa que seja fácil para JIT para x86 (ou SPIRV!). A razão pela qual estamos baseando nosso bytecode em BPF é porque o que o kernel faz com código não confiável coincide quase exatamente com os requisitos que temos:

  1. Tempo determinístico para executar o código
  2. Bytecode portátil entre conjuntos de instruções de máquina
  3. Acessos de memória verificados
  4. Tempo determinístico e curto para carregar o objeto e verificar o bytecode e JIT para o conjunto de instruções da máquina local

Queremos a maneira mais simples, rápida e fácil de verificar o conjunto de instruções.

O gerenciamento de memória é o que realmente importa

Esta é a parte mais crítica do desempenho do mecanismo. Se todos os contratos programados não tiverem dependências de dados, todos podem ser executados simultaneamente. Se tivermos sucesso, isso significa que o desempenho do mecanismo será dimensionado com o número de núcleos disponíveis para executar os contratos. A produção dobrará a cada 2 anos com a lei de Moores.

O gerenciamento de Memória começa com o próprio ELF. Inicialmente, estamos restringindo os contratos a um único código e segmento de dados somente leitura. Isso significa que é composto de código executável somente leitura e dados somente leitura — nenhuma variável global mutável ou variável estática mutável. Podemos flexibilizar esse requisito no futuro, mas por agora, ele fornece uma solução simples para o requisito 4 acima.

Uma vez que os contratos inteligentes em si não têm estado, como você realmente administra o estado do contrato? Nosso tempo de execução fornecerá uma interface para a criação de estado. Essa interface é chamada por meio de uma transação, como qualquer outro método de contrato.

Image description

Alocar Memória

Se a chave pública page_address não tiver nenhuma região de memória atual associada a ela, a região é alocada e a memória alocada é definida como 0.

Se a chave pública page_address estiver sem atribuição, atribua-a à chave pública contract. O único código que pode modificar a memória que é atribuída a ele é o código que é fornecido pelo contract. Assim, todas as transições de estado nessa memória foram feitas pelo contrato.

Se qualquer dessas condições falham, a chamada falha. Essa interface é chamada com uma simples transação.

Execução

A estrutura de chamada é nossa transação básica. Essa estrutura descreve o contexto das transações:

Image description

As duas partes mais importantes dessa estrutura para o contrato são keys e user_data _. O tempo de execução traduz as _keys para os locais de memória associados a elas com a memória que foi criada com allocate_memory. Os user_data são bits de memória randomizados que foram fornecidos pelo usuário para a call. É assim que os usuários podem adicionar um estado externo dinâmico ao tempo de execução.

Contratos inteligentes também podem examinar o vetor required_sigs para verificar quais das ChavesPúblicas na call têm uma assinatura válida. No momento em que a Call chega no método dos contratos,as assinaturas já foram verificadas.

Regras

Um contrato implementa a seguinte interface:

Image description

Com a seguinte definição para Page:

Image description

Desde que o chamador possa pagar a fee para executar o contrato, ele será chamado. Mas, o tempo de execução somente armazenará a Page modificada se uma destas regras forem atendidas:

  1. Para páginas atribuídas ao contrato, a soma total dos tokens nessas páginas não pode aumentar.
  2. Para páginas não atribuídas ao contrato, o saldo individual de cada página não pode diminuir.
  3. Para páginas não atribuídas ao contrato, a memória não pode mudar.
  4. A soma total dos tokens em todas as páginas não pode mudar.

Assim, um contrato pode mover saldos livremente em qualquer uma das páginas que lhe foram atribuídas. Ele pode modificar a memória nessas páginas livremente. Para páginas atribuídas a outros contratos, o contrato pode adicionar tokens a páginas individuais e pode ler seus vetores de memória.

Sinais

Sinais são chamadas assíncronas, mas garantidas. Eles são criados assim como as páginas alocadas e atribuídas a um contrato. A memória que pertence à página do sinal armazena uma estrutura _Call _ que pode ser examinada pelo tempo de execução.

create_signal(page_address: PublicKey, contract: PublicKey)

Um sinal é uma forma do contrato se agendar ou chamar outros contratos no futuro. Assim que um sinal é definido, o tempo de execução garante sua eventual execução.

Um cliente pode chamar um contrato com qualquer número de sinais, o que pode modificar a memory do sinal e construir qualquer método assíncrono que o contrato precise chamar, incluindo allocate_memory ou create_signal

Notas

  • Um contrato não pode alocar memória de forma síncrona. O cliente primeiro faz uma transação para allocate_memory, e em seguida, chama o método de contrato com a página que possui alguma memória alocada para ela.
  • Um contrato pode agendar um sinal para alocar memória e chamar a si mesmo no futuro.
  • O tempo de execução pode executar, em paralelo, todas as chamadas de contratos não sobrepostos. Nossos resultados preliminares mostram que mais de 500.000 chamadas por segundo são possíveis - com espaço para otimização.

Próximos Passos

Estamos prestes a fundir os detalhes básicos da parte de gerenciamento de memória do mecanismo de contratos inteligentes. O que se seguirá imediatamente é SDK, sinais, JIT, carregador, conjunto de ferramentas.Mas há muito mais! Essas primitivas básicas podem ser usadas para implementar todos os recursos regulares dos sistemas operacionais.

  • allocate_memory pode ser usado para criar stack frames para threads, e segmentos graváveis para processos elfs com estado persistente.
  • Sinais podem ser usados como um trampolim do processo de execução para um serviço OS que faz alocação de memória dinâmica, cria threads adicionais e cria outros sinais.

Essa estrutura pode, eventualmente, oferecer suporte a todos os sistemas operacionais primitivos de que você desfruta em um sistema operacional moderno, como chamadas síncronas simples, sem comprometer o desempenho.

Se implementar um JIT ou um vinculador dinâmico em Rust parece divertido, entre em contato comigo pelo e-mail [email protected].

O que mais aprender sobre Solana?

Confira o que temos em Github e encontre maneiras de se conectar em nossa página da community.
Solana

Agradecimentos a Rob Walker, Greg Fitzgerald, Shivani Bhargava e Michael Vines.

Top comments (0)