Browse Source

begin Follower impl for darkfi chain client

elizabeth 2 years ago
parent
commit
3c8009a618

+ 1 - 0
Cargo.lock

@@ -6185,6 +6185,7 @@ dependencies = [
  "easy-parallel",
  "ethers",
  "log",
+ "pasta_curves",
  "serde",
  "signal-hook",
  "signal-hook-async-std",

+ 2 - 1
Cargo.toml

@@ -35,13 +35,14 @@ serde = {version = "1.0.197", features = ["derive"]}
 structopt = "0.3.26"
 structopt-toml = "0.5.1"
 
+async-std = {version = "1.12.0", features = ["attributes", "tokio1"]}
 async-watch = "0.3.1"
 ethers = { version = "2.0", features = ["ethers-solc", "ws"] }
+pasta_curves = "0.5.1"
 thiserror = "1.0.57"
 
 [dev-dependencies]
 swapd = { path = ".", features = ["test-utils"] }
-async-std = {version = "1.12.0", features = ["attributes", "tokio1"]}
 
 [features]
 default = []

+ 14 - 10
src/darkfi/error.rs

@@ -1,25 +1,29 @@
 #[derive(Debug, thiserror::Error)]
 pub enum Error {
-    #[error("failed to create drk struct: {0}")]
+    #[error("failed to create drk struct")]
     DrkInitializationFailed(#[source] darkfi::Error),
-    #[error("failed to initialize wallet: {0}")]
+    #[error("failed to initialize wallet")]
     InitializeWallet(#[source] darkfi::Error),
-    #[error("failed to initialize money: {0}")]
+    #[error("failed to initialize money")]
     InitializeMoney(#[source] drk::WalletDbError),
-    #[error("failed to initialize dao: {0}")]
+    #[error("failed to initialize dao")]
     InitializeDao(#[source] drk::WalletDbError),
-    #[error("failed to get default secret: {0}")]
+    #[error("failed to get default secret")]
     DefaultSecret(#[source] darkfi::Error),
-    #[error("failed to generate keypair: {0}")]
+    #[error("failed to generate keypair")]
     Keygen(#[source] drk::WalletDbError),
-    #[error("failed to get money balance: {0}")]
+    #[error("failed to get money balance")]
     MoneyBalance(#[source] darkfi::Error),
-    #[error("failed to get aliases mapped by token: {0}")]
+    #[error("failed to get aliases mapped by token")]
     GetAliasesMappedByToken(#[source] darkfi::Error),
-    #[error("failed to build transfer: {0}")]
+    #[error("failed to build transfer")]
     BuildTransfer(#[source] darkfi::Error),
-    #[error("failed to submit transaction: {0}")]
+    #[error("failed to submit transaction")]
     SubmitTransaction(#[source] darkfi::Error),
     #[error("swap keypair does not exist; generate one first")]
     SwapKeypairDoesNotExist,
+    #[error("failed to put keypair")]
+    PutKeypair(#[source] drk::WalletDbError),
+    #[error("failed convert counterparty secret bytes to secret key")]
+    CounterpartySecretKeyConversionFailed(#[source] darkfi_sdk::ContractError),
 }

+ 85 - 0
src/darkfi/follower.rs

@@ -0,0 +1,85 @@
+use crate::protocol::traits::FollowerArgs;
+use darkfi_sdk::crypto::{Keypair, SecretKey};
+use darkfi_serial::async_trait;
+
+use super::{wallet::Wallet, Error};
+use crate::protocol::traits::Follower;
+
+/// Implemented on top of the non-initiating chain
+///
+/// Can probably become an extension trait of an RPC client eventually
+pub(crate) trait OtherChainClient {
+    fn claim_funds(&self, our_secret: [u8; 32]) -> Result<(), crate::Error>;
+}
+
+pub(crate) struct DrkFollower<C: OtherChainClient> {
+    other_chain_client: C,
+    secret: SecretKey,
+    wallet: Wallet,
+    args: FollowerArgs,
+}
+
+impl<C: OtherChainClient> DrkFollower<C> {
+    fn new(other_chain_client: C, secret: SecretKey, wallet: Wallet, args: FollowerArgs) -> Self {
+        Self { other_chain_client, secret, wallet, args }
+    }
+}
+
+impl<C: OtherChainClient + Send + Sync> Follower for DrkFollower<C> {
+    // handle the swap initiation by locking funds on chain B
+    fn handle_counterparty_funds_locked(&self) -> Result<(), crate::Error> {
+        // lock DRK funds to shared swap account
+        let shared_swap_public_key = Keypair::new(self.secret).public;
+
+        // TODO: ensure that the funds locked have the correct parameters before locking:
+        // - value
+        // - asset
+        // - commitment to our pubkey
+        // - timeouts
+
+        // cursed hack b/c `exec_sql` takes `dyn ToSql` which is not Send or Sync :/
+        let tx = async_std::task::block_on(
+            self.wallet.build_swap_transfer(self.args.value, shared_swap_public_key),
+        )?;
+        let _tx_hash = async_std::task::block_on(self.wallet.submit_transaction(&tx))?;
+        Ok(())
+    }
+
+    // handle the funds being ready to be claimed by us
+    fn handle_ready_to_claim(&self) -> Result<(), crate::Error> {
+        use darkfi_sdk::crypto::pasta_prelude::PrimeField as _;
+
+        let our_secret: [u8; 32] = self
+            .secret
+            .inner()
+            .to_repr()
+            .as_ref()
+            .try_into()
+            .expect("can convert secret key to 32 byte representation");
+        self.other_chain_client.claim_funds(our_secret)?;
+        Ok(())
+    }
+
+    // handle the counterparty refunding their funds, in case of a timeout
+    fn handle_counterparty_funds_refunded(
+        &self,
+        counterparty_secret: [u8; 32],
+    ) -> Result<(), crate::Error> {
+        let counterparty_secret_key = SecretKey::from_bytes(counterparty_secret)
+            .map_err(|e| crate::Error::from(Error::CounterpartySecretKeyConversionFailed(e)))?;
+        let counterparty_secret_fp = counterparty_secret_key.inner();
+        let our_secret_fp = self.secret.inner();
+        let shared_secret_fp = counterparty_secret_fp + our_secret_fp;
+        let shared_secret = SecretKey::from(shared_secret_fp);
+        let keypair = Keypair::new(shared_secret);
+
+        async_std::task::block_on(self.wallet.put_keypair(keypair))?;
+        let tx = async_std::task::block_on(self.wallet.build_transfer(
+            self.args.value,
+            self.wallet.user_keypair().public,
+            keypair,
+        ))?;
+        let _tx_hash = async_std::task::block_on(self.wallet.submit_transaction(&tx))?;
+        Ok(())
+    }
+}

+ 1 - 0
src/darkfi/mod.rs

@@ -1,4 +1,5 @@
 mod error;
+mod follower;
 pub(crate) mod wallet;
 
 pub(crate) use error::Error;

+ 22 - 0
src/darkfi/wallet.rs

@@ -42,12 +42,20 @@ impl Wallet {
         Ok(Self { drk, user_keypair, swap_keypair: None })
     }
 
+    pub(crate) fn user_keypair(&self) -> Keypair {
+        self.user_keypair
+    }
+
     pub(crate) async fn generate_swap_keypair(&mut self) -> Result<(), crate::Error> {
         self.swap_keypair =
             Some(self.drk.money_keygen().await.map_err(|e| crate::Error::from(Error::Keygen(e)))?);
         Ok(())
     }
 
+    pub(crate) async fn put_keypair(&self, keypair: Keypair) -> Result<(), crate::Error> {
+        self.drk.put_keypair(keypair).await.map_err(|e| crate::Error::from(Error::PutKeypair(e)))
+    }
+
     // get the user's DRK balance
     pub(crate) async fn get_user_balance(&self) -> Result<u64, crate::Error> {
         get_balance(&self.drk, self.user_keypair.secret).await
@@ -91,6 +99,20 @@ impl Wallet {
             .map_err(|e| crate::Error::from(Error::BuildTransfer(e)))
     }
 
+    pub(crate) async fn build_transfer(
+        &self,
+        amount: u128,
+        recipient: PublicKey,
+        signer: Keypair,
+    ) -> Result<Transaction, crate::Error> {
+        let token_id =
+            self.drk.get_token("DRK".to_string()).await.expect("token id must exist for DRK");
+        self.drk
+            .transfer_with_signer(&amount.to_string(), token_id, recipient, signer)
+            .await
+            .map_err(|e| crate::Error::from(Error::BuildTransfer(e)))
+    }
+
     // submit a transaction to the network
     pub(crate) async fn submit_transaction(
         &self,

+ 2 - 0
src/protocol/error.rs

@@ -12,4 +12,6 @@ pub enum Error {
     UnexpectedAlmostTimeout1Event,
     #[error("unexpected past timeout 2 event")]
     UnexpectedPastTimeout2Event,
+    #[error("failed to receive event")]
+    RecvError(#[from] smol::channel::RecvError),
 }

+ 11 - 6
src/protocol/follower.rs

@@ -1,11 +1,13 @@
 use crate::protocol::traits::Follower;
 use smol::channel;
 
+use log::info;
+
 #[allow(dead_code)]
 enum Event {
     CounterpartyFundsLocked,
     ReadyToClaim,
-    CounterpartyFundsRefunded,
+    CounterpartyFundsRefunded([u8; 32]),
 }
 
 #[allow(dead_code)]
@@ -20,22 +22,25 @@ impl Swap {
         Self { handler, event_rx }
     }
 
-    async fn run(&mut self) {
+    async fn run(&mut self) -> Result<(), crate::Error> {
         loop {
             match self.event_rx.recv().await {
                 Ok(Event::CounterpartyFundsLocked) => {
-                    self.handler.handle_counterparty_funds_locked();
+                    self.handler.handle_counterparty_funds_locked()?;
                 }
                 Ok(Event::ReadyToClaim) => {
-                    self.handler.handle_ready_to_claim();
+                    self.handler.handle_ready_to_claim()?;
                 }
-                Ok(Event::CounterpartyFundsRefunded) => {
-                    self.handler.handle_counterparty_funds_refunded();
+                Ok(Event::CounterpartyFundsRefunded(counterparty_secret)) => {
+                    self.handler.handle_counterparty_funds_refunded(counterparty_secret)?;
                 }
                 Err(_) => {
+                    info!("event channel closed, exiting");
                     break;
                 }
             }
         }
+
+        Ok(())
     }
 }

+ 1 - 1
src/protocol/initiator.rs

@@ -32,7 +32,7 @@ struct Swap {
     args: InitiationArgs,
 
     // the chain-specific event handler
-    // TODO: just make this a generic
+    // TODO: just make this a generic?
     handler: Box<dyn Initiator + Send + Sync>,
 
     // the event receiver channel for the swap

+ 14 - 3
src/protocol/traits.rs

@@ -2,6 +2,7 @@ use crate::ethereum::swap_creator::Swap; // TODO: shouldn't depend on this
 use crate::{error::Error, ethereum::swap_creator::SwapCreator, protocol::initiator::Event};
 use darkfi_serial::async_trait;
 use ethers::{prelude::*, utils::hex};
+use pasta_curves::pallas;
 use smol::channel;
 use std::{
     fmt,
@@ -35,6 +36,13 @@ pub(crate) struct InitiateSwapArgs {
     pub(crate) nonce: U256,
 }
 
+// Initial parameters required by the swap follower.
+#[derive(Debug)]
+pub(crate) struct FollowerArgs {
+    pub(crate) counterparty_public_key: pallas::Point, // TODO: make this generic
+    pub(crate) value: u128,                            // TODO: is u128 sufficient?
+}
+
 // TODO: make this generic for both chains
 #[allow(dead_code)]
 #[derive(Debug)]
@@ -130,11 +138,14 @@ pub(crate) trait InitiatorEventWatcher {
 /// the chain that is the counterparty to the swap; ie. the second-mover
 pub(crate) trait Follower {
     // handle the swap initiation by locking funds on chain B
-    fn handle_counterparty_funds_locked(&self);
+    fn handle_counterparty_funds_locked(&self) -> Result<(), crate::Error>;
 
     // handle the funds being ready to be claimed by us
-    fn handle_ready_to_claim(&self);
+    fn handle_ready_to_claim(&self) -> Result<(), crate::Error>;
 
     // handle the counterparty refunding their funds, in case of a timeout
-    fn handle_counterparty_funds_refunded(&self);
+    fn handle_counterparty_funds_refunded(
+        &self,
+        counterparty_secret: [u8; 32],
+    ) -> Result<(), crate::Error>;
 }