Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ impl OckhamClient {
Ok(balance)
}

pub async fn get_transaction_count(
&self,
address: Address,
) -> Result<u64, Box<dyn std::error::Error>> {
let params = rpc_params![address];
let nonce: u64 = self.client.request("get_transaction_count", params).await?;
Ok(nonce)
}

pub async fn send_transaction(
&self,
nonce: u64,
Expand Down
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
committee,
storage.clone(),
tx_pool.clone(),
executor,
executor.clone(),
block_gas_limit,
);

Expand All @@ -82,6 +82,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let rpc_impl = OckhamRpcImpl::new(
storage.clone(),
tx_pool.clone(),
executor.clone(),
block_gas_limit,
bg_tx_sender,
);
Expand Down
138 changes: 138 additions & 0 deletions src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,18 @@ use crate::tx_pool::TxPool;
use crate::types::{Address, Block, Transaction, U256};
use jsonrpsee::core::{RpcResult, async_trait};
use jsonrpsee::proc_macros::rpc;
use serde::Deserialize;
use std::sync::Arc;

#[derive(Deserialize)]
pub struct CallRequest {
pub from: Option<Address>,
pub to: Option<Address>,
pub gas: Option<u64>,
pub gas_price: Option<U256>,
pub value: Option<U256>,
pub data: Option<crate::types::Bytes>,
}
#[rpc(server)]
pub trait OckhamRpc {
#[method(name = "get_block_by_hash")]
Expand All @@ -23,16 +33,32 @@ pub trait OckhamRpc {
#[method(name = "get_balance")]
fn get_balance(&self, address: Address) -> RpcResult<U256>;

#[method(name = "get_transaction_count")]
fn get_transaction_count(&self, address: Address) -> RpcResult<u64>;

#[method(name = "chain_id")]
fn chain_id(&self) -> RpcResult<u64>;

#[method(name = "suggest_base_fee")]
fn suggest_base_fee(&self) -> RpcResult<U256>;

#[method(name = "call")]
fn call(&self, request: CallRequest, _block: Option<String>) -> RpcResult<crate::types::Bytes>;

#[method(name = "estimate_gas")]
fn estimate_gas(&self, request: CallRequest, _block: Option<String>) -> RpcResult<u64>;

#[method(name = "get_code")]
fn get_code(&self, address: Address, _block: Option<String>) -> RpcResult<crate::types::Bytes>;

#[method(name = "get_block_by_number")]
fn get_block_by_number(&self, number: String) -> RpcResult<Option<Block>>;
}

pub struct OckhamRpcImpl {
storage: Arc<dyn Storage>,
tx_pool: Arc<TxPool>,
executor: crate::vm::Executor,
block_gas_limit: u64,
broadcast_sender: tokio::sync::mpsc::Sender<Transaction>,
}
Expand All @@ -41,12 +67,14 @@ impl OckhamRpcImpl {
pub fn new(
storage: Arc<dyn Storage>,
tx_pool: Arc<TxPool>,
executor: crate::vm::Executor,
block_gas_limit: u64,
broadcast_sender: tokio::sync::mpsc::Sender<Transaction>,
) -> Self {
Self {
storage,
tx_pool,
executor,
block_gas_limit,
broadcast_sender,
}
Expand Down Expand Up @@ -132,6 +160,18 @@ impl OckhamRpcServer for OckhamRpcImpl {
Ok(account.map(|a| a.balance).unwrap_or_default())
}

fn get_transaction_count(&self, address: Address) -> RpcResult<u64> {
let account = self.storage.get_account(&address).map_err(|e| {
jsonrpsee::types::ErrorObject::owned(
-32000,
format!("Storage error: {:?}", e),
None::<()>,
)
})?;

Ok(account.map(|a| a.nonce).unwrap_or_default())
}

fn chain_id(&self) -> RpcResult<u64> {
Ok(1337) // TODO: Config
}
Expand Down Expand Up @@ -186,4 +226,102 @@ impl OckhamRpcServer for OckhamRpcImpl {
Ok(parent_base_fee.saturating_sub(base_fee_decrease))
}
}

fn call(&self, request: CallRequest, _block: Option<String>) -> RpcResult<crate::types::Bytes> {
let caller = request.from.unwrap_or_default();
let value = request.value.unwrap_or_default();
let data = request.data.unwrap_or_default();
let gas = request.gas.unwrap_or(self.block_gas_limit);

let (_, output) = self
.executor
.execute_ephemeral(caller, request.to, value, data, gas, vec![])
.map_err(|e| {
jsonrpsee::types::ErrorObject::owned(
-32000,
format!("Execution Error: {:?}", e),
None::<()>,
)
})?;

Ok(crate::types::Bytes::from(output))
}

fn estimate_gas(&self, request: CallRequest, _block: Option<String>) -> RpcResult<u64> {
let caller = request.from.unwrap_or_default();
let value = request.value.unwrap_or_default();
let data = request.data.unwrap_or_default();
let gas = request.gas.unwrap_or(self.block_gas_limit);

let (gas_used, _) = self
.executor
.execute_ephemeral(caller, request.to, value, data, gas, vec![])
.map_err(|e| {
jsonrpsee::types::ErrorObject::owned(
-32000,
format!("Execution Error: {:?}", e),
None::<()>,
)
})?;

Ok(gas_used)
}

fn get_code(&self, address: Address, _block: Option<String>) -> RpcResult<crate::types::Bytes> {
let account = self.storage.get_account(&address).map_err(|e| {
jsonrpsee::types::ErrorObject::owned(
-32000,
format!("Storage Error: {:?}", e),
None::<()>,
)
})?;

if let Some(info) = account {
if let Some(code) = info.code {
Ok(code)
} else if info.code_hash != Hash::default() {
let code = self
.storage
.get_code(&info.code_hash)
.map_err(|e| {
jsonrpsee::types::ErrorObject::owned(
-32000,
format!("Storage Error: {:?}", e),
None::<()>,
)
})?
.unwrap_or_default();
Ok(code)
} else {
Ok(crate::types::Bytes::default())
}
} else {
Ok(crate::types::Bytes::default())
}
}

fn get_block_by_number(&self, number: String) -> RpcResult<Option<Block>> {
let view = if number == "latest" {
if let Some(state) = self.storage.get_consensus_state().unwrap_or(None) {
state.preferred_view
} else {
return Ok(None);
}
} else if let Some(stripped) = number.strip_prefix("0x") {
u64::from_str_radix(stripped, 16).unwrap_or(0)
} else {
number.parse::<u64>().unwrap_or(0)
};

if let Some(qc) = self.storage.get_qc(view).map_err(|e| {
jsonrpsee::types::ErrorObject::owned(-32000, format!("{:?}", e), None::<()>)
})? {
let block = self.storage.get_block(&qc.block_hash).map_err(|e| {
jsonrpsee::types::ErrorObject::owned(-32000, format!("{:?}", e), None::<()>)
})?;
Ok(block)
} else {
Ok(None)
}
}
}
63 changes: 63 additions & 0 deletions src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -549,4 +549,67 @@ impl Executor {

Ok(())
}

/// Execute a transaction ephemerally (no commit, for RPC 'call' and 'estimate_gas')
pub fn execute_ephemeral(
&self,
caller: Address,
to: Option<Address>,
value: U256,
data: crate::types::Bytes,
gas_limit: u64,
_access_list: Vec<crate::types::AccessListItem>, // Future proofing
) -> Result<(u64, Vec<u8>), ExecutionError> {
let mut db = self.state.lock().unwrap();

// Setup EVM
let mut evm = EVM::new();
evm.database(&mut *db);

// Env setup (similar to execute_block but for single tx)
// We might need 'block' info for env.block, use default or current pending?
// For accurate simulation, we should use the 'pending' block context or 'latest'.
//db.get_consensus_state() gives us head.
// For now, use defaults for BlockEnv.

let tx_env = &mut evm.env.tx;
tx_env.caller = caller;
tx_env.transact_to = if let Some(addr) = to {
TransactTo::Call(addr)
} else {
TransactTo::Create(CreateScheme::Create)
};
tx_env.data = data;
tx_env.value = value;
tx_env.gas_limit = gas_limit;
tx_env.gas_price = U256::ZERO; // Simulation usually 0 or free
tx_env.gas_priority_fee = None;
tx_env.nonce = None; // Ignore nonce for simulation

// Execute
let result_and_state = evm
.transact()
.map_err(|e| ExecutionError::Evm(format!("{:?}", e)))?;

let result = result_and_state.result;

match result {
ExecutionResult::Success {
gas_used, output, ..
} => {
let data = match output {
revm::primitives::Output::Call(b) => b.to_vec(),
revm::primitives::Output::Create(b, _) => b.to_vec(),
};
Ok((gas_used, data))
}
ExecutionResult::Revert { gas_used, output } => {
// For 'call', we often want the revert data too.
Ok((gas_used, output.to_vec()))
}
ExecutionResult::Halt { reason, .. } => {
Err(ExecutionError::Evm(format!("Halted: {:?}", reason)))
}
}
}
}
Loading