Răsfoiți Sursa

contract/money: Clean + doc

Luther Blissett 3 ani în urmă
părinte
comite
205a049e93
2 a modificat fișierele cu 152 adăugiri și 145 ștergeri
  1. 33 145
      contract/money/src/lib.rs
  2. 119 0
      contract/money/src/transfer.rs

+ 33 - 145
contract/money/src/lib.rs

@@ -1,5 +1,5 @@
-/// Available functions in this contract.
-/// We only identify them with a single byte passed through the payload.
+/// Available functions for this contract.
+/// We identify them with the first byte passed in through the payload.
 #[repr(u8)]
 pub enum Function {
     Transfer = 0x00,
@@ -14,171 +14,59 @@ impl From<u8> for Function {
     }
 }
 
-/// This state is serialized and stored on-chain. See `src/blockchain/statestore.rs`.
+pub mod transfer;
+
+/// `State` represents this contract's state on-chain. The contract's
+/// entrypoint knows its own `ContractId` since it's passed in by the
+/// wasm runtime, so it knows what to request. Retrieval of the state
+/// from the blockchain is done with a host function called `lookup_state`.
+/// For more info, see:
+/// * `darkfi/src/blockchain/statestore.rs`
+/// * ~~~`darkfi/src/runtime/chain_state.rs`~~~
 #[repr(C)]
-#[derive(SerialEncodable, SerialDecodable)]
+#[derive(Clone, SerialEncodable, SerialDecodable)]
 pub struct State {
+    /// The Merkle tree of all coins used by this contract.
     pub tree: BridgeTree<MerkleNode, MERKLE_DEPTH>,
+    /// List of all previous and current Merkle roots.
     pub merkle_roots: Vec<MerkleNode>,
+    /// Published nullifiers that have been seen.
     pub nullifiers: Vec<Nullifier>,
 }
 
-pub struct Update {
-    pub nullifiers: Vec<Nullifier>,
-    pub coins: Vec<Coin>,
-}
-
 impl State {
     pub fn is_valid_merkle(&self, merkle_root: &MerkleNode) -> bool {
         self.merkle_roots.iter().any(|m| m == merkle_root)
     }
 
-    pub fn nullfier_exists(&self, nullifier: &Nullifier) -> bool {
+    pub fn nullifier_exists(&self, nullifier: &Nullifier) -> bool {
         self.nullifiers.iter().any(|n| n == nullifier)
     }
-
-    pub fn update(&mut self, state_update: Update) {
-        self.nullifiers.extend_from_slice(&state_update.nullifiers);
-        for coin in state_update.coins {
-            self.tree.append(&MerkleNode(coin.inner()));
-            self.merkle_roots.push(self.tree.root(0).unwrap());
-        }
-    }
-}
-
-fn transfer(state: &mut State, payload: &[u8]) -> ContractResult {
-    let tx: Transaction = deserialize(payload)?;
-
-    // TODO: Clear inputs. Cashier + Faucet logic is bad and needs to be
-    // solved in another way.
-
-    // Nullifiers in the transaction
-    let mut nullifiers = Vec::with_capacity(tx.inputs.len());
-
-    msg!("Iterate inputs");
-    for (i, input) in tx.inputs.iter().enumerate() {
-        let merkle_root = *input.revealed.merkle_root;
-        let spend_hook = *input.revealed.spend_hook;
-        let nullifier = *input.revealed.nullifier;
-
-        // The Merkle root is used to know whether this is a coin that
-        // existed in a previous state.
-        if !state.is_valid_merkle(&merkle_root) {
-            msg!("Error: Invalid Merkle root (input {})", i);
-            msg!("Root: {:?}", merkle_root);
-            return Err(ContractError::Custom(30))
-        }
-
-        // 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.
-        if spend_hook != pallas::Base::zero() {
-            // spend_hook is set, so we enforce the rules.
-            todo!();
-        }
-
-        // The nullifiers should not already exists - double-spend protection.
-        if state.nullifier_exists(&nullifier) || nullifiers.contains(&nullifier) {
-            msg!("Duplicate nullifier found (input {})", i);
-            msg!("Nullifier: {:?}", nullifier);
-            return Err(ContractError::Custom(31))
-        }
-
-        nullifiers.push(nullifier);
-
-        // Verify transaction
-        match self.verify() {
-            Ok(()) => msg!("tx verified successfully"),
-            Err(e) => {
-                msg!("tx failed to verify");
-                return Err(e)
-            }
-        }
-
-        let mut coins = Vec::with_capacity(tx.outputs.len());
-        for output in tx.outputs {
-            coins.push(output.revealed.coin);
-        }
-
-        let state_update = Update { nullifiers, coins };
-        state.update(&state_update);
-        apply_state(&serialize(&state))?;
-        Ok(())
-    }
-}
-
-impl Verification for Transaction {
-    pub fn verify(&self) -> ContractResult {
-        // Must have minimum 1 clear or anon input, and 1 output
-        if self.clear_inputs.len() + self.inputs.len() == 0 {
-            msg!("Error: Missing inputs in transaction");
-            return Err(ContractError::Custom(32))
-        }
-
-        if self.outputs.is_empty() {
-            msg!("Error: Missing outputs in transaction");
-            return Err(ContractError::Custom(33))
-        }
-
-        // Accumulator for the value commitments
-        let mut valcom_total = DrkValueCommit::identity();
-
-        // Add values from the clear inputs
-        for input in &self.clear_inputs {
-            valcom_total += pedersen_commitment_u64(input.value, input.value_blind);
-        }
-
-        // Add values from the inputs
-        for input in &self.inputs {
-            valcom_total += input.revealed.value_commit;
-        }
-
-        // Subtract values from the outputs
-        for output in &self.outputs {
-            valcom_total -= output.revealed.value_commit;
-        }
-
-        // If the accumulator is not back in its initial state,
-        // there's a value mismatch.
-        if valcom_total != DrkValueCommit::identity() {
-            msg!("Error: Missing funds");
-            return Err(ContractError::Custom(34))
-        }
-
-        // Verify that the token commitments match
-        let token_commit_value = self.outputs[0].revealed.token_commit;
-        let mut failed =
-            self.inputs.iter().any(|input| input.revealed.token_commit != token_commit_value);
-        failed = failed ||
-            self.outputs.iter().any(|output| output.revealed.token_commit != token_commit_value);
-        failed = failed ||
-            self.clear_inputs.iter().any(|input| {
-                pedersen_commitment_base(input.token_id, input.token_blind) != token_commit_value
-            });
-
-        if !failed {
-            msg!("Error: Token ID mismatch");
-            return Err(ContractError::Custom(35))
-        }
-
-        Ok(())
-    }
 }
 
 #[cfg(not(feature = "no-entrypoint"))]
 entrypoint!(process_instruction);
 fn process_instruction(contract_id: &ContractId, ix: &[u8]) -> ContractResult {
-    // Using the `contract_id` (fed by the wasm runtime), we find our state in
-    // the sled database, and try to deserialize it into the `State` struct that
-    // is defined in this smart contract.
-    // TODO: FIXME: The deserialization needs to be partial, because in the ledger
-    // the smart contract deployer is supposed to allocate the space for this data
-    // and whatever is unused should be zeroes.
+    // This is the entrypoint function of the smart contract which gets executed
+    // by the wasm runtime. The `contract_id` passed in is used to lookup the
+    // current state from the ledger using the `lookup_state` function.
+    // `ix` is an arbitrary payload fed into the contract. In this case, the
+    // first byte of the payload is a pointer to a function we with to run, and
+    // the remainter is a serialized `Transaction` object we'll try to deserialize
+    // and work with.
     let mut state: State = deserialize(&lookup_state(contract_id)?)?;
 
     match Function::from(ix[0]) {
-        Function::Transfer => transfer(&mut state, &ix[1..])?,
+        Function::Transfer => {
+            let transaction = deserialize(&ix[1..])?;
+            transfer::exec(&mut state, transaction)?;
+            // If `transfer` succeeded, `state` will contain the updated state, so
+            // we can change it in the VM environment which is accessible by the
+            // host. Then if everything else outside of the wasm execution is
+            // valid, the host can reference this new state and update it on the
+            // ledger.
+            apply_state(&serialize(&state))?;
+        }
     }
 
     Ok(())

+ 119 - 0
contract/money/src/transfer.rs

@@ -0,0 +1,119 @@
+use super::State;
+
+/// This function is the execution of the `Transfer` functionality.
+pub fn exec(state: &mut State, tx: Transaction) -> ContractResult {
+    // TODO: Clear inputs. Cashier+Faucet logic is bad and needs to
+    // be solved in another better way.
+
+    // Nullifiers in the transaction
+    let mut nullifiers = Vec::with_capacity(tx.inputs.len());
+
+    msg!("Iterating over inputs");
+    for (i, input) in tx.inputs.iter().enumerate() {
+        let merkle_root = input.revealed.merkle_root;
+        let spend_hook = input.revealed.spend_hook;
+        let nullifier = input.revealed.nullifier;
+
+        // The Merkle root is used to know whether this is a coin
+        // that existed in a previous state.
+        if !state.is_valid_merkle(&merkle_root) {
+            msg!("Error: Invalid Merkle root (input {})", i);
+            msg!("Root: {:?}", merkle_root);
+            return Err(ContractError::Custom(30))
+        }
+
+        // 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.
+        if spend_hook != pallas::Base::zero() {
+            todo!();
+        }
+
+        // The nullifiers should not already exist. This gives us
+        // protection against double-spending.
+        if state.nullifier_exists(&nullifier) || nullifiers.contains(&nullifier) {
+            msg!("Duplicate nulliier found (input {})", i);
+            msg!("Nullifier: {:?}", nullifier);
+            return Err(ContractError::Custom(31))
+        }
+
+        // Add the nullifier to the list of seen nullifiers.
+        nullifiers.push(nullifier);
+    }
+
+    // Verify transaction
+    match tx.verify() {
+        Ok(()) => msg!("Transaction verified successfully"),
+        Err(e) => {
+            msg!("Transaction failed to verify");
+            return Err(e)
+        }
+    }
+
+    msg!("Applying state update");
+    state.nullifiers.extend_from_slice(&nullifiers);
+    for output in tx.outputs {
+        state.tree.append(&MerkleNode(coin.inner()));
+        state.merkle_roots.push(state.tree.root(0).unwrap());
+    }
+}
+
+// `Verification` could be a generic trait we implement for doing
+// arbitrary verification in contracts.
+impl Verification for Transaction {
+    pub fn verify(&self) -> ContractResult {
+        // Must have minimum 1 clear or anon input
+        if self.clear_inputs.len() + self.inputs.len() == 0 {
+            msg!("Error: Missing inputs in transaction");
+            return Err(ContractError::Custom(32))
+        }
+
+        // Also minimum 1 output
+        if self.outputs.is_empty() {
+            msg!("Error: Missing outputs in transaction");
+            return Err(ContractError::Custom(33))
+        }
+
+        // Accumulator for the value commitments
+        let mut valcom_total = ValueCommit::identity();
+
+        // Add values from the clear inputs
+        for input in &self.clear_inputs {
+            valcom_total += pedersen_commitment_u64(input.value, input.value_blind);
+        }
+
+        // Add values from the inputs
+        for input in &self.inputs {
+            valcom_total += input.revealed.value_commit;
+        }
+
+        // Subtract values from the outputs
+        for output in &self.outputs {
+            valcom_total -= output.revealed.value_commit;
+        }
+
+        // If the accumulator is not back in its initial state,
+        // there's a value mismatch.
+        if valcom_total != ValueCommit::identity() {
+            msg!("Error: Missing funds");
+            return Err(ContractError::Custom(34))
+        }
+
+        // Verify that the token commitments match
+        let tokval = self.outputs[0].revealed.token_commit;
+        let mut failed = self.inputs.iter().any(|input| input.revealed.token_commit != tokval);
+        failed = failed || self.outputs.iter().any(|output| output.revealed.token_commit != tokval);
+        failed = failed ||
+            self.clear_inputs.iter().any(|input| {
+                pedersen_commitment_base(input.token_id, input.token_blind) != tokval
+            });
+
+        if failed {
+            msg!("Error: Token ID mismatch");
+            return Err(ContractError::Custom(35))
+        }
+
+        Ok(())
+    }
+}