-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fee-sweeper: Ingest latest changes from base repo
- Loading branch information
Showing
9 changed files
with
1,194 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
//! Database code | ||
|
||
pub mod models; | ||
#[allow(missing_docs)] | ||
pub mod schema; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
#![allow(missing_docs)] | ||
#![allow(trivial_bounds)] | ||
|
||
use bigdecimal::BigDecimal; | ||
use diesel::prelude::*; | ||
use num_bigint::BigInt; | ||
use renegade_circuit_types::note::Note; | ||
use renegade_crypto::fields::scalar_to_bigint; | ||
use renegade_util::hex::{biguint_to_hex_addr, jubjub_to_hex_string}; | ||
use uuid::Uuid; | ||
|
||
use crate::db::schema::fees; | ||
|
||
/// A fee that has been indexed by the indexer | ||
#[derive(Queryable, Selectable)] | ||
#[diesel(table_name = crate::db::schema::fees)] | ||
#[diesel(check_for_backend(diesel::pg::Pg))] | ||
#[allow(missing_docs, clippy::missing_docs_in_private_items)] | ||
pub struct Fee { | ||
pub id: i32, | ||
pub tx_hash: String, | ||
pub mint: String, | ||
pub amount: BigDecimal, | ||
pub blinder: BigDecimal, | ||
pub receiver: String, | ||
pub redeemed: bool, | ||
} | ||
|
||
/// A new fee inserted into the database | ||
#[derive(Insertable)] | ||
#[diesel(table_name = fees)] | ||
pub struct NewFee { | ||
pub tx_hash: String, | ||
pub mint: String, | ||
pub amount: BigDecimal, | ||
pub blinder: BigDecimal, | ||
pub receiver: String, | ||
} | ||
|
||
impl NewFee { | ||
/// Construct a fee from a note | ||
pub fn new_from_note(note: &Note, tx_hash: String) -> Self { | ||
let mint = biguint_to_hex_addr(¬e.mint); | ||
let amount = BigInt::from(note.amount).into(); | ||
let blinder = scalar_to_bigint(¬e.blinder).into(); | ||
let receiver = jubjub_to_hex_string(¬e.receiver); | ||
|
||
NewFee { | ||
tx_hash, | ||
mint, | ||
amount, | ||
blinder, | ||
receiver, | ||
} | ||
} | ||
} | ||
|
||
/// Metadata information maintained by the indexer | ||
#[derive(Clone, Queryable, Selectable)] | ||
#[diesel(table_name = crate::db::schema::indexing_metadata)] | ||
#[diesel(check_for_backend(diesel::pg::Pg))] | ||
#[allow(missing_docs, clippy::missing_docs_in_private_items)] | ||
pub struct Metadata { | ||
pub key: String, | ||
pub value: String, | ||
} | ||
|
||
/// A metadata entry for a wallet managed by the indexer | ||
#[derive(Clone, Queryable, Selectable, Insertable)] | ||
#[diesel(table_name = crate::db::schema::wallets)] | ||
#[diesel(check_for_backend(diesel::pg::Pg))] | ||
#[allow(missing_docs, clippy::missing_docs_in_private_items)] | ||
pub struct WalletMetadata { | ||
pub id: Uuid, | ||
pub mints: Vec<Option<String>>, | ||
pub secret_id: String, | ||
} | ||
|
||
impl WalletMetadata { | ||
/// Construct a new wallet metadata entry | ||
pub fn empty(id: Uuid, secret_id: String) -> Self { | ||
WalletMetadata { | ||
id, | ||
mints: vec![], | ||
secret_id, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// @generated automatically by Diesel CLI. | ||
|
||
diesel::table! { | ||
fees (id) { | ||
id -> Int4, | ||
tx_hash -> Text, | ||
mint -> Text, | ||
amount -> Numeric, | ||
blinder -> Numeric, | ||
receiver -> Text, | ||
redeemed -> Bool, | ||
} | ||
} | ||
|
||
diesel::table! { | ||
indexing_metadata (key) { | ||
key -> Text, | ||
value -> Text, | ||
} | ||
} | ||
|
||
diesel::table! { | ||
wallets (id) { | ||
id -> Uuid, | ||
mints -> Array<Nullable<Text>>, | ||
secret_id -> Text, | ||
} | ||
} | ||
|
||
diesel::allow_tables_to_appear_in_same_query!( | ||
fees, | ||
indexing_metadata, | ||
wallets, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
//! Phase one of the sweeper's execution; index all fees since the last consistent block | ||
|
||
use alloy_sol_types::SolCall; | ||
use arbitrum_client::abi::settleOfflineFeeCall; | ||
use arbitrum_client::{ | ||
abi::NotePostedFilter, constants::SELECTOR_LEN, | ||
helpers::parse_note_ciphertext_from_settle_offline_fee, | ||
}; | ||
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}; | ||
use renegade_circuit_types::wallet::NoteCommitment; | ||
use renegade_constants::Scalar; | ||
use renegade_crypto::fields::{scalar_to_biguint, scalar_to_u128, u256_to_scalar}; | ||
use renegade_util::raw_err_str; | ||
use tracing::info; | ||
|
||
use crate::db::models::NewFee; | ||
use crate::Indexer; | ||
|
||
impl Indexer { | ||
/// Index all fees since the given block | ||
pub async fn index_fees(&mut self) -> Result<(), String> { | ||
let block_number = self.get_latest_block()?; | ||
info!("indexing fees from block {block_number}"); | ||
|
||
let filter = self | ||
.arbitrum_client | ||
.get_darkpool_client() | ||
.event::<NotePostedFilter>() | ||
.from_block(block_number); | ||
|
||
let events = filter | ||
.query_with_meta() | ||
.await | ||
.map_err(raw_err_str!("failed to create note posted stream: {}"))?; | ||
|
||
let mut most_recent_block = block_number; | ||
for (event, meta) in events { | ||
let block = meta.block_number.as_u64(); | ||
let note_comm = u256_to_scalar(&event.note_commitment); | ||
self.index_note(note_comm, meta).await?; | ||
|
||
if block > most_recent_block { | ||
most_recent_block = block; | ||
self.update_latest_block(most_recent_block)?; | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
/// 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(¬e, 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(tx_hash) | ||
.await | ||
.map_err(raw_err_str!("failed to query tx: {}"))? | ||
.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(); | ||
let encryption = match selector { | ||
<settleOfflineFeeCall as SolCall>::SELECTOR => { | ||
parse_note_ciphertext_from_settle_offline_fee(&calldata) | ||
.map_err(raw_err_str!("failed to parse ciphertext: {}"))? | ||
} | ||
sel => return Err(format!("invalid selector when parsing note: {sel:?}")), | ||
}; | ||
|
||
// Decrypt the note | ||
let note = self.decrypt_note(&encryption); | ||
Ok(note) | ||
} | ||
|
||
/// Decrypt a note using the decryption key | ||
fn decrypt_note(&self, note: &ElGamalCiphertext<NOTE_CIPHERTEXT_SIZE>) -> Note { | ||
// The ciphertext stores all note values except the encryption key | ||
let cleartext_values: [Scalar; NOTE_CIPHERTEXT_SIZE] = | ||
elgamal_decrypt(note, &self.decryption_key); | ||
|
||
Note { | ||
mint: scalar_to_biguint(&cleartext_values[0]), | ||
amount: scalar_to_u128(&cleartext_values[1]), | ||
receiver: self.decryption_key.public_key(), | ||
blinder: cleartext_values[2], | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
//! The indexer handles the indexing and redemption of fee notes | ||
|
||
use arbitrum_client::{client::ArbitrumClient, constants::Chain}; | ||
use aws_config::SdkConfig as AwsConfig; | ||
use diesel::PgConnection; | ||
use renegade_circuit_types::elgamal::DecryptionKey; | ||
|
||
use crate::relayer_client::RelayerClient; | ||
|
||
pub mod index_fees; | ||
pub mod queries; | ||
pub mod redeem_fees; | ||
|
||
/// Stores the dependencies needed to index the chain | ||
pub(crate) struct Indexer { | ||
/// 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 | ||
pub arbitrum_client: ArbitrumClient, | ||
/// The decryption key | ||
pub decryption_key: DecryptionKey, | ||
/// A connection to the DB | ||
pub db_conn: PgConnection, | ||
/// The AWS config | ||
pub aws_config: AwsConfig, | ||
} | ||
|
||
impl Indexer { | ||
/// Constructor | ||
pub fn new( | ||
chain_id: u64, | ||
chain: Chain, | ||
aws_config: AwsConfig, | ||
arbitrum_client: ArbitrumClient, | ||
decryption_key: DecryptionKey, | ||
db_conn: PgConnection, | ||
relayer_client: RelayerClient, | ||
) -> Self { | ||
Indexer { | ||
chain_id, | ||
chain, | ||
arbitrum_client, | ||
decryption_key, | ||
db_conn, | ||
relayer_client, | ||
aws_config, | ||
} | ||
} | ||
} |
Oops, something went wrong.