drk: use contract id as the key for deployment auths

This commit is contained in:
skoupidi
2025-10-25 13:56:16 +03:00
parent 699c942fb7
commit b419205e8c
5 changed files with 137 additions and 110 deletions

View File

@@ -2,11 +2,12 @@
-- 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
-- Authority Contract ID
contract_id BLOB PRIMARY KEY NOT NULL,
-- Authority keypair secret key
secret_key BLOB NOT NULL,
-- Contract lock flag
is_locked INTEGER NOT NULL,
-- Block height of the transaction this contract was locked on chain
lock_height INTEGER
);

View File

@@ -41,7 +41,7 @@ use darkfi_sdk::{
tx::TransactionHash,
ContractCall,
};
use darkfi_serial::{deserialize_async, serialize, serialize_async, AsyncEncodable};
use darkfi_serial::{deserialize_async, serialize_async, AsyncEncodable};
use rusqlite::types::Value;
use crate::{convert_named_params, error::WalletDbResult, rpc::ScanCache, Drk};
@@ -54,10 +54,10 @@ lazy_static! {
}
// DEPLOY_AUTH_TABLE
pub const DEPLOY_AUTH_COL_ID: &str = "id";
pub const DEPLOY_AUTH_COL_DEPLOY_AUTHORITY: &str = "deploy_authority";
pub const DEPLOY_AUTH_COL_IS_FROZEN: &str = "is_frozen";
pub const DEPLOY_AUTH_COL_FREEZE_HEIGHT: &str = "freeze_height";
pub const DEPLOY_AUTH_COL_CONTRACT_ID: &str = "contract_id";
pub const DEPLOY_AUTH_COL_SECRET_KEY: &str = "secret_key";
pub const DEPLOY_AUTH_COL_IS_LOCKED: &str = "is_locked";
pub const DEPLOY_AUTH_COL_LOCK_HEIGHT: &str = "lock_height";
impl Drk {
/// Initialize wallet with tables for the Deployooor contract.
@@ -73,63 +73,72 @@ impl Drk {
pub async fn deploy_auth_keygen(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
output.push(String::from("Generating a new keypair"));
let keypair = Keypair::random(&mut OsRng);
let freeze_height: Option<u32> = None;
let secret_key = SecretKey::random(&mut OsRng);
let contract_id = ContractId::derive_public(PublicKey::from_secret(secret_key));
let lock_height: Option<u32> = None;
let query = format!(
"INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);",
"INSERT INTO {} ({}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4);",
*DEPLOY_AUTH_TABLE,
DEPLOY_AUTH_COL_DEPLOY_AUTHORITY,
DEPLOY_AUTH_COL_IS_FROZEN,
DEPLOY_AUTH_COL_FREEZE_HEIGHT,
DEPLOY_AUTH_COL_CONTRACT_ID,
DEPLOY_AUTH_COL_SECRET_KEY,
DEPLOY_AUTH_COL_IS_LOCKED,
DEPLOY_AUTH_COL_LOCK_HEIGHT,
);
self.wallet.exec_sql(
&query,
rusqlite::params![serialize_async(&keypair).await, 0, freeze_height],
rusqlite::params![
serialize_async(&contract_id).await,
serialize_async(&secret_key).await,
0,
lock_height
],
)?;
output.push(String::from("Created new contract deploy authority"));
output.push(format!("Contract ID: {}", ContractId::derive_public(keypair.public)));
output.push(format!("Contract ID: {contract_id}"));
Ok(())
}
/// Reset all token deploy authorities frozen status in the wallet.
/// Reset all token deploy authorities locked status in the wallet.
pub fn reset_deploy_authorities(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
output.push(String::from("Resetting deploy authorities frozen status"));
output.push(String::from("Resetting deploy authorities locked status"));
let query = format!(
"UPDATE {} SET {} = 0, {} = NULL;",
*DEPLOY_AUTH_TABLE, DEPLOY_AUTH_COL_IS_FROZEN, DEPLOY_AUTH_COL_FREEZE_HEIGHT
*DEPLOY_AUTH_TABLE, DEPLOY_AUTH_COL_IS_LOCKED, DEPLOY_AUTH_COL_LOCK_HEIGHT
);
self.wallet.exec_sql(&query, &[])?;
output.push(String::from("Successfully reset deploy authorities frozen status"));
output.push(String::from("Successfully reset deploy authorities locked status"));
Ok(())
}
/// Remove deploy authorities frozen status in the wallet that
/// where frozen after provided height.
pub fn unfreeze_deploy_authorities_after(
/// Remove deploy authorities locked status in the wallet that
/// where locked after provided height.
pub fn unlock_deploy_authorities_after(
&self,
height: &u32,
output: &mut Vec<String>,
) -> WalletDbResult<()> {
output.push(format!("Resetting deploy authorities frozen status after: {height}"));
output.push(format!("Resetting deploy authorities locked status after: {height}"));
let query = format!(
"UPDATE {} SET {} = 0, {} = NULL WHERE {} > ?1;",
*DEPLOY_AUTH_TABLE,
DEPLOY_AUTH_COL_IS_FROZEN,
DEPLOY_AUTH_COL_FREEZE_HEIGHT,
DEPLOY_AUTH_COL_FREEZE_HEIGHT
DEPLOY_AUTH_COL_IS_LOCKED,
DEPLOY_AUTH_COL_LOCK_HEIGHT,
DEPLOY_AUTH_COL_LOCK_HEIGHT
);
self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?;
output.push(String::from("Successfully reset deploy authorities frozen status"));
output.push(String::from("Successfully reset deploy authorities locked status"));
Ok(())
}
/// List contract deploy authorities from the wallet
pub async fn list_deploy_auth(&self) -> Result<Vec<(i64, ContractId, bool, Option<u32>)>> {
pub async fn list_deploy_auth(
&self,
) -> Result<Vec<(ContractId, SecretKey, bool, Option<u32>)>> {
let rows = match self.wallet.query_multiple(&DEPLOY_AUTH_TABLE, &[], &[]) {
Ok(r) => r,
Err(e) => {
@@ -141,55 +150,53 @@ impl Drk {
let mut ret = Vec::with_capacity(rows.len());
for row in rows {
let Value::Integer(idx) = row[0] else {
return Err(Error::ParseFailed("[list_deploy_auth] Failed to parse index"))
let Value::Blob(ref contract_id_bytes) = row[0] else {
return Err(Error::ParseFailed(
"[list_deploy_auth] Failed to parse contract id bytes",
))
};
let contract_id: ContractId = deserialize_async(contract_id_bytes).await?;
let Value::Blob(ref secret_key_bytes) = row[1] else {
return Err(Error::ParseFailed(
"[list_deploy_auth] Failed to parse secret key bytes",
))
};
let secret_key: SecretKey = deserialize_async(secret_key_bytes).await?;
let Value::Integer(locked) = row[2] else {
return Err(Error::ParseFailed("[list_deploy_auth] Failed to parse \"is_locked\""))
};
let Value::Blob(ref auth_bytes) = row[1] else {
return Err(Error::ParseFailed("[list_deploy_auth] Failed to parse keypair bytes"))
};
let deploy_auth: Keypair = deserialize_async(auth_bytes).await?;
let Value::Integer(frozen) = row[2] else {
return Err(Error::ParseFailed("[list_deploy_auth] Failed to parse \"is_frozen\""))
};
let freeze_height = match row[3] {
Value::Integer(freeze_height) => {
let Ok(freeze_height) = u32::try_from(freeze_height) else {
let lock_height = match row[3] {
Value::Integer(lock_height) => {
let Ok(lock_height) = u32::try_from(lock_height) else {
return Err(Error::ParseFailed(
"[list_deploy_auth] Freeze height parsing failed",
"[list_deploy_auth] Lock height parsing failed",
))
};
Some(freeze_height)
Some(lock_height)
}
Value::Null => None,
_ => {
return Err(Error::ParseFailed(
"[list_deploy_auth] Freeze height parsing failed",
))
return Err(Error::ParseFailed("[list_deploy_auth] Lock height parsing failed"))
}
};
ret.push((
idx,
ContractId::derive_public(deploy_auth.public),
frozen != 0,
freeze_height,
))
ret.push((contract_id, secret_key, locked != 0, lock_height))
}
Ok(ret)
}
/// Retrieve a deploy authority keypair and status for provided
/// index.
async fn get_deploy_auth(&self, idx: u64) -> Result<(Keypair, bool)> {
/// contract id.
async fn get_deploy_auth(&self, contract_id: &ContractId) -> 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_IS_FROZEN],
convert_named_params! {(DEPLOY_AUTH_COL_ID, idx)},
&[DEPLOY_AUTH_COL_SECRET_KEY, DEPLOY_AUTH_COL_IS_LOCKED],
convert_named_params! {(DEPLOY_AUTH_COL_CONTRACT_ID, serialize_async(contract_id).await)},
) {
Ok(v) => v,
Err(e) => {
@@ -199,43 +206,43 @@ impl Drk {
}
};
let Value::Blob(ref keypair_bytes) = row[0] else {
return Err(Error::ParseFailed("[get_deploy_auth] Failed to parse keypair bytes"))
let Value::Blob(ref secret_key_bytes) = row[0] else {
return Err(Error::ParseFailed("[get_deploy_auth] Failed to parse secret key bytes"))
};
let keypair: Keypair = deserialize_async(keypair_bytes).await?;
let secret_key: SecretKey = deserialize_async(secret_key_bytes).await?;
let keypair = Keypair::new(secret_key);
let Value::Integer(locked) = row[1] else {
return Err(Error::ParseFailed("[get_deploy_auth] Failed to parse \"is_frozen\""))
return Err(Error::ParseFailed("[get_deploy_auth] Failed to parse \"is_locked\""))
};
Ok((keypair, locked != 0))
}
/// Retrieve contract deploy authorities public keys from the
/// wallet.
/// Retrieve contract deploy authorities keys map 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],
&[DEPLOY_AUTH_COL_SECRET_KEY],
&[],
) {
Ok(r) => r,
Err(e) => {
return Err(Error::DatabaseError(format!(
"[get_deploy_auths_keys_map] Failed to retrieve deploy authorities keypairs: {e}",
"[get_deploy_auths_keys_map] Failed to retrieve deploy authorities secret keys: {e}",
)))
}
};
let mut ret = HashMap::new();
for row in rows {
let Value::Blob(ref keypair_bytes) = row[0] else {
let Value::Blob(ref secret_key_bytes) = row[0] else {
return Err(Error::ParseFailed(
"[get_deploy_auths_keys_map] Failed to parse keypair bytes",
"[get_deploy_auths_keys_map] Failed to parse secret key bytes",
))
};
let keypair: Keypair = deserialize_async(keypair_bytes).await?;
ret.insert(keypair.public.to_bytes(), keypair.secret);
let secret_key: SecretKey = deserialize_async(secret_key_bytes).await?;
ret.insert(PublicKey::from_secret(secret_key).to_bytes(), secret_key);
}
Ok(ret)
@@ -268,30 +275,30 @@ impl Drk {
/// data to the wallet.
/// Returns a flag indicating if the provided call refers to our
/// own wallet.
fn apply_deploy_lock_data(
async fn apply_deploy_lock_data(
&self,
scan_cache: &ScanCache,
public_key: &PublicKey,
_tx_hash: &TransactionHash,
freeze_height: &u32,
lock_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
// Lock contract
let secret_key = serialize_async(secret_key).await;
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
DEPLOY_AUTH_COL_IS_LOCKED,
DEPLOY_AUTH_COL_LOCK_HEIGHT,
DEPLOY_AUTH_COL_SECRET_KEY
);
if let Err(e) = self.wallet.exec_sql(
&query,
rusqlite::params![Some(*freeze_height), serialize(&Keypair::new(*secret_key))],
) {
if let Err(e) =
self.wallet.exec_sql(&query, rusqlite::params![Some(*lock_height), secret_key])
{
return Err(Error::DatabaseError(format!(
"[apply_deploy_lock_data] Lock deploy authority failed: {e}"
)))
@@ -325,6 +332,7 @@ impl Drk {
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)
.await
}
}
}
@@ -332,7 +340,7 @@ impl Drk {
/// Create a feeless contract deployment transaction.
pub async fn deploy_contract(
&self,
deploy_auth: u64,
deploy_auth: &ContractId,
wasm_bincode: Vec<u8>,
deploy_ix: Vec<u8>,
) -> Result<Transaction> {
@@ -398,7 +406,7 @@ impl Drk {
}
/// Create a feeless contract redeployment lock transaction.
pub async fn lock_contract(&self, deploy_auth: u64) -> Result<Transaction> {
pub async fn lock_contract(&self, deploy_auth: &ContractId) -> Result<Transaction> {
// Fetch the keypair and its status
let (deploy_keypair, is_locked) = self.get_deploy_auth(deploy_auth).await?;

View File

@@ -48,8 +48,8 @@ use darkfi_dao_contract::{blockwindow, model::DaoProposalBulla, DaoFunction};
use darkfi_money_contract::model::{Coin, CoinAttributes, TokenId};
use darkfi_sdk::{
crypto::{
note::AeadEncryptedNote, BaseBlind, FuncId, FuncRef, Keypair, PublicKey, SecretKey,
DAO_CONTRACT_ID,
note::AeadEncryptedNote, BaseBlind, ContractId, FuncId, FuncRef, Keypair, PublicKey,
SecretKey, DAO_CONTRACT_ID,
},
pasta::{group::ff::PrimeField, pallas},
tx::TransactionHash,
@@ -3124,14 +3124,14 @@ async fn handle_contract_list(drk: &DrkPtr, parts: &[&str], output: &mut Vec<Str
let mut table = Table::new();
table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
table.set_titles(row!["Index", "Contract ID", "Frozen", "Freeze Height"]);
table.set_titles(row!["Contract ID", "Secret Key", "Locked", "Lock Height"]);
for (idx, contract_id, frozen, freeze_height) in auths {
let freeze_height = match freeze_height {
Some(freeze_height) => freeze_height.to_string(),
for (contract_id, secret_key, is_locked, lock_height) in auths {
let lock_height = match lock_height {
Some(lock_height) => lock_height.to_string(),
None => String::from("-"),
};
table.add_row(row![idx, contract_id, frozen, freeze_height]);
table.add_row(row![contract_id, secret_key, is_locked, lock_height]);
}
if table.is_empty() {
@@ -3150,7 +3150,7 @@ async fn handle_contract_deploy(drk: &DrkPtr, parts: &[&str], output: &mut Vec<S
return
}
let deploy_auth = match u64::from_str(parts[2]) {
let deploy_auth = match ContractId::from_str(parts[2]) {
Ok(d) => d,
Err(e) => {
output.push(format!("Invalid deploy authority: {e}"));
@@ -3193,7 +3193,7 @@ async fn handle_contract_deploy(drk: &DrkPtr, parts: &[&str], output: &mut Vec<S
vec![]
};
match drk.read().await.deploy_contract(deploy_auth, wasm_bin, deploy_ix).await {
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}")),
}
@@ -3208,7 +3208,7 @@ async fn handle_contract_lock(drk: &DrkPtr, parts: &[&str], output: &mut Vec<Str
return
}
let deploy_auth = match u64::from_str(parts[2]) {
let deploy_auth = match ContractId::from_str(parts[2]) {
Ok(d) => d,
Err(e) => {
output.push(format!("Invalid deploy authority: {e}"));
@@ -3216,7 +3216,7 @@ async fn handle_contract_lock(drk: &DrkPtr, parts: &[&str], output: &mut Vec<Str
}
};
match drk.read().await.lock_contract(deploy_auth).await {
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}")),
}

View File

@@ -44,8 +44,8 @@ use darkfi_dao_contract::{blockwindow, model::DaoProposalBulla, DaoFunction};
use darkfi_money_contract::model::{Coin, CoinAttributes, TokenId};
use darkfi_sdk::{
crypto::{
note::AeadEncryptedNote, BaseBlind, FuncId, FuncRef, Keypair, PublicKey, SecretKey,
DAO_CONTRACT_ID,
note::AeadEncryptedNote, BaseBlind, ContractId, FuncId, FuncRef, Keypair, PublicKey,
SecretKey, DAO_CONTRACT_ID,
},
pasta::{group::ff::PrimeField, pallas},
tx::TransactionHash,
@@ -525,7 +525,7 @@ enum ContractSubcmd {
/// Deploy a smart contract
Deploy {
/// Contract ID (deploy authority)
deploy_auth: u64,
deploy_auth: String,
/// Path to contract wasm bincode
wasm_path: String,
@@ -537,7 +537,7 @@ enum ContractSubcmd {
/// Lock a smart contract
Lock {
/// Contract ID (deploy authority)
deploy_auth: u64,
deploy_auth: String,
},
}
@@ -2585,14 +2585,14 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
let mut table = Table::new();
table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
table.set_titles(row!["Index", "Contract ID", "Frozen", "Freeze Height"]);
table.set_titles(row!["Contract ID", "Secret Key", "Locked", "Lock Height"]);
for (idx, contract_id, frozen, freeze_height) in auths {
let freeze_height = match freeze_height {
Some(freeze_height) => freeze_height.to_string(),
for (contract_id, secret_key, is_locked, lock_height) in auths {
let lock_height = match lock_height {
Some(lock_height) => lock_height.to_string(),
None => String::from("-"),
};
table.add_row(row![idx, contract_id, frozen, freeze_height]);
table.add_row(row![contract_id, secret_key, is_locked, lock_height]);
}
if table.is_empty() {
@@ -2605,6 +2605,15 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
}
ContractSubcmd::Deploy { deploy_auth, wasm_path, deploy_ix } => {
// Parse the deployment authority contract id
let deploy_auth = match ContractId::from_str(&deploy_auth) {
Ok(d) => d,
Err(e) => {
eprintln!("Invalid deploy authority: {e}");
exit(2);
}
};
// Read the wasm bincode and deploy instruction
let wasm_bin = smol::fs::read(expand_path(&wasm_path)?).await?;
let deploy_ix = match deploy_ix {
@@ -2622,7 +2631,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
)
.await;
let tx = match drk.deploy_contract(deploy_auth, wasm_bin, deploy_ix).await {
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}");
@@ -2636,6 +2645,15 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
}
ContractSubcmd::Lock { deploy_auth } => {
// Parse the deployment authority contract id
let deploy_auth = match ContractId::from_str(&deploy_auth) {
Ok(d) => d,
Err(e) => {
eprintln!("Invalid deploy authority: {e}");
exit(2);
}
};
let drk = new_wallet(
blockchain_config.cache_path,
blockchain_config.wallet_path,
@@ -2646,7 +2664,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
)
.await;
let tx = match drk.lock_contract(deploy_auth).await {
let tx = match drk.lock_contract(&deploy_auth).await {
Ok(tx) => tx,
Err(e) => {
eprintln!("Error creating contract lock tx: {e}");

View File

@@ -226,8 +226,8 @@ impl Drk {
// height.
self.remove_dao_votes_after(&height, output)?;
// Unfreeze all contracts frozen after the reset height
self.unfreeze_deploy_authorities_after(&height, output)?;
// Unlock all contracts frozen after the reset height
self.unlock_deploy_authorities_after(&height, output)?;
// Set reverted status to all transactions executed after reset
// height.