feat(rpc): Add ots_ namespace and trait bindings for Otterscan Endpoints (#3778)

Co-authored-by: Miguel Palhas <mpalhas@gmail.com>
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Resende
2023-07-19 00:53:34 +01:00
committed by GitHub
parent 314e561193
commit 170e6f24d2
7 changed files with 312 additions and 1 deletions

View File

@@ -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,

View File

@@ -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<BlockId>) -> RpcResult<bool>;
/// 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<u64>;
/// Return the internal ETH transfers inside a transaction.
#[method(name = "getInternalOperations")]
async fn get_internal_operations(&self, tx_hash: TxHash) -> RpcResult<Vec<InternalOperation>>;
/// Given a transaction hash, returns its raw revert reason.
#[method(name = "getTransactionError")]
async fn get_transaction_error(&self, tx_hash: TxHash) -> RpcResult<String>;
/// 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<TraceEntry>;
/// 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<Option<BlockDetails>>;
/// 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<Option<BlockDetails>>;
/// 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<OtsBlockTransactions>;
/// 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<TransactionsWithReceipts>;
/// 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<TransactionsWithReceipts>;
/// 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<Option<Transaction>>;
/// Gets the transaction hash and the address who created a contract.
#[method(name = "getContractCreator")]
async fn get_contract_creator(&self, address: Address) -> RpcResult<Option<ContractCreator>>;
}

View File

@@ -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,
);
}

View File

@@ -17,8 +17,10 @@
mod admin;
mod eth;
mod otterscan;
mod rpc;
pub use admin::*;
pub use eth::*;
pub use otterscan::*;
pub use rpc::*;

View File

@@ -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<OtsTransactionReceipt>,
}
/// 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<Transaction>,
receipts: Vec<OtsTransactionReceipt>,
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,
}

View File

@@ -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;

View File

@@ -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: Eth,
}
impl<Eth> OtterscanApi<Eth> {
/// Creates a new instance of `Otterscan`.
pub fn new(eth: Eth) -> Self {
Self { eth }
}
}
#[async_trait]
impl<Eth> OtterscanServer for OtterscanApi<Eth>
where
Eth: EthApiServer,
{
/// Handler for `ots_hasCode`
async fn has_code(&self, address: Address, block_number: Option<BlockId>) -> RpcResult<bool> {
Err(internal_rpc_err("unimplemented"))
}
/// Handler for `ots_getApiLevel`
async fn get_api_level(&self) -> RpcResult<u64> {
Err(internal_rpc_err("unimplemented"))
}
/// Handler for `ots_getInternalOperations`
async fn get_internal_operations(&self, tx_hash: TxHash) -> RpcResult<Vec<InternalOperation>> {
Err(internal_rpc_err("unimplemented"))
}
/// Handler for `ots_getTransactionError`
async fn get_transaction_error(&self, tx_hash: TxHash) -> RpcResult<String> {
Err(internal_rpc_err("unimplemented"))
}
/// Handler for `ots_traceTransaction`
async fn trace_transaction(&self, tx_hash: TxHash) -> RpcResult<TraceEntry> {
Err(internal_rpc_err("unimplemented"))
}
/// Handler for `ots_getBlockDetails`
async fn get_block_details(
&self,
block_number: BlockNumberOrTag,
) -> RpcResult<Option<BlockDetails>> {
Err(internal_rpc_err("unimplemented"))
}
/// Handler for `getBlockDetailsByHash`
async fn get_block_details_by_hash(&self, block_hash: H256) -> RpcResult<Option<BlockDetails>> {
Err(internal_rpc_err("unimplemented"))
}
/// Handler for `getBlockTransactions`
async fn get_block_transactions(
&self,
block_number: BlockNumberOrTag,
page_number: usize,
page_size: usize,
) -> RpcResult<OtsBlockTransactions> {
Err(internal_rpc_err("unimplemented"))
}
/// Handler for `searchTransactionsBefore`
async fn search_transactions_before(
&self,
address: Address,
block_number: BlockNumberOrTag,
page_size: usize,
) -> RpcResult<TransactionsWithReceipts> {
Err(internal_rpc_err("unimplemented"))
}
/// Handler for `searchTransactionsAfter`
async fn search_transactions_after(
&self,
address: Address,
block_number: BlockNumberOrTag,
page_size: usize,
) -> RpcResult<TransactionsWithReceipts> {
Err(internal_rpc_err("unimplemented"))
}
/// Handler for `getTransactionBySenderAndNonce`
async fn get_transaction_by_sender_and_nonce(
&self,
sender: Address,
nonce: u64,
) -> RpcResult<Option<Transaction>> {
Err(internal_rpc_err("unimplemented"))
}
/// Handler for `getContractCreator`
async fn get_contract_creator(&self, address: Address) -> RpcResult<Option<ContractCreator>> {
Err(internal_rpc_err("unimplemented"))
}
}