diff --git a/prover/src/registry_listener.rs b/prover/src/karma_sc_listener.rs similarity index 69% rename from prover/src/registry_listener.rs rename to prover/src/karma_sc_listener.rs index 399dad3..683be39 100644 --- a/prover/src/registry_listener.rs +++ b/prover/src/karma_sc_listener.rs @@ -3,6 +3,7 @@ use alloy::{ contract::Error as AlloyContractError, primitives::{Address, U256}, providers::Provider, + rpc::types::Log, sol_types::SolEvent, }; use num_bigint::BigUint; @@ -14,14 +15,14 @@ use crate::user_db::UserDb; use crate::user_db_error::RegisterError; use smart_contract::{KarmaAmountExt, KarmaRLNSC, KarmaSC, RLNRegister}; -pub(crate) struct RegistryListener { +pub(crate) struct KarmaScEventListener { karma_sc_address: Address, rln_sc_address: Address, user_db: UserDb, minimal_amount: U256, } -impl RegistryListener { +impl KarmaScEventListener { pub(crate) fn new( karma_sc_address: Address, rln_sc_address: Address, @@ -42,17 +43,13 @@ impl RegistryListener { provider: P, provider_with_signer: PS, ) -> Result<(), AppError> { - // let provider = self.setup_provider_ws().await.map_err(AppError::from)?; let karma_sc = KarmaSC::new(self.karma_sc_address, provider.clone()); - - // let provider_with_signer = self.setup_provider_with_signer(self.private_key.clone()) - // .await - // .map_err(AppError::from)?; let rln_sc = KarmaRLNSC::new(self.rln_sc_address, provider_with_signer); let filter = alloy::rpc::types::Filter::new() .address(self.karma_sc_address) - .event(KarmaSC::Transfer::SIGNATURE); + .event(KarmaSC::Transfer::SIGNATURE) + .event(KarmaSC::AccountSlashed::SIGNATURE); // Subscribe to logs matching the filter. let subscription = provider.subscribe_logs(&filter).await?; @@ -60,6 +57,21 @@ impl RegistryListener { // Loop through the incoming event logs while let Some(log) = stream.next().await { + match log.topic0() { + Some(&KarmaSC::Transfer::SIGNATURE_HASH) => { + self.transfer_event(&log, &karma_sc, &rln_sc) + .await + .map_err(AppError::RegistryError)?; + } + Some(&KarmaSC::AccountSlashed::SIGNATURE_HASH) => { + self.slash_event(&log).await; + } + _ => { + debug!("Received unknown topic from karma sc: {:?}", log); + } + } + + /* match KarmaSC::Transfer::decode_log_data(log.data()) { Ok(transfer_event) => { match self @@ -93,6 +105,64 @@ impl RegistryListener { // in order to avoid a too long service interruption? } } + */ + // TODO + /* + match KarmaSC::AccountSlashed::decode_log_data(log.data()) { + + } + */ + } + + Ok(()) + } + + /// Decode transfert event from log and call `handle_transfer_event` + async fn transfer_event< + E: Into, + KSC: KarmaAmountExt, + RLNSC: RLNRegister, + >( + &self, + log: &Log, + karma_sc: &KSC, + rln_sc: &RLNSC, + ) -> Result<(), HandleTransferError> { + match KarmaSC::Transfer::decode_log_data(log.data()) { + Ok(transfer_event) => { + match self + .handle_transfer_event(karma_sc, rln_sc, transfer_event) + .await + { + Ok(addr) => { + info!("Registered new user: {}", addr); + } + Err(HandleTransferError::Register(RegisterError::AlreadyRegistered( + address, + ))) => { + debug!("Already registered: {}", address); + } + Err(e) => { + error!("Unexpected error: {}", e); + // Note: Err(e) == HandleTransferError::FetchBalanceOf + // if we cannot fetch the user balance, something is seriously wrong + // and the prover will fail here + + // return Err(AppError::RegistryError(e)); + return Err(e); + } + }; + } + Err(e) => { + error!("Error decoding log data: {:?}", e); + // It's also useful to print the raw log data for debugging + error!("Raw log topics: {:?}", log.topics()); + error!("Raw log data: {:?}", log.data()); + // Note: This can happen when + // - SC code has been updated but not the Prover + // - In the update process, the Prover has not been shutdown (yet) + // in order to avoid a too long service interruption? + } } Ok(()) @@ -155,7 +225,7 @@ impl RegistryListener { U256::from_le_slice(BigUint::from(id_commitment).to_bytes_le().as_slice()); if let Err(e) = rln_sc.register_user(&to_address, id_co).await { - // Fail to register user on smart contract + // Fail to register the user on smart contract // Remove the user in internal Db if !self.user_db.remove_user(&to_address, false) { // Fails if DB & SC are inconsistent @@ -170,6 +240,28 @@ impl RegistryListener { Ok(to_address) } + + /// Handle slash event from Karma smart contract + async fn slash_event(&self, log: &Log) { + match KarmaSC::AccountSlashed::decode_log_data(log.data()) { + Ok(slash_event) => { + let address_slashed: Address = slash_event.account; + if !self.user_db.remove_user(&address_slashed, false) { + error!("Cannot remove user ({:?}) from DB", address_slashed); + } + } + Err(e) => { + error!("Error decoding log data: {:?}", e); + // It's also useful to print the raw log data for debugging + error!("Raw log topics: {:?}", log.topics()); + error!("Raw log data: {:?}", log.data()); + // Note: This can happen when + // - SC code has been updated but not the Prover + // - In the update process, the Prover has not been shutdown (yet) + // to avoid a too long service interruption? + } + } + } } #[cfg(test)] @@ -247,7 +339,7 @@ mod tests { assert!(user_db_service.get_user_db().get_user(&ADDR_2).is_none()); let minimal_amount = U256::from(25); - let registry = RegistryListener { + let registry = KarmaScEventListener { rln_sc_address: Default::default(), karma_sc_address: Default::default(), user_db, diff --git a/prover/src/lib.rs b/prover/src/lib.rs index f8caa61..6b7122d 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -2,11 +2,11 @@ mod args; mod epoch_service; mod error; mod grpc_service; +mod karma_sc_listener; pub mod metrics; mod mock; mod proof_generation; mod proof_service; -mod registry_listener; mod rocksdb_operands; mod tier; mod tiers_listener; @@ -37,10 +37,10 @@ pub use crate::args::{ARGS_DEFAULT_GENESIS, AppArgs, AppArgsConfig}; use crate::epoch_service::EpochService; use crate::error::AppError; use crate::grpc_service::GrpcProverService; +use crate::karma_sc_listener::KarmaScEventListener; pub use crate::mock::MockUser; use crate::mock::read_mock_user; use crate::proof_service::ProofService; -use crate::registry_listener::RegistryListener; use crate::tier::TierLimits; use crate::tiers_listener::TiersListener; use crate::user_db::{MERKLE_TREE_HEIGHT, UserDbConfig}; @@ -150,7 +150,7 @@ pub async fn run_prover(app_args: AppArgs) -> Result<(), AppError> { // No registry listener when mock is enabled None } else { - Some(RegistryListener::new( + Some(KarmaScEventListener::new( app_args.ksc_address.unwrap(), app_args.rlnsc_address.unwrap(), user_db_service.get_user_db(), diff --git a/prover/tests/grpc_e2e.rs b/prover/tests/grpc_e2e.rs index 31ce506..dc75264 100644 --- a/prover/tests/grpc_e2e.rs +++ b/prover/tests/grpc_e2e.rs @@ -1,20 +1,21 @@ -use alloy::primitives::{Address, U256}; -use futures::FutureExt; -use parking_lot::RwLock; -use prover::{AppArgs, MockUser, run_prover}; use std::io::Write; use std::net::{IpAddr, Ipv4Addr}; use std::num::NonZeroU64; use std::str::FromStr; use std::sync::Arc; use std::time::Duration; +// third-party +use alloy::primitives::{Address, U256}; +use futures::FutureExt; +use parking_lot::RwLock; use tempfile::NamedTempFile; use tokio::task; use tokio::task::JoinSet; use tonic::Response; use tracing::{debug, info}; -use tracing_test::traced_test; - +// use tracing_test::traced_test; +// internal +use prover::{AppArgs, MockUser, run_prover}; pub mod prover_proto { // Include generated code (see build.rs) tonic::include_proto!("prover"); @@ -220,7 +221,7 @@ async fn proof_collector(port: u16, proof_count: usize) -> Vec { } #[tokio::test] -#[traced_test] +// #[traced_test] async fn test_grpc_gen_proof() { let mock_users = vec![ MockUser { diff --git a/smart_contract/Cargo.toml b/smart_contract/Cargo.toml index 3fc6d4c..49695b2 100644 --- a/smart_contract/Cargo.toml +++ b/smart_contract/Cargo.toml @@ -5,15 +5,15 @@ edition = "2024" [[bin]] name = "karma_sc_test" -path = "src/karma_sc_test.rs" +path = "src/bin_tests/karma_sc_test.rs" [[bin]] name = "karma_tiers_test" -path = "src/karma_tiers_test.rs" +path = "src/bin_tests/karma_tiers_test.rs" [[bin]] name = "rln_sc_test" -path = "src/rln_sc_test.rs" +path = "src/bin_tests/rln_sc_test.rs" [dependencies] tokio.workspace = true diff --git a/smart_contract/src/karma_sc_test.rs b/smart_contract/src/bin_tests/karma_sc_test.rs similarity index 100% rename from smart_contract/src/karma_sc_test.rs rename to smart_contract/src/bin_tests/karma_sc_test.rs diff --git a/smart_contract/src/karma_tiers_test.rs b/smart_contract/src/bin_tests/karma_tiers_test.rs similarity index 100% rename from smart_contract/src/karma_tiers_test.rs rename to smart_contract/src/bin_tests/karma_tiers_test.rs diff --git a/smart_contract/src/rln_sc_test.rs b/smart_contract/src/bin_tests/rln_sc_test.rs similarity index 100% rename from smart_contract/src/rln_sc_test.rs rename to smart_contract/src/bin_tests/rln_sc_test.rs diff --git a/smart_contract/src/karma_sc.rs b/smart_contract/src/karma_sc.rs index 6d11f22..814da72 100644 --- a/smart_contract/src/karma_sc.rs +++ b/smart_contract/src/karma_sc.rs @@ -29,7 +29,8 @@ pub trait KarmaAmountExt { } sol! { - // https://github.com/vacp2p/staking-reward-streamer/blob/main/src/Karma.sol + // https://github.com/status-im/status-network-monorepo/blob/develop/status-network-contracts/src/Karma.sol + // OLD: https://github.com/vacp2p/staking-reward-streamer/blob/main/src/Karma.sol // // docker run -v ./:/sources ethereum/solc:stable --bin --via-ir --optimize --optimize-runs 1 --overwrite @openzeppelin/contracts=/sources/lib/openzeppelin-contracts/contracts @openzeppelin/contracts-upgradeable=/sources/lib/openzeppelin-contracts-upgradeable/contracts /sources/src/Karma.sol @@ -53,6 +54,9 @@ sol! { // From: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol#L16 event Transfer(address indexed from, address indexed to, uint256 value); + event AccountSlashed( + address indexed account, uint256 amount, address indexed rewardRecipient, uint256 rewardAmount + ); function balanceOf(address account) public view override returns (uint256);