From ae10eaef968d83d7265477c95d9fb1e940e4986d Mon Sep 17 00:00:00 2001 From: Dmitryii Osipov Date: Tue, 28 Jan 2025 16:50:36 +0700 Subject: [PATCH] feat(ethexe): implement more rpc methods (#4470) --- Cargo.toml | 4 +- ethexe/common/Cargo.toml | 4 +- ethexe/common/src/db.rs | 3 +- ethexe/common/src/events/mirror.rs | 4 +- ethexe/common/src/events/mod.rs | 4 +- ethexe/common/src/events/router.rs | 4 +- ethexe/common/src/events/wvara.rs | 4 +- ethexe/common/src/gear.rs | 3 + ethexe/db/src/database.rs | 2 +- ethexe/rpc/Cargo.toml | 2 +- ethexe/rpc/src/apis/block.rs | 13 +++- ethexe/rpc/src/apis/code.rs | 63 ++++++++++++++++++++ ethexe/rpc/src/apis/mod.rs | 2 + ethexe/rpc/src/apis/program.rs | 96 +++++++++++++++++++++++++----- ethexe/rpc/src/lib.rs | 5 +- ethexe/runtime/common/src/state.rs | 1 + 16 files changed, 183 insertions(+), 31 deletions(-) create mode 100644 ethexe/rpc/src/apis/code.rs diff --git a/Cargo.toml b/Cargo.toml index 5d9165e9494..c47f9789453 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -309,10 +309,10 @@ ethexe-runtime-common = { path = "ethexe/runtime/common", default-features = fal ethexe-prometheus = { path = "ethexe/prometheus", default-features = false } ethexe-validator = { path = "ethexe/validator", default-features = false } ethexe-rpc = { path = "ethexe/rpc", default-features = false } -ethexe-common = { path = "ethexe/common" } +ethexe-common = { path = "ethexe/common", default-features = false } # Common executor between `sandbox-host` and `lazy-pages-fuzzer` -wasmi = { package = "wasmi", version = "0.38"} +wasmi = { package = "wasmi", version = "0.38" } # Substrate deps binary-merkle-tree = { version = "15.0.1", git = "https://github.com/gear-tech/polkadot-sdk.git", branch = "gear-polkadot-stable2409", default-features = false } diff --git a/ethexe/common/Cargo.toml b/ethexe/common/Cargo.toml index 9e0f167594c..65acb6142be 100644 --- a/ethexe/common/Cargo.toml +++ b/ethexe/common/Cargo.toml @@ -12,9 +12,11 @@ repository.workspace = true [dependencies] gear-core.workspace = true gprimitives = { workspace = true, features = ["serde"] } - parity-scale-codec.workspace = true derive_more.workspace = true hex.workspace = true anyhow.workspace = true serde.workspace = true + +[features] +std = ["gear-core/std", "gprimitives/serde"] diff --git a/ethexe/common/src/db.rs b/ethexe/common/src/db.rs index fe333e6a279..37db645b5ae 100644 --- a/ethexe/common/src/db.rs +++ b/ethexe/common/src/db.rs @@ -42,7 +42,8 @@ pub type Sum = ProgramId; /// NOTE: generic keys differs to Vara and have been chosen dependent on storage organization of ethexe. pub type ScheduledTask = gear_core::tasks::ScheduledTask; -#[derive(Debug, Clone, Default, Encode, Decode, serde::Serialize, PartialEq, Eq)] +#[derive(Debug, Clone, Default, Encode, Decode, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct BlockHeader { pub height: u32, pub timestamp: u64, diff --git a/ethexe/common/src/events/mirror.rs b/ethexe/common/src/events/mirror.rs index 473a004a222..103187af005 100644 --- a/ethexe/common/src/events/mirror.rs +++ b/ethexe/common/src/events/mirror.rs @@ -20,7 +20,6 @@ use alloc::vec::Vec; use gear_core::message::ReplyCode; use gprimitives::{ActorId, MessageId, H256}; use parity_scale_codec::{Decode, Encode}; -use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, PartialOrd, Ord)] pub enum Event { @@ -103,7 +102,8 @@ impl Event { } } -#[derive(Clone, Debug, Encode, Decode, Serialize, Deserialize)] +#[derive(Clone, Debug, Encode, Decode)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum RequestEvent { ExecutableBalanceTopUpRequested { value: u128, diff --git a/ethexe/common/src/events/mod.rs b/ethexe/common/src/events/mod.rs index f154817bfb8..a631df269d6 100644 --- a/ethexe/common/src/events/mod.rs +++ b/ethexe/common/src/events/mod.rs @@ -18,7 +18,6 @@ use gprimitives::ActorId; use parity_scale_codec::{Decode, Encode}; -use serde::{Deserialize, Serialize}; mod mirror; mod router; @@ -73,7 +72,8 @@ impl From for BlockEvent { } } -#[derive(Clone, Debug, Encode, Decode, Serialize, Deserialize)] +#[derive(Clone, Debug, Encode, Decode)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum BlockRequestEvent { Router(RouterRequestEvent), Mirror { diff --git a/ethexe/common/src/events/router.rs b/ethexe/common/src/events/router.rs index 68985642f54..ebb8f7c95a8 100644 --- a/ethexe/common/src/events/router.rs +++ b/ethexe/common/src/events/router.rs @@ -18,7 +18,6 @@ use gprimitives::{ActorId, CodeId, H256}; use parity_scale_codec::{Decode, Encode}; -use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, PartialOrd, Ord)] pub enum Event { @@ -72,7 +71,8 @@ impl Event { } } -#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum RequestEvent { CodeValidationRequested { code_id: CodeId, diff --git a/ethexe/common/src/events/wvara.rs b/ethexe/common/src/events/wvara.rs index 22b6a916cfe..e459ceb73b4 100644 --- a/ethexe/common/src/events/wvara.rs +++ b/ethexe/common/src/events/wvara.rs @@ -18,7 +18,6 @@ use gprimitives::{ActorId, U256}; use parity_scale_codec::{Decode, Encode}; -use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, PartialOrd, Ord)] pub enum Event { @@ -43,7 +42,8 @@ impl Event { } } -#[derive(Clone, Debug, Encode, Decode, Serialize, Deserialize)] +#[derive(Clone, Debug, Encode, Decode)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum RequestEvent { Transfer { /// Never router, wvara or zero address. diff --git a/ethexe/common/src/gear.rs b/ethexe/common/src/gear.rs index 287df1b0ef4..e23d79079af 100644 --- a/ethexe/common/src/gear.rs +++ b/ethexe/common/src/gear.rs @@ -100,6 +100,7 @@ pub struct ComputationSettings { } #[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct Message { pub id: MessageId, pub destination: ActorId, @@ -130,6 +131,7 @@ pub struct ProtocolData { } #[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct StateTransition { pub actor_id: ActorId, pub new_state_hash: H256, @@ -147,6 +149,7 @@ pub struct ValidationSettings { } #[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct ValueClaim { pub message_id: MessageId, pub destination: ActorId, diff --git a/ethexe/db/src/database.rs b/ethexe/db/src/database.rs index a714df1652c..dc2c1d99d64 100644 --- a/ethexe/db/src/database.rs +++ b/ethexe/db/src/database.rs @@ -340,7 +340,7 @@ impl CodesStorage for Database { self.kv .iter_prefix(&key_prefix) - .map(|#[allow(unused_variables)] (key, code_id)| { + .map(|(key, code_id)| { let (split_key_prefix, program_id) = key.split_at(key_prefix.len()); debug_assert_eq!(split_key_prefix, key_prefix); let program_id = diff --git a/ethexe/rpc/Cargo.toml b/ethexe/rpc/Cargo.toml index e1a6403f7e5..0757b75a0af 100644 --- a/ethexe/rpc/Cargo.toml +++ b/ethexe/rpc/Cargo.toml @@ -23,7 +23,7 @@ hyper = { workspace = true, features = ["server"] } log.workspace = true parity-scale-codec.workspace = true hex.workspace = true -ethexe-common.workspace = true +ethexe-common = { workspace = true, features = ["std"] } ethexe-runtime-common = { workspace = true, features = ["std"] } sp-core = { workspace = true, features = ["serde"] } gear-core = { workspace = true, features = ["std"] } diff --git a/ethexe/rpc/src/apis/block.rs b/ethexe/rpc/src/apis/block.rs index 70bd33753b3..4d5653e8f66 100644 --- a/ethexe/rpc/src/apis/block.rs +++ b/ethexe/rpc/src/apis/block.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . use crate::{common::block_header_at_or_latest, errors}; -use ethexe_common::events::BlockRequestEvent; +use ethexe_common::{events::BlockRequestEvent, gear::StateTransition}; use ethexe_db::{BlockHeader, BlockMetaStorage, Database}; use gprimitives::H256; use jsonrpsee::{ @@ -36,6 +36,9 @@ pub trait Block { #[method(name = "block_events")] async fn block_events(&self, block_hash: Option) -> RpcResult>; + + #[method(name = "block_outcome")] + async fn block_outcome(&self, block_hash: Option) -> RpcResult>; } #[derive(Clone)] @@ -70,4 +73,12 @@ impl BlockServer for BlockApi { .block_events(block_hash) .ok_or_else(|| errors::db("Block events weren't found")) } + + async fn block_outcome(&self, hash: Option) -> RpcResult> { + let block_hash = block_header_at_or_latest(&self.db, hash)?.0; + + self.db + .block_outcome(block_hash) + .ok_or_else(|| errors::db("Block outcome wasn't found")) + } } diff --git a/ethexe/rpc/src/apis/code.rs b/ethexe/rpc/src/apis/code.rs new file mode 100644 index 00000000000..960076021f3 --- /dev/null +++ b/ethexe/rpc/src/apis/code.rs @@ -0,0 +1,63 @@ +// This file is part of Gear. +// +// Copyright (C) 2024-2025 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::errors; +use ethexe_db::{CodesStorage, Database}; +use gprimitives::H256; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + proc_macros::rpc, +}; +use parity_scale_codec::Encode; +use sp_core::Bytes; + +#[rpc(server)] +pub trait Code { + #[method(name = "code_getOriginal")] + async fn get_original_code(&self, id: H256) -> RpcResult; + + #[method(name = "code_getInstrumented")] + async fn get_instrumented_code(&self, runtime_id: u32, code_id: H256) -> RpcResult; +} + +pub struct CodeApi { + db: Database, +} + +impl CodeApi { + pub fn new(db: Database) -> Self { + Self { db } + } +} + +#[async_trait] +impl CodeServer for CodeApi { + async fn get_original_code(&self, id: H256) -> RpcResult { + self.db + .original_code(id.into()) + .map(|bytes| bytes.encode().into()) + .ok_or_else(|| errors::db("Failed to get code by supplied id")) + } + + async fn get_instrumented_code(&self, runtime_id: u32, code_id: H256) -> RpcResult { + self.db + .instrumented_code(runtime_id, code_id.into()) + .map(|bytes| bytes.encode().into()) + .ok_or_else(|| errors::db("Failed to get code by supplied id")) + } +} diff --git a/ethexe/rpc/src/apis/mod.rs b/ethexe/rpc/src/apis/mod.rs index eb86f52f66d..ebe699a238c 100644 --- a/ethexe/rpc/src/apis/mod.rs +++ b/ethexe/rpc/src/apis/mod.rs @@ -17,9 +17,11 @@ // along with this program. If not, see . mod block; +mod code; mod dev; mod program; pub use block::{BlockApi, BlockServer}; +pub use code::{CodeApi, CodeServer}; pub use dev::{DevApi, DevServer}; pub use program::{ProgramApi, ProgramServer}; diff --git a/ethexe/rpc/src/apis/program.rs b/ethexe/rpc/src/apis/program.rs index 7ecdbd5c65e..a0527c14e9f 100644 --- a/ethexe/rpc/src/apis/program.rs +++ b/ethexe/rpc/src/apis/program.rs @@ -20,7 +20,8 @@ use crate::{common::block_header_at_or_latest, errors}; use ethexe_db::{CodesStorage, Database}; use ethexe_processor::Processor; use ethexe_runtime_common::state::{ - HashOf, Mailbox, MemoryPages, MessageQueue, ProgramState, Storage, Waitlist, + DispatchStash, HashOf, Mailbox, MemoryPages, MessageQueue, Program, ProgramState, Storage, + Waitlist, }; use gear_core::message::ReplyInfo; use gprimitives::{H160, H256}; @@ -29,8 +30,20 @@ use jsonrpsee::{ proc_macros::rpc, }; use parity_scale_codec::Encode; +use serde::{Deserialize, Serialize}; use sp_core::Bytes; +#[derive(Clone, Serialize, Deserialize)] +pub struct FullProgramState { + pub program: Program, + pub queue: Option, + pub waitlist: Option, + pub stash: Option, + pub mailbox: Option, + pub balance: u128, + pub executable_balance: u128, +} + #[rpc(server)] pub trait Program { #[method(name = "program_calculateReplyForHandle")] @@ -55,15 +68,21 @@ pub trait Program { #[method(name = "program_readQueue")] async fn read_queue(&self, hash: H256) -> RpcResult; + #[method(name = "program_readWaitlist")] + async fn read_waitlist(&self, hash: H256) -> RpcResult; + + #[method(name = "program_readStash")] + async fn read_stash(&self, hash: H256) -> RpcResult; + #[method(name = "program_readMailbox")] async fn read_mailbox(&self, hash: H256) -> RpcResult; + #[method(name = "program_readFullState")] + async fn read_full_state(&self, hash: H256) -> RpcResult; + #[method(name = "program_readPages")] async fn read_pages(&self, hash: H256) -> RpcResult; - #[method(name = "program_readWaitlist")] - async fn read_waitlist(&self, hash: H256) -> RpcResult; - #[method(name = "program_readPageData")] async fn read_page_data(&self, hash: H256) -> RpcResult; } @@ -76,6 +95,22 @@ impl ProgramApi { pub fn new(db: Database) -> Self { Self { db } } + + fn read_queue(&self, hash: H256) -> Option { + self.db.read_queue(unsafe { HashOf::new(hash) }) + } + + fn read_waitlist(&self, hash: H256) -> Option { + self.db.read_waitlist(unsafe { HashOf::new(hash) }) + } + + fn read_stash(&self, hash: H256) -> Option { + self.db.read_stash(unsafe { HashOf::new(hash) }) + } + + fn read_mailbox(&self, hash: H256) -> Option { + self.db.read_mailbox(unsafe { HashOf::new(hash) }) + } } #[async_trait] @@ -130,30 +165,61 @@ impl ProgramServer for ProgramApi { } async fn read_queue(&self, hash: H256) -> RpcResult { - self.db - .read_queue(unsafe { HashOf::new(hash) }) + self.read_queue(hash) .ok_or_else(|| errors::db("Failed to read queue by hash")) } + async fn read_waitlist(&self, hash: H256) -> RpcResult { + self.read_waitlist(hash) + .ok_or_else(|| errors::db("Failed to read waitlist by hash")) + } + + async fn read_stash(&self, hash: H256) -> RpcResult { + self.read_stash(hash) + .ok_or_else(|| errors::db("Failed to read stash by hash")) + } + async fn read_mailbox(&self, hash: H256) -> RpcResult { - self.db - .read_mailbox(unsafe { HashOf::new(hash) }) + self.read_mailbox(hash) .ok_or_else(|| errors::db("Failed to read mailbox by hash")) } + async fn read_full_state(&self, hash: H256) -> RpcResult { + let Some(ProgramState { + program, + queue_hash, + waitlist_hash, + stash_hash, + mailbox_hash, + balance, + executable_balance, + }) = self.db.read_state(hash) + else { + return Err(errors::db("Failed to read state by hash")); + }; + + let queue = queue_hash.query(&self.db).ok(); + let waitlist = waitlist_hash.query(&self.db).ok(); + let stash = stash_hash.query(&self.db).ok(); + let mailbox = mailbox_hash.query(&self.db).ok(); + + Ok(FullProgramState { + program, + queue, + waitlist, + stash, + mailbox, + balance, + executable_balance, + }) + } + async fn read_pages(&self, hash: H256) -> RpcResult { self.db .read_pages(unsafe { HashOf::new(hash) }) .ok_or_else(|| errors::db("Failed to read pages by hash")) } - // TODO: read the whole program state in a single query - async fn read_waitlist(&self, hash: H256) -> RpcResult { - self.db - .read_waitlist(unsafe { HashOf::new(hash) }) - .ok_or_else(|| errors::db("Failed to read waitlist by hash")) - } - async fn read_page_data(&self, hash: H256) -> RpcResult { self.db .read_page_data(unsafe { HashOf::new(hash) }) diff --git a/ethexe/rpc/src/lib.rs b/ethexe/rpc/src/lib.rs index c923670b225..26b960b5795 100644 --- a/ethexe/rpc/src/lib.rs +++ b/ethexe/rpc/src/lib.rs @@ -17,7 +17,9 @@ // along with this program. If not, see . use anyhow::{anyhow, Result}; -use apis::{BlockApi, BlockServer, DevApi, DevServer, ProgramApi, ProgramServer}; +use apis::{ + BlockApi, BlockServer, CodeApi, CodeServer, DevApi, DevServer, ProgramApi, ProgramServer, +}; use ethexe_db::Database; use ethexe_observer::MockBlobReader; use futures::FutureExt; @@ -89,6 +91,7 @@ impl RpcService { let mut module = JsonrpcModule::new(()); module.merge(ProgramServer::into_rpc(ProgramApi::new(self.db.clone())))?; module.merge(BlockServer::into_rpc(BlockApi::new(self.db.clone())))?; + module.merge(CodeServer::into_rpc(CodeApi::new(self.db.clone())))?; if self.config.dev { module.merge(DevServer::into_rpc(DevApi::new( diff --git a/ethexe/runtime/common/src/state.rs b/ethexe/runtime/common/src/state.rs index 101843b57b3..e7c02fdc38c 100644 --- a/ethexe/runtime/common/src/state.rs +++ b/ethexe/runtime/common/src/state.rs @@ -700,6 +700,7 @@ impl Waitlist { } #[derive(Clone, Default, Debug, Encode, Decode, PartialEq, Eq, derive_more::Into)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct DispatchStash(BTreeMap)>>); impl DispatchStash {