Для разработчиков / JSON-RPC

Отличия от Ethereum

Pickle сделан в форме Ethereum, но не эквивалентен ему. Это полный список мест, где стандартный клиент получит ответ, которого не ждал, в порядке того, сколько времени стоит выяснить каждое трудным путём.

Причины revert не декодируются

error.data никогда не заполняется

A revert arrives as message text - "execution reverted: 0x<returndata>" - and error.data is absent.

Libraries decode custom errors and revert strings out of error.data, so on this chain they cannot. ethers and viem will both surface the raw message instead of a decoded error. If you need the reason, parse the hex out of the message yourself and decode it against your own ABI.

Если вам нужна причина, возвращённые данные лежат в тексте сообщения, и вы можете декодировать их сами:

javascript
try {
  await client.readContract({ /* … */ });
} catch (err) {
  // "execution reverted: 0x08c379a0…"
  const hex = String(err.message).match(/0x[0-9a-fA-F]+/)?.[0];
  // Decode that hex against your own ABI's errors, or against Error(string).
}

Исторического состояния нет

eth_getBalance, eth_getTransactionCount, eth_getCode и eth_getStorageAt разбирают адрес и затем читают последний опубликованный снимок. Параметр блока принимается и молча игнорируется - запрос баланса на блоке 100 не выдаёт ошибку, он отвечает балансом на сейчас.

eth_call и eth_estimateGas делают то же самое и вдобавок игнорируют переданные им gas, gasPrice и nonce: симуляция всегда идёт при лимите газа блока 30 000 000 против последнего состояния.

Это отказывает молча, потому оно и на втором месте

Любой архивный шаблон - чтение исторического баланса, повторение состояния на прошлом блоке, вычисление снимка - возвращает здесь уверенные, правдоподобные, неверные ответы, а не ошибку. Для всего исторического читайте обозреватель.

Теги блоков схлопываются

latest, pending, safe и finalized разрешаются в одно и то же: в сохранённую вершину. А earliest разрешается в самый ранний сохранённый блок, а не в блок 0, - так что он движется вперёд, пока окно скользит.

Объекты EIP-1898 принимаются: { blockNumber } или { blockHash }, причём хеш разрешается через индекс и отвечает null, когда блок больше не хранится. Всё остальное - -32602 "block tag".

Клиент, различающий safe и finalized, чтобы решить, когда доверять результату, не получает от этой сети никакого сигнала. Финальность здесь - это расчёт на L1, и через эти теги она не видна вовсе.

Корни и фильтры нулевые

И у блоков, и у квитанций stateRoot, transactionsRoot и receiptsRoot нулевые, а logsBloom - это 512 нулевых символов.

Два следствия. Клиент, который проверяет корни, не сможет здесь проверить ничего. И клиент, который предварительно фильтрует по bloom, прежде чем забирать логи, не найдёт совпадений - фильтруйте через eth_getLogs, который читает настоящий индекс.

В остальном заголовки правильной формы Cancun, включая mixHash, withdrawalsRoot, blobGasUsed, excessBlobGas и parentBeaconBlockRoot. mixHash здесь несущий, а не декоративный: без него любой клиент на revm, включая forge script, отвергает заголовок с prevrandao not set ещё до симуляции.

Поля квитанции и транзакции

  • type всегда "0x0", какой бы конверт вы на самом деле ни отправили, и транзакция сообщает единственный плоский gasPrice - ни maxFeePerGas, ни maxPriorityFeePerGas, ни accessList обратно не приходят. Типизированные транзакции принимаются на входе; просто форма ответа тип не отражает.
  • cumulativeGasUsed - это собственный gasUsed той же транзакции, а не нарастающий итог по блоку. Суммирование его по блоку ничего не задваивает, но и ничего вам не говорит.
  • blockHash и blockNumber равны null между исполнением и запечатыванием EVM-блока. В этом окне квитанция уже существует, так что проверка "квитанция не null" - не то же самое, что "включено в блок".
  • Транзакция несёт нестандартные добавки - raw, miniBlockNumber, miniBlockHash, - а квитанция несёт две последние. Они полезны, и они непереносимы.
  • eth_getBlockByNumber с fullTransactions: true может вернуть смешанный массив: объекты для транзакций, чьи квитанции ещё проиндексированы, и голые строки хешей для уже вытесненных.
  • eth_getBlockReceipts молча пропускает вытесненные квитанции, так что массив может быть короче списка транзакций блока.

logIndex нумеруется в пределах транзакции

logIndex нумеруется по логам собственной квитанции, а не по блоку. У двух логов в одном блоке logIndex может быть 0 у обоих.

Если вы ключуете хранилище событий парой (blockNumber, logIndex) - обычный составной первичный ключ индексатора, - вы получите коллизию. Добавляйте transactionHash.

Pending - это не мемпул

Нет публичного мемпула, в котором можно было бы находиться в состоянии pending. Хеш доходит до eth_newPendingTransactionFilter и до подписки newPendingTransactions только после того, как транзакция исполнена и проиндексирована.

То есть это поток уже исполненных транзакций под вводящим в заблуждение именем. Следить за входящими транзакциями до того, как они приземлятся, нельзя, и опережать через этот интерфейс нечего.

Методы, отвечающие константами

Два метода выглядят работающими и возвращают значения, которые не являются измерениями. Оба помечены в справочнике, и оба опаснее метода, который выдаёт ошибку:

  • eth_feeHistory читает только blockCount, полностью игнорирует newestBlock и rewardPercentiles и возвращает baseFeePerGas как повторённое 0x1 и gasUsedRatio как повторённое 0.0.
  • eth_createAccessList возвращает { accessList: [], gasUsed: "0x0" }, не глядя ни на параметры, ни на состояние.

Третья группа отвечает честно, но пусто, потому что понятия неприменимы: eth_accounts всегда пуст, eth_mining - false, eth_hashrate - ноль, а четыре метода про дядей возвращают ноль или null, не проверяя свои параметры.

Пространства имён, которых нет

rpc_modules объявляет eth, net, web3 и pickle. Отсутствуют и не реализованы: debug, trace, txpool и admin. Значит, debug_traceTransaction здесь нет - за трассами вызовов идите в обозреватель, у него они есть.

Методы подписи зарегистрированы и отказывают намеренно: eth_sendTransaction, eth_sign, eth_signTransaction, personal_sign и eth_coinbase отвечают -32601. Узел не хранит пользовательских ключей - подписывайте локально и пользуйтесь eth_sendRawTransaction.