Транзакции
Выполнение программы начинается с отправки транзакции в кластер. Среда выполнения Solana выполнит программу для обработки каждой из инструкций, содержащихся в транзакции, по порядку и атомарно.
Анатомия транзакции
В этом разделе рассматривается двоичный формат транзакции.
Формат транзакции
Транзакция содержит compact-array подписей, за которыми следует сообщение. Каждый элемент в массиве подписей представляет собой цифровую подпись данного сообщения. Среда выполнения Solana проверяет, соответствует ли количество подписей числу в первых 8 битах заголовка сообщения. Он также проверяет, что каждая подпись была подписана закрытым ключом, соответствующим открытому ключу с тем же индексом в массиве адресов учетных записей сообщения.
Формат подписи
Каждая цифровая подпись имеет двоичный формат ed25519 и занимает 64 байта.
Формат сообщения
Сообщение содержит заголовок, за которым следует компактный массив адресов аккаунтов, за которым следует недавний блок-хэш, за которым следует компактный массив инструкций.
Формат заголовка сообщения
Заголовок сообщения содержит три 8-битных значения без знака. Первое значение — это количество необходимых подписей в содержащей транзакции. Второе значение — это количество тех соответствующих адресов учетных записей, которые доступны только для чтения. Третье значение в заголовке сообщения — это количество адресов учетных записей только для чтения, не требующих подписи.
Формат адресов счетов
Адреса, требующие подписи, появляются в начале массива адресов учетных записей, причем адреса, запрашивающие доступ для записи, идут первыми, а учетные записи только для чтения следуют за ними. Адреса, не требующие подписи, следуют за адресами, для которых она требуется, опять же сначала с учетными записями для чтения и записи, а затем с учетными записями только для чтения.
Формат блокчейна
Блокхэш содержит 32-байтовый хэш SHA-256. Он используется для указания, когда клиент в последний раз просматривал реестр. Валидаторы будут отклонять транзакции, когда хеш-блок будет слишком старым.
Формат инструкции
Инструкция содержит индекс идентификатора программы, за которым следует компактный массив индексов адресов учетных записей, за которым следует компактный массив непрозрачных 8-битных данных. Индекс идентификатора программы используется для идентификации сетевой программы, которая может интерпретировать непрозрачные данные. Индекс идентификатора программы представляет собой 8-битный индекс без знака для адреса учетной записи в массиве адресов учетной записи сообщения. Каждый индекс адреса учетной записи представляет собой 8-битный индекс без знака в том же массиве.
Формат компактного массива
Компактный массив сериализуется как длина массива, за которой следует каждый элемент массива. Длина массива — это специальная многобайтовая кодировка, называемая compact-u16.
Компактный формат u16
Compact-u16 — это многобайтовая кодировка из 16 бит. Первый байт содержит младшие 7 бит значения в своих младших 7 битах. Если значение выше 0x7f, устанавливается старший бит, а следующие 7 бит значения помещаются в младшие 7 бит второго байта. Если значение выше 0x3fff, устанавливается старший бит, а оставшиеся 2 бита значения помещаются в младшие 2 бита третьего байта.
Формат адреса учетной записи
Адрес учетной записи — это 32 байта произвольных данных. Когда для адреса требуется цифровая подпись, среда выполнения интерпретирует ее как открытый ключ пары ключей ed25519.
Инструкции
Каждая инструкция определяет одну программу, подмножество учетных записей транзакции, которые должны быть переданы программе, и массив байтов данных, который передается программе. Программа интерпретирует массив данных и оперирует указанными в инструкции счетами. Программа может вернуться успешно или с кодом ошибки. Возврат ошибки приводит к немедленному сбою всей транзакции.
Программы обычно предоставляют вспомогательные функции для создания инструкций, которые они поддерживают. Например, системная программа предоставляет следующий помощник Rust для создания [SystemInstruction::CreateAccount
](https://github.com/solana-labs/solana/blob/6606590b8132e56dab9e60b3f7d20ba7412a736c/sdk/program/src/system_instruction.rs #L63) инструкция:
pub fn create_account(
from_pubkey: &Pubkey,
to_pubkey: &Pubkey,
lamports: u64,
space: u64,
owner: &Pubkey,
) -> Instruction {
let account_metas = vec![
AccountMeta::new(*from_pubkey, true),
AccountMeta::new(*to_pubkey, true),
];
Instruction::new_with_bincode(
system_program::id(),
&SystemInstruction::CreateAccount {
lamports,
space,
owner: *owner,
},
account_metas,
)
}
Которые можно найти здесь:
https://github.com/solana-labs/solana/blob/6606590b8132e56dab9e60b3f7d20ba7412a736c/sdk/program/src/system_instruction.rs#L220
Идентификатор программы
[идентификатор программы] инструкции (terminology.md#program-id) указывает, какая программа будет обрабатывать эту инструкцию. Владелец учетной записи программы указывает, какой загрузчик следует использовать для загрузки и выполнения программы, а данные содержат информацию о том, как среда выполнения должна выполнять программу.
В случае ончейн-программ BPF владельцем является загрузчик BPF, а данные учетной записи содержат байт-код BPF. Учетные записи программ постоянно помечаются загрузчиком как исполняемые после их успешного развертывания. Среда выполнения будет отклонять транзакции, в которых указаны неисполняемые программы.
В отличие от сетевых программ, собственные программы обрабатываются по-другому, поскольку они встроены непосредственно в среду выполнения Solana.
Счета
Учетные записи, на которые ссылается инструкция, представляют состояние в цепочке и служат как входами, так и выходами программы. Более подробную информацию об Аккаунтах можно найти в разделе Аккаунты.
Данные инструкции
Каждая инструкция содержит массив байтов общего назначения, который передается программе вместе с учетными записями. Содержимое данных инструкций зависит от программы и обычно используется для передачи того, какие операции должна выполнять программа, и любой дополнительной информации, которая может потребоваться этим операциям помимо того, что содержится в учетных записях.
Программы могут свободно указывать, как информация кодируется в массив байтов данных инструкции. Выбор способа кодирования данных должен учитывать накладные расходы на декодирование, поскольку этот шаг выполняется программой в цепочке. Было замечено, что некоторые распространенные кодировки (например, бинкод Rust) очень неэффективны.
Программа токена библиотеки программ Solana дает один пример того, как данные инструкции могут быть эффективно закодированы, но обратите внимание, что этот метод поддерживает только типы фиксированного размера. Токен использует трейт Pack для кодирования/декодирования данных инструкций как для инструкций токена, так и для самого токена. состояния аккаунта.
Несколько инструкций в одной транзакции
Транзакция может содержать инструкции в любом порядке. Это означает, что злоумышленник может создавать транзакции, которые могут представлять инструкции в порядке, от которого программа не защищена. Программы должны быть усилены, чтобы правильно и безопасно обрабатывать любую возможную последовательность инструкций.
Одним из не столь очевидных примеров является деинициализация аккаунта. Некоторые программы могут попытаться деинициализировать учетную запись, установив для ее ламповых портов нулевое значение, предполагая, что среда выполнения удалит учетную запись. Это допущение может быть действительным между транзакциями, но не между инструкциями или межпрограммными вызовами. Чтобы защититься от этого, программа также должна явно обнулить данные учетной записи.
Примером того, где это может быть проблемой, является то, что программа токенов после передачи токена из учетной записи устанавливает нулевое значение порта учетной записи, предполагая, что оно будет удалено во время выполнения. Если программа не обнуляет данные учетной записи, злоумышленник может сопроводить эту инструкцию другой, которая передает токены во второй раз.
Подписи
Каждая транзакция явно перечисляет все открытые ключи учетной записи, на которые ссылаются инструкции транзакции. Каждое подмножество этих открытых ключей сопровождается подписью транзакции. Эти подписи сигнализируют ончейн-программам, что владелец аккаунта авторизовал транзакцию. Обычно программа использует авторизацию для разрешения списания средств со счета или изменения его данных. Дополнительную информацию о том, как авторизация передается программе, можно найти в разделе Учетные записи
Недавний блокхэш
Транзакция включает недавний хеш-блок, чтобы предотвратить дублирование и увеличить время жизни транзакций. Любая транзакция, полностью идентичная предыдущей, отклоняется, поэтому добавление нового хэша блока позволяет нескольким транзакциям повторять одно и то же действие. Транзакции также имеют время жизни, которое определяется хэшем блока, поскольку любая транзакция со слишком старым хэшем блока будет отклонена.