From 81d36c86f0beeeea21452a7253e5031471765cf2 Mon Sep 17 00:00:00 2001 From: alenmestrov Date: Wed, 18 Dec 2024 20:31:48 +0100 Subject: [PATCH 1/4] Feat add context variable fetching functionalities (#1018) --- .../config/src/client/env/proxy/query.rs | 22 +- .../proxy/query/context_storage_entries.rs | 105 ++++++++++ .../env/proxy/query/context_variable.rs | 102 +++++++++ .../src/client/env/proxy/types/starknet.rs | 195 +++++++++++++++--- crates/context/config/src/types.rs | 17 ++ crates/context/src/lib.rs | 51 ++++- crates/server/src/admin/handlers/proposals.rs | 67 +++++- crates/server/src/admin/service.rs | 9 + 8 files changed, 538 insertions(+), 30 deletions(-) create mode 100644 crates/context/config/src/client/env/proxy/query/context_storage_entries.rs create mode 100644 crates/context/config/src/client/env/proxy/query/context_variable.rs diff --git a/crates/context/config/src/client/env/proxy/query.rs b/crates/context/config/src/client/env/proxy/query.rs index 0a48d43e9..4fba32984 100644 --- a/crates/context/config/src/client/env/proxy/query.rs +++ b/crates/context/config/src/client/env/proxy/query.rs @@ -1,4 +1,6 @@ use active_proposals::ActiveProposalRequest; +use context_storage_entries::ContextStorageEntriesRequest; +use context_variable::ContextVariableRequest; use proposal::ProposalRequest; use proposal_approvals::ProposalApprovalsRequest; use proposal_approvers::ProposalApproversRequest; @@ -8,10 +10,12 @@ use crate::client::env::utils; use crate::client::transport::Transport; use crate::client::{CallClient, ClientError, Operation}; use crate::repr::Repr; -use crate::types::ContextIdentity; +use crate::types::{ContextIdentity, ContextStorageEntry}; use crate::{Proposal, ProposalId, ProposalWithApprovals}; mod active_proposals; +mod context_storage_entries; +mod context_variable; mod proposal; mod proposal_approvals; mod proposal_approvers; @@ -73,4 +77,20 @@ impl<'a, T: Transport> ContextProxyQuery<'a, T> { utils::send(&self.client, Operation::Read(params)).await } + + pub async fn get_context_value(&self, key: Vec) -> Result, ClientError> { + let params = ContextVariableRequest { key }; + + utils::send(&self.client, Operation::Read(params)).await + } + + pub async fn get_context_storage_entries( + &self, + offset: usize, + limit: usize, + ) -> Result, ClientError> { + let params = ContextStorageEntriesRequest { offset, limit }; + + utils::send(&self.client, Operation::Read(params)).await + } } diff --git a/crates/context/config/src/client/env/proxy/query/context_storage_entries.rs b/crates/context/config/src/client/env/proxy/query/context_storage_entries.rs new file mode 100644 index 000000000..856a345f8 --- /dev/null +++ b/crates/context/config/src/client/env/proxy/query/context_storage_entries.rs @@ -0,0 +1,105 @@ +use candid::{Decode, Encode}; +use serde::Serialize; +use starknet::core::codec::{Decode as StarknetDecode, Encode as StarknetEncode}; +use starknet_crypto::Felt; + +use crate::client::env::proxy::starknet::{ + CallData, ContextStorageEntriesResponse, StarknetContextStorageEntriesRequest, +}; +use crate::client::env::Method; +use crate::client::protocol::icp::Icp; +use crate::client::protocol::near::Near; +use crate::client::protocol::starknet::Starknet; +use crate::types::ContextStorageEntry; + +#[derive(Clone, Debug, Serialize)] +pub(super) struct ContextStorageEntriesRequest { + pub(super) offset: usize, + pub(super) limit: usize, +} + +impl Method for ContextStorageEntriesRequest { + const METHOD: &'static str = "context_storage_entries"; + + type Returns = Vec; + + fn encode(self) -> eyre::Result> { + serde_json::to_vec(&self).map_err(Into::into) + } + + fn decode(response: Vec) -> eyre::Result { + // Decode the response as Vec of tuples with boxed slices + let entries: Vec<(Box<[u8]>, Box<[u8]>)> = serde_json::from_slice(&response) + .map_err(|e| eyre::eyre!("Failed to decode response: {}", e))?; + + // Convert to ContextStorageEntry + Ok(entries + .into_iter() + .map(|(key, value)| ContextStorageEntry { + key: key.into(), + value: value.into(), + }) + .collect()) + } +} + +impl Method for ContextStorageEntriesRequest { + const METHOD: &'static str = "context_storage_entries"; + + type Returns = Vec; + + fn encode(self) -> eyre::Result> { + let req = StarknetContextStorageEntriesRequest { + offset: Felt::from(self.offset as u64), + length: Felt::from(self.limit as u64), + }; + let mut call_data = CallData::default(); + req.encode(&mut call_data)?; + Ok(call_data.0) + } + + fn decode(response: Vec) -> eyre::Result { + if response.is_empty() { + return Ok(vec![]); + } + + // Convert bytes to Felts + let chunks = response.chunks_exact(32); + let felts: Vec = chunks + .map(|chunk| { + let chunk_array: [u8; 32] = chunk + .try_into() + .map_err(|e| eyre::eyre!("Failed to convert chunk to array: {}", e))?; + Ok(Felt::from_bytes_be(&chunk_array)) + }) + .collect::>>()?; + + let response = ContextStorageEntriesResponse::decode_iter(&mut felts.iter())?; + + Ok(response.entries.into_iter().map(Into::into).collect()) + } +} + +impl Method for ContextStorageEntriesRequest { + const METHOD: &'static str = "context_storage_entries"; + + type Returns = Vec; + + fn encode(self) -> eyre::Result> { + // Encode offset and limit using Candid + Encode!(&self.offset, &self.limit) + .map_err(|e| eyre::eyre!("Failed to encode request: {}", e)) + } + + fn decode(response: Vec) -> eyre::Result { + // Decode the response as Vec of tuples + let entries: Vec<(Vec, Vec)> = Decode!(&response, Vec<(Vec, Vec)>) + .map_err(|e| eyre::eyre!("Failed to decode response: {}", e))?; + + // Convert to ContextStorageEntry + Ok(entries + .into_iter() + .map(|(key, value)| ContextStorageEntry { key, value }) + .collect()) + } +} diff --git a/crates/context/config/src/client/env/proxy/query/context_variable.rs b/crates/context/config/src/client/env/proxy/query/context_variable.rs new file mode 100644 index 000000000..f6e09eee2 --- /dev/null +++ b/crates/context/config/src/client/env/proxy/query/context_variable.rs @@ -0,0 +1,102 @@ +use candid::{Decode, Encode}; +use serde::Serialize; +use starknet::core::codec::Encode as StarknetEncode; +use starknet_crypto::Felt; + +use crate::client::env::proxy::starknet::{CallData, ContextVariableKey}; +use crate::client::env::Method; +use crate::client::protocol::icp::Icp; +use crate::client::protocol::near::Near; +use crate::client::protocol::starknet::Starknet; +use crate::icp::repr::ICRepr; + +#[derive(Clone, Debug, Serialize)] +pub(super) struct ContextVariableRequest { + pub(super) key: Vec, +} + +impl Method for ContextVariableRequest { + const METHOD: &'static str = "get_context_value"; + + type Returns = Vec; + + fn encode(self) -> eyre::Result> { + serde_json::to_vec(&self).map_err(Into::into) + } + + fn decode(response: Vec) -> eyre::Result { + serde_json::from_slice(&response).map_err(Into::into) + } +} + +impl Method for ContextVariableRequest { + const METHOD: &'static str = "get_context_value"; + + type Returns = Vec; + + fn encode(self) -> eyre::Result> { + let mut call_data = CallData::default(); + let key: ContextVariableKey = self.key.into(); + key.encode(&mut call_data)?; + + Ok(call_data.0) + } + + fn decode(response: Vec) -> eyre::Result { + if response.is_empty() { + return Ok(vec![]); + } + + let chunks = response.chunks_exact(32); + let felts: Vec = chunks + .map(|chunk| { + let chunk_array: [u8; 32] = chunk + .try_into() + .map_err(|e| eyre::eyre!("Failed to convert chunk to array: {}", e))?; + Ok(Felt::from_bytes_be(&chunk_array)) + }) + .collect::>>()?; + + if felts.is_empty() { + return Ok(vec![]); + } + + // First felt is the discriminant (0 for None, 1 for Some) + match felts[0] { + f if f == Felt::ZERO => { + println!( + "First few bytes after discriminant: {:?}", + &response[32..40] + ); + + // Skip first 64 bytes (discriminant + length) and filter nulls + Ok(response[64..] + .iter() + .filter(|&&b| b != 0) + .copied() + .collect()) + } + v => Err(eyre::eyre!("Invalid option discriminant: {}", v)), + } + } +} + +impl Method for ContextVariableRequest { + const METHOD: &'static str = "get_context_value"; + + type Returns = Vec; + + fn encode(self) -> eyre::Result> { + // Convert the key to ICRepr + let payload = ICRepr::new(self.key); + // Use candid's Encode macro to serialize the data + Encode!(&payload).map_err(Into::into) + } + + fn decode(response: Vec) -> eyre::Result { + // Use candid's Decode macro to deserialize the response + // The response will be an Option> + let decoded = Decode!(&response, Vec)?; + Ok(decoded) + } +} diff --git a/crates/context/config/src/client/env/proxy/types/starknet.rs b/crates/context/config/src/client/env/proxy/types/starknet.rs index bed54ba1f..df3dd029f 100644 --- a/crates/context/config/src/client/env/proxy/types/starknet.rs +++ b/crates/context/config/src/client/env/proxy/types/starknet.rs @@ -1,8 +1,8 @@ -use starknet::core::codec::{Decode, Encode, FeltWriter}; +use starknet::core::codec::{Decode, Encode, Error, FeltWriter}; use starknet::core::types::{Felt, U256}; use crate::repr::{Repr, ReprBytes, ReprTransmute}; -use crate::types::{ContextIdentity, ProposalId, SignerId}; +use crate::types::{ContextIdentity, ContextStorageEntry, ProposalId, SignerId}; use crate::{ Proposal, ProposalAction, ProposalApprovalWithSigner, ProposalWithApprovals, ProxyMutateRequest, }; @@ -22,6 +22,49 @@ pub struct StarknetProposalId(pub FeltPair); #[derive(Clone, Copy, Debug, Encode, Decode)] pub struct StarknetU256(pub FeltPair); +#[derive(Debug, Clone, Decode)] +pub struct ContextVariableKey(pub Vec); + +// Implement From for the conversion +impl From> for ContextVariableKey { + fn from(key: Vec) -> Self { + ContextVariableKey(key) + } +} + +// Implement Encode for ContextVariableKey +impl Encode for ContextVariableKey { + fn encode(&self, writer: &mut W) -> Result<(), Error> { + let bytes = &self.0; + + // Use exactly 16 bytes per chunk + let chunk_size = 16; + #[allow( + clippy::integer_division, + reason = "Using integer division for ceiling division calculation" + )] + let num_chunks = (bytes.len() + chunk_size - 1) / chunk_size; + + // Write number of chunks first + writer.write(Felt::from(num_chunks)); + + // Process each chunk + for i in 0..num_chunks { + let start = i * chunk_size; + let end = std::cmp::min((i + 1) * chunk_size, bytes.len()); + let chunk = &bytes[start..end]; + + let chunk_hex = hex::encode(chunk); + let chunk_felt = Felt::from_hex(&format!("0x{}", chunk_hex)) + .map_err(|e| Error::custom(&format!("Invalid chunk hex: {}", e)))?; + + writer.write(chunk_felt); + } + + Ok(()) + } +} + #[derive(Debug, Encode, Decode)] pub struct StarknetProposal { pub proposal_id: StarknetProposalId, @@ -50,7 +93,7 @@ pub enum StarknetProxyMutateRequestKind { #[derive(Debug, Encode, Decode)] pub enum StarknetProposalActionWithArgs { - ExternalFunctionCall(Felt, Felt, Vec), + ExternalFunctionCall(Felt, Felt, StarknetU256, Vec), Transfer(Felt, StarknetU256), SetNumApprovals(Felt), SetActiveProposalsLimit(Felt), @@ -238,23 +281,41 @@ impl From> for StarknetProposalActionWithArgs { receiver_id, method_name, args, + deposit, .. } => { - let args_vec: Vec = serde_json::from_str(&args).unwrap_or_default(); - let felt_args = args_vec - .iter() - .map(|arg| { - if arg.starts_with("0x") { - Felt::from_hex_unchecked(arg) - } else { - Felt::from_bytes_be_slice(arg.as_bytes()) - } - }) - .collect(); + // Parse the JSON string into a Value first + let args_value: serde_json::Value = + serde_json::from_str(&args).expect("Invalid JSON arguments"); + // Convert JSON values to Starknet-compatible felt arguments + let felt_args = match args_value { + serde_json::Value::Object(map) => { + // For objects, serialize each value to a felt + map.into_iter() + .map(|(_, value)| { + Felt::from_bytes_be_slice(value.to_string().as_bytes()) + }) + .collect() + } + serde_json::Value::Array(arr) => { + // For arrays, convert each element + arr.into_iter() + .map(|value| Felt::from_bytes_be_slice(value.to_string().as_bytes())) + .collect() + } + // Explicitly match all other variants + value @ (serde_json::Value::Null + | serde_json::Value::Bool(_) + | serde_json::Value::Number(_) + | serde_json::Value::String(_)) => { + vec![Felt::from_bytes_be_slice(value.to_string().as_bytes())] + } + }; StarknetProposalActionWithArgs::ExternalFunctionCall( Felt::from_bytes_be_slice(receiver_id.as_bytes()), Felt::from_bytes_be_slice(method_name.as_bytes()), + StarknetU256::from(deposit), felt_args, ) } @@ -289,19 +350,26 @@ impl From> for StarknetProposalActionWithArgs { impl From for ProposalAction { fn from(action: StarknetProposalActionWithArgs) -> Self { match action { - StarknetProposalActionWithArgs::ExternalFunctionCall(contract, selector, calldata) => { - ProposalAction::ExternalFunctionCall { - receiver_id: format!("0x{}", hex::encode(contract.to_bytes_be())), - method_name: format!("0x{}", hex::encode(selector.to_bytes_be())), - args: calldata - .iter() - .map(|felt| format!("0x{}", hex::encode(felt.to_bytes_be()))) - .collect::>() - .join(","), - deposit: 0, - gas: 0, - } - } + StarknetProposalActionWithArgs::ExternalFunctionCall( + contract, + selector, + amount, + calldata, + ) => ProposalAction::ExternalFunctionCall { + receiver_id: format!("0x{}", hex::encode(contract.to_bytes_be())), + method_name: format!("0x{}", hex::encode(selector.to_bytes_be())), + args: calldata + .iter() + .map(|felt| format!("0x{}", hex::encode(felt.to_bytes_be()))) + .collect::>() + .join(","), + deposit: u128::from_be_bytes( + amount.0.low.to_bytes_be()[16..32].try_into().unwrap(), + ) + (u128::from_be_bytes( + amount.0.high.to_bytes_be()[16..32].try_into().unwrap(), + ) << 64), + gas: 0, + }, StarknetProposalActionWithArgs::Transfer(receiver, amount) => { let FeltPair { high, low } = amount.0; ProposalAction::Transfer { @@ -397,3 +465,76 @@ impl From for ProposalId { value.0.into() } } + +#[derive(Clone, Copy, Debug, Encode)] +pub struct StarknetContextStorageEntriesRequest { + pub offset: Felt, + pub length: Felt, +} + +// First, create a type to represent the response structure +#[derive(Debug)] +pub struct ContextStorageEntriesResponse { + pub entries: Vec<(Vec, Vec)>, +} + +impl<'a> Decode<'a> for ContextStorageEntriesResponse { + fn decode_iter(iter: &mut T) -> Result + where + T: Iterator, + { + // First felt is number of entries + let num_entries = match iter.next() { + Some(felt) => felt.to_bytes_be()[31] as usize, + None => return Ok(Self { entries: vec![] }), + }; + + let mut entries = Vec::new(); + + // Read exactly num_entries pairs + for _ in 0..num_entries { + // Get key array length and contents + if let Some(key_len) = iter.next() { + let key_len = key_len.to_bytes_be()[31] as usize; + let mut key = Vec::new(); + for _ in 0..key_len { + if let Some(felt) = iter.next() { + key.push(*felt); + } + } + + // Get value array length and contents + if let Some(value_len) = iter.next() { + let value_len = value_len.to_bytes_be()[31] as usize; + let mut value = Vec::new(); + for _ in 0..value_len { + if let Some(felt) = iter.next() { + value.push(*felt); + } + } + entries.push((key, value)); + } + } + } + + Ok(Self { entries }) + } +} + +impl From<(Vec, Vec)> for ContextStorageEntry { + fn from((key_felts, value_felts): (Vec, Vec)) -> Self { + let key = key_felts + .iter() + .flat_map(|f| f.to_bytes_be()) + .filter(|&b| b != 0) + .collect(); + + let value = value_felts + .iter() + .flat_map(|f| f.to_bytes_be()) + .filter(|&b| b != 0) + .collect(); + + ContextStorageEntry { key, value } + } +} diff --git a/crates/context/config/src/types.rs b/crates/context/config/src/types.rs index 5c0ed74e7..3ecb50819 100644 --- a/crates/context/config/src/types.rs +++ b/crates/context/config/src/types.rs @@ -139,6 +139,23 @@ impl ReprBytes for ContextId { } } +#[derive( + Eq, + Ord, + Debug, + Clone, + PartialEq, + PartialOrd, + BorshSerialize, + BorshDeserialize, + Serialize, + Deserialize, +)] +pub struct ContextStorageEntry { + pub key: Vec, + pub value: Vec, +} + #[derive(Eq, Ord, Copy, Debug, Clone, PartialEq, PartialOrd, BorshSerialize, BorshDeserialize)] pub struct ContextIdentity(Identity); diff --git a/crates/context/src/lib.rs b/crates/context/src/lib.rs index e706b1ee7..051965d4b 100644 --- a/crates/context/src/lib.rs +++ b/crates/context/src/lib.rs @@ -13,7 +13,7 @@ use calimero_context_config::client::{AnyTransport, Client as ExternalClient}; use calimero_context_config::repr::{Repr, ReprBytes, ReprTransmute}; use calimero_context_config::types::{ Application as ApplicationConfig, ApplicationMetadata as ApplicationMetadataConfig, - ApplicationSource as ApplicationSourceConfig, ContextIdentity, ProposalId, + ApplicationSource as ApplicationSourceConfig, ContextIdentity, ContextStorageEntry, ProposalId, }; use calimero_context_config::{Proposal, ProposalAction, ProposalWithApprovals}; use calimero_network::client::NetworkClient; @@ -1343,4 +1343,53 @@ impl ContextManager { .await .map_err(|err| eyre::eyre!("Failed to fetch proposal approvers: {}", err)) } + + pub async fn get_context_value( + &self, + context_id: ContextId, + key: Vec, + ) -> EyreResult> { + let handle = self.store.handle(); + + let Some(context_config) = handle.get(&ContextConfigKey::new(context_id))? else { + bail!("Context not found"); + }; + + let response = self + .config_client + .query::( + context_config.protocol.as_ref().into(), + context_config.network.as_ref().into(), + context_config.proxy_contract.as_ref().into(), + ) + .get_context_value(key) + .await + .map_err(|err| eyre::eyre!("Failed to fetch context value: {}", err))?; + Ok(response) + } + + pub async fn get_context_storage_entries( + &self, + context_id: ContextId, + offset: usize, + limit: usize, + ) -> EyreResult> { + let handle = self.store.handle(); + + let Some(context_config) = handle.get(&ContextConfigKey::new(context_id))? else { + bail!("Context not found"); + }; + + let response = self + .config_client + .query::( + context_config.protocol.as_ref().into(), + context_config.network.as_ref().into(), + context_config.proxy_contract.as_ref().into(), + ) + .get_context_storage_entries(offset, limit) + .await + .map_err(|err| eyre::eyre!("Failed to fetch context storage entries: {}", err))?; + Ok(response) + } } diff --git a/crates/server/src/admin/handlers/proposals.rs b/crates/server/src/admin/handlers/proposals.rs index a150e3895..a8e8e4e7f 100644 --- a/crates/server/src/admin/handlers/proposals.rs +++ b/crates/server/src/admin/handlers/proposals.rs @@ -4,7 +4,7 @@ use axum::extract::Path; use axum::response::IntoResponse; use axum::{Extension, Json}; use calimero_context_config::repr::{Repr, ReprTransmute}; -use calimero_context_config::types::{ContextIdentity, ProposalId}; +use calimero_context_config::types::{ContextIdentity, ContextStorageEntry, ProposalId}; use calimero_context_config::{Proposal as ProposalConfig, ProposalWithApprovals}; use calimero_primitives::context::ContextId; use serde::{Deserialize, Serialize}; @@ -117,6 +117,31 @@ pub struct GetProposalsRequest { pub limit: usize, } +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GetContextValueRequest { + pub key: String, +} + +#[derive(Debug, Deserialize, Serialize, Copy, Clone)] +#[serde(rename_all = "camelCase")] +pub struct GetContextStorageEntriesRequest { + pub offset: usize, + pub limit: usize, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GetContextValueResponse { + pub data: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GetContextStorageEntriesResponse { + pub data: Vec, +} + pub async fn get_proposals_handler( Path(context_id): Path, Extension(state): Extension>, @@ -156,6 +181,46 @@ pub async fn get_proposal_handler( } } +pub async fn get_context_value_handler( + Path(context_id): Path, + Extension(state): Extension>, + Json(req): Json, +) -> impl IntoResponse { + match state + .ctx_manager + .get_context_value(context_id, req.key.as_bytes().to_vec()) + .await + { + Ok(context_value) => ApiResponse { + payload: GetContextValueResponse { + data: context_value, + }, + } + .into_response(), + Err(err) => parse_api_error(err).into_response(), + } +} + +pub async fn get_context_storage_entries_handler( + Path(context_id): Path, + Extension(state): Extension>, + Json(req): Json, +) -> impl IntoResponse { + match state + .ctx_manager + .get_context_storage_entries(context_id, req.offset, req.limit) + .await + { + Ok(context_storage_entries) => ApiResponse { + payload: GetContextStorageEntriesResponse { + data: context_storage_entries, + }, + } + .into_response(), + Err(err) => parse_api_error(err).into_response(), + } +} + #[derive(Debug, Copy, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct GetNumberOfActiveProposalsResponse { diff --git a/crates/server/src/admin/service.rs b/crates/server/src/admin/service.rs index 6c51eae25..f31e31d1a 100644 --- a/crates/server/src/admin/service.rs +++ b/crates/server/src/admin/service.rs @@ -20,6 +20,7 @@ use tracing::info; use super::handlers::did::delete_did_handler; use super::handlers::proposals::{ + get_context_storage_entries_handler, get_context_value_handler, get_number_of_active_proposals_handler, get_number_of_proposal_approvals_handler, get_proposal_approvers_handler, get_proposal_handler, get_proposals_handler, }; @@ -164,6 +165,14 @@ pub(crate) fn setup( .route( "/contexts/:context_id/proposals/:proposal_id", get(get_proposal_handler), + ) + .route( + "/contexts/:context_id/proposals/get-context-value", + post(get_context_value_handler), + ) + .route( + "/contexts/:context_id/proposals/context-storage-entries", + post(get_context_storage_entries_handler), ); let dev_router = Router::new() From 07bd91ca99d34328efceca17f23f11d860fb9c73 Mon Sep 17 00:00:00 2001 From: Matej Vukosav Date: Fri, 20 Dec 2024 01:02:30 +1100 Subject: [PATCH 2/4] chore: add merod and meroctl installation scripts (#1022) --- .../workflows/cross-platform-install-test.yml | 18 ++++- scripts/{install.sh => install-meroctl.sh} | 2 +- scripts/install-merod.sh | 68 +++++++++++++++++++ 3 files changed, 84 insertions(+), 4 deletions(-) rename scripts/{install.sh => install-meroctl.sh} (99%) create mode 100755 scripts/install-merod.sh diff --git a/.github/workflows/cross-platform-install-test.yml b/.github/workflows/cross-platform-install-test.yml index 2c630eaf9..33d2955f8 100644 --- a/.github/workflows/cross-platform-install-test.yml +++ b/.github/workflows/cross-platform-install-test.yml @@ -5,7 +5,8 @@ on: branches: - master - pull_request: + pull_request_target: + types: [opened, reopened] branches: - master @@ -32,10 +33,21 @@ jobs: echo "BRANCH_NAME=${{ github.ref_name }}" >> $GITHUB_ENV fi - # Run the installation script + # Run the merod script - name: Test installation script run: | - curl -s https://raw.githubusercontent.com/calimero-network/core/${{ env.BRANCH_NAME }}/scripts/install.sh | bash + curl -s https://raw.githubusercontent.com/calimero-network/core/${{ env.BRANCH_NAME }}/scripts/install-merod.sh | bash + + # Validate the binary installation + - name: Validate installation + run: | + which meroctl + merod --version + + # Run the meroctl script + - name: Test installation script + run: | + curl -s https://raw.githubusercontent.com/calimero-network/core/${{ env.BRANCH_NAME }}/scripts/install-meroctl.sh | bash # Validate the binary installation - name: Validate installation diff --git a/scripts/install.sh b/scripts/install-meroctl.sh similarity index 99% rename from scripts/install.sh rename to scripts/install-meroctl.sh index 50f0ab6bc..945efac6a 100755 --- a/scripts/install.sh +++ b/scripts/install-meroctl.sh @@ -1,7 +1,7 @@ #!/bin/bash BINARY_NAME="meroctl" -VERSION="v0.1.1" +VERSION="v0.2.0" REPO="calimero-network/core" INSTALL_DIR="$HOME/.local/bin" diff --git a/scripts/install-merod.sh b/scripts/install-merod.sh new file mode 100755 index 000000000..bde848cce --- /dev/null +++ b/scripts/install-merod.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +BINARY_NAME="merod" +VERSION="v0.2.1" +REPO="calimero-network/core" +INSTALL_DIR="$HOME/.local/bin" + +# Detect OS and Architecture +OS=$(uname | tr '[:upper:]' '[:lower:]') +ARCH=$(uname -m) + +case "$ARCH" in + "x86_64") ARCH="x86_64" ;; + "arm64" | "aarch64") ARCH="aarch64" ;; + *) + echo "Unsupported architecture: $ARCH." + exit 1 + ;; +esac + +if [ "$OS" == "darwin" ]; then + PLATFORM="apple-darwin" +elif [ "$OS" == "linux" ]; then + PLATFORM="unknown-linux-gnu" +else + echo "Unsupported operating system: $OS." + exit 1 +fi + +# Construct download URL +TARBALL_NAME="${BINARY_NAME}_${ARCH}-${PLATFORM}.tar.gz" +DOWNLOAD_URL="https://github.com/$REPO/releases/download/$VERSION/$TARBALL_NAME" + +# Ensure installation directory exists +mkdir -p "$INSTALL_DIR" + +# Download binary tarball +echo "Downloading $TARBALL_NAME from $DOWNLOAD_URL..." +curl -L -o "$TARBALL_NAME" "$DOWNLOAD_URL" + +# Extract binary +echo "Extracting $TARBALL_NAME..." +tar -xzf "$TARBALL_NAME" +chmod +x "$BINARY_NAME" + +# Move binary to user-local bin directory +mv "$BINARY_NAME" "$INSTALL_DIR/$BINARY_NAME" +rm "$TARBALL_NAME" + +# Add $HOME/.local/bin to PATH if not already present +if ! echo "$PATH" | grep -q "$INSTALL_DIR"; then + SHELL_CONFIG_FILE="$HOME/.bashrc" + case "$SHELL" in + */zsh) SHELL_CONFIG_FILE="$HOME/.zshrc" ;; + */fish) SHELL_CONFIG_FILE="$HOME/.config/fish/config.fish" ;; + */csh|*/tcsh) SHELL_CONFIG_FILE="$HOME/.cshrc" ;; + esac + + echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$SHELL_CONFIG_FILE" + echo "Added $HOME/.local/bin to PATH in $SHELL_CONFIG_FILE. Please reload your shell or run: source $SHELL_CONFIG_FILE" +fi + +# Final message +echo "$BINARY_NAME installed successfully in $INSTALL_DIR." +echo "To verify the installation, make sure $INSTALL_DIR is in your PATH." +echo "Run the following command to update your current shell session if needed:" +echo "source " +echo "Then run '$BINARY_NAME --version' to confirm the installation." From bae5e0cf467575f210a924a67465818a21d00b4b Mon Sep 17 00:00:00 2001 From: Matej Vukosav Date: Fri, 20 Dec 2024 01:11:46 +1100 Subject: [PATCH 3/4] fix: installation script download url (#1023) --- scripts/install-meroctl.sh | 4 ++-- scripts/install-merod.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/install-meroctl.sh b/scripts/install-meroctl.sh index 945efac6a..bd0ffc29b 100755 --- a/scripts/install-meroctl.sh +++ b/scripts/install-meroctl.sh @@ -1,7 +1,7 @@ #!/bin/bash BINARY_NAME="meroctl" -VERSION="v0.2.0" +VERSION="0.2.0" REPO="calimero-network/core" INSTALL_DIR="$HOME/.local/bin" @@ -29,7 +29,7 @@ fi # Construct download URL TARBALL_NAME="${BINARY_NAME}_${ARCH}-${PLATFORM}.tar.gz" -DOWNLOAD_URL="https://github.com/$REPO/releases/download/$VERSION/$TARBALL_NAME" +DOWNLOAD_URL="https://github.com/$REPO/releases/download/meroctl-$VERSION/$TARBALL_NAME" # Ensure installation directory exists mkdir -p "$INSTALL_DIR" diff --git a/scripts/install-merod.sh b/scripts/install-merod.sh index bde848cce..f329c0f88 100755 --- a/scripts/install-merod.sh +++ b/scripts/install-merod.sh @@ -1,7 +1,7 @@ #!/bin/bash BINARY_NAME="merod" -VERSION="v0.2.1" +VERSION="0.2.1" REPO="calimero-network/core" INSTALL_DIR="$HOME/.local/bin" @@ -29,7 +29,7 @@ fi # Construct download URL TARBALL_NAME="${BINARY_NAME}_${ARCH}-${PLATFORM}.tar.gz" -DOWNLOAD_URL="https://github.com/$REPO/releases/download/$VERSION/$TARBALL_NAME" +DOWNLOAD_URL="https://github.com/$REPO/releases/download/merod-$VERSION/$TARBALL_NAME" # Ensure installation directory exists mkdir -p "$INSTALL_DIR" From 55e713bcb237265285d0122debbfc809a712bc67 Mon Sep 17 00:00:00 2001 From: Matej Vukosav Date: Fri, 20 Dec 2024 02:50:20 +1100 Subject: [PATCH 4/4] chore: fix which check in cli (#1026) --- .github/workflows/cross-platform-install-test.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cross-platform-install-test.yml b/.github/workflows/cross-platform-install-test.yml index 33d2955f8..bbc088f0b 100644 --- a/.github/workflows/cross-platform-install-test.yml +++ b/.github/workflows/cross-platform-install-test.yml @@ -5,8 +5,7 @@ on: branches: - master - pull_request_target: - types: [opened, reopened] + pull_request: branches: - master @@ -39,9 +38,9 @@ jobs: curl -s https://raw.githubusercontent.com/calimero-network/core/${{ env.BRANCH_NAME }}/scripts/install-merod.sh | bash # Validate the binary installation - - name: Validate installation + - name: Validate merod installation run: | - which meroctl + which merod merod --version # Run the meroctl script @@ -50,7 +49,7 @@ jobs: curl -s https://raw.githubusercontent.com/calimero-network/core/${{ env.BRANCH_NAME }}/scripts/install-meroctl.sh | bash # Validate the binary installation - - name: Validate installation + - name: Validate meroctl installation run: | which meroctl meroctl --version