Skip to content

Commit

Permalink
fee-sweeper: Ingest latest changes from base repo
Browse files Browse the repository at this point in the history
  • Loading branch information
joeykraut committed Jun 28, 2024
1 parent b6c0e99 commit 9b8b798
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 51 deletions.
7 changes: 6 additions & 1 deletion dealer/Cargo.toml → Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
[workspace]
members = ["renegade-dealer", "renegade-dealer-api"]
members = [
"dealer/renegade-dealer",
"dealer/renegade-dealer-api",
"fee-sweeper",
"price-reporter",
]

[profile.bench]
opt-level = 3
Expand Down
4 changes: 2 additions & 2 deletions fee-sweeper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ tokio = { version = "1.10", features = ["full"] }
# === Infra === #
aws-sdk-secretsmanager = "1.37"
aws-config = "1.5"
diesel = { version = "2.1", features = ["postgres", "numeric", "uuid"] }
diesel = { version = "2.2", features = ["postgres", "numeric", "uuid"] }

# === Blockchain Interaction === #
alloy-sol-types = "0.3.1"
Expand All @@ -31,7 +31,7 @@ renegade-util = { package = "util", git = "https://github.com/renegade-fi/renega

# === Misc Dependencies === #
base64 = "0.22"
bigdecimal = { version = "0.3", features = ["serde"] }
bigdecimal = { version = "0.4", features = ["serde"] }
futures = "0.3"
http = "1.1"
num-bigint = "0.4"
Expand Down
47 changes: 33 additions & 14 deletions fee-sweeper/src/indexer/index_fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use arbitrum_client::{
};
use ethers::contract::LogMeta;
use ethers::middleware::Middleware;
use ethers::types::TxHash;
use renegade_circuit_types::elgamal::ElGamalCiphertext;
use renegade_circuit_types::native_helpers::elgamal_decrypt;
use renegade_circuit_types::note::{Note, NOTE_CIPHERTEXT_SIZE};
Expand Down Expand Up @@ -54,15 +55,43 @@ impl Indexer {

/// Index a note
async fn index_note(&mut self, note_comm: NoteCommitment, meta: LogMeta) -> Result<(), String> {
let note = self.get_note_from_tx(meta.transaction_hash).await?;
let tx = format!("{:#x}", meta.transaction_hash);
if note.commitment() != note_comm {
info!("not receiver, skipping");
return Ok(());
} else {
info!("indexing note from tx: {tx}");
}

// Check that the note's nullifier has not been spent
let nullifier = note.nullifier();
if self
.arbitrum_client
.check_nullifier_used(nullifier)
.await
.map_err(raw_err_str!("failed to check nullifier: {}"))?
{
info!("note nullifier already spent, skipping");
return Ok(());
}

// Otherwise, index the note
let fee = NewFee::new_from_note(&note, tx);
self.insert_fee(fee)
}

/// Get a note from a transaction body
pub(crate) async fn get_note_from_tx(&self, tx_hash: TxHash) -> Result<Note, String> {
// Parse the note from the tx
let tx = self
.arbitrum_client
.get_darkpool_client()
.client()
.get_transaction(meta.transaction_hash)
.get_transaction(tx_hash)
.await
.map_err(raw_err_str!("failed to query tx: {}"))?
.ok_or_else(|| format!("tx not found: {}", meta.transaction_hash))?;
.ok_or_else(|| format!("tx not found: {}", tx_hash))?;

let calldata: Vec<u8> = tx.input.to_vec();
let selector: [u8; 4] = calldata[..SELECTOR_LEN].try_into().unwrap();
Expand All @@ -74,19 +103,9 @@ impl Indexer {
sel => return Err(format!("invalid selector when parsing note: {sel:?}")),
};

// Decrypt the note and check that the commitment matches the expected value; if not we are not the receiver
// Decrypt the note
let note = self.decrypt_note(&encryption);
let tx = format!("{:#x}", meta.transaction_hash);
if note.commitment() != note_comm {
info!("not receiver, skipping");
return Ok(());
} else {
info!("indexing note from tx: {tx}");
}

// Otherwise, index the note
let fee = NewFee::new_from_note(&note, tx);
self.insert_fee(fee)
Ok(note)
}

/// Decrypt a note using the decryption key
Expand Down
14 changes: 9 additions & 5 deletions fee-sweeper/src/indexer/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! The indexer handles the indexing and redemption of fee notes

use arbitrum_client::client::ArbitrumClient;
use arbitrum_client::{client::ArbitrumClient, constants::Chain};
use aws_config::SdkConfig as AwsConfig;
use diesel::PgConnection;
use renegade_circuit_types::elgamal::DecryptionKey;
Expand All @@ -13,8 +13,10 @@ pub mod redeem_fees;

/// Stores the dependencies needed to index the chain
pub(crate) struct Indexer {
/// The environment this indexer runs in
pub env: String,
/// The id of the chain this indexer targets
pub chain_id: u64,
/// The chain this indexer targets
pub chain: Chain,
/// A client for interacting with the relayer
pub relayer_client: RelayerClient,
/// The Arbitrum client
Expand All @@ -30,15 +32,17 @@ pub(crate) struct Indexer {
impl Indexer {
/// Constructor
pub fn new(
env: String,
chain_id: u64,
chain: Chain,
aws_config: AwsConfig,
arbitrum_client: ArbitrumClient,
decryption_key: DecryptionKey,
db_conn: PgConnection,
relayer_client: RelayerClient,
) -> Self {
Indexer {
env,
chain_id,
chain,
arbitrum_client,
decryption_key,
db_conn,
Expand Down
21 changes: 19 additions & 2 deletions fee-sweeper/src/indexer/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ use renegade_util::raw_err_str;
use crate::db::models::WalletMetadata;
use crate::db::models::{Metadata, NewFee};
use crate::db::schema::{
fees::dsl::{fees as fees_table, mint as mint_col, redeemed as redeemed_col},
fees::dsl::{
fees as fees_table, mint as mint_col, redeemed as redeemed_col, tx_hash as tx_hash_col,
},
indexing_metadata::dsl::{
indexing_metadata as metadata_table, key as metadata_key, value as metadata_value,
},
Expand Down Expand Up @@ -56,6 +58,7 @@ pub(crate) struct FeeValue {
pub mint: String,
/// The value of the fee
#[sql_type = "Numeric"]
#[allow(unused)]
pub value: BigDecimal,
}

Expand Down Expand Up @@ -118,12 +121,23 @@ impl Indexer {
Ok(mints)
}

/// Mark a fee as redeemed
pub(crate) fn mark_fee_as_redeemed(&mut self, tx_hash: &str) -> Result<(), String> {
let filter = tx_hash_col.eq(tx_hash);
diesel::update(fees_table.filter(filter))
.set(redeemed_col.eq(true))
.execute(&mut self.db_conn)
.map_err(raw_err_str!("failed to mark fee as redeemed: {}"))
.map(|_| ())
}

/// Get the most valuable fees to be redeemed
///
/// Returns the tx hashes of the most valuable fees to be redeemed
pub(crate) fn get_most_valuable_fees(
&mut self,
prices: HashMap<String, f64>,
receiver: &str,
) -> Result<Vec<FeeValue>, String> {
if prices.is_empty() {
return Ok(vec![]);
Expand All @@ -149,7 +163,10 @@ impl Indexer {
query_string.push_str(&format!("WHEN mint = '{}' then amount * {} ", mint, price));
}
query_string.push_str("ELSE 0 END as value ");
query_string.push_str("FROM fees WHERE redeemed = false ");
query_string.push_str(&format!(
"FROM fees WHERE redeemed = false and receiver = '{}'",
receiver
));

// Sort and limit
query_string.push_str(&format!("ORDER BY value DESC LIMIT {};", MAX_FEES_REDEEMED));
Expand Down
109 changes: 93 additions & 16 deletions fee-sweeper/src/indexer/redeem_fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ use std::str::FromStr;
use aws_sdk_secretsmanager::Client as SecretsManagerClient;
use ethers::core::rand::thread_rng;
use ethers::signers::LocalWallet;
use ethers::types::TxHash;
use ethers::utils::hex;
use renegade_api::http::wallet::RedeemNoteRequest;
use renegade_circuit_types::note::Note;
use renegade_common::types::wallet::derivation::{
derive_blinder_seed, derive_share_seed, derive_wallet_id, derive_wallet_keychain,
};
use renegade_common::types::wallet::{Wallet, WalletIdentifier};
use renegade_util::hex::jubjub_to_hex_string;
use renegade_util::raw_err_str;
use tracing::{info, warn};

Expand All @@ -28,7 +32,8 @@ impl Indexer {
// Get all mints that have unredeemed fees
let mints = self.get_unredeemed_fee_mints()?;

// Get the prices of each redeemable mint, we want to redeem the most profitable fees first
// Get the prices of each redeemable mint, we want to redeem the most profitable
// fees first
let mut prices = HashMap::new();
for mint in mints.into_iter() {
let maybe_price = self.relayer_client.get_binance_price(&mint).await?;
Expand All @@ -40,17 +45,23 @@ impl Indexer {
}

// Get the most valuable fees and redeem them
let most_valuable_fees = self.get_most_valuable_fees(prices)?;
let recv = jubjub_to_hex_string(&self.decryption_key.public_key());
let most_valuable_fees = self.get_most_valuable_fees(prices, &recv)?;

// TODO: Filter by those fees whose present value exceeds the expected gas costs to redeem
// TODO: Filter by those fees whose present value exceeds the expected gas costs
// to redeem
for fee in most_valuable_fees.into_iter() {
let wallet = self.get_or_create_wallet(&fee.mint).await?;
info!("redeeming into {}", wallet.id);
self.redeem_note_into_wallet(fee.tx_hash.clone(), wallet).await?;
}

Ok(())
}

// -------------------
// | Wallet Creation |
// -------------------

/// Find or create a wallet to store balances of a given mint
async fn get_or_create_wallet(&mut self, mint: &str) -> Result<WalletMetadata, String> {
let maybe_wallet = self.get_wallet_for_mint(mint)?;
Expand All @@ -62,7 +73,7 @@ impl Indexer {
None => {
info!("creating new wallet for {mint}");
self.create_new_wallet().await
}
},
}
}

Expand All @@ -74,9 +85,7 @@ impl Indexer {
let (wallet_id, root_key) = self.create_renegade_wallet().await?;

// 2. Create a secrets manager entry for the new wallet
let secret_name = self
.create_secrets_manager_entry(wallet_id, root_key)
.await?;
let secret_name = self.create_secrets_manager_entry(wallet_id, root_key).await?;

// 3. Add an entry in the wallets table for the newly created wallet
let entry = WalletMetadata::empty(wallet_id, secret_name);
Expand All @@ -87,17 +96,12 @@ impl Indexer {

/// Create a new Renegade wallet on-chain
async fn create_renegade_wallet(&mut self) -> Result<(WalletIdentifier, LocalWallet), String> {
let chain_id = self
.arbitrum_client
.chain_id()
.await
.map_err(raw_err_str!("Error fetching chain ID: {}"))?;
let root_key = LocalWallet::new(&mut thread_rng());

let wallet_id = derive_wallet_id(&root_key)?;
let blinder_seed = derive_blinder_seed(&root_key)?;
let share_seed = derive_share_seed(&root_key)?;
let key_chain = derive_wallet_keychain(&root_key, chain_id)?;
let key_chain = derive_wallet_keychain(&root_key, self.chain_id)?;

let wallet = Wallet::new_empty_wallet(wallet_id, blinder_seed, share_seed, key_chain);
self.relayer_client.create_new_wallet(wallet).await?;
Expand All @@ -106,7 +110,59 @@ impl Indexer {
Ok((wallet_id, root_key))
}

/// Add a Renegade wallet to the secrets manager entry so that it may be recovered later
// ------------------
// | Fee Redemption |
// ------------------

/// Redeem a note into a wallet
pub async fn redeem_note_into_wallet(
&mut self,
tx: String,
wallet: WalletMetadata,
) -> Result<Note, String> {
info!("redeeming fee into {}", wallet.id);
// Get the wallet key for the given wallet
let eth_key = self.get_wallet_private_key(&wallet).await?;
let wallet_keychain = derive_wallet_keychain(&eth_key, self.chain_id).unwrap();
let root_key = wallet_keychain.secret_keys.sk_root.clone().unwrap();

self.relayer_client.check_wallet_indexed(wallet.id, self.chain_id, &eth_key).await?;

// Find the note in the tx body
let tx_hash = TxHash::from_str(&tx).map_err(raw_err_str!("invalid tx hash: {}"))?;
let note = self.get_note_from_tx(tx_hash).await?;

// Redeem the note through the relayer
let req = RedeemNoteRequest { note: note.clone(), decryption_key: self.decryption_key };
self.relayer_client.redeem_note(wallet.id, req, &root_key).await?;

// Mark the fee as redeemed
self.maybe_mark_redeemed(&tx, &note).await?;
Ok(note)
}

/// Mark a fee as redeemed if its nullifier is spent on-chain
async fn maybe_mark_redeemed(&mut self, tx_hash: &str, note: &Note) -> Result<(), String> {
let nullifier = note.nullifier();
if !self
.arbitrum_client
.check_nullifier_used(nullifier)
.await
.map_err(raw_err_str!("failed to check nullifier: {}"))?
{
return Ok(());
}

info!("successfully redeemed fee from tx: {}", tx_hash);
self.mark_fee_as_redeemed(tx_hash)
}

// -------------------
// | Secrets Manager |
// -------------------

/// Add a Renegade wallet to the secrets manager entry so that it may be
/// recovered later
///
/// Returns the name of the secret
async fn create_secrets_manager_entry(
Expand All @@ -115,7 +171,7 @@ impl Indexer {
wallet: LocalWallet,
) -> Result<String, String> {
let client = SecretsManagerClient::new(&self.aws_config);
let secret_name = format!("redemption-wallet-{}-{id}", self.env);
let secret_name = format!("redemption-wallet-{}-{id}", self.chain);
let secret_val = hex::encode(wallet.signer().to_bytes());

// Check that the `LocalWallet` recovers the same
Expand All @@ -133,4 +189,25 @@ impl Indexer {

Ok(secret_name)
}

/// Get the private key for a wallet specified by its metadata
async fn get_wallet_private_key(
&mut self,
metadata: &WalletMetadata,
) -> Result<LocalWallet, String> {
let client = SecretsManagerClient::new(&self.aws_config);
let secret_name = format!("redemption-wallet-{}-{}", self.chain, metadata.id);

let secret = client
.get_secret_value()
.secret_id(secret_name)
.send()
.await
.map_err(raw_err_str!("Error fetching secret: {}"))?;

let secret_str = secret.secret_string().unwrap();
let wallet =
LocalWallet::from_str(secret_str).map_err(raw_err_str!("Invalid wallet secret: {}"))?;
Ok(wallet)
}
}
Loading

0 comments on commit 9b8b798

Please sign in to comment.