Update && enable unit test: test_user_spamming_same_signal (#13)

* Update && enable unit test: test_user_spamming_same_signal
* Add debug_assert in epoch_counters_operands
* Write merkle tree inside db transaction
* Add note about error handling in RegistryListener
* Remove user if it cannot register it to smart contract
* Remove user in db if we cannot add to SC
* Add support for config file
This commit is contained in:
Sydhds
2025-07-04 11:41:36 +02:00
committed by GitHub
parent cac235dbcc
commit d33c1016cd
15 changed files with 681 additions and 489 deletions

92
Cargo.lock generated
View File

@@ -1111,9 +1111,9 @@ dependencies = [
[[package]]
name = "async-channel"
version = "2.3.1"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a"
checksum = "16c74e56284d2188cabb6ad99603d1ace887a5d7e7b695d01b728155ed9ed427"
dependencies = [
"concurrent-queue",
"event-listener-strategy",
@@ -1546,6 +1546,21 @@ dependencies = [
"anstyle",
"clap_lex",
"strsim",
"terminal_size",
]
[[package]]
name = "clap_config"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46efb9cbf691f5505d0b7b2c8055aec0c9a770eaac8a06834b6d84b5be93279a"
dependencies = [
"clap",
"heck",
"proc-macro2",
"quote",
"serde",
"syn 2.0.100",
]
[[package]]
@@ -2720,6 +2735,17 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "io-uring"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013"
dependencies = [
"bitflags 2.9.0",
"cfg-if",
"libc",
]
[[package]]
name = "ipnet"
version = "2.11.0"
@@ -4017,15 +4043,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "scc"
version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22b2d775fb28f245817589471dd49c5edf64237f4a19d10ce9a92ff4651a27f4"
dependencies = [
"sdd",
]
[[package]]
name = "schannel"
version = "0.1.27"
@@ -4041,12 +4058,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sdd"
version = "3.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "584e070911c7017da6cb2eb0788d09f43d789029b5877d3e5ecc8acf86ceee21"
[[package]]
name = "sec1"
version = "0.7.3"
@@ -4168,6 +4179,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@@ -4379,8 +4399,8 @@ dependencies = [
"chrono",
"claims",
"clap",
"clap_config",
"criterion",
"dashmap",
"derive_more",
"futures",
"http",
@@ -4392,13 +4412,13 @@ dependencies = [
"rln",
"rln_proof",
"rocksdb",
"scc",
"serde",
"serde_json",
"smart_contract",
"tempfile",
"thiserror 2.0.12",
"tokio",
"toml",
"tonic",
"tonic-build",
"tonic-reflection",
@@ -4518,6 +4538,16 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "terminal_size"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed"
dependencies = [
"rustix",
"windows-sys 0.59.0",
]
[[package]]
name = "thiserror"
version = "1.0.69"
@@ -4639,15 +4669,17 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.45.1"
version = "1.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
checksum = "1140bb80481756a8cbe10541f37433b459c5aa1e727b4c020fbfebdc25bf3ec4"
dependencies = [
"backtrace",
"bytes",
"io-uring",
"libc",
"mio",
"pin-project-lite",
"slab",
"socket2",
"tokio-macros",
"windows-sys 0.52.0",
@@ -4725,11 +4757,26 @@ dependencies = [
"tokio",
]
[[package]]
name = "toml"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
@@ -4738,6 +4785,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
dependencies = [
"indexmap 2.9.0",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
@@ -5047,6 +5096,7 @@ dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
"serde",
]
[[package]]

View File

@@ -10,7 +10,7 @@ ark-bn254 = { version = "0.5", features = ["std"] }
ark-serialize = "0.5"
ark-groth16 = "0.5"
ark-ff = "0.5"
url = "2.5.4"
url = { version = "2.5.4", features = ["serde"] }
alloy = { version = "1.0", features = ["getrandom", "sol-types", "contract", "provider-ws"] }
async-trait = "0.1"
derive_more = "2.0.1"

View File

@@ -4,10 +4,11 @@ version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4.5.37", features = ["derive"] }
clap = { version = "4.5.37", features = ["derive", "wrap_help"] }
tonic = { version = "0.13", features = ["gzip"] }
tonic-reflection = "*"
tonic-web = "*"
tower-http = { version = "0.6.4", features = ["cors"] }
prost = "0.13"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
@@ -19,12 +20,9 @@ futures = "0.3"
ark-bn254.workspace = true
ark-serialize.workspace = true
ark-ff.workspace = true
dashmap = "6.1.0"
scc = "2.3"
bytesize = "2.0.1"
chrono = "0.4.41"
parking_lot = "0.12"
tower-http = { version = "0.6.4", features = ["cors"] }
http = "*"
async-channel = "2.3.1"
rand = "0.8.5"
@@ -38,6 +36,8 @@ serde_json = "1.0"
rocksdb = { git = "https://github.com/tillrohrmann/rust-rocksdb", branch="issues/836" }
nom = "8.0"
claims = "0.8"
clap_config = "0.1"
toml = "0.8"
rln = { git = "https://github.com/vacp2p/zerokit", features = ["pmtree-ft"] }
zerokit_utils = { git = "https://github.com/vacp2p/zerokit", package = "zerokit_utils", features = ["default"] }
rln_proof = { path = "../rln_proof" }

View File

@@ -2,10 +2,34 @@ use std::net::IpAddr;
use std::path::PathBuf;
// third-party
use alloy::primitives::Address;
use clap::ArgAction::SetTrue;
use clap::Parser;
use clap_config::ClapConfig;
use url::Url;
#[derive(Debug, Clone, Parser)]
/// Broadcast channel size
///
/// A Bounded tokio broadcast channel is used to send RLN proof to the Verifier
/// Warning: There should be only one client receiving the proof, but if there are many, a too
/// low value could stall all the proof services
const ARGS_DEFAULT_BROADCAST_CHANNEL_SIZE: &str = "100";
/// Number of proof services (tasks)
///
/// This service is waiting for new tx to generate the RLN proof. Increase this value
/// if you need to process more Transactions in //.
const ARGS_DEFAULT_PROOF_SERVICE_COUNT: &str = "8";
/// Transaction channel size
///
/// Used by grpc service to send the transaction to one of the proof services. A too low value could stall
/// the grpc service when it receives a transaction.
const ARGS_DEFAULT_TRANSACTION_CHANNEL_SIZE: &str = "100";
/// Proof sender channel size
///
/// Used by grpc service to send the generated proof to the Verifier. A too low value could stall
/// the broadcast channel.
const ARGS_DEFAULT_PROOF_SENDER_CHANNEL_SIZE: &str = "100";
#[derive(Debug, Clone, Parser, ClapConfig)]
#[command(about = "RLN prover service", long_about = None)]
pub struct AppArgs {
#[arg(short = 'i', long = "ip", default_value = "::1", help = "Service ip")]
@@ -37,12 +61,95 @@ pub struct AppArgs {
pub(crate) rlnsc_address: Option<Address>,
#[arg(short = 't', long = "tsc", help = "KarmaTiers smart contract address")]
pub(crate) tsc_address: Option<Address>,
#[arg(long = "mock-sc", help = "Test only - mock smart contracts", action)]
#[arg(
help_heading = "mock",
long = "mock-sc",
help = "Test only - mock smart contracts",
action
)]
pub(crate) mock_sc: Option<bool>,
#[arg(
help_heading = "mock",
long = "mock-user",
help = "Test only - register user (requite --mock-sc to be enabled)",
action
)]
pub(crate) mock_user: Option<PathBuf>,
#[arg(
short = 'c',
long = "config",
help = "Config file path",
default_value = "./config.toml",
help_heading = "config"
)]
pub(crate) config_path: PathBuf,
#[arg(
long = "no-config",
help = "Dont read a config file",
default_missing_value = "false",
action = SetTrue,
help_heading = "config"
)]
pub(crate) no_config: Option<bool>,
// Hidden option - expect user set it via a config file
#[arg(
long = "broadcast-channel-size",
help = "Broadcast bounded channel size",
default_value = ARGS_DEFAULT_BROADCAST_CHANNEL_SIZE,
hide = true,
)] // see const doc for more info
pub(crate) broadcast_channel_size: usize,
#[arg(
long = "proof-service",
help = "Number of proof service (tasks) to generate proof",
default_value = ARGS_DEFAULT_PROOF_SERVICE_COUNT,
hide = true,
)] // see const doc for more info
pub(crate) proof_service_count: u16,
#[arg(
long = "transaction-channel-size",
help = "Proof bounded channel size",
default_value = ARGS_DEFAULT_TRANSACTION_CHANNEL_SIZE,
hide = true,
)] // see const doc for more info
pub(crate) transaction_channel_size: usize,
#[arg(
long = "proof-sender-channel-size",
help = "Proof bounded sender channel size",
default_value = ARGS_DEFAULT_PROOF_SENDER_CHANNEL_SIZE,
hide = true,
)] // see const doc for more info
pub(crate) proof_sender_channel_size: usize,
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn test_args_config_merge() {
let config_port = 42942;
let config = AppArgsConfig {
ip: None,
port: Some(config_port),
mock_sc: Some(true),
..Default::default()
};
{
let args_1 = vec!["program", "--ip", "127.0.0.1", "--port", "50051"];
let cmd = <AppArgs as CommandFactory>::command();
let app_args = cmd.try_get_matches_from(args_1).unwrap(); // .get_matches();
let app_args_2 = AppArgs::from_merged(app_args, Some(config.clone()));
assert_eq!(app_args_2.port, 50051);
}
{
let args_2 = vec!["program", "--ip", "127.0.0.1"];
let cmd = <AppArgs as CommandFactory>::command();
let app_args = cmd.try_get_matches_from(args_2).unwrap(); // .get_matches();
let app_args_2 = AppArgs::from_merged(app_args, Some(config));
assert_eq!(app_args_2.port, config_port);
}
}
}

View File

@@ -72,5 +72,5 @@ pub enum HandleTransferError {
#[error(transparent)]
Register(#[from] RegisterError),
#[error("Unable to query balance: {0}")]
BalanceOf(#[from] alloy::contract::Error),
FetchBalanceOf(#[from] alloy::contract::Error),
}

View File

@@ -85,6 +85,7 @@ pub struct ProverService<KSC: KarmaAmountExt, RLNSC: RLNRegister> {
),
karma_sc: KSC,
karma_rln_sc: RLNSC,
proof_sender_channel_size: usize,
}
#[tonic::async_trait]
@@ -121,7 +122,6 @@ where
// Update the counter as soon as possible (should help to prevent spamming...)
let counter = self.user_db.on_new_tx(&sender, None).unwrap_or_default();
// FIXME: hardcoded
if req.transaction_hash.len() != PROVER_TX_HASH_BYTESIZE {
return Err(Status::invalid_argument(
"Invalid transaction hash (should be 32 bytes)",
@@ -173,11 +173,15 @@ where
let id_co =
U256::from_le_slice(BigUint::from(id_commitment).to_bytes_le().as_slice());
// TODO: on error, remove from user_db?
self.karma_rln_sc
.register(id_co)
.await
.map_err(|e| Status::from_error(Box::new(e)))?;
if let Err(e) = self.karma_rln_sc.register(id_co).await {
// Fail to register user on smart contract
// Remove the user in internal Db
if !self.user_db.remove_user(&user, false) {
// Fails if DB & SC are inconsistent
panic!("Unable to register user to SC and to remove it from DB...");
}
return Err(Status::from_error(Box::new(e)));
}
RegistrationStatus::Success
}
@@ -198,8 +202,9 @@ where
request: Request<RlnProofFilter>,
) -> Result<Response<Self::GetProofsStream>, Status> {
debug!("get_proofs request: {:?}", request);
// FIXME: channel size or unbounded channel?
let (tx, rx) = mpsc::channel(100);
// Channel to send proof to the connected grpc client (aka the Verifier)
let (tx, rx) = mpsc::channel(self.proof_sender_channel_size);
// Channel to receive a RLN proof (from one proof service)
let mut rx2 = self.broadcast_channel.0.subscribe();
tokio::spawn(async move {
// FIXME: Should we send the error here?
@@ -308,6 +313,7 @@ pub(crate) struct GrpcProverService {
pub user_db: UserDb,
pub karma_sc_info: Option<(Url, Address)>,
pub rln_sc_info: Option<(Url, Address)>,
pub proof_sender_channel_size: usize,
}
impl GrpcProverService {
@@ -333,6 +339,7 @@ impl GrpcProverService {
),
karma_sc,
karma_rln_sc,
proof_sender_channel_size: self.proof_sender_channel_size,
};
let reflection_service = tonic_reflection::server::Builder::configure()
@@ -342,7 +349,7 @@ impl GrpcProverService {
let r = RlnProverServer::new(prover_service)
.max_decoding_message_size(PROVER_SERVICE_MESSAGE_DECODING_MAX_SIZE.as_u64() as usize)
.max_encoding_message_size(PROVER_SERVICE_MESSAGE_ENCODING_MAX_SIZE.as_u64() as usize)
// TODO: perf?
// Note: TODO - can be enabled later if network is a bottleneck
//.accept_compressed(CompressionEncoding::Gzip)
//.send_compressed(CompressionEncoding::Gzip)
;
@@ -357,7 +364,7 @@ impl GrpcProverService {
// Method::OPTIONS
])
// Allow requests from any origin
// FIXME: config?
// Note: TODO - to be enabled in a future version
.allow_origin(Any)
.allow_headers(Any);
@@ -394,6 +401,7 @@ impl GrpcProverService {
),
karma_sc: MockKarmaSc {},
karma_rln_sc: MockKarmaRLNSc {},
proof_sender_channel_size: self.proof_sender_channel_size,
};
let reflection_service = tonic_reflection::server::Builder::configure()
@@ -403,7 +411,7 @@ impl GrpcProverService {
let r = RlnProverServer::new(prover_service)
.max_decoding_message_size(PROVER_SERVICE_MESSAGE_DECODING_MAX_SIZE.as_u64() as usize)
.max_encoding_message_size(PROVER_SERVICE_MESSAGE_ENCODING_MAX_SIZE.as_u64() as usize)
// TODO: perf?
// Note: can be enabled later if network is a bottleneck
//.accept_compressed(CompressionEncoding::Gzip)
//.send_compressed(CompressionEncoding::Gzip)
;
@@ -418,7 +426,7 @@ impl GrpcProverService {
// Method::OPTIONS
])
// Allow requests from any origin
// FIXME: config?
// Note: TODO - to be enabled in a future version
.allow_origin(Any)
.allow_headers(Any);

View File

@@ -18,14 +18,12 @@ mod user_db_types;
// std
use std::net::SocketAddr;
use std::path::PathBuf;
use std::time::Duration;
// third-party
use alloy::primitives::U256;
use chrono::{DateTime, Utc};
use clap::Parser;
use rln_proof::RlnIdentifier;
use smart_contract::KarmaTiersSC::KarmaTiersSCInstance;
use smart_contract::TIER_LIMITS;
use clap::CommandFactory;
use tokio::task::JoinSet;
use tracing::level_filters::LevelFilter;
use tracing::{
@@ -35,7 +33,10 @@ use tracing::{
};
use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
// internal
use crate::args::AppArgs;
use rln_proof::RlnIdentifier;
use smart_contract::KarmaTiersSC::KarmaTiersSCInstance;
use smart_contract::TIER_LIMITS;
use crate::args::{AppArgs, AppArgsConfig};
use crate::epoch_service::EpochService;
use crate::grpc_service::GrpcProverService;
use crate::mock::read_mock_user;
@@ -48,7 +49,6 @@ use crate::user_db_types::RateLimit;
const RLN_IDENTIFIER_NAME: &[u8] = b"test-rln-identifier";
const PROVER_SPAM_LIMIT: RateLimit = RateLimit::new(10_000u64);
const PROOF_SERVICE_COUNT: u8 = 8;
const GENESIS: DateTime<Utc> = DateTime::from_timestamp(1431648000, 0).unwrap();
const PROVER_MINIMAL_AMOUNT_FOR_REGISTRATION: U256 =
U256::from_le_slice(10u64.to_le_bytes().as_slice());
@@ -65,9 +65,26 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.with(filter)
.init();
let app_args = AppArgs::parse();
// let app_args = AppArgs::parse();
let app_args = <AppArgs as CommandFactory>::command().get_matches();
debug!("Arguments: {:?}", app_args);
let app_ars_config = if !app_args.get_flag("no_config") {
// Unwrap safe - default value provided
let config_path = app_args.get_one::<PathBuf>("config_path").unwrap();
debug!("Reading config path: {:?}...", config_path);
let config_str = std::fs::read_to_string(config_path)?;
let config: AppArgsConfig = toml::from_str(config_str.as_str())?;
debug!("Config: {:?}", config);
config
} else {
AppArgsConfig::default()
};
// Merge command line args & config
let app_args = AppArgs::from_merged(app_args, Some(app_ars_config));
debug!("Arguments (merged with config): {:?}", app_args);
// Application cli arguments checks
if app_args.ws_rpc_url.is_some() {
if app_args.ksc_address.is_none()
@@ -104,8 +121,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// User db service
let user_db_service = UserDbService::new(
app_args.db_path,
app_args.merkle_tree_path,
app_args.db_path.clone(),
app_args.merkle_tree_path.clone(),
epoch_service.epoch_changes.clone(),
epoch_service.current_epoch.clone(),
PROVER_SPAM_LIMIT,
@@ -153,17 +170,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
};
// proof service
// FIXME: bound
let (tx, rx) = tokio::sync::broadcast::channel(2);
// TODO: bounded channel
let (proof_sender, proof_receiver) = async_channel::unbounded();
let (tx, rx) = tokio::sync::broadcast::channel(app_args.broadcast_channel_size);
let (proof_sender, proof_receiver) = async_channel::bounded(app_args.transaction_channel_size);
// grpc
let rln_identifier = RlnIdentifier::new(RLN_IDENTIFIER_NAME);
let addr = SocketAddr::new(app_args.ip, app_args.port);
debug!("Listening on: {}", addr);
// TODO: broadcast subscribe?
let prover_grpc_service = {
let mut service = GrpcProverService {
proof_sender,
@@ -173,6 +187,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
user_db: user_db_service.get_user_db(),
karma_sc_info: None,
rln_sc_info: None,
proof_sender_channel_size: app_args.proof_sender_channel_size,
};
if app_args.ws_rpc_url.is_some() {
@@ -184,7 +199,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
};
let mut set = JoinSet::new();
for _i in 0..PROOF_SERVICE_COUNT {
for _i in 0..app_args.proof_service_count {
let proof_recv = proof_receiver.clone();
let broadcast_sender = tx.clone();
let current_epoch = epoch_service.current_epoch.clone();

View File

@@ -141,6 +141,7 @@ impl ProofService {
#[cfg(test)]
mod tests {
use super::*;
// std
use std::path::PathBuf;
// third-party
use alloy::primitives::{Address, address};
@@ -153,6 +154,7 @@ mod tests {
// third-party: zerokit
use rln::{
circuit::{Curve, zkey_from_folder},
error::ComputeIdSecretError,
protocol::{compute_id_secret, deserialize_proof_values, verify_proof},
};
// internal
@@ -177,6 +179,8 @@ mod tests {
ProofVerification,
#[error("Exiting...")]
Exit,
#[error(transparent)]
RecoverSecretFailed(ComputeIdSecretError),
#[error("Recovered secret")]
RecoveredSecret(Fr),
}
@@ -401,8 +405,9 @@ mod tests {
let share1 = (proof_values_0.x, proof_values_0.y);
let share2 = (proof_values_1.x, proof_values_1.y);
// TODO: should we check external nullifier as well?
let recovered_identity_secret_hash = compute_id_secret(share1, share2).unwrap();
// Note: if not in test, should check for external nullifier
let recovered_identity_secret_hash =
compute_id_secret(share1, share2).map_err(|e| AppErrorExt::RecoverSecretFailed(e))?;
debug!(
"recovered_identity_secret_hash: {:?}",
@@ -526,7 +531,6 @@ mod tests {
}
#[tokio::test]
#[ignore]
#[tracing_test::traced_test]
async fn test_user_spamming_same_signal() {
// Recover secret from a user spamming the system
@@ -574,7 +578,7 @@ mod tests {
);
info!("Starting...");
let _res = tokio::try_join!(
let res = tokio::try_join!(
proof_service.serve().map_err(AppErrorExt::AppError),
proof_reveal_secret(&mut broadcast_receiver),
proof_sender_2(
@@ -586,7 +590,6 @@ mod tests {
),
);
// TODO: wait for Zerokit 0.8
// assert_matches!(res, Err(AppErrorExt::Exit));
assert_matches!(res, Err(AppErrorExt::RecoverSecretFailed(_)));
}
}

View File

@@ -71,16 +71,21 @@ impl RegistryListener {
}
Err(e) => {
error!("Unexpected error: {}", e);
// FIXME: return / continue?
// 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));
}
};
}
Err(e) => {
eprintln!("Error decoding log data: {:?}", e);
error!("Error decoding log data: {:?}", e);
// It's also useful to print the raw log data for debugging
eprintln!("Raw log topics: {:?}", log.topics());
eprintln!("Raw log data: {:?}", log.data());
error!("Raw log topics: {:?}", log.topics());
error!("Raw log data: {:?}", log.data());
// Note: - Assume that SC code has been updated but not the Prover
// - Assume that in the update process, the Prover has not been shutdown (yet)
// in order to avoid a too long service interruption?
}
}
}
@@ -107,7 +112,8 @@ impl RegistryListener {
let balance = karma_sc
.karma_amount(&to_address)
.await
.map_err(|e| HandleTransferError::BalanceOf(e.into()))?;
.map_err(|e| HandleTransferError::FetchBalanceOf(e.into()))?;
// Only register the user if he has a minimal amount of Karma token
balance >= self.minimal_amount
}
};

View File

@@ -1,4 +1,5 @@
use crate::epoch_service::{Epoch, EpochSlice};
use claims::debug_assert_ge;
use nom::{
IResult,
error::ContextError,
@@ -141,8 +142,12 @@ pub fn epoch_counters_operands(
// thus no error should never happen here
let (_, epoch_incr) = deser.deserialize(x).unwrap();
// TODO - optim: partial deser ?
// TODO: check if increasing ? debug_assert otherwise?
debug_assert_ge!(epoch_incr.epoch, acc.epoch);
debug_assert!(
epoch_incr.epoch_slice > acc.epoch_slice
|| epoch_incr.epoch_slice == EpochSlice::from(0)
);
if acc == Default::default() {
// Default value - so this is the first time
acc = EpochCounters {
@@ -191,6 +196,9 @@ pub fn u64_counter_operands(
existing_val: Option<&[u8]>,
operands: &MergeOperands,
) -> Option<Vec<u8>> {
// Counter value is stored as u64
// But value passed (in merge / merge_cf) is i64 so we can decrease or increase the counter
let counter_current_value = if let Some(existing_val) = existing_val {
u64::from_le_bytes(existing_val.try_into().unwrap())
} else {
@@ -198,8 +206,8 @@ pub fn u64_counter_operands(
};
let counter_value = operands.iter().fold(counter_current_value, |mut acc, x| {
let incr_value = u64::from_le_bytes(x.try_into().unwrap());
acc = acc.saturating_add(incr_value);
let incr_value = i64::from_le_bytes(x.try_into().unwrap());
acc = acc.saturating_add_signed(incr_value);
acc
});

View File

@@ -2,7 +2,7 @@ use std::collections::{BTreeMap, HashSet};
use std::ops::ControlFlow;
// third-party
use alloy::primitives::U256;
use derive_more::{From, Into, Deref, DerefMut};
use derive_more::{Deref, DerefMut, From, Into};
// internal
use smart_contract::{Tier, TierIndex};
@@ -22,10 +22,7 @@ impl From<&str> for TierName {
pub struct TierLimits(BTreeMap<TierIndex, Tier>);
impl<const N: usize> From<[(TierIndex, Tier); N]> for TierLimits {
fn from(
value: [(TierIndex, Tier); N],
) -> Self {
fn from(value: [(TierIndex, Tier); N]) -> Self {
Self(BTreeMap::from(value))
}
}
@@ -63,7 +60,6 @@ impl TierLimits {
self.0
.iter()
.try_fold(Context::default(), |mut state, (tier_index, tier)| {
if !tier.active {
return Err(ValidateTierLimitsError::InactiveTier);
}
@@ -106,7 +102,6 @@ impl TierLimits {
/// Given some karma amount, find the matching Tier. Assume all tiers are active.
pub(crate) fn get_tier_by_karma(&self, karma_amount: &U256) -> TierMatch {
struct Context<'a> {
current: Option<(&'a TierIndex, &'a Tier)>,
}
@@ -116,10 +111,9 @@ impl TierLimits {
.0
.iter()
.try_fold(ctx_initial, |mut state, (tier_index, tier)| {
// Assume all the tier are active but checks it at dev time
debug_assert!(tier.active, "Find a non active tier");
if karma_amount < &tier.min_karma {
// Early break - above lowest tier (< lowest_tier.min_karma)
ControlFlow::Break(state)
@@ -170,31 +164,37 @@ mod tier_limits_tests {
#[test]
fn test_filter_inactive() {
let mut tier_limits = TierLimits::from([
(TierIndex::from(0), Tier {
name: "Basic".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 6,
active: true,
}),
(TierIndex::from(1),
Tier {
name: "Active".to_string(),
min_karma: U256::from(50),
max_karma: U256::from(99),
tx_per_epoch: 120,
active: true,
}),
(TierIndex::from(2),
Tier {
name: "Power User".to_string(),
min_karma: U256::from(500),
max_karma: U256::from(999),
tx_per_epoch: 86400,
active: false,
}),
(
TierIndex::from(0),
Tier {
name: "Basic".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 6,
active: true,
},
),
(
TierIndex::from(1),
Tier {
name: "Active".to_string(),
min_karma: U256::from(50),
max_karma: U256::from(99),
tx_per_epoch: 120,
active: true,
},
),
(
TierIndex::from(2),
Tier {
name: "Power User".to_string(),
min_karma: U256::from(500),
max_karma: U256::from(999),
tx_per_epoch: 86400,
active: false,
},
),
]);
let filtered = tier_limits.filter_inactive();
@@ -203,23 +203,27 @@ mod tier_limits_tests {
#[test]
fn test_validate_fails_with_inactive_tier() {
let tier_limits = TierLimits::from([
(TierIndex::from(1), Tier {
name: "Basic".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 6,
active: true,
}),
(TierIndex::from(2), Tier {
name: "Active".to_string(),
min_karma: U256::from(50),
max_karma: U256::from(99),
tx_per_epoch: 120,
active: false,
}),
(
TierIndex::from(1),
Tier {
name: "Basic".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 6,
active: true,
},
),
(
TierIndex::from(2),
Tier {
name: "Active".to_string(),
min_karma: U256::from(50),
max_karma: U256::from(99),
tx_per_epoch: 120,
active: false,
},
),
]);
assert_matches!(
tier_limits.validate(),
@@ -230,20 +234,26 @@ mod tier_limits_tests {
#[test]
fn test_validate_failed_when_karma_overlapping_between_tier() {
let tier_limits = TierLimits::from([
(TierIndex::from(1), Tier {
name: "Basic".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(100),
tx_per_epoch: 6,
active: true,
}),
(TierIndex::from(2), Tier {
name: "Active".to_string(),
min_karma: U256::from(50),
max_karma: U256::from(150),
tx_per_epoch: 120,
active: true,
}),
(
TierIndex::from(1),
Tier {
name: "Basic".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(100),
tx_per_epoch: 6,
active: true,
},
),
(
TierIndex::from(2),
Tier {
name: "Active".to_string(),
min_karma: U256::from(50),
max_karma: U256::from(150),
tx_per_epoch: 120,
active: true,
},
),
]);
assert_matches!(
@@ -254,32 +264,32 @@ mod tier_limits_tests {
#[test]
fn test_validate_fails_when_min_karma_equal_or_greater_max_karma() {
let tier_limits = TierLimits::from([
(TierIndex::from(1), Tier {
let tier_limits = TierLimits::from([(
TierIndex::from(1),
Tier {
name: "Basic".to_string(),
min_karma: U256::from(100),
max_karma: U256::from(100),
tx_per_epoch: 6,
active: true,
})
]);
},
)]);
assert_matches!(
tier_limits.validate(),
Err(ValidateTierLimitsError::InvalidMaxKarmaAmount)
);
let tier_limits = TierLimits::from([
(TierIndex::from(1),
Tier {
name: "Basic".to_string(),
min_karma: U256::from(500),
max_karma: U256::from(100),
tx_per_epoch: 6,
active: true,
}),
]);
let tier_limits = TierLimits::from([(
TierIndex::from(1),
Tier {
name: "Basic".to_string(),
min_karma: U256::from(500),
max_karma: U256::from(100),
tx_per_epoch: 6,
active: true,
},
)]);
assert_matches!(
tier_limits.validate(),
@@ -289,24 +299,29 @@ mod tier_limits_tests {
#[test]
fn test_validate_fails_with_non_increasing_or_decreasing_min_karma() {
// Case 1: Duplicate min_karma values
{
let tier_limits = TierLimits::from([
(TierIndex::from(1), Tier {
name: "Basic".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 6,
active: true,
}),
(TierIndex::from(2), Tier {
name: "Active".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(99),
tx_per_epoch: 120,
active: true,
}),
(
TierIndex::from(1),
Tier {
name: "Basic".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 6,
active: true,
},
),
(
TierIndex::from(2),
Tier {
name: "Active".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(99),
tx_per_epoch: 120,
active: true,
},
),
]);
assert_matches!(
@@ -318,20 +333,26 @@ mod tier_limits_tests {
// Case 2: Decreasing min_karma values
{
let tier_limits = TierLimits::from([
(TierIndex::from(1), Tier {
name: "Basic".to_string(),
min_karma: U256::from(50),
max_karma: U256::from(99),
tx_per_epoch: 6,
active: true,
}),
(TierIndex::from(2), Tier {
name: "Active".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 120,
active: true,
}),
(
TierIndex::from(1),
Tier {
name: "Basic".to_string(),
min_karma: U256::from(50),
max_karma: U256::from(99),
tx_per_epoch: 6,
active: true,
},
),
(
TierIndex::from(2),
Tier {
name: "Active".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 120,
active: true,
},
),
]);
assert_matches!(
@@ -346,20 +367,26 @@ mod tier_limits_tests {
// Case 1: Duplicate tx_per_epoch values
{
let tier_limits = TierLimits::from([
(TierIndex::from(1), Tier {
name: "Basic".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 120,
active: true,
}),
(TierIndex::from(2), Tier {
name: "Active".to_string(),
min_karma: U256::from(50),
max_karma: U256::from(99),
tx_per_epoch: 120,
active: true,
}),
(
TierIndex::from(1),
Tier {
name: "Basic".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 120,
active: true,
},
),
(
TierIndex::from(2),
Tier {
name: "Active".to_string(),
min_karma: U256::from(50),
max_karma: U256::from(99),
tx_per_epoch: 120,
active: true,
},
),
]);
assert_matches!(
@@ -371,20 +398,26 @@ mod tier_limits_tests {
// Case 2: Decreasing tx_per_epoch values
{
let tier_limits = TierLimits::from([
(TierIndex::from(1), Tier {
name: "Basic".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 120,
active: true,
}),
(TierIndex::from(2), Tier {
name: "Active".to_string(),
min_karma: U256::from(50),
max_karma: U256::from(99),
tx_per_epoch: 6,
active: true,
}),
(
TierIndex::from(1),
Tier {
name: "Basic".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 120,
active: true,
},
),
(
TierIndex::from(2),
Tier {
name: "Active".to_string(),
min_karma: U256::from(50),
max_karma: U256::from(99),
tx_per_epoch: 6,
active: true,
},
),
]);
assert_matches!(
@@ -397,20 +430,26 @@ mod tier_limits_tests {
#[test]
fn test_validate_fails_with_duplicate_tier_names() {
let tier_limits = TierLimits::from([
(TierIndex::from(1), Tier {
name: "Basic".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 6,
active: true,
}),
(TierIndex::from(2), Tier {
name: "Basic".to_string(),
min_karma: U256::from(50),
max_karma: U256::from(99),
tx_per_epoch: 120,
active: true,
}),
(
TierIndex::from(1),
Tier {
name: "Basic".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 6,
active: true,
},
),
(
TierIndex::from(2),
Tier {
name: "Basic".to_string(),
min_karma: U256::from(50),
max_karma: U256::from(99),
tx_per_epoch: 120,
active: true,
},
),
]);
assert_matches!(
@@ -421,24 +460,29 @@ mod tier_limits_tests {
#[test]
fn test_validate_fails_tier_index() {
// Non-consecutive tier index
{
let tier_limits = TierLimits::from([
(TierIndex::from(1), Tier {
name: "Basic".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 6,
active: true,
}),
(TierIndex::from(3), Tier {
name: "Basic".to_string(),
min_karma: U256::from(50),
max_karma: U256::from(99),
tx_per_epoch: 120,
active: true,
}),
(
TierIndex::from(1),
Tier {
name: "Basic".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 6,
active: true,
},
),
(
TierIndex::from(3),
Tier {
name: "Basic".to_string(),
min_karma: U256::from(50),
max_karma: U256::from(99),
tx_per_epoch: 120,
active: true,
},
),
]);
assert_matches!(
@@ -461,27 +505,36 @@ mod tier_limits_tests {
#[test]
fn test_get_tier_by_karma_bounds_and_ranges() {
let tier_limits = TierLimits::from([
(TierIndex::from(1), Tier {
name: "Basic".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 6,
active: true,
}),
(TierIndex::from(2), Tier {
name: "Active".to_string(),
min_karma: U256::from(50),
max_karma: U256::from(99),
tx_per_epoch: 120,
active: true,
}),
(TierIndex::from(3), Tier {
name: "Regular".to_string(),
min_karma: U256::from(100),
max_karma: U256::from(499),
tx_per_epoch: 720,
active: true,
}),
(
TierIndex::from(1),
Tier {
name: "Basic".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 6,
active: true,
},
),
(
TierIndex::from(2),
Tier {
name: "Active".to_string(),
min_karma: U256::from(50),
max_karma: U256::from(99),
tx_per_epoch: 120,
active: true,
},
),
(
TierIndex::from(3),
Tier {
name: "Regular".to_string(),
min_karma: U256::from(100),
max_karma: U256::from(499),
tx_per_epoch: 720,
active: true,
},
),
]);
// Case 1: Zero karma
@@ -531,30 +584,34 @@ mod tier_limits_tests {
// Case 7: Karma above all tiers
let result = tier_limits.get_tier_by_karma(&U256::from(1000));
assert_eq!(result, TierMatch::AboveHighest);
}
#[test]
#[should_panic(expected = "Find a non active tier")]
fn test_get_tier_by_karma_ignores_inactive_tiers() {
let tier_limits = TierLimits::from([
(TierIndex::from(0), Tier {
name: "Basic".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 6,
active: false,
}),
(TierIndex::from(1), Tier {
name: "Active".to_string(),
min_karma: U256::from(50),
max_karma: U256::from(99),
tx_per_epoch: 120,
active: true,
}),
(
TierIndex::from(0),
Tier {
name: "Basic".to_string(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 6,
active: false,
},
),
(
TierIndex::from(1),
Tier {
name: "Active".to_string(),
min_karma: U256::from(50),
max_karma: U256::from(99),
tx_per_epoch: 120,
active: true,
},
),
]);
let _result = tier_limits.get_tier_by_karma(&U256::from(25));
}
}
}

View File

@@ -13,9 +13,10 @@ use rln::{
protocol::keygen,
};
use rocksdb::{
ColumnFamily, ColumnFamilyDescriptor, DB, Options, ReadOptions, WriteBatchWithIndex,
ColumnFamily, ColumnFamilyDescriptor, DB, Options, ReadOptions, WriteBatch, WriteBatchWithIndex,
};
use serde::{Deserialize, Serialize};
use tracing::error;
// internal
use crate::epoch_service::{Epoch, EpochSlice};
use crate::error::GetMerkleTreeProofError;
@@ -57,6 +58,16 @@ pub struct UserTierInfo {
pub(crate) tier_limit: Option<TierLimit>,
}
#[derive(Serialize, Deserialize)]
struct PmTreeConfigJson {
path: PathBuf,
temporary: bool,
cache_capacity: u64,
flush_every_ms: u64,
mode: String,
use_compression: bool,
}
#[derive(Clone)]
pub(crate) struct UserDb {
db: Arc<DB>,
@@ -142,16 +153,6 @@ impl UserDb {
// merkle tree
#[derive(Serialize, Deserialize)]
struct PmTreeConfigJson {
path: PathBuf,
temporary: bool,
cache_capacity: u64,
flush_every_ms: u64,
mode: String,
use_compression: bool,
}
let config_ = PmTreeConfigJson {
path: merkle_tree_path,
temporary: false,
@@ -217,11 +218,14 @@ impl UserDb {
let cf_user = self.get_user_cf();
let index = match self.db.get_cf(cf_user, key) {
let _index = match self.db.get_cf(cf_user, key) {
Ok(Some(_)) => {
return Err(RegisterError::AlreadyRegistered(address));
}
Ok(None) => {
let rate_commit =
poseidon_hash(&[id_commitment, Fr::from(u64::from(self.rate_limit))]);
let cf_mtree = self.get_mtree_cf();
let cf_counter = self.get_counter_cf();
@@ -236,8 +240,6 @@ impl UserDb {
);
let mut db_batch = WriteBatchWithIndex::new(1024, true);
// Increase merkle tree index
db_batch.merge_cf(cf_mtree, MERKLE_TREE_INDEX_KEY, 1u64.to_le_bytes());
// Read the new index
// Unwrap safe - just used merge_cf
let batch_read = db_batch
@@ -248,11 +250,20 @@ impl UserDb {
&ReadOptions::default(),
)?
.unwrap();
// Increase merkle tree index
db_batch.merge_cf(cf_mtree, MERKLE_TREE_INDEX_KEY, 1i64.to_le_bytes());
// Unwrap safe - serialization is handled by the prover
let (_, new_index) = merkle_index_deserializer
.deserialize(batch_read.as_slice())
.unwrap();
// Note: write to Merkle tree in the Db transaction so if the write fails
// the Db transaction will also fails
self.merkle_tree
.write()
.set(new_index.into(), rate_commit)
.map_err(|e| RegisterError::TreeError(e.to_string()))?;
// Add index for user
merkle_index_serializer.serialize(&new_index, &mut buffer);
// Put user
@@ -272,13 +283,6 @@ impl UserDb {
}
};
let rate_commit = poseidon_hash(&[id_commitment, Fr::from(u64::from(self.rate_limit))]);
// FIXME: what to do if write to merkle tree fails? Should we include this in the Db transaction as well?
self.merkle_tree
.write()
.set(index.into(), rate_commit)
.map_err(|e| RegisterError::TreeError(e.to_string()))?;
Ok(id_commitment)
}
@@ -323,6 +327,47 @@ impl UserDb {
}
}
/// Remove user
///
/// Warning: don't use this func. if user is registered in a smart contract (unless you == 💪)
/// This function is intended to be used if registration to the smart contract fails
pub(crate) fn remove_user(&self, address: &Address, sc_registered: bool) -> bool {
let cf_user = self.get_user_cf();
let cf_counter = self.get_counter_cf();
let mut db_batch = WriteBatch::new();
if !sc_registered {
let user_index = self.get_user_merkle_tree_index(address);
let user_index = match user_index {
Ok(user_index) => user_index,
Err(UserMerkleTreeIndexError::NotRegistered(_)) => {
return true;
}
_ => {
error!("Error getting user index: {:?}", user_index);
return false;
}
};
if usize::from(user_index) == self.merkle_tree.read().leaves_set() {
// Only delete it if this is the last index
if let Err(e) = self.merkle_tree.write().delete(user_index.into()) {
error!("Error deleting user in merkle tree: {:?}", e);
return false;
}
}
}
// Remove user
db_batch.delete_cf(cf_user, address.as_slice());
// Remove user tx counter
db_batch.delete_cf(cf_counter, address.as_slice());
self.db.write(db_batch).unwrap();
true
}
fn incr_tx_counter(
&self,
address: &Address,
@@ -576,13 +621,12 @@ impl UserDb {
tier_name: None,
tier_limit: None,
};
// FIXME: Proto changes to return AboveHighest / UnderLowest
if let TierMatch::Matched(_tier_index, tier) = tier_match {
t.tier_name = Some(tier.name.into());
t.tier_limit = Some(TierLimit::from(tier.tx_per_epoch));
}
t
};
@@ -740,165 +784,6 @@ mod tests {
assert_eq!(tier_info.epoch_slice_tx_count, 1);
}
/*
#[tokio::test]
async fn test_update_on_epoch_changes() {
let temp_folder = tempfile::tempdir().unwrap();
let mut epoch = Epoch::from(11);
let mut epoch_slice = EpochSlice::from(42);
let epoch_store = Arc::new(RwLock::new((epoch, epoch_slice)));
let user_db = UserRocksDb::new(
PathBuf::from(temp_folder.path()),
Default::default(),
epoch_store,
).unwrap();
let tier_limits = BTreeMap::from([
(
TierIndex::from(1),
Tier {
name: "Basic".into(),
min_karma: U256::from(10),
max_karma: U256::from(49),
tx_per_epoch: 5,
active: true,
},
),
(
TierIndex::from(2),
Tier {
name: "Active".into(),
min_karma: U256::from(50),
max_karma: U256::from(99),
tx_per_epoch: 10,
active: true,
},
),
(
TierIndex::from(3),
Tier {
name: "Regular".into(),
min_karma: U256::from(100),
max_karma: U256::from(499),
tx_per_epoch: 15,
active: true,
},
),
(
TierIndex::from(4),
Tier {
name: "Power User".into(),
min_karma: U256::from(500),
max_karma: U256::from(4999),
tx_per_epoch: 20,
active: true,
},
),
(
TierIndex::from(5),
Tier {
name: "S-Tier".into(),
min_karma: U256::from(5000),
max_karma: U256::from(9999),
tx_per_epoch: 25,
active: true,
},
),
]);
let tier_limits: TierLimits = tier_limits.into();
tier_limits.validate().unwrap();
let user_db_service = UserDbService2::new(
temp_folder.path().to_path_buf(),
Default::default(),
epoch_store.clone(),
10.into(),
tier_limits,
).unwrap();
let user_db = user_db_service.get_user_db();
let addr_1_tx_count = 2;
let addr_2_tx_count = 820;
user_db.register(ADDR_1).unwrap();
user_db.incr_tx_counter(&ADDR_1, Some(addr_1_tx_count));
println!("user_db tx counter: {:?}", user_db.get_tx_counter(&ADDR_1));
user_db.register(ADDR_2).unwrap();
user_db.incr_tx_counter(&ADDR_2, Some(addr_2_tx_count));
// incr epoch slice (42 -> 43)
{
let new_epoch = epoch;
let new_epoch_slice = epoch_slice + 1;
// FIXME: UserRocksDb rely on EpochStore so is there still need for this func?
user_db_service.update_on_epoch_changes(
&mut epoch,
new_epoch,
&mut epoch_slice,
new_epoch_slice,
);
let mut guard = epoch_store.write();
*guard = (new_epoch, epoch_slice);
drop(guard);
let addr_1_tier_info = user_db
.user_tier_info(&ADDR_1, &MockKarmaSc2 {})
.await
.unwrap();
assert_eq!(addr_1_tier_info.epoch_tx_count, addr_1_tx_count);
assert_eq!(addr_1_tier_info.epoch_slice_tx_count, 0);
assert_eq!(addr_1_tier_info.tier_name, Some(TierName::from("Basic")));
let addr_2_tier_info = user_db
.user_tier_info(&ADDR_2, &MockKarmaSc2 {})
.await
.unwrap();
assert_eq!(addr_2_tier_info.epoch_tx_count, addr_2_tx_count);
assert_eq!(addr_2_tier_info.epoch_slice_tx_count, 0);
assert_eq!(
addr_2_tier_info.tier_name,
Some(TierName::from("Power User"))
);
}
// incr epoch (11 -> 12, epoch slice reset)
{
let new_epoch = epoch + 1;
let new_epoch_slice = EpochSlice::from(0);
user_db_service.update_on_epoch_changes(
&mut epoch,
new_epoch,
&mut epoch_slice,
new_epoch_slice,
);
let mut guard = epoch_store.write();
*guard = (new_epoch, epoch_slice);
drop(guard);
let addr_1_tier_info = user_db
.user_tier_info(&ADDR_1, &MockKarmaSc2 {})
.await
.unwrap();
assert_eq!(addr_1_tier_info.epoch_tx_count, 0);
assert_eq!(addr_1_tier_info.epoch_slice_tx_count, 0);
assert_eq!(addr_1_tier_info.tier_name, Some(TierName::from("Basic")));
let addr_2_tier_info = user_db
.user_tier_info(&ADDR_2, &MockKarmaSc2 {})
.await
.unwrap();
assert_eq!(addr_2_tier_info.epoch_tx_count, 0);
assert_eq!(addr_2_tier_info.epoch_slice_tx_count, 0);
assert_eq!(
addr_2_tier_info.tier_name,
Some(TierName::from("Power User"))
);
}
}
*/
#[tokio::test]
async fn test_persistent_storage() {
let temp_folder = tempfile::tempdir().unwrap();
@@ -916,16 +801,17 @@ mod tests {
)
.unwrap();
// Register user
assert_eq!(
user_db.get_merkle_tree_index().unwrap(),
MerkleTreeIndex::from(0)
);
// Register user
user_db.register(ADDR_1).unwrap();
assert_eq!(
user_db.get_merkle_tree_index().unwrap(),
MerkleTreeIndex::from(1)
);
// + 1 user
user_db.register(ADDR_2).unwrap();
assert_eq!(
user_db.get_merkle_tree_index().unwrap(),
@@ -933,11 +819,11 @@ mod tests {
);
assert_eq!(
user_db.get_user_merkle_tree_index(&ADDR_1).unwrap(),
MerkleTreeIndex::from(1)
MerkleTreeIndex::from(0)
);
assert_eq!(
user_db.get_user_merkle_tree_index(&ADDR_2).unwrap(),
MerkleTreeIndex::from(2)
MerkleTreeIndex::from(1)
);
assert_eq!(
@@ -982,40 +868,89 @@ mod tests {
);
assert_eq!(
user_db.get_user_merkle_tree_index(&ADDR_1).unwrap(),
MerkleTreeIndex::from(1)
MerkleTreeIndex::from(0)
);
assert_eq!(
user_db.get_user_merkle_tree_index(&ADDR_2).unwrap(),
MerkleTreeIndex::from(2)
MerkleTreeIndex::from(1)
);
}
}
/*
// Try to update tx counter without registering first
assert_matches!(
user_db.on_new_tx(&addr, None),
Err(TxCounterError::NotRegistered(_))
);
#[test]
fn test_user_reg_merkle_tree_fail() {
// Try to register some users but init UserDb so the merkle tree write will fail (after 1st register)
// This tests ensures that the DB and the MerkleTree stays in sync
let tier_info = user_db.user_tier_info(&addr, &MockKarmaSc {}).await;
// User is not registered -> no tier info
assert!(matches!(
tier_info,
Err(UserTierInfoError::NotRegistered(_))
));
// Register user
user_db.register(addr).unwrap();
// Now update user tx counter
assert_eq!(
user_db.on_new_tx(&addr, None),
Ok(EpochSliceCounter::from(1))
);
let tier_info = user_db
.user_tier_info(&addr, &MockKarmaSc {})
.await
let temp_folder = tempfile::tempdir().unwrap();
let temp_folder_tree = tempfile::tempdir().unwrap();
let epoch_store = Arc::new(RwLock::new(Default::default()));
let mut user_db = UserDb::new(
PathBuf::from(temp_folder.path()),
PathBuf::from(temp_folder_tree.path()),
epoch_store.clone(),
Default::default(),
Default::default(),
)
.unwrap();
assert_eq!(tier_info.epoch_tx_count, 1);
assert_eq!(tier_info.epoch_slice_tx_count, 1);
*/
let temp_folder_tree_2 = tempfile::tempdir().unwrap();
let config_ = PmTreeConfigJson {
path: temp_folder_tree_2.path().to_path_buf(),
temporary: false,
cache_capacity: 100_000,
flush_every_ms: 12_000,
mode: "HighThroughput".to_string(),
use_compression: false,
};
let config_str = serde_json::to_string(&config_).unwrap();
let config = PmtreeConfig::from_str(config_str.as_str()).unwrap();
let tree = PoseidonTree::new(1, Default::default(), config).unwrap();
let tree = Arc::new(RwLock::new(tree));
user_db.merkle_tree = tree.clone();
let addr = Address::new([0; 20]);
assert_eq!(tree.read().leaves_set(), 0);
user_db.register(addr).unwrap();
assert_eq!(tree.read().leaves_set(), 1);
user_db.register(ADDR_1).unwrap();
assert_eq!(tree.read().leaves_set(), 2);
let res = user_db.register(ADDR_2);
assert_matches!(res, Err(RegisterError::TreeError(_)));
assert_eq!(user_db.has_user(&ADDR_1), Ok(true));
assert_eq!(user_db.has_user(&ADDR_2), Ok(false));
assert_eq!(tree.read().leaves_set(), 2);
}
#[test]
fn test_user_remove() {
let temp_folder = tempfile::tempdir().unwrap();
let temp_folder_tree = tempfile::tempdir().unwrap();
let epoch_store = Arc::new(RwLock::new(Default::default()));
let user_db = UserDb::new(
PathBuf::from(temp_folder.path()),
PathBuf::from(temp_folder_tree.path()),
epoch_store.clone(),
Default::default(),
Default::default(),
)
.unwrap();
user_db.register(ADDR_1).unwrap();
let mtree_index_add_addr_1 = user_db.merkle_tree.read().leaves_set();
user_db.register(ADDR_2).unwrap();
let mtree_index_add_addr_2 = user_db.merkle_tree.read().leaves_set();
assert_ne!(mtree_index_add_addr_1, mtree_index_add_addr_2);
user_db.remove_user(&ADDR_2, false);
let mtree_index_after_rm_addr_2 = user_db.merkle_tree.read().leaves_set();
assert_eq!(user_db.has_user(&ADDR_1), Ok(true));
assert_eq!(user_db.has_user(&ADDR_2), Ok(false));
// No reuse of index in PmTree (as this is a generic impl and could lead to security issue:
// like replay attack...)
assert_eq!(mtree_index_after_rm_addr_2, mtree_index_add_addr_2);
}
}

View File

@@ -50,7 +50,7 @@ impl RlnUserIdentityDeserializer {
let (co_buffer, rem_buffer) = buffer.split_at(compressed_size);
let commitment: Fr = CanonicalDeserialize::deserialize_compressed(co_buffer)?;
let (secret_buffer, user_limit_buffer) = rem_buffer.split_at(compressed_size);
// TODO: IdSecret
// TODO: IdSecret (wait for Zerokit PR: https://github.com/vacp2p/zerokit/pull/320)
let secret_hash: Fr = CanonicalDeserialize::deserialize_compressed(secret_buffer)?;
let user_limit: Fr = CanonicalDeserialize::deserialize_compressed(user_limit_buffer)?;

View File

@@ -7,7 +7,10 @@ pub(crate) struct MerkleTreeIndex(u64);
impl From<MerkleTreeIndex> for usize {
fn from(value: MerkleTreeIndex) -> Self {
// TODO: compile time assert
const _: () = assert!(
size_of::<u64>() == size_of::<usize>(),
"Expect usize to have the same size as of u64"
);
value.0 as usize
}
}

View File

@@ -18,7 +18,7 @@ pub trait KarmaAmountExt {
}
sol! {
// https://github.com/vacp2p/staking-reward-streamer/pull/220
// https://github.com/vacp2p/staking-reward-streamer/blob/main/src/Karma.sol
#[sol(rpc)]
contract KarmaSC {
// From: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol#L16