1use std::collections::HashMap;
20
21use lazy_static::lazy_static;
22use rand::rngs::OsRng;
23
24use darkfi::{
25 tx::{ContractCallLeaf, Transaction, TransactionBuilder},
26 zk::{proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses},
27 zkas::ZkBinary,
28 Error, Result,
29};
30use darkfi_deployooor_contract::{
31 client::{deploy_v1::DeployCallBuilder, lock_v1::LockCallBuilder},
32 model::LockParamsV1,
33 DeployFunction,
34};
35use darkfi_money_contract::MONEY_CONTRACT_ZKAS_FEE_NS_V1;
36use darkfi_sdk::{
37 crypto::{
38 ContractId, Keypair, PublicKey, SecretKey, DEPLOYOOOR_CONTRACT_ID, MONEY_CONTRACT_ID,
39 },
40 deploy::DeployParamsV1,
41 tx::TransactionHash,
42 ContractCall,
43};
44use darkfi_serial::{deserialize_async, serialize, serialize_async, AsyncEncodable};
45use rusqlite::types::Value;
46
47use crate::{convert_named_params, error::WalletDbResult, rpc::ScanCache, Drk};
48
49lazy_static! {
52 pub static ref DEPLOY_AUTH_TABLE: String =
53 format!("{}_deploy_auth", DEPLOYOOOR_CONTRACT_ID.to_string());
54 pub static ref DEPLOY_HISTORY_TABLE: String =
55 format!("{}_deploy_history", DEPLOYOOOR_CONTRACT_ID.to_string());
56}
57
58pub const DEPLOY_AUTH_COL_CONTRACT_ID: &str = "contract_id";
60pub const DEPLOY_AUTH_COL_SECRET_KEY: &str = "secret_key";
61pub const DEPLOY_AUTH_COL_IS_LOCKED: &str = "is_locked";
62pub const DEPLOY_AUTH_COL_LOCK_HEIGHT: &str = "lock_height";
63
64pub const DEPLOY_HISTORY_COL_TX_HASH: &str = "tx_hash";
66pub const DEPLOY_HISTORY_COL_CONTRACT: &str = "contract";
67pub const DEPLOY_HISTORY_COL_TYPE: &str = "type";
68pub const DEPLOY_HISTORY_COL_BLOCK_HEIGHT: &str = "block_height";
69pub const DEPLOY_HISTORY_COL_WASM_BINCODE: &str = "wasm_bincode";
70pub const DEPLOY_HISTORY_COL_DEPLOY_IX: &str = "deploy_ix";
71
72impl Drk {
73 pub fn initialize_deployooor(&self) -> WalletDbResult<()> {
75 let wallet_schema = include_str!("../deploy.sql");
77 self.wallet.exec_batch_sql(wallet_schema)?;
78
79 Ok(())
80 }
81
82 pub async fn deploy_auth_keygen(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
84 output.push(String::from("Generating a new keypair"));
85
86 let secret_key = SecretKey::random(&mut OsRng);
87 let contract_id = ContractId::derive_public(PublicKey::from_secret(secret_key));
88 let lock_height: Option<u32> = None;
89
90 let query = format!(
91 "INSERT INTO {} ({}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4);",
92 *DEPLOY_AUTH_TABLE,
93 DEPLOY_AUTH_COL_CONTRACT_ID,
94 DEPLOY_AUTH_COL_SECRET_KEY,
95 DEPLOY_AUTH_COL_IS_LOCKED,
96 DEPLOY_AUTH_COL_LOCK_HEIGHT,
97 );
98 self.wallet.exec_sql(
99 &query,
100 rusqlite::params![
101 serialize_async(&contract_id).await,
102 serialize_async(&secret_key).await,
103 0,
104 lock_height
105 ],
106 )?;
107
108 output.push(String::from("Created new contract deploy authority"));
109 output.push(format!("Contract ID: {contract_id}"));
110
111 Ok(())
112 }
113
114 pub fn put_deploy_history_record(
116 &self,
117 tx_hash: &TransactionHash,
118 contract: &ContractId,
119 tx_type: &str,
120 block_height: &u32,
121 wasm_bincode: &Option<Vec<u8>>,
122 deploy_ix: &Option<Vec<u8>>,
123 ) -> WalletDbResult<()> {
124 let query = format!(
125 "INSERT INTO {} ({}, {}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5, ?6);",
126 *DEPLOY_HISTORY_TABLE,
127 DEPLOY_HISTORY_COL_TX_HASH,
128 DEPLOY_HISTORY_COL_CONTRACT,
129 DEPLOY_HISTORY_COL_TYPE,
130 DEPLOY_HISTORY_COL_BLOCK_HEIGHT,
131 DEPLOY_HISTORY_COL_WASM_BINCODE,
132 DEPLOY_HISTORY_COL_DEPLOY_IX,
133 );
134 self.wallet.exec_sql(
135 &query,
136 rusqlite::params![
137 tx_hash.to_string(),
138 serialize(contract),
139 tx_type,
140 block_height,
141 serialize(wasm_bincode),
142 serialize(deploy_ix),
143 ],
144 )?;
145
146 Ok(())
147 }
148
149 pub fn reset_deploy_authorities(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
151 output.push(String::from("Resetting deploy authorities locked status"));
152 let query = format!(
153 "UPDATE {} SET {} = 0, {} = NULL;",
154 *DEPLOY_AUTH_TABLE, DEPLOY_AUTH_COL_IS_LOCKED, DEPLOY_AUTH_COL_LOCK_HEIGHT
155 );
156 self.wallet.exec_sql(&query, &[])?;
157 output.push(String::from("Successfully reset deploy authorities locked status"));
158
159 Ok(())
160 }
161
162 pub fn unlock_deploy_authorities_after(
165 &self,
166 height: &u32,
167 output: &mut Vec<String>,
168 ) -> WalletDbResult<()> {
169 output.push(format!("Resetting deploy authorities locked status after: {height}"));
170 let query = format!(
171 "UPDATE {} SET {} = 0, {} = NULL WHERE {} > ?1;",
172 *DEPLOY_AUTH_TABLE,
173 DEPLOY_AUTH_COL_IS_LOCKED,
174 DEPLOY_AUTH_COL_LOCK_HEIGHT,
175 DEPLOY_AUTH_COL_LOCK_HEIGHT
176 );
177 self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?;
178 output.push(String::from("Successfully reset deploy authorities locked status"));
179
180 Ok(())
181 }
182
183 pub fn reset_deploy_history(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
185 output.push(String::from("Resetting deployment history"));
186 let query = format!("DELETE FROM {};", *DEPLOY_HISTORY_TABLE);
187 self.wallet.exec_sql(&query, &[])?;
188 output.push(String::from("Successfully deployment history"));
189
190 Ok(())
191 }
192
193 pub fn remove_deploy_history_after(
196 &self,
197 height: &u32,
198 output: &mut Vec<String>,
199 ) -> WalletDbResult<()> {
200 output.push(format!("Removing deployment history records after: {height}"));
201 let query = format!(
202 "DELETE FROM {} WHERE {} > ?1;",
203 *DEPLOY_HISTORY_TABLE, DEPLOY_HISTORY_COL_BLOCK_HEIGHT
204 );
205 self.wallet.exec_sql(&query, rusqlite::params![height])?;
206 output.push(String::from("Successfully removed deployment history records"));
207
208 Ok(())
209 }
210
211 pub async fn list_deploy_auth(
213 &self,
214 ) -> Result<Vec<(ContractId, SecretKey, bool, Option<u32>)>> {
215 let rows = match self.wallet.query_multiple(&DEPLOY_AUTH_TABLE, &[], &[]) {
216 Ok(r) => r,
217 Err(e) => {
218 return Err(Error::DatabaseError(format!(
219 "[list_deploy_auth] Deploy auth retrieval failed: {e}",
220 )))
221 }
222 };
223
224 let mut ret = Vec::with_capacity(rows.len());
225 for row in rows {
226 let Value::Blob(ref contract_id_bytes) = row[0] else {
227 return Err(Error::ParseFailed(
228 "[list_deploy_auth] Failed to parse contract id bytes",
229 ))
230 };
231 let contract_id: ContractId = deserialize_async(contract_id_bytes).await?;
232
233 let Value::Blob(ref secret_key_bytes) = row[1] else {
234 return Err(Error::ParseFailed(
235 "[list_deploy_auth] Failed to parse secret key bytes",
236 ))
237 };
238 let secret_key: SecretKey = deserialize_async(secret_key_bytes).await?;
239
240 let Value::Integer(locked) = row[2] else {
241 return Err(Error::ParseFailed("[list_deploy_auth] Failed to parse \"is_locked\""))
242 };
243
244 let lock_height = match row[3] {
245 Value::Integer(lock_height) => {
246 let Ok(lock_height) = u32::try_from(lock_height) else {
247 return Err(Error::ParseFailed(
248 "[list_deploy_auth] Lock height parsing failed",
249 ))
250 };
251 Some(lock_height)
252 }
253 Value::Null => None,
254 _ => {
255 return Err(Error::ParseFailed("[list_deploy_auth] Lock height parsing failed"))
256 }
257 };
258
259 ret.push((contract_id, secret_key, locked != 0, lock_height))
260 }
261
262 Ok(ret)
263 }
264
265 async fn get_deploy_auth(&self, contract_id: &ContractId) -> Result<(Keypair, bool)> {
268 let row = match self.wallet.query_single(
270 &DEPLOY_AUTH_TABLE,
271 &[DEPLOY_AUTH_COL_SECRET_KEY, DEPLOY_AUTH_COL_IS_LOCKED],
272 convert_named_params! {(DEPLOY_AUTH_COL_CONTRACT_ID, serialize_async(contract_id).await)},
273 ) {
274 Ok(v) => v,
275 Err(e) => {
276 return Err(Error::DatabaseError(format!(
277 "[get_deploy_auth] Failed to retrieve deploy authority keypair: {e}"
278 )))
279 }
280 };
281
282 let Value::Blob(ref secret_key_bytes) = row[0] else {
283 return Err(Error::ParseFailed("[get_deploy_auth] Failed to parse secret key bytes"))
284 };
285 let secret_key: SecretKey = deserialize_async(secret_key_bytes).await?;
286 let keypair = Keypair::new(secret_key);
287
288 let Value::Integer(locked) = row[1] else {
289 return Err(Error::ParseFailed("[get_deploy_auth] Failed to parse \"is_locked\""))
290 };
291
292 Ok((keypair, locked != 0))
293 }
294
295 pub async fn get_deploy_auths_keys_map(&self) -> Result<HashMap<[u8; 32], SecretKey>> {
297 let rows = match self.wallet.query_multiple(
298 &DEPLOY_AUTH_TABLE,
299 &[DEPLOY_AUTH_COL_SECRET_KEY],
300 &[],
301 ) {
302 Ok(r) => r,
303 Err(e) => {
304 return Err(Error::DatabaseError(format!(
305 "[get_deploy_auths_keys_map] Failed to retrieve deploy authorities secret keys: {e}",
306 )))
307 }
308 };
309
310 let mut ret = HashMap::new();
311 for row in rows {
312 let Value::Blob(ref secret_key_bytes) = row[0] else {
313 return Err(Error::ParseFailed(
314 "[get_deploy_auths_keys_map] Failed to parse secret key bytes",
315 ))
316 };
317 let secret_key: SecretKey = deserialize_async(secret_key_bytes).await?;
318 ret.insert(PublicKey::from_secret(secret_key).to_bytes(), secret_key);
319 }
320
321 Ok(ret)
322 }
323
324 pub async fn get_deploy_auth_history(
327 &self,
328 contract_id: &ContractId,
329 ) -> Result<Vec<(String, String, u32)>> {
330 let rows = match self.wallet.query_multiple(
331 &DEPLOY_HISTORY_TABLE,
332 &[DEPLOY_HISTORY_COL_TX_HASH, DEPLOY_HISTORY_COL_TYPE, DEPLOY_HISTORY_COL_BLOCK_HEIGHT],
333 convert_named_params! {(DEPLOY_HISTORY_COL_CONTRACT, serialize_async(contract_id).await)},
334 ) {
335 Ok(r) => r,
336 Err(e) => {
337 return Err(Error::DatabaseError(format!(
338 "[get_deploy_auth_history] Failed to retrieve deploy authority history records: {e}",
339 )))
340 }
341 };
342
343 let mut ret = Vec::with_capacity(rows.len());
344 for row in rows {
345 let Value::Text(ref tx_hash) = row[0] else {
346 return Err(Error::ParseFailed(
347 "[get_deploy_auth_history] Transaction hash parsing failed",
348 ))
349 };
350
351 let Value::Text(ref tx_type) = row[1] else {
352 return Err(Error::ParseFailed("[get_deploy_auth_history] Type parsing failed"))
353 };
354
355 let Value::Integer(block_height) = row[2] else {
356 return Err(Error::ParseFailed(
357 "[get_deploy_auth_history] Block height parsing failed",
358 ))
359 };
360 let Ok(block_height) = u32::try_from(block_height) else {
361 return Err(Error::ParseFailed(
362 "[get_deploy_auth_history] Block height parsing failed",
363 ))
364 };
365
366 ret.push((tx_hash.clone(), tx_type.clone(), block_height));
367 }
368
369 Ok(ret)
370 }
371
372 pub async fn get_deploy_history_record_data(
375 &self,
376 tx_hash: &str,
377 ) -> Result<(Option<Vec<u8>>, Option<Vec<u8>>)> {
378 let row = match self.wallet.query_single(
379 &DEPLOY_HISTORY_TABLE,
380 &[DEPLOY_HISTORY_COL_WASM_BINCODE, DEPLOY_HISTORY_COL_DEPLOY_IX],
381 convert_named_params! {(DEPLOY_HISTORY_COL_TX_HASH, tx_hash)},
382 ) {
383 Ok(v) => v,
384 Err(e) => {
385 return Err(Error::DatabaseError(format!(
386 "[get_deploy_history_record] Failed to retrieve deploy history record: {e}"
387 )))
388 }
389 };
390
391 let Value::Blob(ref wasm_bincode_bytes) = row[0] else {
392 return Err(Error::ParseFailed(
393 "[get_deploy_history_record] Failed to parse wasm bincode bytes",
394 ))
395 };
396 let wasm_bincode: Option<Vec<u8>> = deserialize_async(wasm_bincode_bytes).await?;
397
398 let Value::Blob(ref deploy_ix_bytes) = row[1] else {
399 return Err(Error::ParseFailed(
400 "[get_deploy_history_record] Failed to parse deploy ix bytes",
401 ))
402 };
403 let deploy_ix: Option<Vec<u8>> = deserialize_async(deploy_ix_bytes).await?;
404
405 Ok((wasm_bincode, deploy_ix))
406 }
407
408 fn apply_deploy_deploy_data(
413 &self,
414 scan_cache: &ScanCache,
415 params: &DeployParamsV1,
416 tx_hash: &TransactionHash,
417 block_height: &u32,
418 ) -> Result<bool> {
419 let Some(_) = scan_cache.own_deploy_auths.get(¶ms.public_key.to_bytes()) else {
421 return Ok(false)
422 };
423
424 if let Err(e) = self.put_deploy_history_record(
426 tx_hash,
427 &ContractId::derive_public(params.public_key),
428 "DEPLOYMENT",
429 block_height,
430 &Some(params.wasm_bincode.clone()),
431 &Some(params.ix.clone()),
432 ) {
433 return Err(Error::DatabaseError(format!(
434 "[apply_deploy_deploy_data] Inserting deploy history recod failed: {e}"
435 )))
436 }
437
438 Ok(true)
439 }
440
441 async fn apply_deploy_lock_data(
446 &self,
447 scan_cache: &ScanCache,
448 public_key: &PublicKey,
449 tx_hash: &TransactionHash,
450 lock_height: &u32,
451 ) -> Result<bool> {
452 let Some(secret_key) = scan_cache.own_deploy_auths.get(&public_key.to_bytes()) else {
454 return Ok(false)
455 };
456
457 let secret_key = serialize_async(secret_key).await;
459 let query = format!(
460 "UPDATE {} SET {} = 1, {} = ?1 WHERE {} = ?2;",
461 *DEPLOY_AUTH_TABLE,
462 DEPLOY_AUTH_COL_IS_LOCKED,
463 DEPLOY_AUTH_COL_LOCK_HEIGHT,
464 DEPLOY_AUTH_COL_SECRET_KEY
465 );
466 if let Err(e) =
467 self.wallet.exec_sql(&query, rusqlite::params![Some(*lock_height), secret_key])
468 {
469 return Err(Error::DatabaseError(format!(
470 "[apply_deploy_lock_data] Lock deploy authority failed: {e}"
471 )))
472 }
473
474 if let Err(e) = self.put_deploy_history_record(
476 tx_hash,
477 &ContractId::derive_public(*public_key),
478 "LOCK",
479 lock_height,
480 &None,
481 &None,
482 ) {
483 return Err(Error::DatabaseError(format!(
484 "[apply_deploy_lock_data] Inserting deploy history recod failed: {e}"
485 )))
486 }
487
488 Ok(true)
489 }
490
491 pub async fn apply_tx_deploy_data(
496 &self,
497 scan_cache: &mut ScanCache,
498 data: &[u8],
499 tx_hash: &TransactionHash,
500 block_height: &u32,
501 ) -> Result<bool> {
502 match DeployFunction::try_from(data[0])? {
504 DeployFunction::DeployV1 => {
505 scan_cache.log(String::from("[apply_tx_deploy_data] Found Deploy::DeployV1 call"));
506 let params: DeployParamsV1 = deserialize_async(&data[1..]).await?;
507 self.apply_deploy_deploy_data(scan_cache, ¶ms, tx_hash, block_height)
508 }
509 DeployFunction::LockV1 => {
510 scan_cache.log(String::from("[apply_tx_deploy_data] Found Deploy::LockV1 call"));
511 let params: LockParamsV1 = deserialize_async(&data[1..]).await?;
512 self.apply_deploy_lock_data(scan_cache, ¶ms.public_key, tx_hash, block_height)
513 .await
514 }
515 }
516 }
517
518 pub async fn deploy_contract(
520 &self,
521 deploy_auth: &ContractId,
522 wasm_bincode: Vec<u8>,
523 deploy_ix: Vec<u8>,
524 ) -> Result<Transaction> {
525 let (deploy_keypair, is_locked) = self.get_deploy_auth(deploy_auth).await?;
527
528 if is_locked {
530 return Err(Error::Custom("[deploy_contract] Contract is locked".to_string()))
531 }
532
533 let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
537
538 let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
539 else {
540 return Err(Error::Custom("[deploy_contract] Fee circuit not found".to_string()))
541 };
542
543 let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
544
545 let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
546
547 let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
549
550 let deploy_call = DeployCallBuilder { deploy_keypair, wasm_bincode, deploy_ix };
552 let deploy_debris = deploy_call.build()?;
553
554 let mut data = vec![DeployFunction::DeployV1 as u8];
556 deploy_debris.params.encode_async(&mut data).await?;
557 let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data };
558
559 let mut tx_builder =
561 TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?;
562
563 let mut tx = tx_builder.build()?;
566 let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
567 tx.signatures.push(sigs);
568
569 let tree = self.get_money_tree().await?;
570 let (fee_call, fee_proofs, fee_secrets) =
571 self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
572
573 tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
575
576 let mut tx = tx_builder.build()?;
578 let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
579 tx.signatures.push(sigs);
580 let sigs = tx.create_sigs(&fee_secrets)?;
581 tx.signatures.push(sigs);
582
583 Ok(tx)
584 }
585
586 pub async fn lock_contract(&self, deploy_auth: &ContractId) -> Result<Transaction> {
588 let (deploy_keypair, is_locked) = self.get_deploy_auth(deploy_auth).await?;
590
591 if is_locked {
593 return Err(Error::Custom("[lock_contract] Contract is already locked".to_string()))
594 }
595
596 let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
600
601 let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
602 else {
603 return Err(Error::Custom("[lock_contract] Fee circuit not found".to_string()))
604 };
605
606 let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
607
608 let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
609
610 let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
612
613 let lock_call = LockCallBuilder { deploy_keypair };
615 let lock_debris = lock_call.build()?;
616
617 let mut data = vec![DeployFunction::LockV1 as u8];
619 lock_debris.params.encode_async(&mut data).await?;
620 let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data };
621
622 let mut tx_builder =
624 TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?;
625
626 let mut tx = tx_builder.build()?;
629 let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
630 tx.signatures.push(sigs);
631
632 let tree = self.get_money_tree().await?;
633 let (fee_call, fee_proofs, fee_secrets) =
634 self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
635
636 tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
638
639 let mut tx = tx_builder.build()?;
641 let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
642 tx.signatures.push(sigs);
643 let sigs = tx.create_sigs(&fee_secrets)?;
644 tx.signatures.push(sigs);
645
646 Ok(tx)
647 }
648}