Repository: Phala-Network/phala-blockchain (language: rust)

Issue:
Create way to verify that code was successfully uploaded to cluster
Currently there is no way to verify that code was successfully uploaded to cluster (or rather to pruntime).
That makes user to always upload code before instantiating (to be sure).
It would to be nice to have query / pruntime API endpoint which would return info that code is already deployed and user can estimateFee / instantiate contract.


Discussion / hints:

Fix patch:
diff --git a/crates/pink-drivers/system/lib.rs b/crates/pink-drivers/system/lib.rs
index 4d4e7b3cc4..ecdf3d3729 100755
--- a/crates/pink-drivers/system/lib.rs
+++ b/crates/pink-drivers/system/lib.rs
@@ -11,7 +11,7 @@ mod system {
     use super::pink;
     use alloc::string::String;
     use ink::{codegen::Env, storage::Mapping};
-    use pink::system::{ContractDeposit, ContractDepositRef, DriverError, Error, Result};
+    use pink::system::{CodeType, ContractDeposit, ContractDepositRef, DriverError, Error, Result};
     use pink::{HookPoint, PinkEnvironment};
 
     /// Pink's system contract.
@@ -209,6 +209,12 @@ mod system {
             pink::upgrade_runtime(version);
             Ok(())
         }
+
+        /// Check if the code is already uploaded to the cluster with given code hash.
+        #[ink(message)]
+        fn code_exists(&self, code_hash: [u8; 32], code_type: CodeType) -> bool {
+            pink::ext().code_exists(code_hash.into(), code_type.is_sidevm())
+        }
     }
 
     impl ContractDeposit for System {
diff --git a/crates/pink/pink-extension/src/system.rs b/crates/pink/pink-extension/src/system.rs
index c9623f70e0..f755dd893b 100644
--- a/crates/pink/pink-extension/src/system.rs
+++ b/crates/pink/pink-extension/src/system.rs
@@ -17,6 +17,23 @@ pub enum Error {
     ConditionNotMet,
 }
 
+/// The code type for existance check.
+#[derive(Debug, PartialEq, Eq, Encode, Decode)]
+#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
+pub enum CodeType {
+    Ink,
+    Sidevm,
+}
+
+impl CodeType {
+    pub fn is_ink(&self) -> bool {
+        matches!(self, CodeType::Ink)
+    }
+    pub fn is_sidevm(&self) -> bool {
+        matches!(self, CodeType::Sidevm)
+    }
+}
+
 /// Result type for the system contract messages
 pub type Result<T, E = Error> = core::result::Result<T, E>;
 
@@ -103,6 +120,10 @@ pub trait System {
     /// Upgrade the contract runtime
     #[ink(message)]
     fn upgrade_runtime(&self, version: (u32, u32)) -> Result<()>;
+
+    /// Check if the code is already uploaded to the cluster with given code hash.
+    #[ink(message)]
+    fn code_exists(&self, code_hash: Hash, code_type: CodeType) -> bool;
 }
 
 /// Errors that can occur upon calling a driver contract.


Test patch:
diff --git a/e2e/src/fullstack.js b/e2e/src/fullstack.js
index 0d8cfe0139..d64c4317d6 100644
--- a/e2e/src/fullstack.js
+++ b/e2e/src/fullstack.js
@@ -558,12 +558,18 @@ describe('A full stack', function () {
 
         it('can upload code with access control', async function () {
             const code = hex(contract.wasm);
-            // For now, there is no way to check whether code is uploaded in script
-            // since this requires monitering the async CodeUploaded event
+            const codeHash = hex(contract.hash);
+
             await assert.txAccepted(
                 api.tx.phalaFatContracts.clusterUploadResource(clusterId, 'InkCode', hex(code)),
                 alice,
             );
+
+            assert.isTrue(await checkUntil(async () => {
+                const { output } = await ContractSystem.query['system::codeExists'](certAlice, {}, codeHash, 'Ink');
+                return output?.eq({ Ok: true })
+            }, 4 * 6000), 'Upload system checker code failed');
+
             await assert.txFailed(
                 api.tx.phalaFatContracts.clusterUploadResource(clusterId, 'InkCode', hex(code)),
                 bob,
@@ -638,6 +644,10 @@ describe('A full stack', function () {
                 api.tx.phalaFatContracts.clusterUploadResource(clusterId, 'IndeterministicInkCode', hex(code)),
                 alice,
             );
+            assert.isTrue(await checkUntil(async () => {
+                const { output } = await ContractSystem.query['system::codeExists'](certAlice, {}, codeHash, 'Ink');
+                return output?.eq({ Ok: true })
+            }, 4 * 6000), 'Upload qjs code failed');
             {
                 // args works
                 const jsCode = `
@@ -646,11 +656,9 @@ describe('A full stack', function () {
                     return scriptArgs[0];
                 })()
                 `;
-                assert.isTrue(await checkUntil(async () => {
-                    const arg0 = "Powered by QuickJS in ink!";
-                    const { output } = await ContractSystemChecker.query.evalJs(certAlice, {}, codeHash, jsCode, [arg0]);
-                    return output?.eq({ Ok: { Ok: { String: arg0 } } });
-                }, 4 * 6000), 'Failed to evaluate js in contract');
+                const arg0 = "Powered by QuickJS in ink!";
+                const { output } = await ContractSystemChecker.query.evalJs(certAlice, {}, codeHash, jsCode, [arg0]);
+                assert.isTrue(output?.eq({ Ok: { Ok: { String: arg0 } } }));
             }
 
             {
@@ -672,9 +680,11 @@ describe('A full stack', function () {
                 alice,
             );
             assert.isTrue(await checkUntil(async () => {
-                const { output } = await ContractSystemChecker.query.parseUsd(certAlice, {}, codeHash, '{"usd":1.23}');
-                return output?.asOk.unwrap()?.usd.eq(123);
-            }, 4 * 6000), 'Failed to parse json in contract');
+                const { output } = await ContractSystem.query['system::codeExists'](certAlice, {}, codeHash, 'Ink');
+                return output?.eq({ Ok: true })
+            }, 4 * 6000), 'Upload test contract code failed');
+            const { output } = await ContractSystemChecker.query.parseUsd(certAlice, {}, codeHash, '{"usd":1.23}');
+            assert.isTrue(output?.asOk.unwrap()?.usd.eq(123));
         });
 
         it('tokenomic driver works', async function () {