Pontos Chave de Serialização
Solana armazena o estado da aplicação como bytes brutos em contas de propriedade do programa. A serialização é o contrato que transforma esses bytes em campos tipados e vice-versa: qual layout, qual o tamanho, como você cresce e como você evolui sem inutilizar dados ativos.
Esta página é o mapa conceitual para Serialização de Dados de Conta - por que você serializa, structs Borsh, discriminadores, espaço e aluguel, realloc, versionamento e migração, e trade-offs de zero-copy no Agave 4.1.1.
Resumo
- Os
dadosda conta de propriedade do programa são um buffer opaco; seu layout de serialização é o esquema on-chain com o qual cada inicialização, atualização, decodificação do cliente e upgrade deve concordar. - Por que Importa: Tamanho incorreto, discriminador ausente ou uma mudança silenciosa de layout criam falhas, desperdiçam aluguel, consomem computação ou interpretam mal fundos e autoridades. As escolhas de serialização são design de banco de dados, não uma preferência de estilo.
- Conceitos Chave: buffer de conta opaco, ordem de campo Borsh, discriminador de conta, ESPAÇO / LEN, mínimo isento de aluguel, realloc, byte de versão / ix de migração, zero-copy (Pod / bytemuck).
- Quando Usar Este Modelo: Definir novos tipos de conta, dimensionar aluguel, escolher Borsh vs zero-copy, planejar upgrades de programa ou depurar falhas de
InvalidAccountDatae inicialização. - Limitações / Trade-offs: Você paga por cada byte em aluguel e muitas vezes em CU; campos de comprimento variável precisam de limites; zero-copy precisa de layouts fixos e alinhados; migrações nunca reescrevem todas as contas atomicamente.
- Tópicos Relacionados: Noções Básicas de Serialização, Discriminadores de Conta, Structs Borsh, Cálculo de Espaço, realloc e Contas em Crescimento, Versionamento de Dados e Migração.
Fundamentos
O campo data de uma conta é uma fatia de bytes que o runtime não interpreta. A propriedade decide quem pode escrever; a serialização decide o que os bytes significam.
Sem um layout compartilhado, o programa e seus clientes discordam sobre os limites dos campos. Um u64 pode se tornar metade de um Pubkey, uma verificação de autoridade pode ler o deslocamento errado e um comprimento "válido" ainda pode conter o tipo errado.
Serialização significa: codificar estado estruturado no buffer na escrita; decodificar apenas após verificações de comprimento e tipo na leitura. Os dados da instrução também são bytes, mas efêmeros por transação. Os dados da conta persistem entre slots e upgrades, portanto, precisam de constantes fixas, discriminadores e planos de migração.
Por que serializar quando você poderia indexar manualmente deslocamentos brutos? Codecs explícitos (Borsh, wrappers Anchor, casts de zero-copy) documentam a ordem e o tamanho, capturam erros de comprimento mais cedo e mantêm clientes TypeScript/Rust alinhados via IDL ou constantes compartilhadas. Empacotar manualmente funciona para cabeçalhos minúsculos; campos aninhados, opções ou programas multitype precisam de um esquema nomeado.
Borsh é o cavalo de batalha padrão para corpos de conta e payloads de instrução. É determinístico e compactado: a ordem de declaração do campo é a ordem de transmissão, inteiros são little-endian, bool é um byte, Pubkey é 32 bytes, e o stream não tem preenchimento de alinhamento Rust. Essa previsibilidade torna a matemática de aluguel e a decodificação entre linguagens práticas.
Dados da conta (conta Borsh típica):
[ discriminador de 8 bytes ][ campo0 ][ campo1 ][ ... ][ versão opcional / cauda ]
tag de tipo corpo compactado na ordem de declaraçãoDiscriminadores ficam na frente de programas multitype. Antes de analisar o corpo, compare o prefixo com a constante esperada para que um Vault nunca seja lido como um Usuário. Anchor 0.32.1 usa oito bytes de SHA256("account:TypeName"). Programas nativos escolhem constantes únicas e as publicam para os clientes. Sem uma tag, qualquer conta longa o suficiente que seu programa possua pode ser convertida para a struct errada (confusão de tipo).
Espaço é decidido no momento da alocação. A criação financia um mínimo isento de aluguel para data_len. A fórmula usual:
ESPAÇO = tamanho_discriminador + tamanho_corpo
+ limites para peças variáveis (Vec: 4 + max_n * elem_size; Option: 1 + inner)Documente LEN / ESPAÇO ao lado da struct. O InitSpace / space = do Anchor deve corresponder. Clientes e criações de CPI usam o mesmo número para que o aluguel e a alocação não se desviem.
Mecânicas e Interações
Caminho de leitura/escrita Borsh
Na inicialização: aloque ESPAÇO, atribua seu programa como proprietário, financie o aluguel, escreva o discriminador e, em seguida, serialize o corpo inicial. Na atualização: pegue os dados emprestados, verifique o comprimento e o discriminador, desserializar, aplique regras, serialize de volta (ou em um buffer realocado).
// Corpo de conta Borsh conceitual (disc geralmente escrito separadamente)
#[derive(BorshSerialize, BorshDeserialize)]
pub struct User {
pub authority: Pubkey, // 32
pub points: u64, // 8
pub bump: u8, // 1
}
impl User {
pub const LEN: usize = 32 + 8 + 1;
}
pub const USER_SPACE: usize = 8 + User::LEN; // disc + corpoTestes de host que o comprimento serializado é igual a LEN capturam bugs de off-by-one antes da mainnet. Mapeie falhas de desserialização para InvalidAccountData (ou erros de conta Anchor), não para panics.
Verificação do discriminador antes da análise do corpo
if data.len() < 8 || data[..8] != USER_DISC {
return Err(ProgramError::InvalidAccountData);
}
let user = User::deserialize(&mut &data[8..])?;Escreva o discriminador apenas na inicialização legítima. Feche zerando ou recuperando para que tags obsoletas não permaneçam. Nunca altere o discriminador de um tipo ativo; isso órfão todas as contas desse tipo. Evolua com um campo de versão após o disc, não um novo disc para o mesmo tipo.
Espaço, aluguel e clientes
let lamports = Rent::get()?.minimum_balance(USER_SPACE);Contas maiores bloqueiam mais SOL como depósito isento de aluguel. Campos variáveis sem limite tornam o aluguel ilimitado e convidam a ataques de griefing; prefira arrays fixos ou max_entries. Off-chain, os codecs @solana/kit 7.0.0 e clientes Anchor/Codama devem decodificar o mesmo layout; trate IDL ou constantes compartilhadas como fonte da verdade.
realloc quando o buffer precisa mudar
Esquemas e coleções crescem. realloc muda data_len sob controle do proprietário. O crescimento precisa de um pagador para a diferença de aluguel; a diminuição pode retornar lamports em excesso quando feita corretamente. Após o crescimento, zere novos bytes (ou escreva padrões) para que a memória obsoleta não se torne novos campos. Limite o comprimento máximo contra griefing de aluguel/CU. Anchor 0.32.1 usa restrições de realloc; código nativo usa o caminho de realloc do programa.
tamanho_antigo ----------------------> novo_tamanho
[ disc | campos conhecidos | .... ]
realloc
[ disc | campos conhecidos | ZEROS novos e inicialização ]
^
financia a diferença de aluguel do pagadorVersionamento e migração
O upgrade do bytecode não reescreve contas de usuário. Se V2 precisar de outro u64, as contas V1 ainda terão o comprimento antigo. Padrões que funcionam:
| Estratégia | Mecanismo | Quando se encaixa |
|---|---|---|
| Campos anexáveis | Novos campos apenas no final; contas antigas permanecem curtas até serem migradas | Pequenas mudanças aditivas |
| Byte de versão / enum | Campo de versão do corpo seleciona o parser | Layouts concorrentes |
| ix de migração dedicado | Lê V1, escreve V2, opcionalmente realoca | Upgrades controlados |
| Migração preguiçosa | Migra no próximo toque do usuário | Distribui o custo de CU e aluguel |
| Novo tipo de conta | Novo disc + fluxo de cópia | Redesigns disruptivos |
Leitura dupla V1 e V2 durante a transição. Não presuma que todas as contas foram atualizadas após a implantação. Restrinja a migração (administrador ou autoridade da conta) para que estranhos não possam fazer ataques de griefing nos caminhos de aluguel. Não remova ou reordene campos no local; é assim que você perde fundos ou autoridades.
Caminho de zero-copy (contraste)
Para layouts fixos grandes (livros de ofertas, arrays de ticks, bitmaps), a análise/serialização Borsh completa a cada instrução consome CU com o tamanho. Zero-copy mapeia o buffer para uma struct #[repr(C)] Pod + Zeroable (bytemuck ou Anchor AccountLoader) e muta no local. Requisitos: tamanho fixo, sem String/Vec na visualização Pod, disciplina de alinhamento e geralmente size_of::<T>() como ESPAÇO. O discriminador ainda importa (geralmente um u64 no deslocamento 0). A capacidade é planejada antecipadamente; crescer zero-copy puro significa uma migração deliberada, não anexos de campo casuais.
Considerações Avançadas e Aplicações
Trate o layout como uma API pública. Revisores devem responder: unicidade do disc, fórmula do ESPAÇO, campos de autoridade, tamanhos máximos de variáveis e a história de migração se o programa permanecer atualizável.
| Abordagem | Pontos fortes | Custos / restrições | Melhor ajuste |
|---|---|---|---|
| Conta completa Borsh | Campos flexíveis, anexação fácil, versionamento simples | CU escala com o tamanho; reescrita completa ao salvar | Configuração, perfis, estado pequeno/médio |
| Pod de Zero-Copy | Atualizações no local, baixo CU em tabelas fixas grandes | Tamanho fixo, alinhamento, churn de esquema mais difícil | Livros, slabs, bitmaps, estado grande ativo |
| Híbrido (cabeçalho Pod + cauda Borsh) | Campos ativos rápidos + cauda flexível | Duas layouts para documentar e testar | Cabeçalho + blob variável ocasional |
| Alocação superdimensionada agora | Evita realloc precoce | Aluguel extra bloqueado cedo | Teto de crescimento conhecido |
| realloc sob demanda | Pague o aluguel quando necessário | Pagador, inicialização com zero, CU, limites máximos | Upgrades de esquema, coleções em crescimento |
Dados da instrução vs dados da conta: mantenha os esquemas separados. Enums de instrução mudam mais livremente (efêmeros); layouts de conta precisam de migração. Compartilhar uma struct em evolução para ambos convida colisões entre tags de ix e campos de versão de conta.
Programas imutáveis congelam a lógica e a evolução prática do esquema. Se você revogar a autoridade de upgrade, reserve espaço (preenchimento ou ESPAÇO superdimensionado) ou aceite um novo ID de programa mais UX de migração para novos campos.
Clientes e indexadores ficam atrás da cadeia. Prefira campos de versão (e eventos de migração) que eles possam filtrar. Envie decodificadores de dupla versão em clientes @solana/kit 7.0.0 / Codama durante rollouts, correspondendo ao programa.
Foco da auditoria: verificações de disc ausentes, ESPAÇO incorreto, Vec/String sem limite, realloc sem zerar, migração não autenticada e tratar propriedade como tipo (um programa, vários tipos de conta).
Em uma pilha alinhada com Agave 4.1.1 (CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1, LiteSVM 0.6.x, Surfpool 0.12.0): serialize roundtrip no host, fuzz discs ruins e buffers curtos, e migre de snapshots de bytes V1 reais.
Equívocos Comuns
- "Propriedade é suficiente; não preciso de um discriminador." O proprietário prova qual programa pode escrever. Discriminadores provam qual dos seus layouts o buffer representa.
- "Posso reordenar campos Borsh; os nomes correspondem em ambos os lados." Borsh é posicional. Reordenar quebra contas ativas e clientes antigos.
- "ESPAÇO é apenas a soma dos tamanhos dos campos Rust." Inclua disc, tags Option, prefixos de comprimento Vec e máximos de string. Para Pod use
size_of, não uma soma adivinhada. - "realloc inicializa novos bytes com segurança por padrão." Trate a zeragem e a inicialização explícita de campos como obrigatórias após o crescimento.
- "Implantar novo código de programa atualiza todos os layouts de conta." Loaders trocam bytecode; contas de dados permanecem até que seu caminho de migração as reescreva.
- "Zero-copy é sempre mais rápido, então sempre o use." Em contas pequenas, Borsh geralmente é bom; a rigidez do Pod e o custo de migração dominam. Perfilar primeiro.
- "Strings variáveis em contas são aceitáveis se os usuários forem honestos." Tamanhos ilimitados permitem griefing de aluguel e CU. Limite os comprimentos ou mantenha o conteúdo off-chain.
- "Mudar o discriminador marca V2 de forma limpa." Isso órfão V1 sob o tipo antigo. Prefira um campo de versão com um disc estável.
- "Se a desserialização for bem-sucedida, a conta é confiável." Ainda verifique disc, tamanho e autoridades. Ajustar ao layout não é correção semântica.
- "Clientes podem inferir o layout apenas pelo ID do programa." Muitos tipos de conta compartilham um ID de programa. Clientes precisam de disc mais esquema (IDL, Codama ou constantes).
FAQs
Por que os programas Solana serializam os dados da conta?
Os dados da conta são um buffer bruto sem esquema de runtime. A serialização é o layout acordado que transforma bytes em campos para programas e clientes, e de volta para armazenamento durável.
Quando devo escolher Borsh para contas?
Use Borsh por padrão para estados pequenos e médios, configurações, registros de usuários e qualquer coisa que possa ganhar campos posteriormente. É o padrão comum do Anchor e nativo e funciona bem com versionamento.
O que é um discriminador de conta?
Um prefixo fixo curto (comumente 8 bytes) que identifica o tipo de conta antes que o corpo seja analisado. Ele impede a leitura de um Vault como um Usuário quando ambos compartilham um ID de programa. Veja Discriminadores de Conta.
Como o Anchor 0.32.1 escolhe discriminadores?
Os discriminadores de conta são os primeiros 8 bytes de SHA256("account:TypeName"). Os discriminadores de instrução usam um pré-imagem "global:...". Programas nativos podem usar constantes manuais se os clientes concordarem.
Como calculo ESPAÇO e aluguel?
ESPAÇO = disc + corpo (+ limites para campos variáveis), então Rent::get()?.minimum_balance(ESPAÇO) (ou equivalente cliente/CLI). Mantenha uma constante nomeada e teste-a. Veja Cálculo de Espaço.
Qual é o tamanho usual de Pubkey, u64, bool e Option em Borsh?
Pubkey 32 bytes, u64/i64 8 bytes little-endian, bool 1 byte, Option<T> tag de 1 byte mais o tamanho interno quando Some.
Quando preciso de realloc?
Quando a conta precisa crescer para novos campos ou mais entradas, ou encolher após a poda. Financie o aluguel no crescimento, zere ou inicialize novos bytes e imponha um comprimento máximo. Veja realloc e Contas em Crescimento.
Como devo versionar layouts de conta após um upgrade de programa?
Mantenha o discriminador estável, adicione um campo de versão, leia duplamente durante o rollout e envie um caminho de migração explícito ou preguiçoso (realloc se V2 for maior). Veja Versionamento de Dados e Migração.
O que é zero-copy e quando vale a pena?
Zero-copy converte bytes da conta em uma struct Pod fixa para atualizações no local, omitindo a análise/serialização Borsh completa. Use-o quando a profilagem mostrar que a desserialização domina em tabelas fixas grandes; aceite tamanho rígido e migrações mais difíceis.
Posso misturar Borsh e zero-copy?
Sim. Padrão comum: cabeçalho Pod fixo (disc, sequência, contadores quentes) mais uma região Borsh ou bruta limitada. Documente ambas as metades e teste os limites de comprimento.
A serialização custa unidades de computação?
Sim. Copiar e analisar buffers grandes custa CU. Zero-copy ajuda em contas grandes; em contas pequenas, a diferença é muitas vezes mínima em comparação com syscalls e CPIs.
De onde os clientes devem obter o layout?
Da mesma fonte de verdade que o programa: IDL do Anchor, codecs Codama / @solana/kit 7.0.0, ou constantes exportadas de disc e campos. Clientes feitos à mão se desviam.
O que falha se ESPAÇO for incorreto na inicialização?
Muito pequeno: a serialização ou escritas posteriores falham ou truncam. Muito grande: os usuários pagam aluguel em excesso até você encolher. Um off-by-eight geralmente significa um discriminador esquecido.
Todo tipo de conta deve incluir um byte de versão desde o início?
Fortemente recomendado para programas atualizáveis. Um único u8 após o disc é um seguro barato contra uma migração forçada "big-bang".
Como os codecs de instrução se relacionam com os codecs de conta?
Ambos são layouts de bytes, mas os dados da instrução são por transação e mais fáceis de alterar. Layouts de conta sobrevivem a upgrades e precisam de discriminadores, planejamento de espaço e migração.
Relacionados
- Noções Básicas de Serialização - Padrões iniciais Borsh vs zero-copy
- Discriminadores de Conta - tags de tipo e defesas contra confusão
- Structs Borsh - ordem de campo, LEN e design de struct
- Cálculo de Espaço - fórmulas de ESPAÇO e mínimos isentos de aluguel
- realloc e Contas em Crescimento - redimensionar, diferença de aluguel, zerar
- Versionamento de Dados e Migração - campos de versão e caminhos de migração
- Contas Zero-Copy - layouts Pod e trade-offs de CU
- Melhores Práticas de Serialização - checklist de layout, migração e testes
Versões da Pilha: Esta página foi escrita para Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 0.0.7, Surfpool 0.12.0, e LiteSVM 0.6.x.