@@ -86,6 +86,8 @@ async fn handle_json_rpc(
8686 let result = match req. method . as_str ( ) {
8787 // Standard Namespace
8888 "eth_blockNumber" => eth_block_number ( & state) . await ,
89+ "eth_getBlockByNumber" => eth_get_block_by_number ( & state, req. params ) . await ,
90+ "eth_getTransactionByHash" => eth_get_transaction_by_hash ( & state, req. params ) . await ,
8991 "eth_getBalance" => eth_get_balance ( & state, req. params ) . await ,
9092 "eth_sendRawTransaction" => eth_send_raw_transaction ( & state, req. params ) . await ,
9193
@@ -131,6 +133,181 @@ async fn eth_block_number(state: &RpcState) -> Result<Value, JsonRpcError> {
131133 Ok ( json ! ( format!( "0x{:x}" , height) ) )
132134}
133135
136+ async fn eth_get_block_by_number ( state : & RpcState , params : Option < Value > ) -> Result < Value , JsonRpcError > {
137+ let params = params. ok_or ( JsonRpcError {
138+ code : -32602 ,
139+ message : "Invalid params" . to_string ( ) ,
140+ data : None ,
141+ } ) ?;
142+
143+ let args = params. as_array ( ) . ok_or ( JsonRpcError {
144+ code : -32602 ,
145+ message : "Params must be an array" . to_string ( ) ,
146+ data : None ,
147+ } ) ?;
148+
149+ if args. is_empty ( ) {
150+ return Err ( JsonRpcError {
151+ code : -32602 ,
152+ message : "Missing block number" . to_string ( ) ,
153+ data : None ,
154+ } ) ;
155+ }
156+
157+ let block_param = args[ 0 ] . as_str ( ) . ok_or ( JsonRpcError {
158+ code : -32602 ,
159+ message : "Block number must be a string" . to_string ( ) ,
160+ data : None ,
161+ } ) ?;
162+
163+ let include_txs = if args. len ( ) > 1 {
164+ args[ 1 ] . as_bool ( ) . unwrap_or ( false )
165+ } else {
166+ false
167+ } ;
168+
169+ let height = if block_param == "latest" {
170+ state. blockchain . height ( )
171+ } else if block_param == "earliest" {
172+ 0
173+ } else if block_param == "pending" {
174+ state. blockchain . height ( ) // TODO: Support pending block
175+ } else {
176+ let hex = block_param. strip_prefix ( "0x" ) . unwrap_or ( block_param) ;
177+ u64:: from_str_radix ( hex, 16 ) . map_err ( |_| JsonRpcError {
178+ code : -32602 ,
179+ message : "Invalid block number format" . to_string ( ) ,
180+ data : None ,
181+ } ) ?
182+ } ;
183+
184+ if let Some ( block) = state. blockchain . get_block ( height) {
185+ let transactions = if include_txs {
186+ let txs: Vec < Value > = block. transactions . iter ( ) . enumerate ( ) . map ( |( i, tx) | {
187+ json ! ( {
188+ "hash" : format!( "0x{}" , hex:: encode( tx. hash( ) . as_bytes( ) ) ) ,
189+ "nonce" : format!( "0x{:x}" , tx. nonce) ,
190+ "blockHash" : format!( "0x{}" , hex:: encode( block. hash( ) . as_bytes( ) ) ) ,
191+ "blockNumber" : format!( "0x{:x}" , block. header. height) ,
192+ "transactionIndex" : format!( "0x{:x}" , i) ,
193+ "from" : format!( "0x{}" , hex:: encode( tx. from. as_bytes( ) ) ) ,
194+ "to" : format!( "0x{}" , hex:: encode( tx. to. as_bytes( ) ) ) ,
195+ "value" : format!( "0x{:x}" , tx. amount) ,
196+ "gas" : format!( "0x{:x}" , tx. gas_limit) ,
197+ "gasPrice" : format!( "0x{:x}" , tx. gas_price) ,
198+ "input" : format!( "0x{}" , hex:: encode( & tx. data) ) ,
199+ } )
200+ } ) . collect ( ) ;
201+ json ! ( txs)
202+ } else {
203+ let tx_hashes: Vec < String > = block. transactions . iter ( )
204+ . map ( |tx| format ! ( "0x{}" , hex:: encode( tx. hash( ) . as_bytes( ) ) ) )
205+ . collect ( ) ;
206+ json ! ( tx_hashes)
207+ } ;
208+
209+ Ok ( json ! ( {
210+ "number" : format!( "0x{:x}" , block. header. height) ,
211+ "hash" : format!( "0x{}" , hex:: encode( block. hash( ) . as_bytes( ) ) ) ,
212+ "parentHash" : format!( "0x{}" , hex:: encode( block. header. prev_hash. as_bytes( ) ) ) ,
213+ "nonce" : "0x0000000000000000" , // TODO: Use work/nonce
214+ "sha3Uncles" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" , // Empty uncle hash
215+ "logsBloom" : "0x00" , // TODO: Bloom filter
216+ "transactionsRoot" : format!( "0x{}" , hex:: encode( block. header. tx_root. as_bytes( ) ) ) ,
217+ "stateRoot" : format!( "0x{}" , hex:: encode( block. header. state_root. as_bytes( ) ) ) ,
218+ "receiptsRoot" : "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" , // Empty receipts root
219+ "miner" : format!( "0x{}" , hex:: encode( block. header. proposer. as_bytes( ) ) ) ,
220+ "difficulty" : "0x1" ,
221+ "totalDifficulty" : format!( "0x{:x}" , block. header. height) , // Simplified
222+ "extraData" : "0x" ,
223+ "size" : format!( "0x{:x}" , 1000 ) , // TODO: Real size
224+ "gasLimit" : "0x1fffffffffffff" ,
225+ "gasUsed" : "0x0" ,
226+ "timestamp" : format!( "0x{:x}" , block. header. timestamp) ,
227+ "transactions" : transactions,
228+ "uncles" : [ ]
229+ } ) )
230+ } else {
231+ Ok ( Value :: Null )
232+ }
233+ }
234+
235+ async fn eth_get_transaction_by_hash ( state : & RpcState , params : Option < Value > ) -> Result < Value , JsonRpcError > {
236+ let params = params. ok_or ( JsonRpcError {
237+ code : -32602 ,
238+ message : "Invalid params" . to_string ( ) ,
239+ data : None ,
240+ } ) ?;
241+
242+ let args = params. as_array ( ) . ok_or ( JsonRpcError {
243+ code : -32602 ,
244+ message : "Params must be an array" . to_string ( ) ,
245+ data : None ,
246+ } ) ?;
247+
248+ if args. is_empty ( ) {
249+ return Err ( JsonRpcError {
250+ code : -32602 ,
251+ message : "Missing transaction hash" . to_string ( ) ,
252+ data : None ,
253+ } ) ;
254+ }
255+
256+ let tx_hash_str = args[ 0 ] . as_str ( ) . ok_or ( JsonRpcError {
257+ code : -32602 ,
258+ message : "Transaction hash must be a string" . to_string ( ) ,
259+ data : None ,
260+ } ) ?;
261+
262+ let tx_hash_hex = tx_hash_str. strip_prefix ( "0x" ) . unwrap_or ( tx_hash_str) ;
263+ let tx_hash_bytes = hex:: decode ( tx_hash_hex) . map_err ( |_| JsonRpcError {
264+ code : -32602 ,
265+ message : "Invalid hex encoding" . to_string ( ) ,
266+ data : None ,
267+ } ) ?;
268+
269+ if tx_hash_bytes. len ( ) != 32 {
270+ return Err ( JsonRpcError {
271+ code : -32602 ,
272+ message : "Transaction hash must be 32 bytes" . to_string ( ) ,
273+ data : None ,
274+ } ) ;
275+ }
276+
277+ let mut hash = [ 0u8 ; 32 ] ;
278+ hash. copy_from_slice ( & tx_hash_bytes) ;
279+ let target_hash = bitcell_crypto:: Hash256 :: from ( hash) ;
280+
281+ // Search in blockchain (inefficient linear scan for now, need index later)
282+ let height = state. blockchain . height ( ) ;
283+ // Scan last 100 blocks for efficiency in this demo
284+ let start_height = if height > 100 { height - 100 } else { 0 } ;
285+
286+ for h in ( start_height..=height) . rev ( ) {
287+ if let Some ( block) = state. blockchain . get_block ( h) {
288+ for ( i, tx) in block. transactions . iter ( ) . enumerate ( ) {
289+ if tx. hash ( ) == target_hash {
290+ return Ok ( json ! ( {
291+ "hash" : format!( "0x{}" , hex:: encode( tx. hash( ) . as_bytes( ) ) ) ,
292+ "nonce" : format!( "0x{:x}" , tx. nonce) ,
293+ "blockHash" : format!( "0x{}" , hex:: encode( block. hash( ) . as_bytes( ) ) ) ,
294+ "blockNumber" : format!( "0x{:x}" , block. header. height) ,
295+ "transactionIndex" : format!( "0x{:x}" , i) ,
296+ "from" : format!( "0x{}" , hex:: encode( tx. from. as_bytes( ) ) ) ,
297+ "to" : format!( "0x{}" , hex:: encode( tx. to. as_bytes( ) ) ) ,
298+ "value" : format!( "0x{:x}" , tx. amount) ,
299+ "gas" : format!( "0x{:x}" , tx. gas_limit) ,
300+ "gasPrice" : format!( "0x{:x}" , tx. gas_price) ,
301+ "input" : format!( "0x{}" , hex:: encode( & tx. data) ) ,
302+ } ) ) ;
303+ }
304+ }
305+ }
306+ }
307+
308+ Ok ( Value :: Null )
309+ }
310+
134311async fn eth_get_balance ( state : & RpcState , params : Option < Value > ) -> Result < Value , JsonRpcError > {
135312 let params = params. ok_or ( JsonRpcError {
136313 code : -32602 ,
0 commit comments