Bläddra i källkod

first part of dao exec, create the tx and apply spend_hook rule

narodnik 4 år sedan
förälder
incheckning
119c5ebe56

+ 2 - 0
bin/daod/src/dao_contract/mod.rs

@@ -4,6 +4,8 @@ pub mod mint;
 pub mod propose;
 pub mod propose;
 // vote{}
 // vote{}
 pub mod vote;
 pub mod vote;
+// exec{}
+pub mod exec;
 
 
 pub mod state;
 pub mod state;
 
 

+ 152 - 16
bin/daod/src/demo.rs

@@ -114,7 +114,6 @@ impl Transaction {
             for (i, (proof, (key, public_vals))) in
             for (i, (proof, (key, public_vals))) in
                 func_call.proofs.iter().zip(proofs_public_vals.iter()).enumerate()
                 func_call.proofs.iter().zip(proofs_public_vals.iter()).enumerate()
             {
             {
-                debug!(target: "demo", "Tranaction::zk_verify i: {}, key: {}", i, key);
                 match zk_bins.lookup(key).unwrap() {
                 match zk_bins.lookup(key).unwrap() {
                     ZkContractInfo::Binary(info) => {
                     ZkContractInfo::Binary(info) => {
                         let verifying_key = &info.verifying_key;
                         let verifying_key = &info.verifying_key;
@@ -127,7 +126,7 @@ impl Transaction {
                         assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key);
                         assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key);
                     }
                     }
                 };
                 };
-                debug!("zk_verify({}) passed", key);
+                debug!(target: "demo", "zk_verify({}) passed [i={}]", key, i);
             }
             }
         }
         }
     }
     }
@@ -178,6 +177,10 @@ impl StateRegistry {
     }
     }
 }
 }
 
 
+pub trait UpdateBase {
+    fn apply(self: Box<Self>, states: &mut StateRegistry);
+}
+
 ///////////////////////////////////////////////////
 ///////////////////////////////////////////////////
 ///// Example contract
 ///// Example contract
 ///////////////////////////////////////////////////
 ///////////////////////////////////////////////////
@@ -196,16 +199,21 @@ pub async fn example() -> Result<()> {
     let example_state = example_contract::state::State::new();
     let example_state = example_contract::state::State::new();
     states.register("Example".to_string(), example_state);
     states.register("Example".to_string(), example_state);
 
 
+    //// Wallet
+
     let foo = example_contract::foo::wallet::Foo { a: 5, b: 10 };
     let foo = example_contract::foo::wallet::Foo { a: 5, b: 10 };
 
 
     let builder = example_contract::foo::wallet::Builder { foo };
     let builder = example_contract::foo::wallet::Builder { foo };
     let func_call = builder.build(&zk_bins);
     let func_call = builder.build(&zk_bins);
     let tx = Transaction { func_calls: vec![func_call] };
     let tx = Transaction { func_calls: vec![func_call] };
 
 
+    //// Validator
+
     for (idx, func_call) in tx.func_calls.iter().enumerate() {
     for (idx, func_call) in tx.func_calls.iter().enumerate() {
         if func_call.func_id == "Example::foo()" {
         if func_call.func_id == "Example::foo()" {
             debug!("example_contract::foo::state_transition()");
             debug!("example_contract::foo::state_transition()");
 
 
+            // TODO: separate this 2 things
             let update = example_contract::foo::validate::state_transition(&states, idx, &tx)
             let update = example_contract::foo::validate::state_transition(&states, idx, &tx)
                 .expect("example_contract::foo::validate::state_transition() failed!");
                 .expect("example_contract::foo::validate::state_transition() failed!");
             example_contract::foo::validate::apply(&mut states, update);
             example_contract::foo::validate::apply(&mut states, update);
@@ -216,6 +224,7 @@ pub async fn example() -> Result<()> {
 
 
     Ok(())
     Ok(())
 }
 }
+
 pub async fn demo() -> Result<()> {
 pub async fn demo() -> Result<()> {
     // Example smart contract
     // Example smart contract
     //// TODO: this will be moved to a different file
     //// TODO: this will be moved to a different file
@@ -398,6 +407,8 @@ pub async fn demo() -> Result<()> {
             value: xdrk_supply,
             value: xdrk_supply,
             token_id: xdrk_token_id,
             token_id: xdrk_token_id,
             public: dao_keypair.public,
             public: dao_keypair.public,
+            serial: pallas::Base::random(&mut OsRng),
+            coin_blind: pallas::Base::random(&mut OsRng),
             spend_hook,
             spend_hook,
             user_data,
             user_data,
         }],
         }],
@@ -417,7 +428,7 @@ pub async fn demo() -> Result<()> {
 
 
             let update = money_contract::transfer::validate::state_transition(&states, idx, &tx)
             let update = money_contract::transfer::validate::state_transition(&states, idx, &tx)
                 .expect("money_contract::transfer::validate::state_transition() failed!");
                 .expect("money_contract::transfer::validate::state_transition() failed!");
-            money_contract::transfer::validate::apply(&mut states, update);
+            update.apply(&mut states);
         }
         }
     }
     }
 
 
@@ -429,8 +440,8 @@ pub async fn demo() -> Result<()> {
     let state = states.lookup_mut::<money_contract::State>(&"Money".to_string()).unwrap();
     let state = states.lookup_mut::<money_contract::State>(&"Money".to_string()).unwrap();
     let mut recv_coins = state.wallet_cache.get_received(&dao_keypair.secret);
     let mut recv_coins = state.wallet_cache.get_received(&dao_keypair.secret);
     assert_eq!(recv_coins.len(), 1);
     assert_eq!(recv_coins.len(), 1);
-    let recv_coin = recv_coins.pop().unwrap();
-    let note = &recv_coin.note;
+    let dao_recv_coin = recv_coins.pop().unwrap();
+    let treasury_note = dao_recv_coin.note;
 
 
     // Check the actual coin received is valid before accepting it
     // Check the actual coin received is valid before accepting it
 
 
@@ -438,19 +449,19 @@ pub async fn demo() -> Result<()> {
     let coin = poseidon_hash::<8>([
     let coin = poseidon_hash::<8>([
         *coords.x(),
         *coords.x(),
         *coords.y(),
         *coords.y(),
-        DrkValue::from(note.value),
-        note.token_id,
-        note.serial,
-        note.spend_hook,
-        note.user_data,
-        note.coin_blind,
+        DrkValue::from(treasury_note.value),
+        treasury_note.token_id,
+        treasury_note.serial,
+        treasury_note.spend_hook,
+        treasury_note.user_data,
+        treasury_note.coin_blind,
     ]);
     ]);
-    assert_eq!(coin, recv_coin.coin.0);
+    assert_eq!(coin, dao_recv_coin.coin.0);
 
 
-    assert_eq!(note.spend_hook, hook_dao_exec);
-    assert_eq!(note.user_data, dao_bulla.0);
+    assert_eq!(treasury_note.spend_hook, hook_dao_exec);
+    assert_eq!(treasury_note.user_data, dao_bulla.0);
 
 
-    debug!("DAO received a coin worth {} xDRK", note.value);
+    debug!("DAO received a coin worth {} xDRK", treasury_note.value);
 
 
     ///////////////////////////////////////////////////
     ///////////////////////////////////////////////////
     //// Mint the governance token
     //// Mint the governance token
@@ -485,6 +496,8 @@ pub async fn demo() -> Result<()> {
         value: 400000,
         value: 400000,
         token_id: gdrk_token_id,
         token_id: gdrk_token_id,
         public: gov_keypair_1.public,
         public: gov_keypair_1.public,
+        serial: pallas::Base::random(&mut OsRng),
+        coin_blind: pallas::Base::random(&mut OsRng),
         spend_hook,
         spend_hook,
         user_data,
         user_data,
     };
     };
@@ -493,6 +506,8 @@ pub async fn demo() -> Result<()> {
         value: 400000,
         value: 400000,
         token_id: gdrk_token_id,
         token_id: gdrk_token_id,
         public: gov_keypair_2.public,
         public: gov_keypair_2.public,
+        serial: pallas::Base::random(&mut OsRng),
+        coin_blind: pallas::Base::random(&mut OsRng),
         spend_hook,
         spend_hook,
         user_data,
         user_data,
     };
     };
@@ -501,6 +516,8 @@ pub async fn demo() -> Result<()> {
         value: 200000,
         value: 200000,
         token_id: gdrk_token_id,
         token_id: gdrk_token_id,
         public: gov_keypair_3.public,
         public: gov_keypair_3.public,
+        serial: pallas::Base::random(&mut OsRng),
+        coin_blind: pallas::Base::random(&mut OsRng),
         spend_hook,
         spend_hook,
         user_data,
         user_data,
     };
     };
@@ -531,7 +548,7 @@ pub async fn demo() -> Result<()> {
 
 
             let update = money_contract::transfer::validate::state_transition(&states, idx, &tx)
             let update = money_contract::transfer::validate::state_transition(&states, idx, &tx)
                 .expect("money_contract::transfer::validate::state_transition() failed!");
                 .expect("money_contract::transfer::validate::state_transition() failed!");
-            money_contract::transfer::validate::apply(&mut states, update);
+            update.apply(&mut states);
         }
         }
     }
     }
 
 
@@ -1006,5 +1023,124 @@ pub async fn demo() -> Result<()> {
     assert!(total_value_commit == pedersen_commitment_u64(total_votes, total_value_blinds));
     assert!(total_value_commit == pedersen_commitment_u64(total_votes, total_value_blinds));
     assert!(total_vote_commit == pedersen_commitment_u64(win_votes, total_vote_blinds));
     assert!(total_vote_commit == pedersen_commitment_u64(win_votes, total_vote_blinds));
 
 
+    ///////////////////////////////////////////////////
+    // Execute the vote
+    ///////////////////////////////////////////////////
+
+    //// Wallet
+
+    // Used to export user_data from this coin so it can be accessed by DAO::exec()
+    let user_data_blind = pallas::Base::random(&mut OsRng);
+
+    let user_serial = pallas::Base::random(&mut OsRng);
+    let user_coin_blind = pallas::Base::random(&mut OsRng);
+    let dao_serial = pallas::Base::random(&mut OsRng);
+    let dao_coin_blind = pallas::Base::random(&mut OsRng);
+
+    let (treasury_leaf_position, treasury_merkle_path) = {
+        let state = states.lookup::<money_contract::State>(&"Money".to_string()).unwrap();
+        let tree = &state.tree;
+        let leaf_position = dao_recv_coin.leaf_position.clone();
+        let root = tree.root(0).unwrap();
+        let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
+        (leaf_position, merkle_path)
+    };
+
+    let builder = money_contract::transfer::wallet::Builder {
+        clear_inputs: vec![],
+        inputs: vec![money_contract::transfer::wallet::BuilderInputInfo {
+            leaf_position: treasury_leaf_position,
+            merkle_path: treasury_merkle_path,
+            secret: dao_keypair.secret,
+            note: treasury_note,
+            user_data_blind,
+        }],
+        outputs: vec![
+            // Sending money
+            money_contract::transfer::wallet::BuilderOutputInfo {
+                value: 1000,
+                token_id: xdrk_token_id,
+                public: user_keypair.public,
+                serial: user_serial,
+                coin_blind: user_coin_blind,
+                spend_hook: pallas::Base::from(0),
+                user_data: pallas::Base::from(0),
+            },
+            // Change back to DAO
+            money_contract::transfer::wallet::BuilderOutputInfo {
+                value: xdrk_supply - 1000,
+                token_id: xdrk_token_id,
+                public: dao_keypair.public,
+                serial: dao_serial,
+                coin_blind: dao_coin_blind,
+                spend_hook: hook_dao_exec,
+                user_data: dao_bulla.0,
+            },
+        ],
+    };
+
+    let transfer_func_call = builder.build(&zk_bins)?;
+
+    let foo = dao_contract::exec::wallet::Foo { a: 5, b: 10 };
+
+    let builder = dao_contract::exec::wallet::Builder { foo };
+    let exec_func_call = builder.build(&zk_bins);
+
+    let tx = Transaction { func_calls: vec![transfer_func_call, exec_func_call] };
+
+    {
+        // Now the spend_hook field specifies the function DAO::exec()
+        // so Money::transfer() must also be combined with DAO::exec()
+
+        assert_eq!(tx.func_calls.len(), 2);
+        let transfer_func_call = &tx.func_calls[0];
+        let transfer_call_data = transfer_func_call.call_data.as_any();
+
+        assert_eq!(
+            (&*transfer_call_data).type_id(),
+            TypeId::of::<money_contract::transfer::validate::CallData>()
+        );
+        let transfer_call_data =
+            transfer_call_data.downcast_ref::<money_contract::transfer::validate::CallData>();
+        let transfer_call_data = transfer_call_data.unwrap();
+        // At least one input has this field value which means DAO::exec() is invoked.
+        assert_eq!(transfer_call_data.inputs.len(), 1);
+        let input = &transfer_call_data.inputs[0];
+        assert_eq!(input.revealed.spend_hook, hook_dao_exec);
+        let user_data_enc = poseidon_hash::<2>([dao_bulla.0, user_data_blind]);
+        assert_eq!(input.revealed.user_data_enc, user_data_enc);
+    }
+
+    //// Validator
+
+    let mut updates = vec![];
+    // Validate all function calls in the tx
+    for (idx, func_call) in tx.func_calls.iter().enumerate() {
+        if func_call.func_id == "DAO::exec()" {
+            debug!("dao_contract::exec::state_transition()");
+
+            let update = dao_contract::exec::validate::state_transition(&states, idx, &tx)
+                .expect("dao_contract::exec::validate::state_transition() failed!");
+            updates.push(update);
+        } else if func_call.func_id == "Money::transfer()" {
+            debug!("money_contract::transfer::state_transition()");
+
+            let update = money_contract::transfer::validate::state_transition(&states, idx, &tx)
+                .expect("money_contract::transfer::validate::state_transition() failed!");
+            updates.push(update);
+        }
+    }
+
+    // Atomically apply all changes
+    for update in updates {
+        update.apply(&mut states);
+    }
+
+    // Other stuff
+    tx.zk_verify(&zk_bins);
+    // TODO: signature verification
+
+    //// Wallet
+
     Ok(())
     Ok(())
 }
 }

+ 47 - 16
bin/daod/src/money_contract/transfer/validate.rs

@@ -3,7 +3,7 @@ use std::any::{Any, TypeId};
 use incrementalmerkletree::Tree;
 use incrementalmerkletree::Tree;
 use log::{debug, error};
 use log::{debug, error};
 
 
-use pasta_curves::group::Group;
+use pasta_curves::{group::Group, pallas};
 
 
 use darkfi::{
 use darkfi::{
     crypto::{
     crypto::{
@@ -22,7 +22,7 @@ use darkfi::{
 };
 };
 
 
 use crate::{
 use crate::{
-    demo::{CallDataBase, StateRegistry, Transaction},
+    demo::{CallDataBase, StateRegistry, Transaction, UpdateBase},
     money_contract::state::State,
     money_contract::state::State,
     note::EncryptedNote2,
     note::EncryptedNote2,
 };
 };
@@ -41,22 +41,24 @@ pub struct Update {
     pub enc_notes: Vec<EncryptedNote2>,
     pub enc_notes: Vec<EncryptedNote2>,
 }
 }
 
 
-pub fn apply(states: &mut StateRegistry, mut update: Update) {
-    let state = states.lookup_mut::<State>(&"Money".to_string()).unwrap();
+impl UpdateBase for Update {
+    fn apply(mut self: Box<Self>, states: &mut StateRegistry) {
+        let state = states.lookup_mut::<State>(&"Money".to_string()).unwrap();
 
 
-    // Extend our list of nullifiers with the ones from the update
-    state.nullifiers.append(&mut update.nullifiers);
+        // Extend our list of nullifiers with the ones from the update
+        state.nullifiers.append(&mut self.nullifiers);
 
 
-    //// Update merkle tree and witnesses
-    for (coin, enc_note) in update.coins.into_iter().zip(update.enc_notes.into_iter()) {
-        // Add the new coins to the Merkle tree
-        let node = MerkleNode(coin.0);
-        state.tree.append(&node);
+        //// Update merkle tree and witnesses
+        for (coin, enc_note) in self.coins.into_iter().zip(self.enc_notes.into_iter()) {
+            // Add the new coins to the Merkle tree
+            let node = MerkleNode(coin.0);
+            state.tree.append(&node);
 
 
-        // Keep track of all Merkle roots that have existed
-        state.merkle_roots.push(state.tree.root(0).unwrap());
+            // Keep track of all Merkle roots that have existed
+            state.merkle_roots.push(state.tree.root(0).unwrap());
 
 
-        state.wallet_cache.try_decrypt_note(coin, enc_note, &mut state.tree);
+            state.wallet_cache.try_decrypt_note(coin, enc_note, &mut state.tree);
+        }
     }
     }
 }
 }
 
 
@@ -64,7 +66,7 @@ pub fn state_transition(
     states: &StateRegistry,
     states: &StateRegistry,
     func_call_index: usize,
     func_call_index: usize,
     parent_tx: &Transaction,
     parent_tx: &Transaction,
-) -> Result<Update> {
+) -> Result<Box<dyn UpdateBase>> {
     // Check the public keys in the clear inputs to see if they're coming
     // Check the public keys in the clear inputs to see if they're coming
     // from a valid cashier or faucet.
     // from a valid cashier or faucet.
     debug!(target: TARGET, "Iterate clear_inputs");
     debug!(target: TARGET, "Iterate clear_inputs");
@@ -105,6 +107,32 @@ pub fn state_transition(
             return Err(Error::VerifyFailed(VerifyFailed::InvalidMerkle(i)))
             return Err(Error::VerifyFailed(VerifyFailed::InvalidMerkle(i)))
         }
         }
 
 
+        // Check the spend_hook is satisfied
+        // The spend_hook says a coin must invoke another contract function when being spent
+        // If the value is set, then we check the function call exists
+        let spend_hook = &input.revealed.spend_hook;
+        if spend_hook != &pallas::Base::from(0) {
+            // spend_hook is set so we enforce the rules
+            let mut is_found = false;
+            for (i, func_call) in parent_tx.func_calls.iter().enumerate() {
+                // Skip current func_call
+                if i == func_call_index {
+                    continue
+                }
+
+                // TODO: we need to change these to pallas::Base
+                // temporary workaround for now
+                // if func_call.func_id == spend_hook ...
+                if func_call.func_id == "DAO::exec()" {
+                    is_found = true;
+                    break
+                }
+            }
+            if !is_found {
+                return Err(Error::VerifyFailed(VerifyFailed::SpendHookNotSatisfied))
+            }
+        }
+
         // The nullifiers should not already exist.
         // The nullifiers should not already exist.
         // It is the double-spend protection.
         // It is the double-spend protection.
         let nullifier = &input.revealed.nullifier;
         let nullifier = &input.revealed.nullifier;
@@ -140,7 +168,7 @@ pub fn state_transition(
         enc_notes.push(output.enc_note.clone());
         enc_notes.push(output.enc_note.clone());
     }
     }
 
 
-    Ok(Update { nullifiers, coins, enc_notes })
+    Ok(Box::new(Update { nullifiers, coins, enc_notes }))
 }
 }
 
 
 /// A DarkFi transaction
 /// A DarkFi transaction
@@ -316,6 +344,9 @@ pub enum VerifyFailed {
     #[error("Invalid Merkle root for input {0}")]
     #[error("Invalid Merkle root for input {0}")]
     InvalidMerkle(usize),
     InvalidMerkle(usize),
 
 
+    #[error("Spend hook invoking function is not attached")]
+    SpendHookNotSatisfied,
+
     #[error("Nullifier already exists for input {0}")]
     #[error("Nullifier already exists for input {0}")]
     NullifierExists(usize),
     NullifierExists(usize),
 
 

+ 4 - 2
bin/daod/src/money_contract/transfer/wallet/builder.rs

@@ -59,6 +59,8 @@ pub struct BuilderOutputInfo {
     pub value: u64,
     pub value: u64,
     pub token_id: DrkTokenId,
     pub token_id: DrkTokenId,
     pub public: PublicKey,
     pub public: PublicKey,
+    pub serial: DrkSerial,
+    pub coin_blind: DrkCoinBlind,
     pub spend_hook: DrkSpendHook,
     pub spend_hook: DrkSpendHook,
     pub user_data: DrkUserData,
     pub user_data: DrkUserData,
 }
 }
@@ -165,8 +167,8 @@ impl Builder {
             };
             };
             output_blinds.push(value_blind);
             output_blinds.push(value_blind);
 
 
-            let serial = DrkSerial::random(&mut OsRng);
-            let coin_blind = DrkCoinBlind::random(&mut OsRng);
+            let serial = output.serial;
+            let coin_blind = output.coin_blind;
 
 
             let zk_info = zk_bins.lookup(&"money-transfer-mint".to_string()).unwrap();
             let zk_info = zk_bins.lookup(&"money-transfer-mint".to_string()).unwrap();
             let zk_info = if let ZkContractInfo::Native(info) = zk_info {
             let zk_info = if let ZkContractInfo::Native(info) = zk_info {