mirror of
https://github.com/vacp2p/status-rln-prover.git
synced 2026-01-08 05:03:54 -05:00
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:
92
Cargo.lock
generated
92
Cargo.lock
generated
@@ -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]]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(_)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user