Возврат данных из программ BPF

Проблема

В языке Solidity разрешено возвращать любое количество значений из функции, например, может быть возвращена строка переменной длины:

function foo1() public returns (string) {
    return "Hello, world!\n";
}

Допускаются также множественные значения, массивы и структуры.

struct S {
    int f1;
    bool f2
};

function foo2() public returns (string, int[], S) {
    return (a, b, c);
}

Все возвращаемые значения кодируются eth abi в массив байтов переменной длины.

В Эфириуме тоже могут быть возвращены ошибки:

function withdraw() public {
    require(msg.sender == owner, "Permission denied");
}

function failure() public {
    revert("I afraid I can't do that dave");
}

Эти ошибки помогают разработчику отладить любую проблему, с которой они столкнулись, а также могут быть обнаружены в блоке try .. catch Solidity. Вне блока try .. catch любой из них приведет к сбою транзакции или rpc.

Существующее решение

Существующее решение, которое использует Solang, записывает возвращаемые данные в данные учетной записи вызываемого абонента. Учетная запись вызывающего абонента не может быть использована, так как вызываемый объект может быть не той же программой BPF, поэтому у него не будет разрешения на запись данных учетной записи вызываемого абонента.

Другим решением было бы иметь единую учетную запись данных возврата, которая передается через CPI. Опять же, это не работает для CPI, поскольку вызываемый объект может не иметь разрешения на запись в него.

Проблема с этим решением заключается в следующем:

Требования к решению

Он должен работать для:

Обзор других сетей

Эфириум (EVM)

Код операции RETURN позволяет контракту установить буфер в качестве возвращаемых данных. Этот код операции принимает указатель на память и размер. Опкод REVERT работает аналогично, но сигнализирует о том, что вызов не удался, и все изменения данных учетной записи должны быть отменены.

Для CPI вызывающая сторона может получить возвращенные данные вызываемого объекта с помощью кода операции RETURNDATASIZE, который возвращает длину, и кода операции RETURNDATACOPY, который принимает указатель места назначения в памяти, смещение в возвращаемых данных и аргумент длины.

Ethereum хранит возвращаемые данные в блоках.

Parity Substrate

Возвращаемые данные можно установить с помощью системного вызова seal_return (флаги u32, указатель u32, размер u32).

CPI: системный вызов seal_call() принимает указатель на буфер и указатель на размер буфера, куда возвращаются данные.

Parity Substrate не записывает возвращаемые данные в блоки.

Отклонено Решение

Для решения этой проблемы была предложена концепция эфемерных счетов. Это определенно сработает для случая CPI, но не сработает для случая RPC или транзакции.

Предложенное решение

Вызываемая сторона может установить возвращаемые данные с помощью нового системного вызова sol_set_return_data(buf: *const u8, length: u64)`. Для возвращаемых данных существует ограничение в 1024 байта. Эту функцию можно вызывать несколько раз, и просто перезапишет то, что было написано при последнем вызове.

Возвращаемые данные можно получить с помощью sol_get_return_data(buf: *mut u8, length: u64, program_id: *mut Pubkey) -> u64. Эта функция копирует буфер возврата и идентификатор программы, который устанавливает возвращаемые данные, и возвращает длину возвращаемых данных или 0, если возвращаемые данные не установлены. В этом случае program_id не устанавливается.

Когда инструкция вызывает sol_invoke(), возвращаемые данные вызываемого объекта копируются в возвращаемые данные текущей инструкции. Это означает, что любые возвращаемые данные автоматически передаются вверх по стеку вызовов вызываемому объекту текущей инструкции (или вызову RPC).

Обратите внимание, что sol_invoke() очищает возвращаемые данные перед вызовом вызываемого, так что любые возвращаемые данные из предыдущего вызова не используются повторно, если вызываемый не может установить возвращаемые данные. Например:

Другой сценарий для рассмотрения:

Затраты на вычисления рассчитываются для получения и установки возвращаемых данных с помощью системных вызовов.

Для обычного RPC или транзакции возвращаемые данные имеют кодировку base64 и хранятся вместе со строками sol_log в [стабильном журнале](https://github.com/solana-labs/solana/blob/95292841947763bdd47ef116b40fc34d0585bca8/sdk/src/process_instruction .rs#L275-L281).

Примечание о возврате ошибок

Solidity на Ethereum позволяет контракту возвращать ошибку в возвращаемых данных. В этом случае все изменения данных учетной записи для учетной записи должны быть отменены. В Solana любой ненулевой код выхода для программы BPF означает сбой всей транзакции. Мы не хотим поддерживать возврат ошибки, возвращая успех, а затем возвращая ошибку в возвращаемых данных. Это означало бы, что нам придется поддерживать отмену изменений данных учетной записи; это слишком дорого как на стороне VM, так и на стороне контракта BPF.

Об ошибках будет сообщаться через sol_log.