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,
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<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(())
@@ -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,

View File

@@ -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(),

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::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<RlnProofReply> {
}
#[tokio::test]
#[traced_test]
// #[traced_test]
async fn test_grpc_gen_proof() {
let mock_users = vec![
MockUser {

View File

@@ -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

View File

@@ -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);