Bläddra i källkod

contract/money: Implement tx-local state

x 4 månader sedan
förälder
incheckning
15d399e6f5

+ 2 - 0
bin/drk/src/money.rs

@@ -1348,12 +1348,14 @@ impl Drk {
                 merkle_root: public_inputs.merkle_root,
                 user_data_enc: public_inputs.input_user_data_enc,
                 signature_public: public_inputs.signature_public,
+                tx_local: false,
             },
             output: Output {
                 value_commit: public_inputs.output_value_commit,
                 token_commit: public_inputs.token_commit,
                 coin: public_inputs.output_coin,
                 note: encrypted_note,
+                tx_local: false,
             },
             fee_value_blind,
             token_blind,

+ 1 - 0
src/contract/money/src/client/burn_v1.rs

@@ -84,6 +84,7 @@ impl BurnCallBuilder {
                 merkle_root: public_inputs.merkle_root,
                 user_data_enc: public_inputs.user_data_enc,
                 signature_public: public_inputs.signature_public,
+                tx_local: false,
             });
 
             proofs.push(proof);

+ 1 - 0
src/contract/money/src/client/genesis_mint_v1.rs

@@ -161,6 +161,7 @@ impl GenesisMintCallBuilder {
                 token_commit: public_inputs.token_commit,
                 coin: public_inputs.coin,
                 note: encrypted_note,
+                tx_local: false,
             };
 
             outputs.push(output);

+ 1 - 0
src/contract/money/src/client/pow_reward_v1.rs

@@ -143,6 +143,7 @@ impl PoWRewardCallBuilder {
             token_commit: public_inputs.token_commit,
             coin: public_inputs.coin,
             note: encrypted_note,
+            tx_local: false,
         };
 
         let params = MoneyPoWRewardParamsV1 { input: c_input, output: c_output };

+ 2 - 0
src/contract/money/src/client/swap_v1.rs

@@ -162,6 +162,7 @@ impl SwapCallBuilder {
             merkle_root: public_inputs.merkle_root,
             user_data_enc: public_inputs.user_data_enc,
             signature_public: public_inputs.signature_public,
+            tx_local: false,
         });
 
         proofs.push(proof);
@@ -203,6 +204,7 @@ impl SwapCallBuilder {
             token_commit: public_inputs.token_commit,
             coin: public_inputs.coin,
             note: encrypted_note,
+            tx_local: false,
         });
 
         // Now we should have all the params, zk proofs, and signature secrets.

+ 2 - 0
src/contract/money/src/client/transfer_v1/builder.rs

@@ -116,6 +116,7 @@ impl TransferCallBuilder {
                 merkle_root: public_inputs.merkle_root,
                 user_data_enc: public_inputs.user_data_enc,
                 signature_public: public_inputs.signature_public,
+                tx_local: false,
             });
 
             proofs.push(proof);
@@ -173,6 +174,7 @@ impl TransferCallBuilder {
                 token_commit: public_inputs.token_commit,
                 coin: public_inputs.coin,
                 note: encrypted_note,
+                tx_local: false,
             });
         }
 

+ 66 - 25
src/contract/money/src/entrypoint/fee_v1.rs

@@ -30,7 +30,14 @@ use darkfi_sdk::{
     error::{ContractError, ContractResult},
     msg,
     pasta::pallas,
-    wasm, ContractCall,
+    wasm::{
+        self,
+        db::{
+            db_contains_key, db_contains_key_local, db_get, db_lookup, db_lookup_local, db_set,
+            db_set_local,
+        },
+    },
+    ContractCall,
 };
 use darkfi_serial::{deserialize, serialize, Encodable};
 
@@ -107,10 +114,14 @@ pub(crate) fn money_fee_process_instruction_v1(
 
     // Access the necessary databases where there is information to
     // validate this state transition.
-    let coins_db = wasm::db::db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?;
-    let nullifiers_db = wasm::db::db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
-    let coin_roots_db = wasm::db::db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
-    let fees_db = wasm::db::db_lookup(cid, MONEY_CONTRACT_FEES_TREE)?;
+    let coins_db = db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?;
+    let coins_db_local = db_lookup_local(cid, MONEY_CONTRACT_COINS_TREE)?;
+
+    let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+    let coin_roots_db_local = db_lookup_local(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+
+    let fees_db = db_lookup(cid, MONEY_CONTRACT_FEES_TREE)?;
+    let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
 
     // Fees can only be paid using the native token, so we'll compare
     // the token commitments with this one:
@@ -132,8 +143,13 @@ pub(crate) fn money_fee_process_instruction_v1(
 
     // The Merkle root is used to know whether this is a coin that
     // existed in a previous state.
-    if !wasm::db::db_contains_key(coin_roots_db, &serialize(&params.input.merkle_root))? {
-        msg!("[FeeV1] Error: Input Merkle root not found in previous state");
+    if params.input.tx_local {
+        if !db_contains_key_local(coin_roots_db_local, &serialize(&params.input.merkle_root))? {
+            msg!("[FeeV1] Error: Input Merkle root not found in tx-local state");
+            return Err(MoneyError::CoinMerkleRootNotFound.into())
+        }
+    } else if !db_contains_key(coin_roots_db, &serialize(&params.input.merkle_root))? {
+        msg!("[FeeV1] Error: Input Merkle root not found in on-chain state");
         return Err(MoneyError::CoinMerkleRootNotFound.into())
     }
 
@@ -149,8 +165,13 @@ pub(crate) fn money_fee_process_instruction_v1(
     }
 
     // The new coin should not exist
-    if wasm::db::db_contains_key(coins_db, &serialize(&params.output.coin))? {
-        msg!("[FeeV1] Error: Duplicate coin found");
+    if params.output.tx_local {
+        if db_contains_key_local(coins_db_local, &serialize(&params.output.coin))? {
+            msg!("[FeeV1] Error: Duplicate tx-local coin found");
+            return Err(MoneyError::DuplicateCoin.into())
+        }
+    } else if db_contains_key(coins_db, &serialize(&params.output.coin))? {
+        msg!("[FeeV1] Error: Duplicate on-chain coin found");
         return Err(MoneyError::DuplicateCoin.into())
     }
 
@@ -179,13 +200,14 @@ pub(crate) fn money_fee_process_instruction_v1(
     // Accumulate the height paid fee
     let verifying_block_height = wasm::util::get_verifying_block_height()?;
     let mut paid_fee: u64 =
-        deserialize(&wasm::db::db_get(fees_db, &serialize(&verifying_block_height))?.unwrap())?;
+        deserialize(&db_get(fees_db, &serialize(&verifying_block_height))?.unwrap())?;
     paid_fee += fee;
 
     // At this point the state transition has passed, so we create a state update.
     let update = MoneyFeeUpdateV1 {
         nullifier: params.input.nullifier,
         coin: params.output.coin,
+        tx_local: params.output.tx_local,
         height: verifying_block_height,
         fee: paid_fee,
     };
@@ -199,14 +221,21 @@ pub(crate) fn money_fee_process_update_v1(
     update: MoneyFeeUpdateV1,
 ) -> ContractResult {
     // Grab all necessary db handles for where we want to write
-    let info_db = wasm::db::db_lookup(cid, MONEY_CONTRACT_INFO_TREE)?;
-    let coins_db = wasm::db::db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?;
-    let nullifiers_db = wasm::db::db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
-    let coin_roots_db = wasm::db::db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
-    let nullifier_roots_db = wasm::db::db_lookup(cid, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE)?;
-    let fees_db = wasm::db::db_lookup(cid, MONEY_CONTRACT_FEES_TREE)?;
+    let info_db = db_lookup(cid, MONEY_CONTRACT_INFO_TREE)?;
+    let info_db_local = db_lookup_local(cid, MONEY_CONTRACT_INFO_TREE)?;
+
+    let coins_db = db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?;
+    let coins_db_local = db_lookup_local(cid, MONEY_CONTRACT_COINS_TREE)?;
+
+    let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
+    let nullifier_roots_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE)?;
 
-    wasm::db::db_set(fees_db, &serialize(&update.height), &serialize(&update.fee))?;
+    let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+    let coin_roots_db_local = db_lookup_local(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+
+    let fees_db = db_lookup(cid, MONEY_CONTRACT_FEES_TREE)?;
+
+    db_set(fees_db, &serialize(&update.height), &serialize(&update.fee))?;
 
     wasm::merkle::sparse_merkle_insert_batch(
         info_db,
@@ -216,15 +245,27 @@ pub(crate) fn money_fee_process_update_v1(
         &[update.nullifier.inner()],
     )?;
 
-    wasm::db::db_set(coins_db, &serialize(&update.coin), &[])?;
+    if update.tx_local {
+        db_set_local(coins_db_local, &serialize(&update.coin), &[])?;
 
-    wasm::merkle::merkle_add(
-        info_db,
-        coin_roots_db,
-        MONEY_CONTRACT_LATEST_COIN_ROOT,
-        MONEY_CONTRACT_COIN_MERKLE_TREE,
-        &[MerkleNode::from(update.coin.inner())],
-    )?;
+        wasm::merkle::merkle_add_local(
+            info_db_local,
+            coin_roots_db_local,
+            MONEY_CONTRACT_LATEST_COIN_ROOT,
+            MONEY_CONTRACT_COIN_MERKLE_TREE,
+            &[MerkleNode::from(update.coin.inner())],
+        )?;
+    } else {
+        db_set(coins_db, &serialize(&update.coin), &[])?;
+
+        wasm::merkle::merkle_add(
+            info_db,
+            coin_roots_db,
+            MONEY_CONTRACT_LATEST_COIN_ROOT,
+            MONEY_CONTRACT_COIN_MERKLE_TREE,
+            &[MerkleNode::from(update.coin.inner())],
+        )?;
+    }
 
     Ok(())
 }

+ 5 - 1
src/contract/money/src/entrypoint/swap_v1.rs

@@ -147,7 +147,11 @@ pub(crate) fn money_otcswap_process_instruction_v1(
     // Create a state update. We also use `MoneyTransferUpdateV1` because
     // they're essentially the same thing, just with a different transition
     // ruleset.
-    let update = MoneyTransferUpdateV1 { nullifiers: new_nullifiers, coins: new_coins };
+    let update = MoneyTransferUpdateV1 {
+        nullifiers: new_nullifiers,
+        global_coins: new_coins,
+        local_coins: vec![],
+    };
     Ok(serialize(&update))
 }
 

+ 103 - 35
src/contract/money/src/entrypoint/transfer_v1.rs

@@ -29,7 +29,14 @@ use darkfi_sdk::{
     error::{ContractError, ContractResult},
     msg,
     pasta::pallas,
-    wasm, ContractCall,
+    wasm::{
+        self,
+        db::{
+            db_contains_key, db_contains_key_local, db_lookup, db_lookup_local, db_set,
+            db_set_local,
+        },
+    },
+    ContractCall,
 };
 use darkfi_serial::{deserialize, serialize, Encodable};
 
@@ -150,10 +157,15 @@ pub(crate) fn money_transfer_process_instruction_v1(
 
     // Access the necessary databases where there is information to
     // validate this state transition.
-    let coins_db = wasm::db::db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?;
-    let nullifiers_db = wasm::db::db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
-    let coin_roots_db = wasm::db::db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+    let coins_db = db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?;
+    let coins_db_local = db_lookup_local(cid, MONEY_CONTRACT_COINS_TREE)?;
+
+    let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+    let coin_roots_db_local = db_lookup_local(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
 
+    let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
+
+    // Initialize the sparse Merkle tree for nullifier storage
     let hasher = PoseidonFp::new();
     let empty_leaf = pallas::Base::ZERO;
     let smt_store = SmtWasmDbStorage::new(nullifiers_db);
@@ -169,19 +181,32 @@ pub(crate) fn money_transfer_process_instruction_v1(
     // Perform the actual state transition
     // ===================================
 
-    // For anonymous inputs, we must also gather all the new nullifiers
-    // that are introduced, and verify their token commitments.
+    // Keep track of new introduced nullifiers
     let mut new_nullifiers = Vec::with_capacity(params.inputs.len());
+
+    // For anonymous inputs, we must gather all the new nullifiers
+    // that are introduced, and verify their token commitments.
     msg!("[TransferV1] Iterating over anonymous inputs");
     for (i, input) in params.inputs.iter().enumerate() {
         // The Merkle root is used to know whether this is a coin that
         // existed in a previous state.
-        if !wasm::db::db_contains_key(coin_roots_db, &serialize(&input.merkle_root))? {
-            msg!("[TransferV1] Error: Merkle root not found in previous state (input {})", i);
+        //
+        // If the input was created from a tx-local output, we will check
+        // it against the tx-local Merkle tree, otherwise we will check
+        // it against the on-chain Merkle tree.
+        let merkle_root_ser = serialize(&input.merkle_root);
+        if input.tx_local {
+            if !db_contains_key_local(coin_roots_db_local, &merkle_root_ser)? {
+                msg!("[TransferV1] Error: Merkle root not found in tx-local state (input {})", i);
+                return Err(MoneyError::TransferMerkleRootNotFound.into())
+            }
+        } else if !db_contains_key(coin_roots_db, &merkle_root_ser)? {
+            msg!("[TransferV1] Error: Merkle root not found in on-chain state (input {})", i);
             return Err(MoneyError::TransferMerkleRootNotFound.into())
         }
 
-        // The nullifiers should not already exist. It is the double-spend protection.
+        // The nullifier should not already exist. It's the double-spend protection.
+        // Nullifiers are always written on-chain regardless if tx-local or not.
         if new_nullifiers.contains(&input.nullifier) ||
             smt.get_leaf(&input.nullifier.inner()) != empty_leaf
         {
@@ -197,13 +222,18 @@ pub(crate) fn money_transfer_process_instruction_v1(
         new_nullifiers.push(input.nullifier);
     }
 
-    // Newly created coins for this call are in the outputs. Here we gather them,
-    // check that they haven't existed before and their token commitment is valid.
-    let mut new_coins = Vec::with_capacity(params.outputs.len());
+    // Newly created coins for this call are in the outputs.
+    // Here we gather them, check that they haven't existed before and their
+    // token commitment is valid.
+    let mut new_global_coins = Vec::new();
+    let mut new_local_coins = Vec::new();
     msg!("[TransferV1] Iterating over anonymous outputs");
     for (i, output) in params.outputs.iter().enumerate() {
-        if new_coins.contains(&output.coin) ||
-            wasm::db::db_contains_key(coins_db, &serialize(&output.coin))?
+        let coin_ser = serialize(&output.coin);
+        if new_global_coins.contains(&output.coin) ||
+            new_local_coins.contains(&output.coin) ||
+            db_contains_key(coins_db, &coin_ser)? ||
+            db_contains_key_local(coins_db_local, &coin_ser)?
         {
             msg!("[TransferV1] Error: Duplicate coin found in output {}", i);
             return Err(MoneyError::DuplicateCoin.into())
@@ -214,7 +244,11 @@ pub(crate) fn money_transfer_process_instruction_v1(
         *acc -= output.value_commit;
 
         // Append this new coin to seen coins
-        new_coins.push(output.coin);
+        if output.tx_local {
+            new_local_coins.push(output.coin);
+        } else {
+            new_global_coins.push(output.coin);
+        }
     }
 
     // Verify all token groups balance. The accumulators should be back
@@ -227,9 +261,14 @@ pub(crate) fn money_transfer_process_instruction_v1(
         }
     }
 
-    // At this point the state transition has passed, so we create a state update
-    let update = MoneyTransferUpdateV1 { nullifiers: new_nullifiers, coins: new_coins };
-    // and return it
+    // At this point the state transition has passed. Create a state update.
+    let update = MoneyTransferUpdateV1 {
+        nullifiers: new_nullifiers,
+        global_coins: new_global_coins,
+        local_coins: new_local_coins,
+    };
+
+    // Return it
     Ok(serialize(&update))
 }
 
@@ -239,11 +278,17 @@ pub(crate) fn money_transfer_process_update_v1(
     update: MoneyTransferUpdateV1,
 ) -> ContractResult {
     // Grab all necessary db handles for where we want to write
-    let info_db = wasm::db::db_lookup(cid, MONEY_CONTRACT_INFO_TREE)?;
-    let coins_db = wasm::db::db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?;
-    let nullifiers_db = wasm::db::db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
-    let coin_roots_db = wasm::db::db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
-    let nullifier_roots_db = wasm::db::db_lookup(cid, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE)?;
+    let info_db = db_lookup(cid, MONEY_CONTRACT_INFO_TREE)?;
+    let info_db_local = db_lookup_local(cid, MONEY_CONTRACT_INFO_TREE)?;
+
+    let coins_db = db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?;
+    let coins_db_local = db_lookup_local(cid, MONEY_CONTRACT_COINS_TREE)?;
+
+    let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
+    let nullifier_roots_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE)?;
+
+    let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+    let coin_roots_db_local = db_lookup_local(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
 
     msg!("[TransferV1] Adding new nullifiers to the set");
     wasm::merkle::sparse_merkle_insert_batch(
@@ -255,20 +300,43 @@ pub(crate) fn money_transfer_process_update_v1(
     )?;
 
     msg!("[TransferV1] Adding new coins to the set");
-    let mut new_coins = Vec::with_capacity(update.coins.len());
-    for coin in &update.coins {
-        wasm::db::db_set(coins_db, &serialize(coin), &[])?;
-        new_coins.push(MerkleNode::from(coin.inner()));
+    for coin in &update.global_coins {
+        db_set(coins_db, &serialize(coin), &[])?;
     }
 
-    msg!("[TransferV1] Adding new coins to the Merkle tree");
-    wasm::merkle::merkle_add(
-        info_db,
-        coin_roots_db,
-        MONEY_CONTRACT_LATEST_COIN_ROOT,
-        MONEY_CONTRACT_COIN_MERKLE_TREE,
-        &new_coins,
-    )?;
+    for coin in &update.local_coins {
+        db_set_local(coins_db_local, &serialize(coin), &[])?;
+    }
+
+    if !update.global_coins.is_empty() {
+        msg!("[TransferV1] Adding new coins to on-chain Merkle tree");
+        wasm::merkle::merkle_add(
+            info_db,
+            coin_roots_db,
+            MONEY_CONTRACT_LATEST_COIN_ROOT,
+            MONEY_CONTRACT_COIN_MERKLE_TREE,
+            &update
+                .global_coins
+                .iter()
+                .map(|c| MerkleNode::from(c.inner()))
+                .collect::<Vec<MerkleNode>>(),
+        )?;
+    }
+
+    if !update.local_coins.is_empty() {
+        msg!("[TransferV1] Adding new coins to tx-local Merkle tree");
+        wasm::merkle::merkle_add_local(
+            info_db_local,
+            coin_roots_db_local,
+            MONEY_CONTRACT_LATEST_COIN_ROOT,
+            MONEY_CONTRACT_COIN_MERKLE_TREE,
+            &update
+                .local_coins
+                .iter()
+                .map(|c| MerkleNode::from(c.inner()))
+                .collect::<Vec<MerkleNode>>(),
+        )?;
+    }
 
     Ok(())
 }

+ 10 - 2
src/contract/money/src/model/mod.rs

@@ -148,6 +148,8 @@ pub struct Input {
     pub user_data_enc: pallas::Base,
     /// Public key for the signature
     pub signature_public: PublicKey,
+    /// Marker if the input used is transaction-local
+    pub tx_local: bool,
 }
 // ANCHOR_END: money-input
 
@@ -163,6 +165,8 @@ pub struct Output {
     pub coin: Coin,
     /// AEAD encrypted note
     pub note: AeadEncryptedNote,
+    /// Marker if the output used is transaction-local
+    pub tx_local: bool,
 }
 // ANCHOR_END: money-output
 
@@ -186,6 +190,8 @@ pub struct MoneyFeeUpdateV1 {
     pub nullifier: Nullifier,
     /// Minted coin
     pub coin: Coin,
+    /// Marker whether the output will be used tx-local
+    pub tx_local: bool,
     /// Block height the fee was verified against
     pub height: u32,
     /// Height accumulated fee paid
@@ -208,8 +214,10 @@ pub struct MoneyTransferParamsV1 {
 pub struct MoneyTransferUpdateV1 {
     /// Revealed nullifiers
     pub nullifiers: Vec<Nullifier>,
-    /// Minted coins
-    pub coins: Vec<Coin>,
+    /// Minted global-state coins
+    pub global_coins: Vec<Coin>,
+    /// Minted transaction-local-state coins
+    pub local_coins: Vec<Coin>,
 }
 
 /// Parameters for `Money::GenesisMint`

+ 2 - 0
src/contract/money/tests/delayed_tx.rs

@@ -217,12 +217,14 @@ fn delayed_tx() -> Result<()> {
                 merkle_root: public_inputs.merkle_root,
                 user_data_enc: public_inputs.input_user_data_enc,
                 signature_public: public_inputs.signature_public,
+                tx_local: false,
             },
             output: Output {
                 value_commit: public_inputs.output_value_commit,
                 token_commit: public_inputs.token_commit,
                 coin: public_inputs.output_coin,
                 note: encrypted_note,
+                tx_local: false,
             },
             fee_value_blind,
             token_blind,

+ 4 - 0
src/contract/test-harness/src/money_fee.rs

@@ -131,12 +131,14 @@ impl TestHarness {
                 merkle_root: public_inputs.merkle_root,
                 user_data_enc: public_inputs.input_user_data_enc,
                 signature_public: public_inputs.signature_public,
+                tx_local: false,
             },
             output: Output {
                 value_commit: public_inputs.output_value_commit,
                 token_commit: public_inputs.token_commit,
                 coin: public_inputs.output_coin,
                 note: encrypted_note,
+                tx_local: false,
             },
             fee_value_blind,
             token_blind,
@@ -277,12 +279,14 @@ impl TestHarness {
                 merkle_root: public_inputs.merkle_root,
                 user_data_enc: public_inputs.input_user_data_enc,
                 signature_public: public_inputs.signature_public,
+                tx_local: false,
             },
             output: Output {
                 value_commit: public_inputs.output_value_commit,
                 token_commit: public_inputs.token_commit,
                 coin: public_inputs.output_coin,
                 note: encrypted_note,
+                tx_local: false,
             },
             fee_value_blind,
             token_blind,