diff --git a/crates/phactory/api/proto b/crates/phactory/api/proto index 6dfdacbf95..a5be2dee62 160000 --- a/crates/phactory/api/proto +++ b/crates/phactory/api/proto @@ -1 +1 @@ -Subproject commit 6dfdacbf95e71dec63763a0088e7cbe22c385d8e +Subproject commit a5be2dee620b58a0aea4a7d92a2b1d0616b14fb6 diff --git a/crates/phactory/src/contracts/support.rs b/crates/phactory/src/contracts/support.rs index d29d029db8..b26f9dcef0 100644 --- a/crates/phactory/src/contracts/support.rs +++ b/crates/phactory/src/contracts/support.rs @@ -112,6 +112,11 @@ struct SidevmInfo { handle: Arc>, } +pub(crate) enum SidevmCode { + Hash(H256), + Code(Vec), +} + #[derive(Serialize, Deserialize)] pub struct FatContract { #[serde(with = "more::scale_bytes")] @@ -232,24 +237,50 @@ impl FatContract { pub(crate) fn start_sidevm( &mut self, spawner: &sidevm::service::Spawner, - code: Vec, - auto_restart: bool, + code: SidevmCode, + check_hash: bool, ) -> Result<()> { if let Some(info) = &self.sidevm_info { if let SidevmHandle::Running(_) = &*info.handle.lock().unwrap() { bail!("Sidevm can only be started once"); } } - let handle = do_start_sidevm(spawner, &code, self.contract_id.0, self.weight)?; - let code_hash = sp_core::blake2_256(&code).into(); + let (code, code_hash) = match code { + SidevmCode::Hash(hash) => (vec![], hash), + SidevmCode::Code(code) => { + let actual_hash = sp_core::blake2_256(&code).into(); + if check_hash { + let expected_hash = self + .sidevm_info + .as_ref() + .ok_or(anyhow!("No sidevm info"))? + .code_hash; + if actual_hash != expected_hash { + bail!( + "Code hash mismatch, expected: {expected_hash:?}, actual: {actual_hash:?}" + ); + } + } + (code, actual_hash) + } + }; + + let handle = if code.is_empty() { + Arc::new(Mutex::new(SidevmHandle::Terminated( + ExitReason::WaitingForCode, + ))) + } else { + do_start_sidevm(spawner, &code, self.contract_id.0, self.weight)? + }; + let start_time = chrono::Utc::now().to_rfc3339(); self.sidevm_info = Some(SidevmInfo { code, code_hash, start_time, handle, - auto_restart, + auto_restart: true, }); Ok(()) } @@ -272,6 +303,7 @@ impl FatContract { ExitReason::OcallAborted(OcallAborted::GasExhausted) => false, ExitReason::OcallAborted(OcallAborted::Stifled) => true, ExitReason::Restore => true, + ExitReason::WaitingForCode => false, }; if !need_restart { return Ok(()); diff --git a/crates/phactory/src/prpc_service.rs b/crates/phactory/src/prpc_service.rs index b0a2b1d60a..b39e65ac4e 100644 --- a/crates/phactory/src/prpc_service.rs +++ b/crates/phactory/src/prpc_service.rs @@ -817,6 +817,12 @@ impl Phactory .collect(); Ok(pb::GetClusterInfoResponse { clusters }) } + + pub fn upload_sidevm_code(&mut self, contract_id: ContractId, code: Vec) -> RpcResult<()> { + self.system()? + .upload_sidevm_code(contract_id, code) + .map_err(from_display) + } } #[derive(Clone)] @@ -1388,7 +1394,8 @@ impl PhactoryApi for Rpc &mut self, request: pb::GetContractInfoRequest, ) -> Result { - self.lock_phactory().get_contract_info(&request.contract_ids) + self.lock_phactory() + .get_contract_info(&request.contract_ids) } async fn get_cluster_info( @@ -1397,6 +1404,18 @@ impl PhactoryApi for Rpc ) -> Result { self.lock_phactory().get_cluster_info() } + + async fn upload_sidevm_code( + &mut self, + request: pb::SidevmCode, + ) -> Result<(), prpc::server::Error> { + let contract_id: [u8; 32] = request + .contract + .try_into() + .or(Err(from_display("Invalid contract id")))?; + self.lock_phactory() + .upload_sidevm_code(contract_id.into(), request.code) + } } fn try_decode_hex(hex_str: &str) -> Result, hex::FromHexError> { diff --git a/crates/phactory/src/system/mod.rs b/crates/phactory/src/system/mod.rs index 4304899a7c..7e1faaf707 100644 --- a/crates/phactory/src/system/mod.rs +++ b/crates/phactory/src/system/mod.rs @@ -4,7 +4,7 @@ mod side_tasks; use crate::{ benchmark, - contracts::{pink::cluster::Cluster, AnyContract, ContractsKeeper, ExecuteEnv}, + contracts::{pink::cluster::Cluster, AnyContract, ContractsKeeper, ExecuteEnv, SidevmCode}, pink::{cluster::ClusterKeeper, ContractEventCallback, Pink}, secret_channel::{ecdh_serde, SecretReceiver}, types::{BlockInfo, OpaqueError, OpaqueQuery, OpaqueReply}, @@ -1626,6 +1626,19 @@ impl System

{ &self.sidevm_spawner, ); } + + pub(crate) fn upload_sidevm_code( + &mut self, + contract_id: ContractId, + code: Vec, + ) -> Result<()> { + let contract = self + .contracts + .get_mut(&contract_id) + .ok_or_else(|| anyhow!("Contract not found"))?; + + contract.start_sidevm(&self.sidevm_spawner, SidevmCode::Code(code), true) + } } pub fn handle_contract_command_result( @@ -1809,14 +1822,11 @@ pub(crate) fn apply_pink_events( let vmid = sidevm::ShortId(target_contract.as_ref()); let target_contract = get_contract!(&target_contract); let code_hash = code_hash.into(); - let wasm_code = match cluster.get_resource(ResourceType::SidevmCode, &code_hash) { - Some(code) => code, - None => { - error!(target: "sidevm", "[{vmid}] Start sidevm failed: code not found, code_hash={code_hash:?}"); - continue; - } + let code = match cluster.get_resource(ResourceType::SidevmCode, &code_hash) { + Some(code) => SidevmCode::Code(code), + None => SidevmCode::Hash(code_hash), }; - if let Err(err) = target_contract.start_sidevm(&spawner, wasm_code, true) { + if let Err(err) = target_contract.start_sidevm(&spawner, code, false) { error!(target: "sidevm", "[{vmid}] Start sidevm failed: {:?}", err); } } diff --git a/crates/sidevm/host-runtime/src/service.rs b/crates/sidevm/host-runtime/src/service.rs index 3023768f55..0c782f34a5 100644 --- a/crates/sidevm/host-runtime/src/service.rs +++ b/crates/sidevm/host-runtime/src/service.rs @@ -37,6 +37,8 @@ pub enum ExitReason { OcallAborted(OcallAborted), /// When a previous running instance restored from a checkpoint. Restore, + /// The sidevm was deployed without code, so it it waiting to a custom code uploading. + WaitingForCode, } pub enum Command { diff --git a/standalone/pruntime/src/api_server.rs b/standalone/pruntime/src/api_server.rs index 54e18f8157..a4a5e3c980 100644 --- a/standalone/pruntime/src/api_server.rs +++ b/standalone/pruntime/src/api_server.rs @@ -184,6 +184,7 @@ fn default_payload_limit_for_method(method: PhactoryAPIMethod) -> ByteUnit { HttpFetch => 100.mebibytes(), GetContractInfo => 100.kibibytes(), GetClusterInfo => 1.kibibytes(), + UploadSidevmCode => 32.mebibytes(), } } @@ -227,6 +228,7 @@ async fn prpc_proxy_acl(method: String, data: Data<'_>, limits: &Limits) -> Cust "PhactoryAPI.GetInfo", "PhactoryAPI.GetContractInfo", "PhactoryAPI.GetClusterInfo", + "PhactoryAPI.UploadSidevmCode", ]; if !permitted_method.contains(&&method[..]) { error!("prpc_acl: access denied");