Add AccountSlashed event in KarmaSC contract (#52)

* Add AccountSlashed event in KarmaSC contract
* Handle slash event in KarmaSCEventListener
This commit is contained in:
Sydhds
2025-10-21 10:27:08 +02:00
committed by GitHub
parent 85acaad8d2
commit 894677563c
8 changed files with 121 additions and 24 deletions

View File

@@ -3,6 +3,7 @@ use alloy::{
contract::Error as AlloyContractError, contract::Error as AlloyContractError,
primitives::{Address, U256}, primitives::{Address, U256},
providers::Provider, providers::Provider,
rpc::types::Log,
sol_types::SolEvent, sol_types::SolEvent,
}; };
use num_bigint::BigUint; use num_bigint::BigUint;
@@ -14,14 +15,14 @@ use crate::user_db::UserDb;
use crate::user_db_error::RegisterError; use crate::user_db_error::RegisterError;
use smart_contract::{KarmaAmountExt, KarmaRLNSC, KarmaSC, RLNRegister}; use smart_contract::{KarmaAmountExt, KarmaRLNSC, KarmaSC, RLNRegister};
pub(crate) struct RegistryListener { pub(crate) struct KarmaScEventListener {
karma_sc_address: Address, karma_sc_address: Address,
rln_sc_address: Address, rln_sc_address: Address,
user_db: UserDb, user_db: UserDb,
minimal_amount: U256, minimal_amount: U256,
} }
impl RegistryListener { impl KarmaScEventListener {
pub(crate) fn new( pub(crate) fn new(
karma_sc_address: Address, karma_sc_address: Address,
rln_sc_address: Address, rln_sc_address: Address,
@@ -42,17 +43,13 @@ impl RegistryListener {
provider: P, provider: P,
provider_with_signer: PS, provider_with_signer: PS,
) -> Result<(), AppError> { ) -> 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 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 rln_sc = KarmaRLNSC::new(self.rln_sc_address, provider_with_signer);
let filter = alloy::rpc::types::Filter::new() let filter = alloy::rpc::types::Filter::new()
.address(self.karma_sc_address) .address(self.karma_sc_address)
.event(KarmaSC::Transfer::SIGNATURE); .event(KarmaSC::Transfer::SIGNATURE)
.event(KarmaSC::AccountSlashed::SIGNATURE);
// Subscribe to logs matching the filter. // Subscribe to logs matching the filter.
let subscription = provider.subscribe_logs(&filter).await?; let subscription = provider.subscribe_logs(&filter).await?;
@@ -60,6 +57,21 @@ impl RegistryListener {
// Loop through the incoming event logs // Loop through the incoming event logs
while let Some(log) = stream.next().await { 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()) { match KarmaSC::Transfer::decode_log_data(log.data()) {
Ok(transfer_event) => { Ok(transfer_event) => {
match self match self
@@ -93,6 +105,64 @@ impl RegistryListener {
// in order to avoid a too long service interruption? // 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<AlloyContractError>,
KSC: KarmaAmountExt<Error = E>,
RLNSC: RLNRegister<Error = E>,
>(
&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(()) Ok(())
@@ -155,7 +225,7 @@ impl RegistryListener {
U256::from_le_slice(BigUint::from(id_commitment).to_bytes_le().as_slice()); 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 { 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 // Remove the user in internal Db
if !self.user_db.remove_user(&to_address, false) { if !self.user_db.remove_user(&to_address, false) {
// Fails if DB & SC are inconsistent // Fails if DB & SC are inconsistent
@@ -170,6 +240,28 @@ impl RegistryListener {
Ok(to_address) 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)] #[cfg(test)]
@@ -247,7 +339,7 @@ mod tests {
assert!(user_db_service.get_user_db().get_user(&ADDR_2).is_none()); assert!(user_db_service.get_user_db().get_user(&ADDR_2).is_none());
let minimal_amount = U256::from(25); let minimal_amount = U256::from(25);
let registry = RegistryListener { let registry = KarmaScEventListener {
rln_sc_address: Default::default(), rln_sc_address: Default::default(),
karma_sc_address: Default::default(), karma_sc_address: Default::default(),
user_db, user_db,

View File

@@ -2,11 +2,11 @@ mod args;
mod epoch_service; mod epoch_service;
mod error; mod error;
mod grpc_service; mod grpc_service;
mod karma_sc_listener;
pub mod metrics; pub mod metrics;
mod mock; mod mock;
mod proof_generation; mod proof_generation;
mod proof_service; mod proof_service;
mod registry_listener;
mod rocksdb_operands; mod rocksdb_operands;
mod tier; mod tier;
mod tiers_listener; mod tiers_listener;
@@ -37,10 +37,10 @@ pub use crate::args::{ARGS_DEFAULT_GENESIS, AppArgs, AppArgsConfig};
use crate::epoch_service::EpochService; use crate::epoch_service::EpochService;
use crate::error::AppError; use crate::error::AppError;
use crate::grpc_service::GrpcProverService; use crate::grpc_service::GrpcProverService;
use crate::karma_sc_listener::KarmaScEventListener;
pub use crate::mock::MockUser; pub use crate::mock::MockUser;
use crate::mock::read_mock_user; use crate::mock::read_mock_user;
use crate::proof_service::ProofService; use crate::proof_service::ProofService;
use crate::registry_listener::RegistryListener;
use crate::tier::TierLimits; use crate::tier::TierLimits;
use crate::tiers_listener::TiersListener; use crate::tiers_listener::TiersListener;
use crate::user_db::{MERKLE_TREE_HEIGHT, UserDbConfig}; 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 // No registry listener when mock is enabled
None None
} else { } else {
Some(RegistryListener::new( Some(KarmaScEventListener::new(
app_args.ksc_address.unwrap(), app_args.ksc_address.unwrap(),
app_args.rlnsc_address.unwrap(), app_args.rlnsc_address.unwrap(),
user_db_service.get_user_db(), user_db_service.get_user_db(),

View File

@@ -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::io::Write;
use std::net::{IpAddr, Ipv4Addr}; use std::net::{IpAddr, Ipv4Addr};
use std::num::NonZeroU64; use std::num::NonZeroU64;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
// third-party
use alloy::primitives::{Address, U256};
use futures::FutureExt;
use parking_lot::RwLock;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
use tokio::task; use tokio::task;
use tokio::task::JoinSet; use tokio::task::JoinSet;
use tonic::Response; use tonic::Response;
use tracing::{debug, info}; 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 { pub mod prover_proto {
// Include generated code (see build.rs) // Include generated code (see build.rs)
tonic::include_proto!("prover"); tonic::include_proto!("prover");
@@ -220,7 +221,7 @@ async fn proof_collector(port: u16, proof_count: usize) -> Vec<RlnProofReply> {
} }
#[tokio::test] #[tokio::test]
#[traced_test] // #[traced_test]
async fn test_grpc_gen_proof() { async fn test_grpc_gen_proof() {
let mock_users = vec![ let mock_users = vec![
MockUser { MockUser {

View File

@@ -5,15 +5,15 @@ edition = "2024"
[[bin]] [[bin]]
name = "karma_sc_test" name = "karma_sc_test"
path = "src/karma_sc_test.rs" path = "src/bin_tests/karma_sc_test.rs"
[[bin]] [[bin]]
name = "karma_tiers_test" name = "karma_tiers_test"
path = "src/karma_tiers_test.rs" path = "src/bin_tests/karma_tiers_test.rs"
[[bin]] [[bin]]
name = "rln_sc_test" name = "rln_sc_test"
path = "src/rln_sc_test.rs" path = "src/bin_tests/rln_sc_test.rs"
[dependencies] [dependencies]
tokio.workspace = true tokio.workspace = true

View File

@@ -29,7 +29,8 @@ pub trait KarmaAmountExt {
} }
sol! { 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 // 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 // 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 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); function balanceOf(address account) public view override returns (uint256);