diff --git a/Cargo.lock b/Cargo.lock index 0b0ffdea4..95940c774 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -767,9 +767,6 @@ name = "bytes" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" -dependencies = [ - "serde", -] [[package]] name = "bytesize" @@ -913,7 +910,7 @@ dependencies = [ "bs58 0.5.1", "ed25519-dalek", "hex", - "libp2p", + "libp2p-identity", "semver", "serde", "serde_json", @@ -962,6 +959,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "calimero-sdk-near" +version = "0.1.0" +dependencies = [ + "calimero-primitives", + "calimero-sdk", + "near-account-id", + "serde", + "serde_json", + "serde_with", + "thiserror", +] + [[package]] name = "calimero-server" version = "0.1.0" @@ -1564,16 +1574,15 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.2" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if 1.0.0", "cpufeatures", "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "platforms", "rand_core 0.6.4", "rustc_version", "subtle", @@ -2332,6 +2341,14 @@ dependencies = [ "byteorder", ] +[[package]] +name = "gen-ext" +version = "0.1.0" +dependencies = [ + "calimero-sdk", + "calimero-sdk-near", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -3261,7 +3278,6 @@ dependencies = [ "quick-protobuf", "rand 0.8.5", "rw-stream-sink", - "serde", "smallvec", "thiserror", "tracing", @@ -3333,7 +3349,6 @@ dependencies = [ "quick-protobuf-codec 0.3.1", "rand 0.8.5", "regex", - "serde", "sha2", "smallvec", "tracing", @@ -3365,9 +3380,9 @@ dependencies = [ [[package]] name = "libp2p-identity" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "999ec70441b2fb35355076726a6bc466c932e9bdc66f6a11c6c0aa17c7ab9be0" +checksum = "55cca1eb2bc1fd29f099f3daaab7effd01e1a54b7c577d0ed082521034d912e8" dependencies = [ "bs58 0.5.1", "ed25519-dalek", @@ -3403,7 +3418,6 @@ dependencies = [ "quick-protobuf", "quick-protobuf-codec 0.3.1", "rand 0.8.5", - "serde", "sha2", "smallvec", "thiserror", @@ -3984,7 +3998,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "076d548d76a0e2a0d4ab471d0b1c36c577786dfc4471242035d97a12a735c492" dependencies = [ "core2", - "serde", "unsigned-varint 0.7.2", ] @@ -5008,12 +5021,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" -[[package]] -name = "platforms" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" - [[package]] name = "polling" version = "3.6.0" @@ -6177,11 +6184,11 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.7.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" +checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" dependencies = [ - "base64 0.21.7", + "base64 0.22.0", "chrono", "hex", "indexmap 1.9.3", @@ -6195,9 +6202,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.7.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" +checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" dependencies = [ "darling", "proc-macro2", @@ -8224,9 +8231,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] diff --git a/Cargo.toml b/Cargo.toml index e09105b21..b69e9aab8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "./crates/primitives", "./crates/runtime", "./crates/sdk", + "./crates/sdk/libs/near", "./crates/sdk/macros", "./crates/server", "./crates/server-primitives", @@ -23,6 +24,7 @@ members = [ "./apps/kv-store", "./apps/only-peers", + "./apps/gen-ext", "./contracts/package-manager", ] @@ -46,8 +48,10 @@ futures-util = "0.3.30" hex = "0.4.3" libp2p = "0.53.2" libp2p-stream = "0.1.0-alpha.1" +libp2p-identity = "0.2.9" multiaddr = "0.18.1" # multibase = "0.9.1" +near-account-id = "1.0.0" near-jsonrpc-client = "0.8.0" near-jsonrpc-primitives = "0.20.0" near-primitives = "0.20.0" @@ -67,6 +71,7 @@ sha3 = "0.10.8" semver = "1.0.22" serde = "1.0.196" serde_json = "1.0.113" +serde_with = "3.8.1" syn = "2.0" thiserror = "1.0.56" tokio = "1.35.1" diff --git a/apps/gen-ext/Cargo.toml b/apps/gen-ext/Cargo.toml new file mode 100644 index 000000000..a29fe22be --- /dev/null +++ b/apps/gen-ext/Cargo.toml @@ -0,0 +1,14 @@ +[package] +authors.workspace = true +edition.workspace = true +license.workspace = true +name = "gen-ext" +repository.workspace = true +version = "0.1.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +calimero-sdk = {path = "../../crates/sdk"} +calimero-sdk-near = { path = "../../crates/sdk/libs/near" } diff --git a/apps/gen-ext/build.sh b/apps/gen-ext/build.sh new file mode 100755 index 000000000..8a9e420d8 --- /dev/null +++ b/apps/gen-ext/build.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -e + +cd "$(dirname $0)" + +TARGET="${CARGO_TARGET_DIR:-../../target}" + +rustup target add wasm32-unknown-unknown + +cargo build --target wasm32-unknown-unknown --profile app-release + +mkdir -p res + +cp $TARGET/wasm32-unknown-unknown/app-release/gen_ext.wasm ./res/ + +if command -v wasm-opt > /dev/null; then + wasm-opt -Oz ./res/gen_ext.wasm -o ./res/gen_ext.wasm +fi diff --git a/apps/gen-ext/src/lib.rs b/apps/gen-ext/src/lib.rs new file mode 100644 index 000000000..0ae5c8766 --- /dev/null +++ b/apps/gen-ext/src/lib.rs @@ -0,0 +1,27 @@ +use calimero_sdk::app; +use calimero_sdk::borsh::{BorshDeserialize, BorshSerialize}; +use calimero_sdk_near::query::RpcQueryRequest; +use calimero_sdk_near::views::QueryRequest; +use calimero_sdk_near::Client; + +#[app::state] +#[derive(Default, BorshSerialize, BorshDeserialize)] +#[borsh(crate = "calimero_sdk::borsh")] +struct GenExt; + +#[app::logic] +impl GenExt { + pub fn view_account(&mut self, account_id: &str, block_height: u64) -> String { + let client = Client::testnet(); + let request = RpcQueryRequest { + block_id: calimero_sdk_near::BlockId::Height(block_height), + request: QueryRequest::ViewAccount { + account_id: account_id.parse().unwrap(), + }, + }; + match client.call(request) { + Ok(r) => format!("{:?}", r), + Err(e) => format!("{:?}", e), + } + } +} diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 85449581f..3157d63eb 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -9,7 +9,7 @@ license.workspace = true [dependencies] bs58.workspace = true ed25519-dalek.workspace = true -libp2p = { workspace = true, features = ["serde"] } +libp2p-identity = { workspace = true, features = ["peerid", "serde"] } semver = { workspace = true, features = ["serde"] } serde = { workspace = true, features = ["derive"] } serde_json.workspace = true diff --git a/crates/primitives/src/common.rs b/crates/primitives/src/common.rs index 0f1182e32..91a6e7f2d 100644 --- a/crates/primitives/src/common.rs +++ b/crates/primitives/src/common.rs @@ -1,3 +1,23 @@ +use serde::{Deserialize, Serialize}; + pub const fn bool_true() -> bool { true } + +#[derive(Serialize, Deserialize)] +#[serde(remote = "Result")] +pub enum ResultAlt { + #[serde(rename = "result")] + Ok(T), + #[serde(rename = "error")] + Err(E), +} + +impl From> for Result { + fn from(result: ResultAlt) -> Self { + match result { + ResultAlt::Ok(value) => Ok(value), + ResultAlt::Err(err) => Err(err), + } + } +} diff --git a/crates/primitives/src/events.rs b/crates/primitives/src/events.rs index beedc7495..7159ee04b 100644 --- a/crates/primitives/src/events.rs +++ b/crates/primitives/src/events.rs @@ -34,7 +34,7 @@ pub struct ExecutedTransactionPayload { #[derive(Clone, Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct PeerJoinedPayload { - pub peer_id: libp2p::PeerId, + pub peer_id: libp2p_identity::PeerId, } #[derive(Clone, Serialize, Deserialize, Debug)] diff --git a/crates/primitives/src/identity.rs b/crates/primitives/src/identity.rs index 2d2aa5e9f..5ad948fd9 100644 --- a/crates/primitives/src/identity.rs +++ b/crates/primitives/src/identity.rs @@ -124,7 +124,7 @@ pub mod serde_signing_key { pub mod serde_identity { use std::fmt; - use libp2p::identity::Keypair; + use libp2p_identity::Keypair; use serde::de::{self, MapAccess}; use serde::ser::{self, SerializeMap}; use serde::{Deserializer, Serializer}; diff --git a/crates/runtime/examples/fetch.rs b/crates/runtime/examples/fetch.rs index a6baa3c0a..fe625cdc6 100644 --- a/crates/runtime/examples/fetch.rs +++ b/crates/runtime/examples/fetch.rs @@ -2,7 +2,7 @@ use calimero_runtime::{logic, run, store, Constraint}; use serde_json::json; fn main() -> eyre::Result<()> { - let file = include_bytes!("../../../apps/only-peers/res/only_peers.wasm"); + let file = include_bytes!("../../../apps/gen-ext/res/gen_ext.wasm"); let mut storage = store::InMemoryStorage::default(); @@ -22,9 +22,12 @@ fn main() -> eyre::Result<()> { }; let cx = logic::VMContext { - input: serde_json::to_vec(&json!({}))?, + input: serde_json::to_vec(&json!({ + "block_height": 167345193, + "account_id": "nearkat.testnet", + }))?, }; - let get_outcome = run(file, "fetch", cx, &mut storage, &limits)?; + let get_outcome = run(file, "view_account", cx, &mut storage, &limits)?; let returns = String::from_utf8(get_outcome.returns.unwrap().unwrap()).unwrap(); println!("{returns}"); diff --git a/crates/runtime/src/logic.rs b/crates/runtime/src/logic.rs index ecb1a4fa4..5ffe6052f 100644 --- a/crates/runtime/src/logic.rs +++ b/crates/runtime/src/logic.rs @@ -321,7 +321,11 @@ impl<'a> VMHostFunctions<'a> { let url = self.get_string(url_ptr, url_len)?; let method = self.get_string(method_ptr, method_len)?; let headers = self.read_guest_memory(headers_ptr, headers_len)?; - let headers: Vec<(String, String)> = borsh::from_slice(&headers).unwrap(); // safety: headers are coming from an inner source. Safe to deserialize. + +// Safety: The `fetch` function cannot be directly called by applications. +// Therefore, the headers are generated exclusively by our code, ensuring +// that it is safe to deserialize them. + let headers: Vec<(String, String)> = borsh::from_slice(&headers).unwrap(); let body = self.read_guest_memory(body_ptr, body_len)?; let mut request = ureq::request(&method, &url); diff --git a/crates/sdk/libs/near/Cargo.toml b/crates/sdk/libs/near/Cargo.toml new file mode 100644 index 000000000..1a8c8db74 --- /dev/null +++ b/crates/sdk/libs/near/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "calimero-sdk-near" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +near-account-id = { workspace = true, features = ["serde"]} +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true +serde_with = {workspace = true, features = ["base64"]} +thiserror.workspace = true + +calimero-sdk = { path = "../../" } +calimero-primitives = { path = "../../../primitives" } \ No newline at end of file diff --git a/crates/sdk/libs/near/src/error.rs b/crates/sdk/libs/near/src/error.rs new file mode 100644 index 000000000..6de9a0b9b --- /dev/null +++ b/crates/sdk/libs/near/src/error.rs @@ -0,0 +1,39 @@ +use serde_json::Value; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error(transparent)] + JsonError(#[from] serde_json::Error), + + #[error("Failed to fetch: {0}")] + FetchError(String), + + #[error("Server error: {0}")] + ServerError(RpcError), +} + +#[derive(Debug, serde::Deserialize, Clone, PartialEq)] +#[serde(tag = "name", content = "cause", rename_all = "SCREAMING_SNAKE_CASE")] +pub enum RpcErrorKind { + RequestValidationError(RpcRequestValidationErrorKind), + HandlerError(R), + InternalError(Value), +} + +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)] +#[serde(tag = "name", content = "info", rename_all = "SCREAMING_SNAKE_CASE")] +pub enum RpcRequestValidationErrorKind { + MethodNotFound { method_name: String }, + ParseError { error_message: String }, +} + +#[derive(Debug, serde::Deserialize, Clone, PartialEq)] +#[serde(deny_unknown_fields)] +pub struct RpcError { + #[serde(flatten)] + pub error_struct: Option>, + pub code: i64, + pub message: String, + pub data: Option, +} diff --git a/crates/sdk/libs/near/src/jsonrpc.rs b/crates/sdk/libs/near/src/jsonrpc.rs new file mode 100644 index 000000000..be9935cc6 --- /dev/null +++ b/crates/sdk/libs/near/src/jsonrpc.rs @@ -0,0 +1,68 @@ +use calimero_sdk::env; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; + +use crate::error::RpcError; +use crate::{Error, RpcMethod}; + +pub struct Client { + url: String, + id: std::cell::RefCell, +} + +impl Client { + pub fn testnet() -> Self { + Self::new("https://rpc.testnet.near.org/".to_string()) + } + + pub fn mainnet() -> Self { + Self::new("https://rpc.mainnet.near.org/".to_string()) + } + + fn new(url: String) -> Self { + Self { + url, + id: std::cell::RefCell::new(0), + } + } + + pub fn call(&self, method: M) -> Result> + where + M: RpcMethod, + { + let headers = [("Content-Type", "application/json")]; + + *self.id.borrow_mut() += 1; + let body = serde_json::to_vec(&Request { + jsonrpc: "2.0", + id: *self.id.borrow(), + method: method.method_name(), + params: method.params(), + })?; + + let response = unsafe { env::ext::fetch(&self.url, "POST", &headers, &body) } + .map_err(Error::FetchError)?; + + serde_json::from_slice::>(&response)? + .data + .map_err(Error::ServerError) + } +} + +#[derive(Debug, Clone, Serialize)] +struct Request<'a, P: Serialize> { + jsonrpc: &'a str, + id: u64, + method: &'a str, + + params: P, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Response { + pub jsonrpc: Option, + pub id: u64, + + #[serde(with = "calimero_primitives::common::ResultAlt", flatten)] + pub data: Result>, +} diff --git a/crates/sdk/libs/near/src/lib.rs b/crates/sdk/libs/near/src/lib.rs new file mode 100644 index 000000000..ebeab9562 --- /dev/null +++ b/crates/sdk/libs/near/src/lib.rs @@ -0,0 +1,18 @@ +mod error; +mod jsonrpc; +pub mod query; +pub mod types; +pub mod views; + +pub use error::Error; +pub use jsonrpc::Client; +pub use query::*; +pub use types::*; + +pub trait RpcMethod { + type Response: serde::de::DeserializeOwned; + type Error: serde::de::DeserializeOwned; + + fn method_name(&self) -> &str; + fn params(&self) -> serde_json::Value; +} diff --git a/crates/sdk/libs/near/src/query.rs b/crates/sdk/libs/near/src/query.rs new file mode 100644 index 000000000..08123e9a4 --- /dev/null +++ b/crates/sdk/libs/near/src/query.rs @@ -0,0 +1,105 @@ +use serde_json::json; + +use crate::types::{AccountId, BlockHash, BlockHeight, BlockId, ShardId}; +use crate::views::{ + AccessKeyList, AccessKeyView, AccountView, BlockReference, CallResult, ContractCodeView, + QueryRequest, ViewStateResult, +}; +use crate::RpcMethod; + +#[derive(serde::Serialize, Debug)] +pub struct RpcQueryRequest { + pub block_id: BlockId, + #[serde(flatten)] + pub request: QueryRequest, +} + +#[derive(serde::Deserialize, Debug)] +pub struct RpcQueryResponse { + #[serde(flatten)] + pub kind: QueryResponseKind, + pub block_height: BlockHeight, + pub block_hash: BlockHash, +} + +#[derive(serde::Deserialize, Debug)] +#[serde(untagged)] +pub enum QueryResponseKind { + ViewAccount(AccountView), + ViewCode(ContractCodeView), + ViewState(ViewStateResult), + AccessKey(AccessKeyView), + AccessKeyList(AccessKeyList), + CallResult(CallResult), +} + +impl RpcMethod for RpcQueryRequest { + type Response = RpcQueryResponse; + type Error = RpcQueryError; + + fn method_name(&self) -> &str { + "query" + } + + fn params(&self) -> serde_json::Value { + json!(self) + } +} + +#[derive(thiserror::Error, Debug, serde::Serialize, serde::Deserialize)] +#[serde(tag = "name", content = "info", rename_all = "SCREAMING_SNAKE_CASE")] +pub enum RpcQueryError { + #[error("There are no fully synchronized blocks on the node yet")] + NoSyncedBlocks, + #[error("The node does not track the shard ID {requested_shard_id}")] + UnavailableShard { requested_shard_id: ShardId }, + #[error( + "The data for block #{block_height} is garbage collected on this node, use an archival node to fetch historical data" + )] + GarbageCollectedBlock { + block_height: BlockHeight, + block_hash: calimero_primitives::hash::Hash, + }, + #[error("Block either has never been observed on the node or has been garbage collected: {block_reference:?}")] + UnknownBlock { block_reference: BlockReference }, + #[error("Account ID {requested_account_id} is invalid")] + InvalidAccount { + requested_account_id: AccountId, + block_height: BlockHeight, + block_hash: calimero_primitives::hash::Hash, + }, + #[error("account {requested_account_id} does not exist while viewing")] + UnknownAccount { + requested_account_id: AccountId, + block_height: BlockHeight, + block_hash: calimero_primitives::hash::Hash, + }, + #[error( + "Contract code for contract ID #{contract_account_id} has never been observed on the node" + )] + NoContractCode { + contract_account_id: AccountId, + block_height: BlockHeight, + block_hash: calimero_primitives::hash::Hash, + }, + #[error("State of contract {contract_account_id} is too large to be viewed")] + TooLargeContractState { + contract_account_id: AccountId, + block_height: BlockHeight, + block_hash: calimero_primitives::hash::Hash, + }, + #[error("Access key for public key {public_key} has never been observed on the node")] + UnknownAccessKey { + public_key: String, + block_height: BlockHeight, + block_hash: calimero_primitives::hash::Hash, + }, + #[error("Function call returned an error: {vm_error}")] + ContractExecutionError { + vm_error: String, + block_height: BlockHeight, + block_hash: calimero_primitives::hash::Hash, + }, + #[error("The node reached its limits. Try again later. More details: {error_message}")] + InternalError { error_message: String }, +} diff --git a/crates/sdk/libs/near/src/types.rs b/crates/sdk/libs/near/src/types.rs new file mode 100644 index 000000000..78dfa2b4d --- /dev/null +++ b/crates/sdk/libs/near/src/types.rs @@ -0,0 +1,14 @@ +pub type BlockHeight = u64; +pub type BlockHash = calimero_primitives::hash::Hash; +pub type AccountId = near_account_id::AccountId; +pub type StorageUsage = u64; +pub type Nonce = u64; +pub type Balance = u128; +pub type ShardId = u64; + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(untagged)] +pub enum BlockId { + Height(BlockHeight), + Hash(BlockHash), +} diff --git a/crates/sdk/libs/near/src/views.rs b/crates/sdk/libs/near/src/views.rs new file mode 100644 index 000000000..b6620d647 --- /dev/null +++ b/crates/sdk/libs/near/src/views.rs @@ -0,0 +1,150 @@ +use serde_with::DisplayFromStr; +use std::sync::Arc; + +use serde_with::base64::Base64; +use serde_with::serde_as; + +use crate::types::{AccountId, Balance, BlockHeight, BlockId, Nonce, StorageUsage}; + +#[derive(serde::Serialize, Debug)] +#[serde(tag = "request_type", rename_all = "snake_case")] +pub enum QueryRequest { + ViewAccount { + account_id: AccountId, + }, + ViewCode { + account_id: AccountId, + }, + ViewState { + account_id: AccountId, + #[serde(rename = "prefix_base64")] + prefix: StoreKey, + #[serde(default)] + include_proof: bool, + }, + ViewAccessKey { + account_id: AccountId, + public_key: Box, + }, + ViewAccessKeyList { + account_id: AccountId, + }, + CallFunction { + account_id: AccountId, + method_name: Box, + #[serde(rename = "args_base64")] + args: FunctionArgs, + }, +} + +#[serde_as] +#[derive(serde::Deserialize, Debug, Clone)] +pub struct AccountView { + #[serde_as(as = "DisplayFromStr")] + pub amount: Balance, + #[serde_as(as = "DisplayFromStr")] + pub locked: Balance, + pub code_hash: calimero_primitives::hash::Hash, + pub storage_usage: StorageUsage, + pub storage_paid_at: BlockHeight, +} + +#[serde_as] +#[derive(serde::Deserialize, Debug, Clone)] +pub struct ContractCodeView { + #[serde(rename = "code_base64")] + #[serde_as(as = "Base64")] + pub code: Box<[u8]>, + pub hash: Box, +} + +#[derive(serde::Deserialize, Debug, Clone)] +pub struct StateItem { + pub key: StoreKey, + pub value: StoreValue, +} + +#[serde_as] +#[derive(serde::Deserialize, Debug, Clone)] +pub struct ViewStateResult { + pub values: Box<[StateItem]>, + #[serde_as(as = "Box<[Base64]>")] + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub proof: Box<[Arc<[u8]>]>, +} + +#[derive(Debug, Clone, serde::Deserialize)] +pub struct AccessKeyView { + pub nonce: Nonce, + pub permission: AccessKeyPermissionView, +} + +#[serde_as] +#[derive(Debug, Clone, serde::Deserialize)] +pub enum AccessKeyPermissionView { + FunctionCall { + #[serde_as(as = "Option")] + allowance: Option, + receiver_id: Box, + method_names: Box<[Box]>, + }, + FullAccess, +} + +#[derive(serde::Deserialize, Debug, Clone)] +pub struct AccessKeyList { + pub keys: Box<[AccessKeyInfoView]>, +} + +#[derive(serde::Deserialize, Debug, Clone)] +pub struct AccessKeyInfoView { + pub public_key: Box, + pub access_key: AccessKeyView, +} + +#[derive(serde::Deserialize, Debug, Clone)] +pub struct CallResult { + pub result: Box<[u8]>, + pub logs: Box<[Box]>, +} + +#[serde_as] +#[derive(serde::Deserialize, Clone, Debug)] +#[serde(transparent)] +pub struct StoreValue(#[serde_as(as = "Base64")] pub Box<[u8]>); + +#[serde_as] +#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] +#[serde(transparent)] +pub struct StoreKey(#[serde_as(as = "Base64")] pub Box<[u8]>); + +#[serde_as] +#[derive(serde::Serialize, Clone, Debug)] +#[serde(transparent)] +pub struct FunctionArgs(#[serde_as(as = "Base64")] Box<[u8]>); + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum BlockReference { + BlockId(BlockId), + Finality(Finality), + SyncCheckpoint(SyncCheckpoint), +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum SyncCheckpoint { + Genesis, + EarliestAvailable, +} + +#[derive(serde::Serialize, serde::Deserialize, Default, Clone, Debug)] +pub enum Finality { + #[serde(rename = "optimistic")] + None, + #[serde(rename = "near-final")] + DoomSlug, + #[serde(rename = "final")] + #[default] + Final, +} diff --git a/crates/server/src/admin/handlers/context.rs b/crates/server/src/admin/handlers/context.rs index 23ef17f28..34093601c 100644 --- a/crates/server/src/admin/handlers/context.rs +++ b/crates/server/src/admin/handlers/context.rs @@ -14,9 +14,14 @@ use crate::admin::service::{parse_api_error, AdminState, ApiError, ApiResponse}; use crate::admin::storage::client_keys::get_context_client_key; use crate::admin::storage::context::{add_context, delete_context, get_context, get_contexts}; +#[derive(Debug, Serialize, Deserialize)] +pub struct ContextObject { + context: Context, +} + #[derive(Debug, Serialize, Deserialize)] pub struct GetContextResponse { - data: Context, + data: ContextObject, } pub async fn get_context_handler( @@ -29,7 +34,7 @@ pub async fn get_context_handler( match context_result { Ok(ctx) => match ctx { Some(context) => ApiResponse { - payload: GetContextResponse { data: context }, + payload: GetContextResponse { data: ContextObject { context } }, } .into_response(), None => ApiError { @@ -95,9 +100,14 @@ pub async fn get_context_users_handler( .into_response() } +#[derive(Debug, Serialize, Deserialize)] +pub struct ContextList { + contexts: Vec, +} + #[derive(Debug, Serialize, Deserialize)] pub struct GetContextsResponse { - data: Vec, + data: ContextList, } pub async fn get_contexts_handler( @@ -106,7 +116,9 @@ pub async fn get_contexts_handler( let contexts = get_contexts(&state.store).map_err(|err| parse_api_error(err)); return match contexts { Ok(contexts) => ApiResponse { - payload: GetContextsResponse { data: contexts }, + payload: GetContextsResponse { + data: ContextList { contexts }, + }, } .into_response(), Err(err) => err.into_response(), diff --git a/crates/server/src/admin/handlers/root_keys.rs b/crates/server/src/admin/handlers/root_keys.rs index 0e0c7559b..e4e263112 100644 --- a/crates/server/src/admin/handlers/root_keys.rs +++ b/crates/server/src/admin/handlers/root_keys.rs @@ -6,12 +6,12 @@ use calimero_primitives::identity::{RootKey, WalletType}; use calimero_server_primitives::admin::{AddPublicKeyRequest, IntermediateAddPublicKeyRequest}; use calimero_store::Store; use chrono::Utc; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use tracing::info; use super::add_client_key::transform_request; -use crate::admin::service::{parse_api_error, AdminState, ApiError, ApiResponse}; -use crate::admin::storage::root_key::add_root_key; +use crate::admin::service::{parse_api_error, AdminState, ApiError, ApiResponse, Empty}; +use crate::admin::storage::root_key::{add_root_key, clean_auth_keys}; use crate::admin::utils::auth::validate_challenge; #[derive(Debug, Serialize)] @@ -67,3 +67,21 @@ pub fn store_root_key( info!("Root key stored successfully."); Ok(true) } + +#[derive(Debug, Serialize, Deserialize)] +pub struct DeleteKeysResponse { + data: Empty, +} +pub async fn delete_auth_keys_handler( + Extension(state): Extension>, +) -> impl IntoResponse { + clean_auth_keys(&state.store).map_or_else( + |err| parse_api_error(err).into_response(), + |_| { + ApiResponse { + payload: DeleteKeysResponse { data: Empty {} }, + } + .into_response() + }, + ); +} diff --git a/crates/server/src/admin/service.rs b/crates/server/src/admin/service.rs index dcb3e5686..db6ac5c5c 100644 --- a/crates/server/src/admin/service.rs +++ b/crates/server/src/admin/service.rs @@ -52,7 +52,6 @@ pub(crate) fn setup( keypair: config.identity.clone(), application_manager, }); - let protected_router = Router::new() .route( "/root-key", @@ -83,6 +82,10 @@ pub(crate) fn setup( get(handlers::context::get_context_storage_handler), ) .route("/contexts", get(handlers::context::get_contexts_handler)) + .route( + "/identity/keys", + delete(handlers::root_keys::delete_auth_keys_handler), + ) .layer(middleware::auth::AuthSignatureLayer::new(store)) .layer(Extension(shared_state.clone())); @@ -106,6 +109,9 @@ pub(crate) fn setup( Ok(Some((admin_path, admin_router))) } +#[derive(Debug, Serialize, Deserialize)] +pub struct Empty {} + pub struct ApiResponse { pub(crate) payload: T, } @@ -181,6 +187,11 @@ async fn health_check_handler() -> impl IntoResponse { .into_response() } +#[derive(Debug, Serialize)] +struct InstallApplicationResponse { + data: bool, +} + async fn install_application_handler( Extension(state): Extension>, Json(req): Json, @@ -190,7 +201,10 @@ async fn install_application_handler( .install_application(req.application, &req.version) .await { - Ok(()) => ApiResponse { payload: () }.into_response(), + Ok(()) => ApiResponse { + payload: InstallApplicationResponse { data: true }, + } + .into_response(), Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response(), } } diff --git a/crates/server/src/admin/storage/root_key.rs b/crates/server/src/admin/storage/root_key.rs index 9342b814b..e4396ffaf 100644 --- a/crates/server/src/admin/storage/root_key.rs +++ b/crates/server/src/admin/storage/root_key.rs @@ -36,7 +36,7 @@ pub fn exists_root_keys(store: &Store) -> eyre::Result { Ok(!did.root_keys.is_empty()) } -pub fn clean_keys(store: &Store) -> eyre::Result<()> { +pub fn clean_auth_keys(store: &Store) -> eyre::Result<()> { let mut did = get_or_create_did(store)?; did.client_keys.clear(); diff --git a/crates/server/src/lib.rs b/crates/server/src/lib.rs index 04d435157..bc2605d3f 100644 --- a/crates/server/src/lib.rs +++ b/crates/server/src/lib.rs @@ -106,7 +106,7 @@ pub async fn start( cors::CorsLayer::new() .allow_origin(cors::Any) .allow_headers(cors::Any) - .allow_methods([http::Method::POST]), + .allow_methods([http::Method::POST, http::Method::GET, http::Method::DELETE, http::Method::PUT, http::Method::OPTIONS]), ); let mut set = tokio::task::JoinSet::new();