Просмотр исходного кода

dao_demo: quick error handling

lunar-mining 3 лет назад
Родитель
Сommit
81c675f109
3 измененных файлов с 380 добавлено и 139 удалено
  1. 54 0
      bin/dao/daod/src/error.rs
  2. 112 69
      bin/dao/daod/src/main.rs
  3. 214 70
      bin/dao/daod/src/rpc.rs

+ 54 - 0
bin/dao/daod/src/error.rs

@@ -0,0 +1,54 @@
+use serde_json::Value;
+
+use darkfi::rpc::jsonrpc::{ErrorCode::ServerError, JsonError, JsonResult};
+
+#[derive(Debug, thiserror::Error)]
+pub enum DaoError {
+    #[error("No Proposals found")]
+    NoProposals,
+    #[error("No DAO params found")]
+    DaoNotConfigured,
+    #[error("State transition failed: '{0}'")]
+    StateTransitionFailed(String),
+    #[error("Wallet does not exist")]
+    NoWalletFound,
+    #[error("State not found")]
+    StateNotFound,
+    #[error("InternalError")]
+    Darkfi(#[from] darkfi::error::Error),
+}
+
+pub type DaoResult<T> = std::result::Result<T, DaoError>;
+
+pub enum RpcError {
+    Vote = -32101,
+    Propose = -32102,
+    Exec = -32103,
+    Airdrop = -32104,
+    Mint = -32105,
+    Keygen = -32106,
+    Create = -32107,
+    Parse = -32108,
+    Balance = -32109,
+}
+
+fn to_tuple(e: RpcError) -> (i64, String) {
+    let msg = match e {
+        RpcError::Vote => "Failed to cast a Vote",
+        RpcError::Propose => "Failed to generate a Proposal",
+        RpcError::Airdrop => "Failed to transfer an airdrop",
+        RpcError::Keygen => "Failed to generate keypair",
+        RpcError::Create => "Failed to create DAO",
+        RpcError::Exec => "Failed to execute Proposal",
+        RpcError::Mint => "Failed to mint DAO treasury",
+        RpcError::Parse => "Generic parsing error",
+        RpcError::Balance => "Failed to get balance",
+    };
+
+    (e as i64, msg.to_string())
+}
+
+pub fn server_error(e: RpcError, id: Value) -> JsonResult {
+    let (code, msg) = to_tuple(e);
+    JsonError::new(ServerError(code), Some(msg), id).into()
+}

+ 112 - 69
bin/dao/daod/src/main.rs

@@ -27,6 +27,7 @@ use darkfi::{
 };
 
 mod contract;
+mod error;
 mod note;
 mod rpc;
 mod util;
@@ -36,6 +37,7 @@ use crate::{
         dao_contract::{self, mint::wallet::DaoParams, propose::wallet::Proposal, DaoBulla},
         money_contract::{self, state::OwnCoin},
     },
+    error::{DaoError, DaoResult},
     rpc::JsonRpcInterface,
     util::{sign, StateRegistry, Transaction, ZkContractTable, DRK_ID, GOV_ID},
 };
@@ -227,11 +229,6 @@ impl Client {
         Ok(())
     }
 
-    // // Strictly for demo purposes.
-    // fn new_money_wallet(&mut self) {
-
-    // }
-
     fn create_dao(
         &mut self,
         dao_proposer_limit: u64,
@@ -239,7 +236,7 @@ impl Client {
         dao_approval_ratio_quot: u64,
         dao_approval_ratio_base: u64,
         token_id: pallas::Base,
-    ) -> Result<pallas::Base> {
+    ) -> DaoResult<pallas::Base> {
         let tx = self.dao_wallet.mint_tx(
             dao_proposer_limit,
             dao_quorum,
@@ -249,7 +246,7 @@ impl Client {
             &self.zk_bins,
         );
 
-        self.validate(&tx).unwrap();
+        self.validate(&tx)?;
         // Only witness the value once the transaction is confirmed.
         self.dao_wallet.update_witness(&mut self.states).unwrap();
 
@@ -286,7 +283,7 @@ impl Client {
         token_id: pallas::Base,
         token_supply: u64,
         recipient: PublicKey,
-    ) -> Result<()> {
+    ) -> DaoResult<()> {
         self.dao_wallet.track(&mut self.states)?;
 
         let tx = self
@@ -294,25 +291,30 @@ impl Client {
             .mint(token_id, token_supply, self.dao_wallet.bullas[0].0, recipient, &self.zk_bins)
             .unwrap();
 
-        self.validate(&tx).unwrap();
-        self.update_wallets().unwrap();
+        self.validate(&tx)?;
+        self.update_wallets()?;
 
         Ok(())
     }
 
-    fn airdrop_user(&mut self, value: u64, token_id: pallas::Base, addr: PublicKey) -> Result<()> {
+    fn airdrop_user(
+        &mut self,
+        value: u64,
+        token_id: pallas::Base,
+        addr: PublicKey,
+    ) -> DaoResult<()> {
         // let wallet = self.money_wallets.get(&nym).unwrap();
         // let addr = wallet.get_public_key();
 
         let tx = self.cashier_wallet.airdrop(value, token_id, addr, &self.zk_bins).unwrap();
-        self.validate(&tx).unwrap();
-        self.update_wallets().unwrap();
+        self.validate(&tx)?;
+        self.update_wallets()?;
 
         Ok(())
     }
 
     // TODO: Change these into errors instead of expects.
-    fn validate(&mut self, tx: &Transaction) -> Result<()> {
+    fn validate(&mut self, tx: &Transaction) -> DaoResult<()> {
         debug!(target: "dao_demo::client::validate()", "commencing validate sequence");
         let mut updates = vec![];
 
@@ -323,31 +325,44 @@ impl Client {
 
             if func_call.func_id == *money_contract::transfer::FUNC_ID {
                 debug!("money_contract::transfer::state_transition()");
-                let update =
-                    money_contract::transfer::validate::state_transition(&self.states, idx, &tx)
-                        .expect("money_contract::transfer::validate::state_transition() failed!");
-                updates.push(update);
+                match money_contract::transfer::validate::state_transition(&self.states, idx, &tx) {
+                    Ok(update) => {
+                        updates.push(update);
+                    }
+                    Err(e) => return Err(DaoError::StateTransitionFailed(e.to_string())),
+                }
             } else if func_call.func_id == *dao_contract::mint::FUNC_ID {
                 debug!("dao_contract::mint::state_transition()");
-                let update = dao_contract::mint::validate::state_transition(&self.states, idx, &tx)
-                    .expect("dao_contract::mint::validate::state_transition() failed!");
-                updates.push(update);
+                match dao_contract::mint::validate::state_transition(&self.states, idx, &tx) {
+                    Ok(update) => {
+                        updates.push(update);
+                    }
+                    Err(e) => return Err(DaoError::StateTransitionFailed(e.to_string())),
+                }
             } else if func_call.func_id == *dao_contract::propose::FUNC_ID {
                 debug!(target: "demo", "dao_contract::propose::state_transition()");
-                let update =
-                    dao_contract::propose::validate::state_transition(&self.states, idx, &tx)
-                        .expect("dao_contract::propose::validate::state_transition() failed!");
-                updates.push(update);
+                match dao_contract::propose::validate::state_transition(&self.states, idx, &tx) {
+                    Ok(update) => {
+                        updates.push(update);
+                    }
+                    Err(e) => return Err(DaoError::StateTransitionFailed(e.to_string())),
+                }
             } else if func_call.func_id == *dao_contract::vote::FUNC_ID {
                 debug!(target: "demo", "dao_contract::vote::state_transition()");
-                let update = dao_contract::vote::validate::state_transition(&self.states, idx, &tx)
-                    .expect("dao_contract::vote::validate::state_transition() failed!");
-                updates.push(update);
+                match dao_contract::vote::validate::state_transition(&self.states, idx, &tx) {
+                    Ok(update) => {
+                        updates.push(update);
+                    }
+                    Err(e) => return Err(DaoError::StateTransitionFailed(e.to_string())),
+                }
             } else if func_call.func_id == *dao_contract::exec::FUNC_ID {
                 debug!("dao_contract::exec::state_transition()");
-                let update = dao_contract::exec::validate::state_transition(&self.states, idx, &tx)
-                    .expect("dao_contract::exec::validate::state_transition() failed!");
-                updates.push(update);
+                match dao_contract::exec::validate::state_transition(&self.states, idx, &tx) {
+                    Ok(update) => {
+                        updates.push(update);
+                    }
+                    Err(e) => return Err(DaoError::StateTransitionFailed(e.to_string())),
+                }
             }
         }
 
@@ -362,9 +377,12 @@ impl Client {
         Ok(())
     }
 
-    fn update_wallets(&mut self) -> Result<()> {
-        let state =
-            self.states.lookup_mut::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
+    fn update_wallets(&mut self) -> DaoResult<()> {
+        let state = self.states.lookup_mut::<money_contract::State>(*money_contract::CONTRACT_ID);
+        if state.is_none() {
+            return Err(DaoError::StateNotFound)
+        }
+        let state = state.unwrap();
 
         let dao_coins = state.wallet_cache.get_received(&self.dao_wallet.keypair.secret);
         for coin in dao_coins {
@@ -421,7 +439,7 @@ impl Client {
         token_id: pallas::Base,
         amount: u64,
         sender: PublicKey,
-    ) -> Result<pallas::Base> {
+    ) -> DaoResult<pallas::Base> {
         let params = self.dao_wallet.params[0].clone();
 
         let dao_leaf_position = self.dao_wallet.leaf_position;
@@ -429,7 +447,11 @@ impl Client {
         // To be able to make a proposal, we must prove we have ownership
         // of governance tokens, and that the quantity of governance
         // tokens is within the accepted proposer limit.
-        let sender_wallet = self.money_wallets.get_mut(&sender).unwrap();
+        let sender_wallet = self.money_wallets.get_mut(&sender);
+        if sender_wallet.is_none() {
+            return Err(DaoError::NoWalletFound)
+        }
+        let sender_wallet = sender_wallet.unwrap();
 
         let tx = sender_wallet.propose_tx(
             params.clone(),
@@ -442,48 +464,59 @@ impl Client {
         )?;
 
         self.validate(&tx)?;
-        self.update_wallets().unwrap();
+        self.update_wallets()?;
 
         let proposal_bulla = self.dao_wallet.store_proposal(&tx)?;
 
         Ok(proposal_bulla)
     }
 
-    // fn get_addr_from_nym(&self, nym: String) -> Result<PublicKey> {
-    //     let wallet = self.money_wallets.get(&nym).unwrap();
-    //     Ok(wallet.get_public_key())
-    // }
-
-    fn cast_vote(&mut self, pubkey: PublicKey, vote: bool) -> Result<()> {
+    fn cast_vote(&mut self, pubkey: PublicKey, vote: bool) -> DaoResult<()> {
         let dao_key = self.dao_wallet.keypair;
+        if self.dao_wallet.proposals.is_empty() {
+            return Err(DaoError::NoProposals)
+        }
         let proposal = self.dao_wallet.proposals[0].clone();
+
+        if self.dao_wallet.params.is_empty() {
+            return Err(DaoError::DaoNotConfigured)
+        }
         let dao_params = self.dao_wallet.params[0].clone();
         let dao_keypair = self.dao_wallet.keypair;
 
-        let voter_wallet = self.money_wallets.get_mut(&pubkey).unwrap();
-
-        let tx = voter_wallet
-            .vote_tx(
-                vote,
-                dao_key,
-                proposal,
-                dao_params,
-                dao_keypair,
-                &self.zk_bins,
-                &mut self.states,
-            )
-            .unwrap();
+        let voter_wallet = self.money_wallets.get_mut(&pubkey);
+        if voter_wallet.is_none() {
+            return Err(DaoError::NoWalletFound)
+        }
+        let voter_wallet = voter_wallet.unwrap();
+
+        let tx = voter_wallet.vote_tx(
+            vote,
+            dao_key,
+            proposal,
+            dao_params,
+            dao_keypair,
+            &self.zk_bins,
+            &mut self.states,
+        )?;
 
-        self.validate(&tx).unwrap();
-        self.update_wallets().unwrap();
+        self.validate(&tx)?;
+        self.update_wallets()?;
 
         self.dao_wallet.store_vote(&tx).unwrap();
 
         Ok(())
     }
 
-    fn exec_proposal(&mut self, bulla: pallas::Base) -> Result<()> {
+    fn exec_proposal(&mut self, bulla: pallas::Base) -> DaoResult<()> {
+        if self.dao_wallet.proposals.is_empty() {
+            return Err(DaoError::NoProposals)
+        }
         let proposal = self.dao_wallet.proposals[0].clone();
+
+        if self.dao_wallet.params.is_empty() {
+            return Err(DaoError::DaoNotConfigured)
+        }
         let dao_params = self.dao_wallet.params[0].clone();
 
         let tx = self
@@ -491,8 +524,8 @@ impl Client {
             .exec_tx(proposal, bulla, dao_params, &self.zk_bins, &mut self.states)
             .unwrap();
 
-        self.validate(&tx).unwrap();
-        self.update_wallets().unwrap();
+        self.validate(&tx)?;
+        self.update_wallets()?;
 
         Ok(())
     }
@@ -541,9 +574,12 @@ impl DaoWallet {
         self.keypair.public
     }
 
-    fn track(&self, states: &mut StateRegistry) -> Result<()> {
-        let state =
-            states.lookup_mut::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
+    fn track(&self, states: &mut StateRegistry) -> DaoResult<()> {
+        let state = states.lookup_mut::<money_contract::State>(*money_contract::CONTRACT_ID);
+        if state.is_none() {
+            return Err(DaoError::StateNotFound)
+        }
+        let state = state.unwrap();
         state.wallet_cache.track(self.keypair.secret);
         Ok(())
     }
@@ -576,8 +612,12 @@ impl DaoWallet {
         Transaction { func_calls, signatures }
     }
 
-    fn update_witness(&mut self, states: &mut StateRegistry) -> Result<()> {
-        let state = states.lookup_mut::<dao_contract::State>(*dao_contract::CONTRACT_ID).unwrap();
+    fn update_witness(&mut self, states: &mut StateRegistry) -> DaoResult<()> {
+        let state = states.lookup_mut::<dao_contract::State>(*dao_contract::CONTRACT_ID);
+        if state.is_none() {
+            return Err(DaoError::StateNotFound)
+        }
+        let state = state.unwrap();
         let path = state.dao_tree.witness().unwrap();
         self.leaf_position = path;
         Ok(())
@@ -836,9 +876,12 @@ impl MoneyWallet {
     //     self.keypair.public
     // }
 
-    fn track(&self, states: &mut StateRegistry) -> Result<()> {
-        let state =
-            states.lookup_mut::<money_contract::State>(*money_contract::CONTRACT_ID).unwrap();
+    fn track(&self, states: &mut StateRegistry) -> DaoResult<()> {
+        let state = states.lookup_mut::<money_contract::State>(*money_contract::CONTRACT_ID);
+        if state.is_none() {
+            return Err(DaoError::StateNotFound)
+        }
+        let state = state.unwrap();
         state.wallet_cache.track(self.keypair.secret);
         Ok(())
     }

+ 214 - 70
bin/dao/daod/src/rpc.rs

@@ -2,8 +2,8 @@ use async_std::sync::Mutex;
 use std::{str::FromStr, sync::Arc};
 
 use async_trait::async_trait;
-use log::debug;
-use pasta_curves::{group::ff::PrimeField, pallas};
+use log::{debug, error};
+use pasta_curves::group::ff::PrimeField;
 use rand::rngs::OsRng;
 use serde_json::{json, Value};
 
@@ -17,6 +17,7 @@ use darkfi::{
 
 use crate::{
     contract::money_contract::state::OwnCoin,
+    error::{server_error, RpcError},
     util::{parse_b58, DRK_ID, GOV_ID},
     Client, MoneyWallet,
 };
@@ -64,25 +65,48 @@ impl JsonRpcInterface {
     // --> {"method": "create", "params": []}
     // <-- {"result": "creating dao..."}
     async fn create_dao(&self, id: Value, params: &[Value]) -> JsonResult {
-        let dao_proposer_limit = params[0].as_u64().unwrap();
-        let dao_quorum = params[1].as_u64().unwrap();
-        let dao_approval_ratio_quot = params[2].as_u64().unwrap();
-        let dao_approval_ratio_base = params[3].as_u64().unwrap();
+        let dao_proposer_limit = params[0].as_u64();
+        if dao_proposer_limit.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let dao_proposer_limit = dao_proposer_limit.unwrap();
+
+        let dao_quorum = params[1].as_u64();
+        if dao_quorum.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let dao_quorum = dao_quorum.unwrap();
+
+        let dao_approval_ratio_quot = params[2].as_u64();
+        if dao_approval_ratio_quot.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let dao_approval_ratio_quot = dao_approval_ratio_quot.unwrap();
+
+        let dao_approval_ratio_base = params[3].as_u64();
+        if dao_approval_ratio_base.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let dao_approval_ratio_base = dao_approval_ratio_base.unwrap();
 
         let mut client = self.client.lock().await;
 
-        let dao_bulla = client
-            .create_dao(
-                dao_proposer_limit,
-                dao_quorum,
-                dao_approval_ratio_quot,
-                dao_approval_ratio_base,
-                *GOV_ID,
-            )
-            .unwrap();
-
-        let bulla: String = bs58::encode(dao_bulla.to_repr()).into_string();
-        JsonResponse::new(json!(bulla), id).into()
+        match client.create_dao(
+            dao_proposer_limit,
+            dao_quorum,
+            dao_approval_ratio_quot,
+            dao_approval_ratio_base,
+            *GOV_ID,
+        ) {
+            Ok(bulla) => {
+                let bulla: String = bs58::encode(bulla.to_repr()).into_string();
+                JsonResponse::new(json!(bulla), id).into()
+            }
+            Err(e) => {
+                error!("Failed to create DAO: {}", e);
+                return server_error(RpcError::Create, id)
+            }
+        }
     }
 
     // --> {"method": "get_dao_addr", "params": []}
@@ -149,26 +173,60 @@ impl JsonRpcInterface {
 
     async fn user_balance(&self, id: Value, params: &[Value]) -> JsonResult {
         let client = self.client.lock().await;
-        let nym = params[0].as_str().unwrap();
-
-        let pubkey = PublicKey::from_str(nym).unwrap();
-
-        let wallet = client.money_wallets.get(&pubkey).unwrap();
-        let balance = wallet.balances().unwrap();
-        JsonResponse::new(json!(balance), id).into()
+        let nym = params[0].as_str();
+        if nym.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let nym = nym.unwrap();
+
+        match PublicKey::from_str(nym) {
+            Ok(key) => match client.money_wallets.get(&key) {
+                Some(wallet) => {
+                    let balance = wallet.balances().unwrap();
+                    JsonResponse::new(json!(balance), id).into()
+                }
+                None => {
+                    error!("No wallet found for provided key");
+                    return server_error(RpcError::Balance, id)
+                }
+            },
+            Err(_) => {
+                error!("Could not parse PublicKey from string");
+                return server_error(RpcError::Parse, id)
+            }
+        }
     }
+
     // --> {"method": "mint_treasury", "params": []}
     // <-- {"result": "minting treasury..."}
     async fn mint_treasury(&self, id: Value, params: &[Value]) -> JsonResult {
         let mut client = self.client.lock().await;
 
-        let token_supply = params[0].as_u64().unwrap();
-        let addr = params[1].as_str().unwrap();
-        let dao_addr = PublicKey::from_str(addr).unwrap();
-
-        client.mint_treasury(*DRK_ID, token_supply, dao_addr).unwrap();
+        let token_supply = params[0].as_u64();
+        if token_supply.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let token_supply = token_supply.unwrap();
 
-        JsonResponse::new(json!("DAO treasury minted successfully."), id).into()
+        let addr = params[1].as_str();
+        if addr.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let addr = addr.unwrap();
+
+        match PublicKey::from_str(addr) {
+            Ok(dao_addr) => match client.mint_treasury(*DRK_ID, token_supply, dao_addr) {
+                Ok(_) => JsonResponse::new(json!("DAO treasury minted successfully."), id).into(),
+                Err(e) => {
+                    error!("Failed to mint treasury: {}", e);
+                    return server_error(RpcError::Mint, id)
+                }
+            },
+            Err(_) => {
+                error!("Failed to parse PublicKey from String");
+                return server_error(RpcError::Parse, id)
+            }
+        }
     }
 
     // Create a new wallet for governance tokens.
@@ -180,15 +238,18 @@ impl JsonRpcInterface {
         let signature_secret = SecretKey::random(&mut OsRng);
         let own_coins: Vec<(OwnCoin, bool)> = Vec::new();
         let money_wallet = MoneyWallet { keypair, signature_secret, own_coins };
-        money_wallet.track(&mut client.states).unwrap();
 
-        client.money_wallets.insert(keypair.public, money_wallet);
-
-        // let wallet = client.money_wallets.get(&nym).unwrap();
-        // let pubkey = wallet.get_public_key();
-
-        let addr: String = bs58::encode(keypair.public.to_bytes()).into_string();
-        JsonResponse::new(json!(addr), id).into()
+        match money_wallet.track(&mut client.states) {
+            Ok(_) => {
+                client.money_wallets.insert(keypair.public, money_wallet);
+                let addr: String = bs58::encode(keypair.public.to_bytes()).into_string();
+                JsonResponse::new(json!(addr), id).into()
+            }
+            Err(e) => {
+                error!("Failed to airdrop tokens: {}", e);
+                return server_error(RpcError::Keygen, id)
+            }
+        }
     }
 
     // --> {"method": "airdrop_tokens", "params": []}
@@ -197,62 +258,145 @@ impl JsonRpcInterface {
         let mut client = self.client.lock().await;
         // let zk_bins = &client.zk_bins;
 
-        let addr = PublicKey::from_str(params[0].as_str().unwrap()).unwrap();
-        let value = params[1].as_u64().unwrap();
-
-        client.airdrop_user(value, *GOV_ID, addr).unwrap();
+        let addr = params[0].as_str();
+        if addr.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let addr = addr.unwrap();
 
-        JsonResponse::new(json!("Tokens airdropped successfully."), id).into()
+        let value = params[1].as_u64();
+        if value.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let value = value.unwrap();
+
+        match PublicKey::from_str(addr) {
+            Ok(key) => match client.airdrop_user(value, *GOV_ID, key) {
+                Ok(_) => JsonResponse::new(json!("Tokens airdropped successfully."), id).into(),
+                Err(e) => {
+                    error!("Failed to airdrop tokens: {}", e);
+                    return server_error(RpcError::Airdrop, id)
+                }
+            },
+            Err(_) => {
+                error!("Failed parsing PublicKey from String");
+                return server_error(RpcError::Parse, id)
+            }
+        }
     }
     // --> {"method": "create_proposal", "params": []}
     // <-- {"result": "creating proposal..."}
     async fn create_proposal(&self, id: Value, params: &[Value]) -> JsonResult {
         let mut client = self.client.lock().await;
 
-        let sender = params[0].as_str().unwrap();
-        let recipient = params[1].as_str().unwrap();
-        let amount = params[2].as_u64().unwrap();
+        if params.is_empty() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
 
-        let recv_addr = PublicKey::from_str(recipient).unwrap();
-        let sndr_addr = PublicKey::from_str(sender).unwrap();
+        let sender = params[0].as_str();
+        if sender.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let sender = sender.unwrap();
 
-        let proposal_bulla = client.propose(recv_addr, *DRK_ID, amount, sndr_addr).unwrap();
-        let bulla: String = bs58::encode(proposal_bulla.to_repr()).into_string();
+        let recipient = params[1].as_str();
+        if recipient.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let recipient = recipient.unwrap();
 
-        JsonResponse::new(json!(bulla), id).into()
+        let amount = params[2].as_u64();
+        if amount.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let amount = amount.unwrap();
+
+        let recv_addr = PublicKey::from_str(recipient);
+        if recv_addr.is_err() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let recv_addr = recv_addr.unwrap();
+
+        let sndr_addr = PublicKey::from_str(sender);
+        if sndr_addr.is_err() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let sndr_addr = sndr_addr.unwrap();
+
+        match client.propose(recv_addr, *DRK_ID, amount, sndr_addr) {
+            Ok(bulla) => {
+                let bulla: String = bs58::encode(bulla.to_repr()).into_string();
+
+                JsonResponse::new(json!(bulla), id).into()
+            }
+            Err(e) => {
+                error!("Failed to make Proposal: {}", e);
+                return server_error(RpcError::Propose, id)
+            }
+        }
     }
     // --> {"method": "vote", "params": []}
     // <-- {"result": "voting..."}
     async fn vote(&self, id: Value, params: &[Value]) -> JsonResult {
         let mut client = self.client.lock().await;
+        let mut vote_bool = true;
 
-        let addr = params[0].as_str().unwrap();
-        let vote_str = params[1].as_str().unwrap();
-
-        let addr = PublicKey::from_str(addr).unwrap();
+        let addr = params[0].as_str();
+        if addr.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let addr = addr.unwrap();
 
-        // This would be cleaner as a match statement,
-        // but we need to sort out error handling first.
-        let mut vote_bool = true;
+        let vote_str = params[1].as_str();
+        if vote_str.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let vote_str = vote_str.unwrap();
 
-        if vote_str == "yes" {}
-        if vote_str == "no" {
-            vote_bool = false
+        match vote_str {
+            "yes" => {}
+            "no" => vote_bool = false,
+            _ => return JsonError::new(InvalidParams, None, id).into(),
         }
 
-        client.cast_vote(addr, vote_bool).unwrap();
-        JsonResponse::new(json!("Vote cast successfully."), id).into()
+        match PublicKey::from_str(addr) {
+            Ok(key) => match client.cast_vote(key, vote_bool) {
+                Ok(_) => JsonResponse::new(json!("Vote cast successfully."), id).into(),
+                Err(e) => {
+                    error!("Failed casting vote: {}", e);
+                    return server_error(RpcError::Vote, id)
+                }
+            },
+            Err(_) => {
+                error!("Failed parsing PublicKey from String");
+                return server_error(RpcError::Parse, id)
+            }
+        }
     }
     // --> {"method": "execute", "params": []}
     // <-- {"result": "executing..."}
     async fn execute(&self, id: Value, params: &[Value]) -> JsonResult {
         let mut client = self.client.lock().await;
 
-        let bulla_str = params[0].as_str().unwrap();
-        let bulla: pallas::Base = parse_b58(bulla_str).unwrap();
-
-        client.exec_proposal(bulla).unwrap();
-
-        JsonResponse::new(json!("Proposal executed successfully."), id).into()
+        let bulla_str = params[0].as_str();
+        if bulla_str.is_none() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+        let bulla_str = bulla_str.unwrap();
+
+        let bulla = parse_b58(bulla_str);
+        match bulla {
+            Ok(bulla) => match client.exec_proposal(bulla) {
+                Ok(_) => JsonResponse::new(json!("Proposal executed successfully."), id).into(),
+                Err(e) => {
+                    error!("Failed executing proposal: {}", e);
+                    return server_error(RpcError::Exec, id)
+                }
+            },
+            Err(e) => {
+                error!("Failed parsing bulla: {}", e);
+                return server_error(RpcError::Parse, id)
+            }
+        }
     }
 }