瀏覽代碼

drk: updated contract deployment code to be fully functional

skoupidi 9 月之前
父節點
當前提交
699c942fb7
共有 5 個文件被更改,包括 256 次插入54 次删除
  1. 3 0
      bin/drk/deploy.sql
  2. 228 15
      bin/drk/src/deploy.rs
  3. 6 24
      bin/drk/src/interactive.rs
  4. 4 14
      bin/drk/src/main.rs
  5. 15 1
      bin/drk/src/rpc.rs

+ 3 - 0
bin/drk/deploy.sql

@@ -2,8 +2,11 @@
 -- Native Contract ID: EJs7oEjKkvCeEVCmpRsd6fEoTGCFJ7WKUBfmAjwaegN
 
 CREATE TABLE IF NOT EXISTS EJs7oEjKkvCeEVCmpRsd6fEoTGCFJ7WKUBfmAjwaegN_deploy_auth (
+	-- TODO: this should be the contract id
 	id INTEGER PRIMARY KEY AUTOINCREMENT,
+	-- TODO: this should be just the secret
 	deploy_authority BLOB UNIQUE NOT NULL,
 	is_frozen INTEGER NOT NULL,
+	-- TODO: rename to lock height
 	freeze_height INTEGER
 );

+ 228 - 15
bin/drk/src/deploy.rs

@@ -16,25 +16,35 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+use std::collections::HashMap;
+
 use lazy_static::lazy_static;
 use rand::rngs::OsRng;
 
 use darkfi::{
     tx::{ContractCallLeaf, Transaction, TransactionBuilder},
+    zk::{proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses},
+    zkas::ZkBinary,
     Error, Result,
 };
 use darkfi_deployooor_contract::{
     client::{deploy_v1::DeployCallBuilder, lock_v1::LockCallBuilder},
+    model::LockParamsV1,
     DeployFunction,
 };
+use darkfi_money_contract::MONEY_CONTRACT_ZKAS_FEE_NS_V1;
 use darkfi_sdk::{
-    crypto::{ContractId, Keypair, DEPLOYOOOR_CONTRACT_ID},
+    crypto::{
+        ContractId, Keypair, PublicKey, SecretKey, DEPLOYOOOR_CONTRACT_ID, MONEY_CONTRACT_ID,
+    },
+    deploy::DeployParamsV1,
+    tx::TransactionHash,
     ContractCall,
 };
-use darkfi_serial::{deserialize_async, serialize_async, AsyncEncodable};
+use darkfi_serial::{deserialize_async, serialize, serialize_async, AsyncEncodable};
 use rusqlite::types::Value;
 
-use crate::{convert_named_params, error::WalletDbResult, Drk};
+use crate::{convert_named_params, error::WalletDbResult, rpc::ScanCache, Drk};
 
 // Wallet SQL table constant names. These have to represent the `wallet.sql`
 // SQL schema. Table names are prefixed with the contract ID to avoid collisions.
@@ -172,28 +182,151 @@ impl Drk {
         Ok(ret)
     }
 
-    /// Retrieve a deploy authority keypair given an index
-    async fn get_deploy_auth(&self, idx: u64) -> Result<Keypair> {
+    /// Retrieve a deploy authority keypair and status for provided
+    /// index.
+    async fn get_deploy_auth(&self, idx: u64) -> Result<(Keypair, bool)> {
         // Find the deploy authority keypair
         let row = match self.wallet.query_single(
             &DEPLOY_AUTH_TABLE,
-            &[DEPLOY_AUTH_COL_DEPLOY_AUTHORITY],
+            &[DEPLOY_AUTH_COL_DEPLOY_AUTHORITY, DEPLOY_AUTH_COL_IS_FROZEN],
             convert_named_params! {(DEPLOY_AUTH_COL_ID, idx)},
         ) {
             Ok(v) => v,
             Err(e) => {
                 return Err(Error::DatabaseError(format!(
-                    "[deploy_contract] Failed to retrieve deploy authority keypair: {e}"
+                    "[get_deploy_auth] Failed to retrieve deploy authority keypair: {e}"
                 )))
             }
         };
 
         let Value::Blob(ref keypair_bytes) = row[0] else {
-            return Err(Error::ParseFailed("[deploy_contract] Failed to parse keypair bytes"))
+            return Err(Error::ParseFailed("[get_deploy_auth] Failed to parse keypair bytes"))
         };
         let keypair: Keypair = deserialize_async(keypair_bytes).await?;
 
-        Ok(keypair)
+        let Value::Integer(locked) = row[1] else {
+            return Err(Error::ParseFailed("[get_deploy_auth] Failed to parse \"is_frozen\""))
+        };
+
+        Ok((keypair, locked != 0))
+    }
+
+    /// Retrieve contract deploy authorities public keys from the
+    /// wallet.
+    pub async fn get_deploy_auths_keys_map(&self) -> Result<HashMap<[u8; 32], SecretKey>> {
+        let rows = match self.wallet.query_multiple(
+            &DEPLOY_AUTH_TABLE,
+            &[DEPLOY_AUTH_COL_DEPLOY_AUTHORITY],
+            &[],
+        ) {
+            Ok(r) => r,
+            Err(e) => {
+                return Err(Error::DatabaseError(format!(
+                "[get_deploy_auths_keys_map] Failed to retrieve deploy authorities keypairs: {e}",
+            )))
+            }
+        };
+
+        let mut ret = HashMap::new();
+        for row in rows {
+            let Value::Blob(ref keypair_bytes) = row[0] else {
+                return Err(Error::ParseFailed(
+                    "[get_deploy_auths_keys_map] Failed to parse keypair bytes",
+                ))
+            };
+            let keypair: Keypair = deserialize_async(keypair_bytes).await?;
+            ret.insert(keypair.public.to_bytes(), keypair.secret);
+        }
+
+        Ok(ret)
+    }
+
+    /// Auxiliary function to apply `DeployFunction::DeployV1` call
+    /// data to the wallet.
+    /// Returns a flag indicating if the provided call refers to our
+    /// own wallet.
+    fn apply_deploy_deploy_data(
+        &self,
+        scan_cache: &ScanCache,
+        params: &DeployParamsV1,
+        _tx_hash: &TransactionHash,
+        _block_height: &u32,
+    ) -> Result<bool> {
+        // Check if we have the deploy authority key
+        let Some(_secret_key) = scan_cache.own_deploy_auths.get(&params.public_key.to_bytes())
+        else {
+            return Ok(false)
+        };
+
+        // Create a new history record containing the deployment data
+        // TODO
+
+        Ok(true)
+    }
+
+    /// Auxiliary function to apply `DeployFunction::LockV1` call
+    /// data to the wallet.
+    /// Returns a flag indicating if the provided call refers to our
+    /// own wallet.
+    fn apply_deploy_lock_data(
+        &self,
+        scan_cache: &ScanCache,
+        public_key: &PublicKey,
+        _tx_hash: &TransactionHash,
+        freeze_height: &u32,
+    ) -> Result<bool> {
+        // Check if we have the deploy authority key
+        let Some(secret_key) = scan_cache.own_deploy_auths.get(&public_key.to_bytes()) else {
+            return Ok(false)
+        };
+
+        // Freeze contract
+        let query = format!(
+            "UPDATE {} SET {} = 1, {} = ?1 WHERE {} = ?2;",
+            *DEPLOY_AUTH_TABLE,
+            DEPLOY_AUTH_COL_IS_FROZEN,
+            DEPLOY_AUTH_COL_FREEZE_HEIGHT,
+            DEPLOY_AUTH_COL_DEPLOY_AUTHORITY
+        );
+        if let Err(e) = self.wallet.exec_sql(
+            &query,
+            rusqlite::params![Some(*freeze_height), serialize(&Keypair::new(*secret_key))],
+        ) {
+            return Err(Error::DatabaseError(format!(
+                "[apply_deploy_lock_data] Lock deploy authority failed: {e}"
+            )))
+        }
+
+        // Create a new history record for the lock transaction
+        // TODO
+
+        Ok(true)
+    }
+
+    /// Append data related to DeployoOor contract transactions into
+    /// the wallet database and update the provided scan cache.
+    /// Returns a flag indicating if provided data refer to our own
+    /// wallet.
+    pub async fn apply_tx_deploy_data(
+        &self,
+        scan_cache: &mut ScanCache,
+        data: &[u8],
+        tx_hash: &TransactionHash,
+        block_height: &u32,
+    ) -> Result<bool> {
+        // Run through the transaction call data and see what we got:
+        match DeployFunction::try_from(data[0])? {
+            DeployFunction::DeployV1 => {
+                scan_cache.log(String::from("[apply_tx_deploy_data] Found Deploy::DeployV1 call"));
+                let params: DeployParamsV1 = deserialize_async(&data[1..]).await?;
+                self.apply_deploy_deploy_data(scan_cache, &params, tx_hash, block_height)
+            }
+            DeployFunction::LockV1 => {
+                scan_cache.log(String::from("[apply_tx_deploy_data] Found Deploy::LockV1 call"));
+                let params: LockParamsV1 = deserialize_async(&data[1..]).await?;
+                self.apply_deploy_lock_data(scan_cache, &params.public_key, tx_hash, block_height)
+            }
+        }
     }
 
     /// Create a feeless contract deployment transaction.
@@ -203,8 +336,30 @@ impl Drk {
         wasm_bincode: Vec<u8>,
         deploy_ix: Vec<u8>,
     ) -> Result<Transaction> {
-        // Fetch the keypair
-        let deploy_keypair = self.get_deploy_auth(deploy_auth).await?;
+        // Fetch the keypair and its status
+        let (deploy_keypair, is_locked) = self.get_deploy_auth(deploy_auth).await?;
+
+        // Check lock status
+        if is_locked {
+            return Err(Error::Custom("[deploy_contract] Contract is locked".to_string()))
+        }
+
+        // Now we need to do a lookup for the zkas proof bincodes, and create
+        // the circuit objects and proving keys so we can build the transaction.
+        // We also do this through the RPC.
+        let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
+
+        let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
+        else {
+            return Err(Error::Custom("[deploy_contract] Fee circuit not found".to_string()))
+        };
+
+        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
+
+        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
+
+        // Creating Fee circuit proving keys
+        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
 
         // Create the contract call
         let deploy_call = DeployCallBuilder { deploy_keypair, wasm_bincode, deploy_ix };
@@ -214,20 +369,60 @@ impl Drk {
         let mut data = vec![DeployFunction::DeployV1 as u8];
         deploy_debris.params.encode_async(&mut data).await?;
         let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data };
+
+        // Create the TransactionBuilder containing above cal
         let mut tx_builder =
             TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?;
 
+        // We first have to execute the fee-less tx to gather its used gas, and then we feed
+        // it into the fee-creating function.
         let mut tx = tx_builder.build()?;
         let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
-        tx.signatures = vec![sigs];
+        tx.signatures.push(sigs);
+
+        let tree = self.get_money_tree().await?;
+        let (fee_call, fee_proofs, fee_secrets) =
+            self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
+
+        // Append the fee call to the transaction
+        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
+
+        // Now build the actual transaction and sign it with all necessary keys.
+        let mut tx = tx_builder.build()?;
+        let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
+        tx.signatures.push(sigs);
+        let sigs = tx.create_sigs(&fee_secrets)?;
+        tx.signatures.push(sigs);
 
         Ok(tx)
     }
 
     /// Create a feeless contract redeployment lock transaction.
     pub async fn lock_contract(&self, deploy_auth: u64) -> Result<Transaction> {
-        // Fetch the keypair
-        let deploy_keypair = self.get_deploy_auth(deploy_auth).await?;
+        // Fetch the keypair and its status
+        let (deploy_keypair, is_locked) = self.get_deploy_auth(deploy_auth).await?;
+
+        // Check lock status
+        if is_locked {
+            return Err(Error::Custom("[lock_contract] Contract is already locked".to_string()))
+        }
+
+        // Now we need to do a lookup for the zkas proof bincodes, and create
+        // the circuit objects and proving keys so we can build the transaction.
+        // We also do this through the RPC.
+        let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
+
+        let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
+        else {
+            return Err(Error::Custom("[lock_contract] Fee circuit not found".to_string()))
+        };
+
+        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
+
+        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
+
+        // Creating Fee circuit proving keys
+        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
 
         // Create the contract call
         let lock_call = LockCallBuilder { deploy_keypair };
@@ -237,12 +432,30 @@ impl Drk {
         let mut data = vec![DeployFunction::LockV1 as u8];
         lock_debris.params.encode_async(&mut data).await?;
         let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data };
+
+        // Create the TransactionBuilder containing above cal
         let mut tx_builder =
             TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?;
 
+        // We first have to execute the fee-less tx to gather its used gas, and then we feed
+        // it into the fee-creating function.
+        let mut tx = tx_builder.build()?;
+        let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
+        tx.signatures.push(sigs);
+
+        let tree = self.get_money_tree().await?;
+        let (fee_call, fee_proofs, fee_secrets) =
+            self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
+
+        // Append the fee call to the transaction
+        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
+
+        // Now build the actual transaction and sign it with all necessary keys.
         let mut tx = tx_builder.build()?;
         let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
-        tx.signatures = vec![sigs];
+        tx.signatures.push(sigs);
+        let sigs = tx.create_sigs(&fee_secrets)?;
+        tx.signatures.push(sigs);
 
         Ok(tx)
     }

+ 6 - 24
bin/drk/src/interactive.rs

@@ -3193,18 +3193,9 @@ async fn handle_contract_deploy(drk: &DrkPtr, parts: &[&str], output: &mut Vec<S
         vec![]
     };
 
-    let lock = drk.read().await;
-    let mut tx = match lock.deploy_contract(deploy_auth, wasm_bin, deploy_ix).await {
-        Ok(v) => v,
-        Err(e) => {
-            output.push(format!("Error creating contract deployment tx: {e}"));
-            return
-        }
-    };
-
-    match lock.attach_fee(&mut tx).await {
-        Ok(_) => output.push(base64::encode(&serialize_async(&tx).await)),
-        Err(e) => output.push(format!("Failed to attach the fee call to the transaction: {e}")),
+    match drk.read().await.deploy_contract(deploy_auth, wasm_bin, deploy_ix).await {
+        Ok(t) => output.push(base64::encode(&serialize_async(&t).await)),
+        Err(e) => output.push(format!("Failed to create contract deployment transaction: {e}")),
     }
 }
 
@@ -3225,17 +3216,8 @@ async fn handle_contract_lock(drk: &DrkPtr, parts: &[&str], output: &mut Vec<Str
         }
     };
 
-    let lock = drk.read().await;
-    let mut tx = match lock.lock_contract(deploy_auth).await {
-        Ok(v) => v,
-        Err(e) => {
-            output.push(format!("Error creating contract lock tx: {e}"));
-            return
-        }
-    };
-
-    match lock.attach_fee(&mut tx).await {
-        Ok(_) => output.push(base64::encode(&serialize_async(&tx).await)),
-        Err(e) => output.push(format!("Failed to attach the fee call to the transaction: {e}")),
+    match drk.read().await.lock_contract(deploy_auth).await {
+        Ok(t) => output.push(base64::encode(&serialize_async(&t).await)),
+        Err(e) => output.push(format!("Failed to create contract lock transaction: {e}")),
     }
 }

+ 4 - 14
bin/drk/src/main.rs

@@ -2622,19 +2622,14 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 )
                 .await;
 
-                let mut tx = match drk.deploy_contract(deploy_auth, wasm_bin, deploy_ix).await {
-                    Ok(t) => t,
+                let tx = match drk.deploy_contract(deploy_auth, wasm_bin, deploy_ix).await {
+                    Ok(tx) => tx,
                     Err(e) => {
                         eprintln!("Error creating contract deployment tx: {e}");
                         exit(2);
                     }
                 };
 
-                if let Err(e) = drk.attach_fee(&mut tx).await {
-                    eprintln!("Failed to attach the fee call to the transaction: {e}");
-                    exit(2);
-                };
-
                 println!("{}", base64::encode(&serialize_async(&tx).await));
 
                 drk.stop_rpc_client().await
@@ -2651,19 +2646,14 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 )
                 .await;
 
-                let mut tx = match drk.lock_contract(deploy_auth).await {
-                    Ok(t) => t,
+                let tx = match drk.lock_contract(deploy_auth).await {
+                    Ok(tx) => tx,
                     Err(e) => {
                         eprintln!("Error creating contract lock tx: {e}");
                         exit(2);
                     }
                 };
 
-                if let Err(e) = drk.attach_fee(&mut tx).await {
-                    eprintln!("Failed to attach the fee call to the transaction: {e}");
-                    exit(2);
-                };
-
                 println!("{}", base64::encode(&serialize_async(&tx).await));
 
                 drk.stop_rpc_client().await

+ 15 - 1
bin/drk/src/rpc.rs

@@ -78,6 +78,8 @@ pub struct ScanCache {
     pub own_daos: HashMap<DaoBulla, (Option<SecretKey>, Option<SecretKey>)>,
     /// Our own DAOs proposals with their corresponding DAO reference
     pub own_proposals: HashMap<DaoProposalBulla, DaoBulla>,
+    /// Our own deploy authorities
+    pub own_deploy_auths: HashMap<[u8; 32], SecretKey>,
     /// Messages buffer for better downstream prints handling
     pub messages_buffer: Vec<String>,
 }
@@ -129,6 +131,7 @@ impl Drk {
         for proposal in self.get_proposals().await? {
             own_proposals.insert(proposal.bulla(), proposal.proposal.dao_bulla);
         }
+        let own_deploy_auths = self.get_deploy_auths_keys_map().await?;
 
         Ok(ScanCache {
             money_tree,
@@ -140,6 +143,7 @@ impl Drk {
             dao_proposals_tree,
             own_daos,
             own_proposals,
+            own_deploy_auths,
             messages_buffer: vec![],
         })
     }
@@ -202,7 +206,17 @@ impl Drk {
 
                 if call.data.contract_id == *DEPLOYOOOR_CONTRACT_ID {
                     scan_cache.log(format!("[scan_block] Found DeployoOor contract in call {i}"));
-                    // TODO: implement
+                    if self
+                        .apply_tx_deploy_data(
+                            scan_cache,
+                            &call.data.data,
+                            &tx_hash,
+                            &block.header.height,
+                        )
+                        .await?
+                    {
+                        wallet_tx = true;
+                    }
                     continue
                 }