feat: the full eth*/op* mirror, generated — and CI to gate it

Grows the typed surface from 8 methods to 60: all 30 eth_* the library
dispatches, plus the 30 op_* mirrors. Everything was already reachable through
rpc(); what was missing was discoverability — `lm methods`, the LIDL contract,
and a caller's type checker.

Generated, not hand-written. Each wrapper is three lines over the same shared
path, so sixty-one of them by hand is sixty-one chances to transpose an
argument; the table is extracted from the library's OWN dispatch table (the
`case $name` in c_frontend.nim), including each parameter's real type — which
is how ethGetBlockByNumber gets a bool, ethFeeHistory a uint64 and a list, and
eth_call an object rather than everything being a string.

They are still committed as literal text: the module's code generator parses
verified_proxy_impl.h as TEXT to build the LIDL contract, so anything hidden
behind a macro would simply not exist to it. `--check` proves the committed
blocks still match, and fails on a one-character edit (verified).

eth_syncing is deliberately excluded from the typed surface — the runtime
issues it as its own keep-alive and a wrapper would invite callers to fight it.
Still reachable through rpc().

CI is new for this repo, which had none: build on Linux and macOS, unit tests
(the check derivation runs the suite as part of building it), and the codegen
drift check. Named explicitly rather than via `nix flake check`, which would
also evaluate the x86_64-windows pseudo-system.

Verified live on sepolia through a real logoscore daemon: ethGasPrice,
ethMaxPriorityFeePerGas, ethBlobBaseFee, ethGetBlockTransactionCountByNumber,
ethGetUncleCountByBlockNumber and ethGetBlockByNumber all answer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dario Gabriel Lipicar
2026-08-26 23:09:04 -03:00
co-authored by Claude Opus 5
parent 3d08debc57
commit 35fc0365ce
9 changed files with 1187 additions and 58 deletions
+63
View File
@@ -0,0 +1,63 @@
name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main
- uses: cachix/cachix-action@v15
with:
name: logos-co
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
# libverifproxy is a ~25 minute source build of the whole nimbus/Nim
# toolchain, so the Logos cache is doing real work here rather than
# shaving seconds.
- name: Build module
run: nix build -L
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main
- uses: cachix/cachix-action@v15
with:
name: logos-co
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
# The check derivation RUNS the suite as part of building it, so a red
# test is a failed build. Named explicitly rather than via `nix flake
# check`, which would also evaluate the x86_64-windows pseudo-system.
- name: Unit tests
run: |
nix build -L ".#checks.$(nix eval --impure --raw --expr builtins.currentSystem).unit-tests"
codegen:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# The 60 typed eth_*/op_* wrappers are generated from the library's own
# dispatch table but COMMITTED as literal text, because the module's code
# generator parses verified_proxy_impl.h as text and cannot see through a
# macro. This proves nobody hand-edited the generated blocks, and that a
# bumped nimbus input has not left them behind.
- name: RPC wrappers match the generator
run: python3 tools/gen_rpc_methods.py --check
+1
View File
@@ -18,6 +18,7 @@ logos_module(
SOURCES
src/verified_proxy_impl.h
src/verified_proxy_impl.cpp
src/verified_proxy_rpc.cpp
src/proxy_config.h
src/proxy_config.cpp
src/proxy_runtime.h
+2 -1
View File
@@ -68,7 +68,8 @@ impossible without it. (Infura notably does not.)
| `supportedNetworks()` | The accepted networks with their chain ids and a default endpoint pair. Build a UI selector from this, not a hardcoded list. |
| `fetchFinalizedRoot(beaconUrl)` | Convenience: asks a beacon node for its current finalized root. **Not** a trust anchor — see below. |
| `rpc(method, params)` | Any method the proxy supports. `params` is a JSON-RPC array. |
| `ethBlockNumber()`, `ethGetBalance(...)`, `ethCall(...)`, … | Typed wrappers over the same path. |
| `ethBlockNumber()`, `ethGetBalance(...)`, `ethCall(...)`, … | 30 typed `eth*` wrappers over the same path. |
| `opBlockNumber()`, `opGetBalance(...)`, … | The 30 `op*` mirrors. Need an OP-Stack network and `opExecutionApiUrls`. |
Events: `proxyStarted`, `proxyStopped`, `proxyStateChanged`.
-37
View File
@@ -284,40 +284,3 @@ StdLogosResult VerifiedProxyImpl::fetchFinalizedRoot(const std::string& beaconUr
StdLogosResult VerifiedProxyImpl::rpc(const std::string& method, const LogosList& params) {
return m_rt->call(method, params.is_null() ? json::array() : params);
}
StdLogosResult VerifiedProxyImpl::ethBlockNumber() {
return m_rt->call("eth_blockNumber", json::array());
}
StdLogosResult VerifiedProxyImpl::ethChainId() {
return m_rt->call("eth_chainId", json::array());
}
StdLogosResult VerifiedProxyImpl::ethGetBalance(const std::string& address,
const std::string& blockTag) {
return m_rt->call("eth_getBalance", json::array({ address, blockTag }));
}
StdLogosResult VerifiedProxyImpl::ethGetCode(const std::string& address,
const std::string& blockTag) {
return m_rt->call("eth_getCode", json::array({ address, blockTag }));
}
StdLogosResult VerifiedProxyImpl::ethGetBlockByNumber(const std::string& blockTag,
bool fullTransactions) {
return m_rt->call("eth_getBlockByNumber", json::array({ blockTag, fullTransactions }));
}
StdLogosResult VerifiedProxyImpl::ethCall(const LogosMap& txArgs,
const std::string& blockTag,
bool optimisticStateFetch) {
// The third positional parameter is upstream's own extension; a standard
// JSON-RPC eth_call has only two.
return m_rt->call("eth_call", json::array({ txArgs, blockTag, optimisticStateFetch }));
}
StdLogosResult VerifiedProxyImpl::ethGetTransactionByBlockNumberAndIndex(
const std::string& blockTag, uint64_t index) {
return m_rt->call("eth_getTransactionByBlockNumberAndIndex",
json::array({ blockTag, index }));
}
+282 -20
View File
@@ -202,35 +202,297 @@ public:
///
/// Returns the decoded result value on success.
StdLogosResult rpc(const std::string& method, const LogosList& params);
/// Current verified head block number.
///
/// Returns a JSON **number**, not a hex quantity string — upstream's
/// encoding is not uniform (`ethChainId` and `ethGasPrice` do return hex
/// strings). Measured against sepolia, not inferred from the JSON-RPC spec.
StdLogosResult ethBlockNumber();
/// The chain id the proxy is configured for, as a hex quantity string.
// BEGIN GENERATED RPC WRAPPERS -- edit tools/gen_rpc_methods.py, not this
/// `eth_chainId`, verified.
StdLogosResult ethChainId();
/// Verified account balance in wei, as a hex quantity string.
/// `blockTag` is "latest", "pending", "earliest", or a hex block number.
/// `eth_blockNumber`, verified.
StdLogosResult ethBlockNumber();
/// `eth_getBalance`, verified.
StdLogosResult ethGetBalance(const std::string& address, const std::string& blockTag);
/// Verified contract code at `address`, as a hex byte string.
/// `eth_getStorageAt`, verified.
StdLogosResult ethGetStorageAt(const std::string& address, const std::string& slot, const std::string& blockTag);
/// `eth_getTransactionCount`, verified.
StdLogosResult ethGetTransactionCount(const std::string& address, const std::string& blockTag);
/// `eth_getCode`, verified.
StdLogosResult ethGetCode(const std::string& address, const std::string& blockTag);
/// Verified block. `fullTransactions` selects full objects over hashes.
/// `eth_getBlockByHash`, verified.
StdLogosResult ethGetBlockByHash(const std::string& blockHash, bool fullTransactions);
/// `eth_getBlockByNumber`, verified.
StdLogosResult ethGetBlockByNumber(const std::string& blockTag, bool fullTransactions);
/// Verified `eth_call`. `txArgs` is a transaction object ({to, data, ...}).
/// `optimisticStateFetch` trades a stricter state check for latency.
StdLogosResult ethCall(const LogosMap& txArgs, const std::string& blockTag,
bool optimisticStateFetch);
/// `eth_getUncleCountByBlockNumber`, verified.
StdLogosResult ethGetUncleCountByBlockNumber(const std::string& blockTag);
/// `eth_getUncleCountByBlockHash`, verified.
StdLogosResult ethGetUncleCountByBlockHash(const std::string& blockHash);
/// `eth_getBlockTransactionCountByNumber`, verified.
StdLogosResult ethGetBlockTransactionCountByNumber(const std::string& blockTag);
/// `eth_getBlockTransactionCountByHash`, verified.
StdLogosResult ethGetBlockTransactionCountByHash(const std::string& blockHash);
/// `eth_getTransactionByBlockNumberAndIndex`, verified.
StdLogosResult ethGetTransactionByBlockNumberAndIndex(const std::string& blockTag, uint64_t index);
/// `eth_getTransactionByBlockHashAndIndex`, verified.
StdLogosResult ethGetTransactionByBlockHashAndIndex(const std::string& blockHash, uint64_t index);
/// `eth_call`, verified.
///
/// `optimisticStateFetch` is upstream's own extension to the standard
/// JSON-RPC signature, not a parameter callers will know from elsewhere.
StdLogosResult ethCall(const LogosMap& txArgs, const std::string& blockTag, bool optimisticStateFetch);
/// `eth_createAccessList`, verified.
///
/// `optimisticStateFetch` is upstream's own extension to the standard
/// JSON-RPC signature, not a parameter callers will know from elsewhere.
StdLogosResult ethCreateAccessList(const LogosMap& txArgs, const std::string& blockTag, bool optimisticStateFetch);
/// `eth_estimateGas`, verified.
///
/// `optimisticStateFetch` is upstream's own extension to the standard
/// JSON-RPC signature, not a parameter callers will know from elsewhere.
StdLogosResult ethEstimateGas(const LogosMap& txArgs, const std::string& blockTag, bool optimisticStateFetch);
/// `eth_getTransactionByHash`, verified.
StdLogosResult ethGetTransactionByHash(const std::string& txHash);
/// `eth_getBlockReceipts`, verified.
StdLogosResult ethGetBlockReceipts(const std::string& blockTag);
/// `eth_getTransactionReceipt`, verified.
StdLogosResult ethGetTransactionReceipt(const std::string& txHash);
/// `eth_getLogs`, verified.
StdLogosResult ethGetLogs(const LogosMap& filterOptions);
/// `eth_newFilter`, verified.
StdLogosResult ethNewFilter(const LogosMap& filterOptions);
/// `eth_uninstallFilter`, verified.
StdLogosResult ethUninstallFilter(const std::string& filterId);
/// `eth_getFilterLogs`, verified.
StdLogosResult ethGetFilterLogs(const std::string& filterId);
/// `eth_getFilterChanges`, verified.
StdLogosResult ethGetFilterChanges(const std::string& filterId);
/// `eth_blobBaseFee`, verified.
StdLogosResult ethBlobBaseFee();
/// `eth_gasPrice`, verified.
StdLogosResult ethGasPrice();
/// `eth_maxPriorityFeePerGas`, verified.
StdLogosResult ethMaxPriorityFeePerGas();
/// `eth_feeHistory`, verified.
StdLogosResult ethFeeHistory(uint64_t blockCount, const std::string& newestBlock, const LogosList& rewardPercentiles);
/// `eth_sendRawTransaction`, verified.
StdLogosResult ethSendRawTransaction(const std::string& txHexBytes);
/// `op_chainId`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opChainId();
/// `op_blockNumber`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opBlockNumber();
/// `op_getBalance`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opGetBalance(const std::string& address, const std::string& blockTag);
/// `op_getStorageAt`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opGetStorageAt(const std::string& address, const std::string& slot, const std::string& blockTag);
/// `op_getTransactionCount`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opGetTransactionCount(const std::string& address, const std::string& blockTag);
/// `op_getCode`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opGetCode(const std::string& address, const std::string& blockTag);
/// `op_getBlockByHash`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opGetBlockByHash(const std::string& blockHash, bool fullTransactions);
/// `op_getBlockByNumber`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opGetBlockByNumber(const std::string& blockTag, bool fullTransactions);
/// `op_getUncleCountByBlockNumber`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opGetUncleCountByBlockNumber(const std::string& blockTag);
/// `op_getUncleCountByBlockHash`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opGetUncleCountByBlockHash(const std::string& blockHash);
/// `op_getBlockTransactionCountByNumber`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opGetBlockTransactionCountByNumber(const std::string& blockTag);
/// `op_getBlockTransactionCountByHash`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opGetBlockTransactionCountByHash(const std::string& blockHash);
/// `op_getTransactionByBlockNumberAndIndex`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opGetTransactionByBlockNumberAndIndex(const std::string& blockTag, uint64_t index);
/// `op_getTransactionByBlockHashAndIndex`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opGetTransactionByBlockHashAndIndex(const std::string& blockHash, uint64_t index);
/// `op_call`, verified.
///
/// `optimisticStateFetch` is upstream's own extension to the standard
/// JSON-RPC signature, not a parameter callers will know from elsewhere.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opCall(const LogosMap& txArgs, const std::string& blockTag, bool optimisticStateFetch);
/// `op_createAccessList`, verified.
///
/// `optimisticStateFetch` is upstream's own extension to the standard
/// JSON-RPC signature, not a parameter callers will know from elsewhere.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opCreateAccessList(const LogosMap& txArgs, const std::string& blockTag, bool optimisticStateFetch);
/// `op_estimateGas`, verified.
///
/// `optimisticStateFetch` is upstream's own extension to the standard
/// JSON-RPC signature, not a parameter callers will know from elsewhere.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opEstimateGas(const LogosMap& txArgs, const std::string& blockTag, bool optimisticStateFetch);
/// `op_getTransactionByHash`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opGetTransactionByHash(const std::string& txHash);
/// `op_getBlockReceipts`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opGetBlockReceipts(const std::string& blockTag);
/// `op_getTransactionReceipt`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opGetTransactionReceipt(const std::string& txHash);
/// `op_getLogs`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opGetLogs(const LogosMap& filterOptions);
/// `op_newFilter`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opNewFilter(const LogosMap& filterOptions);
/// `op_uninstallFilter`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opUninstallFilter(const std::string& filterId);
/// `op_getFilterLogs`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opGetFilterLogs(const std::string& filterId);
/// `op_getFilterChanges`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opGetFilterChanges(const std::string& filterId);
/// `op_blobBaseFee`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opBlobBaseFee();
/// `op_gasPrice`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opGasPrice();
/// `op_maxPriorityFeePerGas`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opMaxPriorityFeePerGas();
/// `op_feeHistory`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opFeeHistory(uint64_t blockCount, const std::string& newestBlock, const LogosList& rewardPercentiles);
/// `op_sendRawTransaction`, verified.
///
/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the
/// library answers with a clear error rather than a wrong value.
StdLogosResult opSendRawTransaction(const std::string& txHexBytes);
// END GENERATED RPC WRAPPERS
/// Verified transaction by index within a block.
StdLogosResult ethGetTransactionByBlockNumberAndIndex(const std::string& blockTag,
uint64_t index);
logos_events:
/// Emitted when start() finishes. {"success":bool,"chainId":number,"error":string}
+260
View File
@@ -0,0 +1,260 @@
// The typed eth_*/op_* surface.
//
// Every one of these is three lines over the SAME dispatch path: the library's
// proxyCall is a string `case` over the very procs its typed C entry points
// call, so there is one FFI path here rather than sixty-one. They exist for
// discoverability — `lm methods`, the LIDL contract, and a caller's type
// checker — not because each needs its own binding.
//
// Generated. See tools/gen_rpc_methods.py; the table comes from the library's
// own dispatch table, so a bumped nimbus input cannot silently leave this
// behind.
#include "verified_proxy_impl.h"
#include <nlohmann/json.hpp>
using json = nlohmann::json;
// BEGIN GENERATED RPC WRAPPERS -- edit tools/gen_rpc_methods.py, not this
StdLogosResult VerifiedProxyImpl::ethChainId() {
return rpc("eth_chainId", json::array({}));
}
StdLogosResult VerifiedProxyImpl::ethBlockNumber() {
return rpc("eth_blockNumber", json::array({}));
}
StdLogosResult VerifiedProxyImpl::ethGetBalance(const std::string& address, const std::string& blockTag) {
return rpc("eth_getBalance", json::array({address, blockTag}));
}
StdLogosResult VerifiedProxyImpl::ethGetStorageAt(const std::string& address, const std::string& slot, const std::string& blockTag) {
return rpc("eth_getStorageAt", json::array({address, slot, blockTag}));
}
StdLogosResult VerifiedProxyImpl::ethGetTransactionCount(const std::string& address, const std::string& blockTag) {
return rpc("eth_getTransactionCount", json::array({address, blockTag}));
}
StdLogosResult VerifiedProxyImpl::ethGetCode(const std::string& address, const std::string& blockTag) {
return rpc("eth_getCode", json::array({address, blockTag}));
}
StdLogosResult VerifiedProxyImpl::ethGetBlockByHash(const std::string& blockHash, bool fullTransactions) {
return rpc("eth_getBlockByHash", json::array({blockHash, fullTransactions}));
}
StdLogosResult VerifiedProxyImpl::ethGetBlockByNumber(const std::string& blockTag, bool fullTransactions) {
return rpc("eth_getBlockByNumber", json::array({blockTag, fullTransactions}));
}
StdLogosResult VerifiedProxyImpl::ethGetUncleCountByBlockNumber(const std::string& blockTag) {
return rpc("eth_getUncleCountByBlockNumber", json::array({blockTag}));
}
StdLogosResult VerifiedProxyImpl::ethGetUncleCountByBlockHash(const std::string& blockHash) {
return rpc("eth_getUncleCountByBlockHash", json::array({blockHash}));
}
StdLogosResult VerifiedProxyImpl::ethGetBlockTransactionCountByNumber(const std::string& blockTag) {
return rpc("eth_getBlockTransactionCountByNumber", json::array({blockTag}));
}
StdLogosResult VerifiedProxyImpl::ethGetBlockTransactionCountByHash(const std::string& blockHash) {
return rpc("eth_getBlockTransactionCountByHash", json::array({blockHash}));
}
StdLogosResult VerifiedProxyImpl::ethGetTransactionByBlockNumberAndIndex(const std::string& blockTag, uint64_t index) {
return rpc("eth_getTransactionByBlockNumberAndIndex", json::array({blockTag, index}));
}
StdLogosResult VerifiedProxyImpl::ethGetTransactionByBlockHashAndIndex(const std::string& blockHash, uint64_t index) {
return rpc("eth_getTransactionByBlockHashAndIndex", json::array({blockHash, index}));
}
StdLogosResult VerifiedProxyImpl::ethCall(const LogosMap& txArgs, const std::string& blockTag, bool optimisticStateFetch) {
return rpc("eth_call", json::array({txArgs, blockTag, optimisticStateFetch}));
}
StdLogosResult VerifiedProxyImpl::ethCreateAccessList(const LogosMap& txArgs, const std::string& blockTag, bool optimisticStateFetch) {
return rpc("eth_createAccessList", json::array({txArgs, blockTag, optimisticStateFetch}));
}
StdLogosResult VerifiedProxyImpl::ethEstimateGas(const LogosMap& txArgs, const std::string& blockTag, bool optimisticStateFetch) {
return rpc("eth_estimateGas", json::array({txArgs, blockTag, optimisticStateFetch}));
}
StdLogosResult VerifiedProxyImpl::ethGetTransactionByHash(const std::string& txHash) {
return rpc("eth_getTransactionByHash", json::array({txHash}));
}
StdLogosResult VerifiedProxyImpl::ethGetBlockReceipts(const std::string& blockTag) {
return rpc("eth_getBlockReceipts", json::array({blockTag}));
}
StdLogosResult VerifiedProxyImpl::ethGetTransactionReceipt(const std::string& txHash) {
return rpc("eth_getTransactionReceipt", json::array({txHash}));
}
StdLogosResult VerifiedProxyImpl::ethGetLogs(const LogosMap& filterOptions) {
return rpc("eth_getLogs", json::array({filterOptions}));
}
StdLogosResult VerifiedProxyImpl::ethNewFilter(const LogosMap& filterOptions) {
return rpc("eth_newFilter", json::array({filterOptions}));
}
StdLogosResult VerifiedProxyImpl::ethUninstallFilter(const std::string& filterId) {
return rpc("eth_uninstallFilter", json::array({filterId}));
}
StdLogosResult VerifiedProxyImpl::ethGetFilterLogs(const std::string& filterId) {
return rpc("eth_getFilterLogs", json::array({filterId}));
}
StdLogosResult VerifiedProxyImpl::ethGetFilterChanges(const std::string& filterId) {
return rpc("eth_getFilterChanges", json::array({filterId}));
}
StdLogosResult VerifiedProxyImpl::ethBlobBaseFee() {
return rpc("eth_blobBaseFee", json::array({}));
}
StdLogosResult VerifiedProxyImpl::ethGasPrice() {
return rpc("eth_gasPrice", json::array({}));
}
StdLogosResult VerifiedProxyImpl::ethMaxPriorityFeePerGas() {
return rpc("eth_maxPriorityFeePerGas", json::array({}));
}
StdLogosResult VerifiedProxyImpl::ethFeeHistory(uint64_t blockCount, const std::string& newestBlock, const LogosList& rewardPercentiles) {
return rpc("eth_feeHistory", json::array({blockCount, newestBlock, rewardPercentiles}));
}
StdLogosResult VerifiedProxyImpl::ethSendRawTransaction(const std::string& txHexBytes) {
return rpc("eth_sendRawTransaction", json::array({txHexBytes}));
}
StdLogosResult VerifiedProxyImpl::opChainId() {
return rpc("op_chainId", json::array({}));
}
StdLogosResult VerifiedProxyImpl::opBlockNumber() {
return rpc("op_blockNumber", json::array({}));
}
StdLogosResult VerifiedProxyImpl::opGetBalance(const std::string& address, const std::string& blockTag) {
return rpc("op_getBalance", json::array({address, blockTag}));
}
StdLogosResult VerifiedProxyImpl::opGetStorageAt(const std::string& address, const std::string& slot, const std::string& blockTag) {
return rpc("op_getStorageAt", json::array({address, slot, blockTag}));
}
StdLogosResult VerifiedProxyImpl::opGetTransactionCount(const std::string& address, const std::string& blockTag) {
return rpc("op_getTransactionCount", json::array({address, blockTag}));
}
StdLogosResult VerifiedProxyImpl::opGetCode(const std::string& address, const std::string& blockTag) {
return rpc("op_getCode", json::array({address, blockTag}));
}
StdLogosResult VerifiedProxyImpl::opGetBlockByHash(const std::string& blockHash, bool fullTransactions) {
return rpc("op_getBlockByHash", json::array({blockHash, fullTransactions}));
}
StdLogosResult VerifiedProxyImpl::opGetBlockByNumber(const std::string& blockTag, bool fullTransactions) {
return rpc("op_getBlockByNumber", json::array({blockTag, fullTransactions}));
}
StdLogosResult VerifiedProxyImpl::opGetUncleCountByBlockNumber(const std::string& blockTag) {
return rpc("op_getUncleCountByBlockNumber", json::array({blockTag}));
}
StdLogosResult VerifiedProxyImpl::opGetUncleCountByBlockHash(const std::string& blockHash) {
return rpc("op_getUncleCountByBlockHash", json::array({blockHash}));
}
StdLogosResult VerifiedProxyImpl::opGetBlockTransactionCountByNumber(const std::string& blockTag) {
return rpc("op_getBlockTransactionCountByNumber", json::array({blockTag}));
}
StdLogosResult VerifiedProxyImpl::opGetBlockTransactionCountByHash(const std::string& blockHash) {
return rpc("op_getBlockTransactionCountByHash", json::array({blockHash}));
}
StdLogosResult VerifiedProxyImpl::opGetTransactionByBlockNumberAndIndex(const std::string& blockTag, uint64_t index) {
return rpc("op_getTransactionByBlockNumberAndIndex", json::array({blockTag, index}));
}
StdLogosResult VerifiedProxyImpl::opGetTransactionByBlockHashAndIndex(const std::string& blockHash, uint64_t index) {
return rpc("op_getTransactionByBlockHashAndIndex", json::array({blockHash, index}));
}
StdLogosResult VerifiedProxyImpl::opCall(const LogosMap& txArgs, const std::string& blockTag, bool optimisticStateFetch) {
return rpc("op_call", json::array({txArgs, blockTag, optimisticStateFetch}));
}
StdLogosResult VerifiedProxyImpl::opCreateAccessList(const LogosMap& txArgs, const std::string& blockTag, bool optimisticStateFetch) {
return rpc("op_createAccessList", json::array({txArgs, blockTag, optimisticStateFetch}));
}
StdLogosResult VerifiedProxyImpl::opEstimateGas(const LogosMap& txArgs, const std::string& blockTag, bool optimisticStateFetch) {
return rpc("op_estimateGas", json::array({txArgs, blockTag, optimisticStateFetch}));
}
StdLogosResult VerifiedProxyImpl::opGetTransactionByHash(const std::string& txHash) {
return rpc("op_getTransactionByHash", json::array({txHash}));
}
StdLogosResult VerifiedProxyImpl::opGetBlockReceipts(const std::string& blockTag) {
return rpc("op_getBlockReceipts", json::array({blockTag}));
}
StdLogosResult VerifiedProxyImpl::opGetTransactionReceipt(const std::string& txHash) {
return rpc("op_getTransactionReceipt", json::array({txHash}));
}
StdLogosResult VerifiedProxyImpl::opGetLogs(const LogosMap& filterOptions) {
return rpc("op_getLogs", json::array({filterOptions}));
}
StdLogosResult VerifiedProxyImpl::opNewFilter(const LogosMap& filterOptions) {
return rpc("op_newFilter", json::array({filterOptions}));
}
StdLogosResult VerifiedProxyImpl::opUninstallFilter(const std::string& filterId) {
return rpc("op_uninstallFilter", json::array({filterId}));
}
StdLogosResult VerifiedProxyImpl::opGetFilterLogs(const std::string& filterId) {
return rpc("op_getFilterLogs", json::array({filterId}));
}
StdLogosResult VerifiedProxyImpl::opGetFilterChanges(const std::string& filterId) {
return rpc("op_getFilterChanges", json::array({filterId}));
}
StdLogosResult VerifiedProxyImpl::opBlobBaseFee() {
return rpc("op_blobBaseFee", json::array({}));
}
StdLogosResult VerifiedProxyImpl::opGasPrice() {
return rpc("op_gasPrice", json::array({}));
}
StdLogosResult VerifiedProxyImpl::opMaxPriorityFeePerGas() {
return rpc("op_maxPriorityFeePerGas", json::array({}));
}
StdLogosResult VerifiedProxyImpl::opFeeHistory(uint64_t blockCount, const std::string& newestBlock, const LogosList& rewardPercentiles) {
return rpc("op_feeHistory", json::array({blockCount, newestBlock, rewardPercentiles}));
}
StdLogosResult VerifiedProxyImpl::opSendRawTransaction(const std::string& txHexBytes) {
return rpc("op_sendRawTransaction", json::array({txHexBytes}));
}
// END GENERATED RPC WRAPPERS
+1
View File
@@ -22,6 +22,7 @@ logos_test(
../src/proxy_config.cpp
../src/proxy_runtime.cpp
../src/verified_proxy_impl.cpp
../src/verified_proxy_rpc.cpp
../src/rpc_http_server.cpp
../src/beacon_client.cpp
TEST_SOURCES
+198
View File
@@ -0,0 +1,198 @@
#!/usr/bin/env python3
"""Generate the typed eth_*/op_* wrappers from the library's own dispatch table.
The wrappers are three lines each over one shared `rpc()` path, so hand-writing
sixty-one of them would be sixty-one chances to transpose an argument. They are
generated instead — but COMMITTED as literal text, because the module's code
generator parses verified_proxy_impl.h as TEXT to build the LIDL contract and
would not see anything hidden behind a macro.
Run with --check in CI to prove the committed blocks still match.
The table below was extracted mechanically from nimbus-eth1's
nimbus_verified_proxy/library/c_frontend.nim (the `case $name` inside proxyCall)
at the revision this module pins. Re-extract with --from-source <c_frontend.nim>
after bumping that input.
"""
import argparse
import json
import re
import sys
from pathlib import Path
HERE = Path(__file__).resolve().parent
SRC = HERE.parent / "src"
BEGIN = "// BEGIN GENERATED RPC WRAPPERS -- edit tools/gen_rpc_methods.py, not this"
END = "// END GENERATED RPC WRAPPERS"
# Parameter names, per RPC method, in order. Only the eth_ spellings are listed;
# the op_ mirror reuses them. Names reach the LIDL contract and `lm methods`, so
# they are the API's documentation as much as its signature.
PARAM_NAMES = {
"getBalance": ["address", "blockTag"],
"getCode": ["address", "blockTag"],
"getTransactionCount": ["address", "blockTag"],
"getStorageAt": ["address", "slot", "blockTag"],
"getBlockByNumber": ["blockTag", "fullTransactions"],
"getBlockByHash": ["blockHash", "fullTransactions"],
"getUncleCountByBlockNumber": ["blockTag"],
"getUncleCountByBlockHash": ["blockHash"],
"getBlockTransactionCountByNumber": ["blockTag"],
"getBlockTransactionCountByHash": ["blockHash"],
"getTransactionByBlockNumberAndIndex": ["blockTag", "index"],
"getTransactionByBlockHashAndIndex": ["blockHash", "index"],
"getTransactionByHash": ["txHash"],
"getTransactionReceipt": ["txHash"],
"getBlockReceipts": ["blockTag"],
"call": ["txArgs", "blockTag", "optimisticStateFetch"],
"estimateGas": ["txArgs", "blockTag", "optimisticStateFetch"],
"createAccessList": ["txArgs", "blockTag", "optimisticStateFetch"],
"getLogs": ["filterOptions"],
"newFilter": ["filterOptions"],
"uninstallFilter": ["filterId"],
"getFilterLogs": ["filterId"],
"getFilterChanges": ["filterId"],
"feeHistory": ["blockCount", "newestBlock", "rewardPercentiles"],
"sendRawTransaction": ["txHexBytes"],
}
# The one JSON parameter that is an ARRAY rather than an object. Everything else
# reaching the library as a raw JSON node is a transaction or filter object.
JSON_LISTS = {("feeHistory", 2)}
CPP_TYPE = {
"str": "const std::string& ",
"bool": "bool ",
"u64": "uint64_t ",
}
# Methods this module drives itself; a typed wrapper would invite callers to
# fight the runtime for control of them.
SKIP = {"eth_syncing"}
def extract(path):
body = Path(path).read_text()
body = body[body.index("proc proxyCall("):]
out = []
for block in re.split(r'\n of "', body)[1:]:
name = block.split('"', 1)[0]
seg = block.split('\n of "')[0]
kinds = []
for m in re.finditer(
r"parsedParams\[(\d+)\]\.(getStr|getBool|getBiggestInt)\(\)"
r"|\(\$parsedParams\[(\d+)\]\)", seg):
if m.group(2):
kinds.append((int(m.group(1)),
{"getStr": "str", "getBool": "bool",
"getBiggestInt": "u64"}[m.group(2)]))
else:
kinds.append((int(m.group(3)), "json"))
out.append({"rpc": name, "params": [k for _, k in sorted(set(kinds))]})
return out
def cpp_name(rpc):
prefix, rest = rpc.split("_", 1)
return prefix + rest[0].upper() + rest[1:]
def stem(rpc):
return rpc.split("_", 1)[1]
def signature(rpc, params):
base = stem(rpc)
names = PARAM_NAMES.get(base, [f"arg{i}" for i in range(len(params))])
args = []
for i, kind in enumerate(params):
if kind == "json":
t = "const LogosList& " if (base, i) in JSON_LISTS else "const LogosMap& "
else:
t = CPP_TYPE[kind]
args.append(t + names[i])
return f"StdLogosResult {cpp_name(rpc)}({', '.join(args)})"
def doc(rpc, params):
base = stem(rpc)
lines = [f"/// `{rpc}`, verified."]
if base in ("call", "estimateGas", "createAccessList"):
lines.append("///")
lines.append("/// `optimisticStateFetch` is upstream's own extension to the standard")
lines.append("/// JSON-RPC signature, not a parameter callers will know from elsewhere.")
if rpc.startswith("op_"):
lines.append("///")
lines.append("/// Requires an OP-Stack network and `opExecutionApiUrls`; otherwise the")
lines.append("/// library answers with a clear error rather than a wrong value.")
return lines
def emit_header(table):
out = [BEGIN]
for e in table:
out += [" " + l for l in doc(e["rpc"], e["params"])]
out.append(f" {signature(e['rpc'], e['params'])};")
out.append("")
out.append(" " + END)
return "\n".join(out)
def emit_impl(table):
out = [BEGIN]
for e in table:
base, params = stem(e["rpc"]), e["params"]
names = PARAM_NAMES.get(base, [f"arg{i}" for i in range(len(params))])
sig = signature(e["rpc"], params).replace(
"StdLogosResult ", "StdLogosResult VerifiedProxyImpl::", 1)
args = ", ".join(names)
out.append(f"{sig} {{")
out.append(f' return rpc("{e["rpc"]}", json::array({{{args}}}));')
out.append("}")
out.append("")
out.append(END)
return "\n".join(out)
def splice(path, block):
text = Path(path).read_text()
i, j = text.index(BEGIN), text.index(END) + len(END)
return text[:i] + block.strip() + text[j:]
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--from-source", help="path to c_frontend.nim to re-extract the table")
ap.add_argument("--check", action="store_true", help="fail if the committed files differ")
a = ap.parse_args()
tbl_path = HERE / "rpc_methods.json"
if a.from_source:
table = extract(a.from_source)
tbl_path.write_text(json.dumps(table, indent=1) + "\n")
table = json.loads(tbl_path.read_text())
table = [e for e in table if e["rpc"] not in SKIP]
targets = {
SRC / "verified_proxy_impl.h": emit_header(table),
SRC / "verified_proxy_rpc.cpp": emit_impl(table),
}
bad = False
for path, block in targets.items():
new = splice(path, block)
if a.check:
if new != path.read_text():
print(f"DRIFT: {path.name} does not match the generator", file=sys.stderr)
bad = True
else:
path.write_text(new)
if a.check and bad:
print("run: python3 tools/gen_rpc_methods.py", file=sys.stderr)
return 1
print(f"{len(table)} wrappers {'checked' if a.check else 'generated'}")
return 0
if __name__ == "__main__":
sys.exit(main())
+380
View File
@@ -0,0 +1,380 @@
[
{
"rpc": "eth_chainId",
"params": []
},
{
"rpc": "eth_blockNumber",
"params": []
},
{
"rpc": "eth_syncing",
"params": []
},
{
"rpc": "eth_getBalance",
"params": [
"str",
"str"
]
},
{
"rpc": "eth_getStorageAt",
"params": [
"str",
"str",
"str"
]
},
{
"rpc": "eth_getTransactionCount",
"params": [
"str",
"str"
]
},
{
"rpc": "eth_getCode",
"params": [
"str",
"str"
]
},
{
"rpc": "eth_getBlockByHash",
"params": [
"str",
"bool"
]
},
{
"rpc": "eth_getBlockByNumber",
"params": [
"str",
"bool"
]
},
{
"rpc": "eth_getUncleCountByBlockNumber",
"params": [
"str"
]
},
{
"rpc": "eth_getUncleCountByBlockHash",
"params": [
"str"
]
},
{
"rpc": "eth_getBlockTransactionCountByNumber",
"params": [
"str"
]
},
{
"rpc": "eth_getBlockTransactionCountByHash",
"params": [
"str"
]
},
{
"rpc": "eth_getTransactionByBlockNumberAndIndex",
"params": [
"str",
"u64"
]
},
{
"rpc": "eth_getTransactionByBlockHashAndIndex",
"params": [
"str",
"u64"
]
},
{
"rpc": "eth_call",
"params": [
"json",
"str",
"bool"
]
},
{
"rpc": "eth_createAccessList",
"params": [
"json",
"str",
"bool"
]
},
{
"rpc": "eth_estimateGas",
"params": [
"json",
"str",
"bool"
]
},
{
"rpc": "eth_getTransactionByHash",
"params": [
"str"
]
},
{
"rpc": "eth_getBlockReceipts",
"params": [
"str"
]
},
{
"rpc": "eth_getTransactionReceipt",
"params": [
"str"
]
},
{
"rpc": "eth_getLogs",
"params": [
"json"
]
},
{
"rpc": "eth_newFilter",
"params": [
"json"
]
},
{
"rpc": "eth_uninstallFilter",
"params": [
"str"
]
},
{
"rpc": "eth_getFilterLogs",
"params": [
"str"
]
},
{
"rpc": "eth_getFilterChanges",
"params": [
"str"
]
},
{
"rpc": "eth_blobBaseFee",
"params": []
},
{
"rpc": "eth_gasPrice",
"params": []
},
{
"rpc": "eth_maxPriorityFeePerGas",
"params": []
},
{
"rpc": "eth_feeHistory",
"params": [
"u64",
"str",
"json"
]
},
{
"rpc": "eth_sendRawTransaction",
"params": [
"str"
]
},
{
"rpc": "op_chainId",
"params": []
},
{
"rpc": "op_blockNumber",
"params": []
},
{
"rpc": "op_getBalance",
"params": [
"str",
"str"
]
},
{
"rpc": "op_getStorageAt",
"params": [
"str",
"str",
"str"
]
},
{
"rpc": "op_getTransactionCount",
"params": [
"str",
"str"
]
},
{
"rpc": "op_getCode",
"params": [
"str",
"str"
]
},
{
"rpc": "op_getBlockByHash",
"params": [
"str",
"bool"
]
},
{
"rpc": "op_getBlockByNumber",
"params": [
"str",
"bool"
]
},
{
"rpc": "op_getUncleCountByBlockNumber",
"params": [
"str"
]
},
{
"rpc": "op_getUncleCountByBlockHash",
"params": [
"str"
]
},
{
"rpc": "op_getBlockTransactionCountByNumber",
"params": [
"str"
]
},
{
"rpc": "op_getBlockTransactionCountByHash",
"params": [
"str"
]
},
{
"rpc": "op_getTransactionByBlockNumberAndIndex",
"params": [
"str",
"u64"
]
},
{
"rpc": "op_getTransactionByBlockHashAndIndex",
"params": [
"str",
"u64"
]
},
{
"rpc": "op_call",
"params": [
"json",
"str",
"bool"
]
},
{
"rpc": "op_createAccessList",
"params": [
"json",
"str",
"bool"
]
},
{
"rpc": "op_estimateGas",
"params": [
"json",
"str",
"bool"
]
},
{
"rpc": "op_getTransactionByHash",
"params": [
"str"
]
},
{
"rpc": "op_getBlockReceipts",
"params": [
"str"
]
},
{
"rpc": "op_getTransactionReceipt",
"params": [
"str"
]
},
{
"rpc": "op_getLogs",
"params": [
"json"
]
},
{
"rpc": "op_newFilter",
"params": [
"json"
]
},
{
"rpc": "op_uninstallFilter",
"params": [
"str"
]
},
{
"rpc": "op_getFilterLogs",
"params": [
"str"
]
},
{
"rpc": "op_getFilterChanges",
"params": [
"str"
]
},
{
"rpc": "op_blobBaseFee",
"params": []
},
{
"rpc": "op_gasPrice",
"params": []
},
{
"rpc": "op_maxPriorityFeePerGas",
"params": []
},
{
"rpc": "op_feeHistory",
"params": [
"u64",
"str",
"json"
]
},
{
"rpc": "op_sendRawTransaction",
"params": [
"str"
]
}
]