faucetd: Initialization of wallet and zkvm keys.

This commit is contained in:
parazyd
2022-11-17 21:25:05 +01:00
parent 8bc645098e
commit e07f035c62
2 changed files with 191 additions and 52 deletions

View File

@@ -15,7 +15,7 @@ blake3 = "1.3.1"
bs58 = "0.4.0"
chrono = "0.4.22"
ctrlc = { version = "3.2.3", features = ["termination"] }
darkfi = {path = "../../", features = ["blockchain", "wallet", "rpc", "net"]}
darkfi = {path = "../../", features = ["blockchain", "wallet", "rpc", "net", "zkas", "crypto"]}
darkfi-serial = {path = "../../src/serial"}
darkfi-sdk = {path = "../../src/sdk"}
easy-parallel = "3.2.0"
@@ -27,6 +27,8 @@ serde_json = "1.0.87"
simplelog = "0.12.0"
sled = "0.34.7"
smol = "1.2.5"
libsqlite3-sys = {version = "0.24.2", features = ["bundled-sqlcipher"]}
sqlx = {version = "0.6.2", features = ["runtime-async-std-native-tls", "sqlite"]}
url = "2.3.1"
# Argument parsing

View File

@@ -18,27 +18,39 @@
use std::{collections::HashMap, str::FromStr};
use async_std::sync::{Arc, Mutex};
use async_std::sync::{Arc, Mutex, RwLock};
use async_trait::async_trait;
use chrono::Utc;
use darkfi_sdk::crypto::{Address, PublicKey, TokenId};
use darkfi_serial::serialize;
use darkfi::{
crypto::proof::{ProvingKey, VerifyingKey},
zk::{vm::ZkCircuit, vm_stack::empty_witnesses},
zkas::ZkBinary,
};
use darkfi_sdk::{
crypto::{constants::MERKLE_DEPTH, ContractId, MerkleNode, PublicKey, TokenId},
incrementalmerkletree::bridgetree::BridgeTree,
pasta::{group::ff::PrimeField, pallas},
};
use darkfi_serial::{deserialize, serialize};
use log::{debug, error, info};
use serde_json::{json, Value};
use sqlx::Row;
use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
use url::Url;
use darkfi::{
async_daemonize, cli_desc,
consensus::{
constants::{
MAINNET_GENESIS_HASH_BYTES, MAINNET_GENESIS_TIMESTAMP, TESTNET_GENESIS_HASH_BYTES,
TESTNET_GENESIS_TIMESTAMP,
},
proto::{ProtocolSync, ProtocolTx},
task::block_sync_task,
ValidatorState, ValidatorStatePtr, MAINNET_GENESIS_HASH_BYTES, MAINNET_GENESIS_TIMESTAMP,
TESTNET_GENESIS_HASH_BYTES, TESTNET_GENESIS_TIMESTAMP,
ValidatorState, ValidatorStatePtr,
},
net,
net::P2pPtr,
node::Client,
rpc::{
jsonrpc::{
ErrorCode::{InternalError, InvalidParams, MethodNotFound},
@@ -47,13 +59,47 @@ use darkfi::{
server::{listen_and_serve, RequestHandler},
},
util::{async_util::sleep, parse::decode_base10, path::expand_path},
wallet::walletdb::init_wallet,
wallet::{walletdb::init_wallet, WalletPtr},
Error, Result,
};
mod error;
use error::{server_error, RpcError};
// TODO: FIXME:
// Find a way to have these constants be deterministic for the actual
// contract. e.g. they could be prefixed with the contract_id in order
// not to have collisions happen. This is because right now it's easy
// to overwrite any table in the wallet if the developer doesn't take
// care of it. The wallet's SQL schema comes from the money contract
// and here we just hardcode it. There should be a nice way to parse
// the schema and fill some map.
const MONEY_INFO_TABLE: &str = "money_info";
const MONEY_INFO_COL_LAST_SCANNED_SLOT: &str = "last_scanned_slot";
const MONEY_TREE_TABLE: &str = "money_tree";
const MONEY_TREE_COL_TREE: &str = "tree";
const MONEY_KEYS_TABLE: &str = "money_keys";
const MONEY_KEYS_COL_KEY_ID: &str = "key_id";
const MONEY_KEYS_COL_IS_DEFAULT: &str = "is_default";
const MONEY_KEYS_COL_PUBLIC: &str = "public";
const MONEY_KEYS_COL_SECRET: &str = "secret";
const MONEY_COINS_TABLE: &str = "money_coins";
const MONEY_COINS_COL_COIN: &str = "coin";
const MONEY_COINS_COL_IS_SPENT: &str = "is_spent";
const MONEY_COINS_COL_SERIAL: &str = "serial";
const MONEY_COINS_COL_VALUE: &str = "value";
const MONEY_COINS_COL_TOKEN_ID: &str = "token_id";
const MONEY_COINS_COL_COIN_BLIND: &str = "coin_blind";
const MONEY_COINS_COL_VALUE_BLIND: &str = "value_blind";
const MONEY_COINS_COL_TOKEN_BLIND: &str = "token_blind";
const MONEY_COINS_COL_SECRET: &str = "secret";
const MONEY_COINS_COL_NULLIFIER: &str = "nullifier";
const MONEY_COINS_COL_LEAF_POSITION: &str = "leaf_position";
const MONEY_COINS_COL_MEMO: &str = "memo";
const CONFIG_FILE: &str = "faucetd_config.toml";
const CONFIG_FILE_CONTENTS: &str = include_str!("../faucetd_config.toml");
@@ -141,11 +187,14 @@ struct Args {
pub struct Faucetd {
synced: Mutex<bool>, // AtomicBool is weird in Arc
sync_p2p: P2pPtr,
client: Arc<Client>,
validator_state: ValidatorStatePtr,
wallet: WalletPtr,
merkle_tree: BridgeTree<MerkleNode, MERKLE_DEPTH>,
airdrop_timeout: i64,
airdrop_limit: u64,
airdrop_map: Arc<Mutex<HashMap<Address, i64>>>,
airdrop_map: Arc<Mutex<HashMap<[u8; 32], i64>>>,
proving_keys: Arc<RwLock<HashMap<[u8; 32], Vec<(String, ProvingKey)>>>>,
verifying_keys: Arc<RwLock<HashMap<[u8; 32], Vec<(String, VerifyingKey)>>>>,
}
#[async_trait]
@@ -168,20 +217,117 @@ impl Faucetd {
pub async fn new(
validator_state: ValidatorStatePtr,
sync_p2p: P2pPtr,
wallet: WalletPtr,
timeout: i64,
limit: u64,
) -> Result<Self> {
let client = validator_state.read().await.client.clone();
// Here we initialize the wallet for the money contract.
let merkle_tree = Self::initialize_wallet(wallet.clone()).await?;
Ok(Self {
// This is kinda bad, but whatever. The hashmaps hold proving and verifying
// keys for various zk circuits. They should be periodically pruned because
// of memory issues.
let proving_keys = Arc::new(RwLock::new(HashMap::new()));
let verifying_keys = Arc::new(RwLock::new(HashMap::new()));
// For now we'll create the keys for the money contract
// FIXME: This hardcoded shit (see consensus/state.rs)
let cid = ContractId::from(pallas::Base::from(u64::MAX - 420));
let zkas_tree = String::from("zkas");
let zkas_mint_ns = String::from("Mint");
let zkas_burn_ns = String::from("Burn");
// Do a lookup for the money contract's zkas database and fetch the circuits.
let blockchain = { validator_state.read().await.blockchain.clone() };
let db_handle = blockchain.contracts.lookup(&blockchain.sled_db, &cid, &zkas_tree)?;
// TODO: Handle possible panic of these Option unwraps
let mint_zkbin = db_handle.get(&serialize(&zkas_mint_ns))?.unwrap();
let burn_zkbin = db_handle.get(&serialize(&zkas_burn_ns))?.unwrap();
let mint_zkbin = ZkBinary::decode(&mint_zkbin)?;
let burn_zkbin = ZkBinary::decode(&burn_zkbin)?;
let k = 13;
let mint_witnesses = empty_witnesses(&mint_zkbin);
let burn_witnesses = empty_witnesses(&burn_zkbin);
let mint_circuit = ZkCircuit::new(mint_witnesses, mint_zkbin);
let burn_circuit = ZkCircuit::new(burn_witnesses, burn_zkbin);
info!("Creating mint circuit proving and verifying keys");
let mint_provingkey = ProvingKey::build(k, &mint_circuit);
let mint_verifyingkey = VerifyingKey::build(k, &mint_circuit);
info!("Creating burn circuit proving and verifying keys");
let burn_verifyingkey = VerifyingKey::build(k, &burn_circuit);
let burn_provingkey = ProvingKey::build(k, &burn_circuit);
{
let provingkeys = vec![
(zkas_mint_ns.clone(), mint_provingkey),
(zkas_burn_ns.clone(), burn_provingkey),
];
let mut proving_keys_w = proving_keys.write().await;
proving_keys_w.insert(cid.inner().to_repr(), provingkeys);
}
{
let verifyingkeys = vec![
(zkas_mint_ns.clone(), mint_verifyingkey),
(zkas_burn_ns.clone(), burn_verifyingkey),
];
let mut verifying_keys_w = verifying_keys.write().await;
verifying_keys_w.insert(cid.inner().to_repr(), verifyingkeys);
}
let faucetd = Self {
synced: Mutex::new(false),
sync_p2p,
client,
validator_state,
wallet,
merkle_tree,
airdrop_timeout: timeout,
airdrop_limit: limit,
airdrop_map: Arc::new(Mutex::new(HashMap::new())),
})
proving_keys,
verifying_keys,
};
Ok(faucetd)
}
async fn initialize_wallet(wallet: WalletPtr) -> Result<BridgeTree<MerkleNode, MERKLE_DEPTH>> {
// Perform wallet initialization for the money contract
let wallet_schema = include_str!("../../../src/contract/money/wallet.sql");
// Get a wallet connection
info!("Acquiring wallet connection");
let mut conn = wallet.conn.acquire().await?;
info!("Initializing wallet schema");
sqlx::query(wallet_schema).execute(&mut conn).await?;
let query = format!("SELECT * FROM {}", MONEY_TREE_COL_TREE);
let merkle_tree = match sqlx::query(&query).fetch_one(&mut conn).await {
Ok(t) => {
info!("Merkle tree already exists");
let tree = deserialize(t.get(MONEY_TREE_COL_TREE))?;
tree
}
Err(_) => {
let tree = BridgeTree::<MerkleNode, MERKLE_DEPTH>::new(100);
let tree_bytes = serialize(&tree);
let query = format!(
"DELETE FROM {}; INSERT INTO {} ({}) VALUES (?1)",
MONEY_TREE_TABLE, MONEY_TREE_TABLE, MONEY_TREE_COL_TREE
);
sqlx::query(&query).bind(tree_bytes).execute(&mut conn).await?;
info!("Successfully initialized Merkle tree");
tree
}
};
Ok(merkle_tree)
}
// RPCAPI:
@@ -204,18 +350,10 @@ impl Faucetd {
return JsonError::new(InternalError, None, id).into()
}
let address = match Address::from_str(params[0].as_str().unwrap()) {
let pubkey = match PublicKey::from_str(params[0].as_str().unwrap()) {
Ok(v) => v,
Err(_) => {
error!("airdrop(): Failed parsing address from string");
return server_error(RpcError::ParseError, id)
}
};
let pubkey = match PublicKey::try_from(address) {
Ok(v) => v,
Err(_) => {
error!("airdrop(): Failed parsing PublicKey from Address");
Err(e) => {
error!("airdrop(): Failed parsing PublicKey from String: {}", e);
return server_error(RpcError::ParseError, id)
}
};
@@ -246,13 +384,16 @@ impl Faucetd {
// Check if there as a previous airdrop and the timeout has passed.
let now = Utc::now().timestamp();
let map = self.airdrop_map.lock().await;
if let Some(last_airdrop) = map.get(&address) {
if let Some(last_airdrop) = map.get(&pubkey.to_bytes()) {
if now - last_airdrop <= self.airdrop_timeout {
return server_error(RpcError::TimeLimitReached, id)
}
};
drop(map);
// Create airdrop transaction
/*
let tx = match self
.client
.build_transaction(
@@ -279,18 +420,20 @@ impl Faucetd {
return JsonError::new(InternalError, None, id).into()
}
}
*/
// Add/Update this airdrop into the hashmap
let mut map = self.airdrop_map.lock().await;
map.insert(address, now);
map.insert(pubkey.to_bytes(), now);
drop(map);
let tx_hash = blake3::hash(&serialize(&tx)).to_hex().as_str().to_string();
//let tx_hash = blake3::hash(&serialize(&tx)).to_hex().as_str().to_string();
let tx_hash = "foo";
JsonResponse::new(json!(tx_hash), id).into()
}
}
async fn prune_airdrop_map(map: Arc<Mutex<HashMap<Address, i64>>>, timeout: i64) {
async fn prune_airdrop_map(map: Arc<Mutex<HashMap<[u8; 32], i64>>>, timeout: i64) {
loop {
sleep(timeout as u64).await;
debug!("Pruning airdrop map");
@@ -330,6 +473,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'_>>) -> Result<()> {
let wallet = init_wallet(&args.wallet_path, &args.wallet_pass).await?;
// Initialize or open sled database
// TODO: Use proper OsPath here, not {}/{}
let db_path = format!("{}/{}", expand_path(&args.database)?.to_str().unwrap(), args.chain);
let sled_db = sled::open(&db_path)?;
@@ -343,36 +487,23 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'_>>) -> Result<()> {
}
};
// TODO: sqldb init cleanup
// Initialize client
let client = Arc::new(Client::new(wallet.clone()).await?);
// Parse faucet addresses
let mut faucet_pubkeys = vec![];
// Parse cashier addresses
let mut cashier_pubkeys = vec![];
for i in args.cashier_pub {
let addr = Address::from_str(&i)?;
let pk = PublicKey::try_from(addr)?;
cashier_pubkeys.push(pk);
let pk = PublicKey::from_str(&i)?;
faucet_pubkeys.push(pk);
}
// Parse faucet addresses
let mut faucet_pubkeys = vec![wallet.get_default_keypair().await?.public];
for i in args.faucet_pub {
let addr = Address::from_str(&i)?;
let pk = PublicKey::try_from(addr)?;
let pk = PublicKey::from_str(&i)?;
faucet_pubkeys.push(pk);
}
// Initialize validator state
let state = ValidatorState::new(
&sled_db,
genesis_ts,
genesis_data,
client,
cashier_pubkeys,
faucet_pubkeys,
)
.await?;
let state =
ValidatorState::new(&sled_db, genesis_ts, genesis_data, wallet.clone(), faucet_pubkeys)
.await?;
// P2P network. The faucet doesn't participate in consensus, so we only
// build the sync protocol.
@@ -412,8 +543,14 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'_>>) -> Result<()> {
let airdrop_limit = decode_base10(&args.airdrop_limit, 8, true)?;
// Initialize program state
let faucetd =
Faucetd::new(state.clone(), sync_p2p.clone(), airdrop_timeout, airdrop_limit).await?;
let faucetd = Faucetd::new(
state.clone(),
sync_p2p.clone(),
wallet.clone(),
airdrop_timeout,
airdrop_limit,
)
.await?;
let faucetd = Arc::new(faucetd);
// Task to periodically clean up the hashmap of airdrops.