diff --git a/crates/rpc/rpc-api/src/lib.rs b/crates/rpc/rpc-api/src/lib.rs index 7fba5adba9e0..073631c986e7 100644 --- a/crates/rpc/rpc-api/src/lib.rs +++ b/crates/rpc/rpc-api/src/lib.rs @@ -26,6 +26,7 @@ mod eth; mod eth_filter; mod eth_pubsub; mod net; +mod otterscan; mod reth; mod rpc; mod trace; @@ -45,6 +46,7 @@ pub mod servers { eth_filter::EthFilterApiServer, eth_pubsub::EthPubSubApiServer, net::NetApiServer, + otterscan::OtterscanServer, reth::RethApiServer, rpc::RpcApiServer, trace::TraceApiServer, @@ -66,6 +68,7 @@ pub mod clients { engine::{EngineApiClient, EngineEthApiClient}, eth::EthApiClient, net::NetApiClient, + otterscan::OtterscanClient, rpc::RpcApiServer, trace::TraceApiClient, txpool::TxPoolApiClient, diff --git a/crates/rpc/rpc-api/src/otterscan.rs b/crates/rpc/rpc-api/src/otterscan.rs new file mode 100644 index 000000000000..9ac585991fed --- /dev/null +++ b/crates/rpc/rpc-api/src/otterscan.rs @@ -0,0 +1,85 @@ +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +use reth_primitives::{Address, BlockId, BlockNumberOrTag, TxHash, H256}; +use reth_rpc_types::{ + BlockDetails, ContractCreator, InternalOperation, OtsBlockTransactions, TraceEntry, + Transaction, TransactionsWithReceipts, +}; + +/// Otterscan rpc interface. +#[cfg_attr(not(feature = "client"), rpc(server, namespace = "ots"))] +#[cfg_attr(feature = "client", rpc(server, client, namespace = "ots"))] +pub trait Otterscan { + /// Check if a certain address contains a deployed code. + #[method(name = "hasCode")] + async fn has_code(&self, address: Address, block_number: Option) -> RpcResult; + + /// Very simple API versioning scheme. Every time we add a new capability, the number is + /// incremented. This allows for Otterscan to check if the node contains all API it + /// needs. + #[method(name = "getApiLevel")] + async fn get_api_level(&self) -> RpcResult; + + /// Return the internal ETH transfers inside a transaction. + #[method(name = "getInternalOperations")] + async fn get_internal_operations(&self, tx_hash: TxHash) -> RpcResult>; + + /// Given a transaction hash, returns its raw revert reason. + #[method(name = "getTransactionError")] + async fn get_transaction_error(&self, tx_hash: TxHash) -> RpcResult; + + /// Extract all variations of calls, contract creation and self-destructs and returns a call + /// tree. + #[method(name = "traceTransaction")] + async fn trace_transaction(&self, tx_hash: TxHash) -> RpcResult; + + /// Tailor-made and expanded version of eth_getBlockByNumber for block details page in + /// Otterscan. + #[method(name = "getBlockDetails")] + async fn get_block_details( + &self, + block_number: BlockNumberOrTag, + ) -> RpcResult>; + + /// Tailor-made and expanded version of eth_getBlockByHash for block details page in Otterscan. + #[method(name = "getBlockDetailsByHash")] + async fn get_block_details_by_hash(&self, block_hash: H256) -> RpcResult>; + + /// Get paginated transactions for a certain block. Also remove some verbose fields like logs. + #[method(name = "getBlockTransactions")] + async fn get_block_transactions( + &self, + block_number: BlockNumberOrTag, + page_number: usize, + page_size: usize, + ) -> RpcResult; + + /// Gets paginated inbound/outbound transaction calls for a certain address. + #[method(name = "searchTransactionsBefore")] + async fn search_transactions_before( + &self, + address: Address, + block_number: BlockNumberOrTag, + page_size: usize, + ) -> RpcResult; + + /// Gets paginated inbound/outbound transaction calls for a certain address. + #[method(name = "searchTransactionsAfter")] + async fn search_transactions_after( + &self, + address: Address, + block_number: BlockNumberOrTag, + page_size: usize, + ) -> RpcResult; + + /// Gets the transaction hash for a certain sender address, given its nonce. + #[method(name = "getTransactionBySenderAndNonce")] + async fn get_transaction_by_sender_and_nonce( + &self, + sender: Address, + nonce: u64, + ) -> RpcResult>; + + /// Gets the transaction hash and the address who created a contract. + #[method(name = "getContractCreator")] + async fn get_contract_creator(&self, address: Address) -> RpcResult>; +} diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index 368960517b7c..8737519fa81b 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -122,7 +122,7 @@ use reth_rpc::{ gas_oracle::GasPriceOracle, }, AdminApi, DebugApi, EngineEthApi, EthApi, EthFilter, EthPubSub, EthSubscriptionIdProvider, - NetApi, RPCApi, RethApi, TraceApi, TracingCallGuard, TxPoolApi, Web3Api, + NetApi, OtterscanApi, RPCApi, RethApi, TraceApi, TracingCallGuard, TxPoolApi, Web3Api, }; use reth_rpc_api::{servers::*, EngineApiServer}; use reth_tasks::{TaskSpawner, TokioTaskExecutor}; @@ -651,6 +651,8 @@ pub enum RethRpcModule { Rpc, /// `reth_` module Reth, + /// `ots_` module + Ots, } // === impl RethRpcModule === @@ -779,6 +781,13 @@ where self } + /// Register Otterscan Namespace + pub fn register_ots(&mut self) -> &mut Self { + let eth_api = self.eth_api(); + self.modules.insert(RethRpcModule::Ots, OtterscanApi::new(eth_api).into_rpc().into()); + self + } + /// Register Debug Namespace pub fn register_debug(&mut self) -> &mut Self { let eth_api = self.eth_api(); @@ -936,6 +945,7 @@ where ) .into_rpc() .into(), + RethRpcModule::Ots => OtterscanApi::new(eth_api.clone()).into_rpc().into(), RethRpcModule::Reth => { RethApi::new(self.provider.clone(), Box::new(self.executor.clone())) .into_rpc() @@ -1774,6 +1784,7 @@ mod tests { "trace" => RethRpcModule::Trace, "web3" => RethRpcModule::Web3, "rpc" => RethRpcModule::Rpc, + "ots" => RethRpcModule::Ots, "reth" => RethRpcModule::Reth, ); } diff --git a/crates/rpc/rpc-types/src/lib.rs b/crates/rpc/rpc-types/src/lib.rs index 64d6537291e3..8b3542e6d35f 100644 --- a/crates/rpc/rpc-types/src/lib.rs +++ b/crates/rpc/rpc-types/src/lib.rs @@ -17,8 +17,10 @@ mod admin; mod eth; +mod otterscan; mod rpc; pub use admin::*; pub use eth::*; +pub use otterscan::*; pub use rpc::*; diff --git a/crates/rpc/rpc-types/src/otterscan.rs b/crates/rpc/rpc-types/src/otterscan.rs new file mode 100644 index 000000000000..57e12e8bf123 --- /dev/null +++ b/crates/rpc/rpc-types/src/otterscan.rs @@ -0,0 +1,97 @@ +use crate::{Block, Transaction, TransactionReceipt}; +use reth_primitives::{Address, Bytes, U256}; +use serde::{Deserialize, Serialize}; + +/// Operation type enum for `InternalOperation` struct +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub enum OperationType { + /// Operation Transfer + OpTransfer = 0, + /// Operation Contract self destruct + OpSelfDestruct = 1, + /// Operation Create + OpCreate = 2, + /// Operation Create2 + OpCreate2 = 3, +} + +/// Custom struct for otterscan `getInternalOperations` RPC response +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct InternalOperation { + r#type: OperationType, + from: Address, + to: Address, + value: u128, +} + +/// Custom struct for otterscan `traceTransaction` RPC response +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct TraceEntry { + r#type: String, + depth: u32, + from: Address, + to: Address, + value: u128, + input: Bytes, +} + +/// Internal issuance struct for `BlockDetails` struct +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InternalIssuance { + block_reward: U256, + uncle_reward: U256, + issuance: U256, +} + +/// Custom `Block` struct that includes transaction count for Otterscan responses +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct OtsBlock { + #[serde(flatten)] + block: Block, + transaction_count: usize, +} + +/// Custom struct for otterscan `getBlockDetails` RPC response +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BlockDetails { + block: OtsBlock, + issuance: InternalIssuance, + total_fees: U256, +} + +/// Custom transaction receipt struct for otterscan `OtsBlockTransactions` struct +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct OtsTransactionReceipt { + #[serde(flatten)] + receipt: TransactionReceipt, + timestamp: u64, +} + +/// Custom struct for otterscan `getBlockTransactions` RPC response +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct OtsBlockTransactions { + fullblock: OtsBlock, + receipts: Vec, +} + +/// Custom struct for otterscan `searchTransactionsAfter`and `searchTransactionsBefore` RPC +/// responses +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionsWithReceipts { + txs: Vec, + receipts: Vec, + first_page: bool, + last_page: bool, +} + +/// Custom struct for otterscan `getContractCreator` RPC responses +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct ContractCreator { + tx: Transaction, + creator: Address, +} diff --git a/crates/rpc/rpc/src/lib.rs b/crates/rpc/rpc/src/lib.rs index 64083f746b2b..b885608139a7 100644 --- a/crates/rpc/rpc/src/lib.rs +++ b/crates/rpc/rpc/src/lib.rs @@ -37,6 +37,7 @@ mod engine; pub mod eth; mod layers; mod net; +mod otterscan; mod reth; mod rpc; mod trace; @@ -50,6 +51,7 @@ pub use engine::{EngineApi, EngineEthApi}; pub use eth::{EthApi, EthApiSpec, EthFilter, EthPubSub, EthSubscriptionIdProvider}; pub use layers::{AuthLayer, AuthValidator, Claims, JwtAuthValidator, JwtError, JwtSecret}; pub use net::NetApi; +pub use otterscan::OtterscanApi; pub use reth::RethApi; pub use rpc::RPCApi; pub use trace::TraceApi; diff --git a/crates/rpc/rpc/src/otterscan.rs b/crates/rpc/rpc/src/otterscan.rs new file mode 100644 index 000000000000..93d0ba167e24 --- /dev/null +++ b/crates/rpc/rpc/src/otterscan.rs @@ -0,0 +1,111 @@ +#![allow(dead_code, unused_variables)] +use crate::result::internal_rpc_err; +use async_trait::async_trait; +use jsonrpsee::core::RpcResult; +use reth_primitives::{Address, BlockId, BlockNumberOrTag, TxHash, H256}; +use reth_rpc_api::{EthApiServer, OtterscanServer}; +use reth_rpc_types::{ + BlockDetails, ContractCreator, InternalOperation, OtsBlockTransactions, TraceEntry, + Transaction, TransactionsWithReceipts, +}; + +/// Otterscan Api +#[derive(Debug)] +pub struct OtterscanApi { + eth: Eth, +} + +impl OtterscanApi { + /// Creates a new instance of `Otterscan`. + pub fn new(eth: Eth) -> Self { + Self { eth } + } +} + +#[async_trait] +impl OtterscanServer for OtterscanApi +where + Eth: EthApiServer, +{ + /// Handler for `ots_hasCode` + async fn has_code(&self, address: Address, block_number: Option) -> RpcResult { + Err(internal_rpc_err("unimplemented")) + } + + /// Handler for `ots_getApiLevel` + async fn get_api_level(&self) -> RpcResult { + Err(internal_rpc_err("unimplemented")) + } + + /// Handler for `ots_getInternalOperations` + async fn get_internal_operations(&self, tx_hash: TxHash) -> RpcResult> { + Err(internal_rpc_err("unimplemented")) + } + + /// Handler for `ots_getTransactionError` + async fn get_transaction_error(&self, tx_hash: TxHash) -> RpcResult { + Err(internal_rpc_err("unimplemented")) + } + + /// Handler for `ots_traceTransaction` + async fn trace_transaction(&self, tx_hash: TxHash) -> RpcResult { + Err(internal_rpc_err("unimplemented")) + } + + /// Handler for `ots_getBlockDetails` + async fn get_block_details( + &self, + block_number: BlockNumberOrTag, + ) -> RpcResult> { + Err(internal_rpc_err("unimplemented")) + } + + /// Handler for `getBlockDetailsByHash` + async fn get_block_details_by_hash(&self, block_hash: H256) -> RpcResult> { + Err(internal_rpc_err("unimplemented")) + } + + /// Handler for `getBlockTransactions` + async fn get_block_transactions( + &self, + block_number: BlockNumberOrTag, + page_number: usize, + page_size: usize, + ) -> RpcResult { + Err(internal_rpc_err("unimplemented")) + } + + /// Handler for `searchTransactionsBefore` + async fn search_transactions_before( + &self, + address: Address, + block_number: BlockNumberOrTag, + page_size: usize, + ) -> RpcResult { + Err(internal_rpc_err("unimplemented")) + } + + /// Handler for `searchTransactionsAfter` + async fn search_transactions_after( + &self, + address: Address, + block_number: BlockNumberOrTag, + page_size: usize, + ) -> RpcResult { + Err(internal_rpc_err("unimplemented")) + } + + /// Handler for `getTransactionBySenderAndNonce` + async fn get_transaction_by_sender_and_nonce( + &self, + sender: Address, + nonce: u64, + ) -> RpcResult> { + Err(internal_rpc_err("unimplemented")) + } + + /// Handler for `getContractCreator` + async fn get_contract_creator(&self, address: Address) -> RpcResult> { + Err(internal_rpc_err("unimplemented")) + } +}