diff --git a/tig-protocol/src/add_block.rs b/tig-protocol/src/add_block.rs index 76f455f..c056440 100644 --- a/tig-protocol/src/add_block.rs +++ b/tig-protocol/src/add_block.rs @@ -1,6 +1,6 @@ use crate::context::*; use logging_timer::time; -use rand::{prelude::SliceRandom, rngs::StdRng, SeedableRng}; +use rand::{prelude::SliceRandom, rngs::StdRng, Rng, SeedableRng}; use std::collections::{HashMap, HashSet}; use tig_structs::{config::*, core::*}; use tig_utils::*; @@ -12,17 +12,18 @@ pub(crate) async fn execute(ctx: &T) -> String { confirm_mempool_algorithms(&block, &mut cache).await; confirm_mempool_precommits(&block, &mut cache).await; confirm_mempool_benchmarks(&block, &mut cache).await; - confirm_mempool_proofs(ctx, &block, &mut cache).await; + confirm_mempool_proofs(&block, &mut cache).await; confirm_mempool_frauds(&block, &mut cache).await; + confirm_mempool_topups(&block, &mut cache).await; confirm_mempool_wasms(&block, &mut cache).await; update_deposits(ctx, &block, &mut cache).await; update_cutoffs(&block, &mut cache).await; update_qualifiers(&block, &mut cache).await; update_frontiers(&block, &mut cache).await; update_solution_signature_thresholds(&block, &mut cache).await; - update_base_fees(&block, &mut cache).await; + update_fees(&block, &mut cache).await; update_influence(&block, &mut cache).await; - update_adoption(&block, &mut cache).await; + update_adoption(&mut cache).await; update_innovator_rewards(&block, &mut cache).await; update_benchmarker_rewards(&block, &mut cache).await; update_merge_points(&block, &mut cache).await; @@ -38,11 +39,14 @@ struct AddBlockCache { pub mempool_precommits: Vec, pub mempool_proofs: Vec, pub mempool_frauds: Vec, + pub mempool_topups: Vec, pub mempool_wasms: Vec, + pub confirmed_precommits: HashMap, pub active_challenges: HashMap, pub active_algorithms: HashMap, pub active_benchmarks: HashMap, pub active_players: HashMap, + pub active_fee_players: HashMap, pub prev_challenges: HashMap, pub prev_algorithms: HashMap, pub prev_players: HashMap, @@ -57,6 +61,14 @@ async fn setup_cache( let from_block_started = details .height .saturating_sub(config.benchmark_submissions.lifespan_period); + let mut confirmed_precommits = HashMap::new(); + for precommit in ctx + .get_precommits(PrecommitsFilter::Confirmed { from_block_started }) + .await + .unwrap_or_else(|e| panic!("get_precommits error: {:?}", e)) + { + confirmed_precommits.insert(precommit.benchmark_id.clone(), precommit); + } let mut mempool_challenges = Vec::new(); for mut challenge in ctx .get_challenges(ChallengesFilter::Mempool, None) @@ -90,6 +102,9 @@ async fn setup_cache( .await .unwrap_or_else(|e| panic!("get_benchmarks error: {:?}", e)) { + if !confirmed_precommits.contains_key(&benchmark.id) { + continue; + } benchmark.state = Some(BenchmarkState { block_confirmed: None, sampled_nonces: None, @@ -104,6 +119,7 @@ async fn setup_cache( { precommit.state = Some(PrecommitState { block_confirmed: None, + rand_hash: None, }); mempool_precommits.push(precommit); } @@ -113,6 +129,9 @@ async fn setup_cache( .await .unwrap_or_else(|e| panic!("mempool_proofs error: {:?}", e)) { + if !confirmed_precommits.contains_key(&proof.benchmark_id) { + continue; + } proof.state = Some(ProofState { block_confirmed: None, submission_delay: None, @@ -125,11 +144,25 @@ async fn setup_cache( .await .unwrap_or_else(|e| panic!("mempool_frauds error: {:?}", e)) { + if !confirmed_precommits.contains_key(&fraud.benchmark_id) { + continue; + } fraud.state = Some(FraudState { block_confirmed: None, }); mempool_frauds.push(fraud); } + let mut mempool_topups = Vec::new(); + for mut topup in ctx + .get_topups(TopUpsFilter::Mempool) + .await + .unwrap_or_else(|e| panic!("get_topups error: {:?}", e)) + { + topup.state = Some(TopUpState { + block_confirmed: None, + }); + mempool_topups.push(topup); + } let mut mempool_wasms = Vec::new(); for mut wasm in ctx .get_wasms(WasmsFilter::Mempool, false) @@ -163,6 +196,7 @@ async fn setup_cache( scaling_factor: None, qualifier_difficulties: None, base_fee: None, + per_nonce_fee: None, }); active_challenges.insert(challenge.id.clone(), challenge); } @@ -240,11 +274,9 @@ async fn setup_cache( } let mut active_players = HashMap::new(); for benchmark in active_benchmarks.values() { + let settings = &confirmed_precommits[&benchmark.id].settings; let mut player = ctx - .get_players( - PlayersFilter::Id(benchmark.settings.player_id.clone()), - None, - ) + .get_players(PlayersFilter::Id(settings.player_id.clone()), None) .await .unwrap() .pop() @@ -263,6 +295,40 @@ async fn setup_cache( }); active_players.insert(player.id.clone(), player); } + let mut active_fee_players = HashMap::new(); + for topup in mempool_topups.iter() { + let mut player = ctx + .get_players(PlayersFilter::Id(topup.details.player_id.clone()), None) + .await + .unwrap() + .pop() + .unwrap(); + if player.state.is_none() { + player.state = Some(PlayerState { + total_fees_paid: Some(PreciseNumber::from(0)), + available_fee_balance: Some(PreciseNumber::from(0)), + }); + } + active_fee_players.insert(player.id.clone(), player); + } + for precommit in mempool_precommits.iter() { + let mut player = ctx + .get_players( + PlayersFilter::Id(precommit.settings.player_id.clone()), + None, + ) + .await + .unwrap() + .pop() + .unwrap(); + if player.state.is_none() { + player.state = Some(PlayerState { + total_fees_paid: Some(PreciseNumber::from(0)), + available_fee_balance: Some(PreciseNumber::from(0)), + }); + } + active_fee_players.insert(player.id.clone(), player); + } let mut prev_players = HashMap::::new(); for player_id in active_players.keys() { let player = ctx @@ -310,11 +376,14 @@ async fn setup_cache( mempool_precommits, mempool_proofs, mempool_frauds, + mempool_topups, mempool_wasms, + confirmed_precommits, active_challenges, active_algorithms, active_benchmarks, active_players, + active_fee_players, prev_challenges, prev_algorithms, prev_players, @@ -362,11 +431,17 @@ async fn create_block(ctx: &T) -> (Block, AddBlockCache) { .iter() .map(|f| f.benchmark_id.clone()) .collect(), + mempool_precommit_ids: cache + .mempool_precommits + .iter() + .map(|p| p.benchmark_id.clone()) + .collect(), mempool_proof_ids: cache .mempool_proofs .iter() .map(|p| p.benchmark_id.clone()) .collect(), + mempool_topup_ids: cache.mempool_topups.iter().map(|t| t.id.clone()).collect(), mempool_wasm_ids: cache .mempool_wasms .iter() @@ -413,28 +488,77 @@ async fn confirm_mempool_algorithms(block: &Block, cache: &mut AddBlockCache) { #[time] async fn confirm_mempool_precommits(block: &Block, cache: &mut AddBlockCache) { - let config = block.config(); - // FIXME + for precommit in cache.mempool_precommits.iter_mut() { + let state = precommit.state.as_mut().unwrap(); + state.block_confirmed = Some(block.details.height); + state.rand_hash = Some(block.id.clone()); + + let player_state = cache + .active_fee_players + .get_mut(&precommit.settings.player_id) + .unwrap() + .state + .as_mut() + .unwrap(); + *player_state.available_fee_balance.as_mut().unwrap() -= precommit.details.fee_paid; + *player_state.total_fees_paid.as_mut().unwrap() += precommit.details.fee_paid; + } } #[time] async fn confirm_mempool_benchmarks(block: &Block, cache: &mut AddBlockCache) { let config = block.config(); - - // FIXME sample solutions and non-solutions for benchmark in cache.mempool_benchmarks.iter_mut() { - let seed = u32_from_str(format!("{:?}|{:?}", block.id, benchmark.id).as_str()); - let mut rng = StdRng::seed_from_u64(seed as u64); - let sampled_nonces = { - let solutions_meta_data = benchmark.solutions_meta_data.as_ref().unwrap(); - let mut indexes: Vec = (0..solutions_meta_data.len()).collect(); - indexes.shuffle(&mut rng); - indexes - .into_iter() - .take(config.benchmark_submissions.max_samples) - .map(|i| solutions_meta_data[i].nonce) - .collect() - }; + let seed = u64s_from_str(format!("{:?}|{:?}", block.id, benchmark.id).as_str())[0]; + let mut rng = StdRng::seed_from_u64(seed); + let mut sampled_nonces = Vec::new(); + let mut solution_nonces = benchmark + .solution_nonces + .as_ref() + .unwrap() + .iter() + .cloned() + .collect::>(); + if solution_nonces.len() > 0 { + solution_nonces.shuffle(&mut rng); + sampled_nonces.extend( + solution_nonces + .iter() + .take(config.benchmark_submissions.max_samples) + .cloned(), + ); + } + let precommit = &cache.confirmed_precommits[&benchmark.id]; + let solution_nonces = benchmark.solution_nonces.as_ref().unwrap(); + if precommit.details.num_nonces as usize > solution_nonces.len() { + if precommit.details.num_nonces as usize > solution_nonces.len() * 2 { + // use rejection sampling + let stop_length = config + .benchmark_submissions + .max_samples + .min(precommit.details.num_nonces as usize - solution_nonces.len()) + + sampled_nonces.len(); + while sampled_nonces.len() < stop_length { + let nonce = rng.gen_range(0..precommit.details.num_nonces); + if sampled_nonces.contains(&nonce) || solution_nonces.contains(&nonce) { + continue; + } + sampled_nonces.push(nonce); + } + } else { + let mut non_solution_nonces: Vec = (0..precommit.details.num_nonces) + .filter(|n| !solution_nonces.contains(n)) + .collect(); + non_solution_nonces.shuffle(&mut rng); + sampled_nonces.extend( + non_solution_nonces + .iter() + .take(config.benchmark_submissions.max_samples) + .cloned(), + ); + } + } + let state = benchmark.state.as_mut().unwrap(); state.sampled_nonces = Some(sampled_nonces); state.block_confirmed = Some(block.details.height); @@ -442,17 +566,12 @@ async fn confirm_mempool_benchmarks(block: &Block, cache: &mut AddBlockCache) { } #[time] -async fn confirm_mempool_proofs(ctx: &T, block: &Block, cache: &mut AddBlockCache) { +async fn confirm_mempool_proofs(block: &Block, cache: &mut AddBlockCache) { for proof in cache.mempool_proofs.iter_mut() { - let benchmark = ctx - .get_benchmarks(BenchmarksFilter::Id(proof.benchmark_id.clone()), false) - .await - .unwrap_or_else(|e| panic!("get_benchmarks error: {:?}", e)) - .pop() - .unwrap(); + let precommit = &cache.confirmed_precommits[&proof.benchmark_id]; let state = proof.state.as_mut().unwrap(); state.block_confirmed = Some(block.details.height); - state.submission_delay = Some(block.details.height - benchmark.details.block_started); + state.submission_delay = Some(block.details.height - precommit.details.block_started); } } @@ -465,6 +584,23 @@ async fn confirm_mempool_frauds(block: &Block, cache: &mut AddBlockCache) { } } +#[time] +async fn confirm_mempool_topups(block: &Block, cache: &mut AddBlockCache) { + for topup in cache.mempool_topups.iter_mut() { + let state = topup.state.as_mut().unwrap(); + state.block_confirmed = Some(block.details.height); + + let player_state = cache + .active_fee_players + .get_mut(&topup.details.player_id) + .unwrap() + .state + .as_mut() + .unwrap(); + *player_state.available_fee_balance.as_mut().unwrap() += topup.details.amount; + } +} + #[time] async fn confirm_mempool_wasms(block: &Block, cache: &mut AddBlockCache) { for wasm in cache.mempool_wasms.iter_mut() { @@ -522,10 +658,11 @@ async fn update_cutoffs(block: &Block, cache: &mut AddBlockCache) { let mut num_solutions_by_player_by_challenge = HashMap::>::new(); for benchmark in cache.active_benchmarks.values() { + let settings = &cache.confirmed_precommits[&benchmark.id].settings; *num_solutions_by_player_by_challenge - .entry(benchmark.settings.player_id.clone()) + .entry(settings.player_id.clone()) .or_default() - .entry(benchmark.settings.challenge_id.clone()) + .entry(settings.challenge_id.clone()) .or_default() += benchmark.details.num_solutions; } @@ -578,16 +715,17 @@ async fn update_solution_signature_thresholds(block: &Block, cache: &mut AddBloc let mut num_solutions_by_player_by_challenge = HashMap::>::new(); let mut new_solutions_by_player_by_challenge = HashMap::>::new(); for benchmark in cache.active_benchmarks.values() { + let settings = &cache.confirmed_precommits[&benchmark.id].settings; *num_solutions_by_player_by_challenge - .entry(benchmark.settings.player_id.clone()) + .entry(settings.player_id.clone()) .or_default() - .entry(benchmark.settings.challenge_id.clone()) + .entry(settings.challenge_id.clone()) .or_default() += benchmark.details.num_solutions; if mempool_proof_ids.contains(&benchmark.id) { *new_solutions_by_player_by_challenge - .entry(benchmark.settings.player_id.clone()) + .entry(settings.player_id.clone()) .or_default() - .entry(benchmark.settings.challenge_id.clone()) + .entry(settings.challenge_id.clone()) .or_default() += benchmark.details.num_solutions; } } @@ -638,8 +776,54 @@ async fn update_solution_signature_thresholds(block: &Block, cache: &mut AddBloc } #[time] -async fn update_base_fees(block: &Block, cache: &mut AddBlockCache) { - // FIXME +async fn update_fees(block: &Block, cache: &mut AddBlockCache) { + let config = block.config(); + let num_precommits_by_challenge = cache.mempool_precommits.iter().fold( + HashMap::::new(), + |mut map, precommit| { + *map.entry(precommit.settings.challenge_id.clone()) + .or_default() += 1; + map + }, + ); + let target_num_precommits = + PreciseNumber::from(config.precommit_submissions.target_num_precommits); + let max_fee_percent_delta = + PreciseNumber::from_f64(config.precommit_submissions.max_fee_percentage_delta); + let one = PreciseNumber::from(1); + let zero = PreciseNumber::from(0); + for challenge in cache.active_challenges.values_mut() { + let num_precommits = PreciseNumber::from( + num_precommits_by_challenge + .get(&challenge.id) + .unwrap_or(&0) + .clone(), + ); + let mut percent_delta = num_precommits / target_num_precommits; + if num_precommits >= target_num_precommits { + percent_delta = percent_delta - one; + } else { + percent_delta = one - percent_delta; + } + if percent_delta > max_fee_percent_delta { + percent_delta = max_fee_percent_delta; + } + let current_base_fee = match &cache.prev_challenges.get(&challenge.id).unwrap().block_data { + Some(data) => *data.base_fee(), + None => zero.clone(), + }; + let mut base_fee = if num_precommits >= target_num_precommits { + current_base_fee * (one + percent_delta) + } else { + current_base_fee * (one - percent_delta) + }; + if base_fee < config.precommit_submissions.min_base_fee { + base_fee = config.precommit_submissions.min_base_fee; + } + let block_data = challenge.block_data.as_mut().unwrap(); + block_data.base_fee = Some(base_fee); + block_data.per_nonce_fee = Some(config.precommit_submissions.min_per_nonce_fee); + } } fn find_smallest_range_dimension(points: &Frontier) -> usize { @@ -696,8 +880,9 @@ async fn update_qualifiers(block: &Block, cache: &mut AddBlockCache) { let mut benchmarks_by_challenge = HashMap::>::new(); for benchmark in cache.active_benchmarks.values() { + let settings = &cache.confirmed_precommits[&benchmark.id].settings; benchmarks_by_challenge - .entry(benchmark.settings.challenge_id.clone()) + .entry(settings.challenge_id.clone()) .or_default() .push(benchmark); } @@ -725,7 +910,12 @@ async fn update_qualifiers(block: &Block, cache: &mut AddBlockCache) { let benchmarks = benchmarks_by_challenge.get_mut(challenge_id).unwrap(); let points = benchmarks .iter() - .map(|b| b.settings.difficulty.clone()) + .map(|b| { + cache.confirmed_precommits[&b.id] + .settings + .difficulty + .clone() + }) .collect::(); let mut frontier_indexes = HashMap::::new(); for (frontier_index, frontier) in pareto_algorithm(points, false).into_iter().enumerate() { @@ -734,8 +924,10 @@ async fn update_qualifiers(block: &Block, cache: &mut AddBlockCache) { } } benchmarks.sort_by(|a, b| { - let a_index = frontier_indexes[&a.settings.difficulty]; - let b_index = frontier_indexes[&b.settings.difficulty]; + let a_settings = &cache.confirmed_precommits[&a.id].settings; + let b_settings = &cache.confirmed_precommits[&b.id].settings; + let a_index = frontier_indexes[&a_settings.difficulty]; + let b_index = frontier_indexes[&b_settings.difficulty]; a_index.cmp(&b_index) }); @@ -749,7 +941,7 @@ async fn update_qualifiers(block: &Block, cache: &mut AddBlockCache) { challenge_id, difficulty, .. - } = &benchmark.settings; + } = &cache.confirmed_precommits[&benchmark.id].settings; if curr_frontier_index != frontier_indexes[difficulty] && *challenge_data.num_qualifiers() > config.qualifiers.total_qualifiers_threshold @@ -974,7 +1166,7 @@ async fn update_influence(block: &Block, cache: &mut AddBlockCache) { } #[time] -async fn update_adoption(block: &Block, cache: &mut AddBlockCache) { +async fn update_adoption(cache: &mut AddBlockCache) { let mut algorithms_by_challenge = HashMap::>::new(); for algorithm in cache.active_algorithms.values_mut() { algorithms_by_challenge @@ -1142,6 +1334,11 @@ async fn update_merges(block: &Block, cache: &mut AddBlockCache) { #[time] async fn commit_changes(ctx: &T, block: &Block, cache: &mut AddBlockCache) { + for precommit in cache.mempool_precommits.drain(..) { + ctx.update_precommit_state(&precommit.benchmark_id, precommit.state.unwrap()) + .await + .unwrap_or_else(|e| panic!("update_precommit_state error: {:?}", e)); + } for algorithm in cache.mempool_algorithms.drain(..) { ctx.update_algorithm_state(&algorithm.id, algorithm.state.unwrap()) .await @@ -1172,6 +1369,11 @@ async fn commit_changes(ctx: &T, block: &Block, cache: &mut AddBlock .await .unwrap_or_else(|e| panic!("update_wasm_state error: {:?}", e)); } + for (player_id, player) in cache.active_fee_players.drain() { + ctx.update_player_state(&player_id, player.state.unwrap()) + .await + .unwrap_or_else(|e| panic!("update_player_state error: {:?}", e)); + } for (_, algorithm) in cache.active_algorithms.drain() { let state = algorithm.state.unwrap(); if state.round_pushed.is_some_and(|r| r == block.details.round) { diff --git a/tig-protocol/src/context.rs b/tig-protocol/src/context.rs index 80a58a3..d7e022a 100644 --- a/tig-protocol/src/context.rs +++ b/tig-protocol/src/context.rs @@ -66,6 +66,13 @@ pub enum ProofsFilter { Confirmed { from_block_started: u32 }, } #[derive(Debug, Clone, PartialEq)] +pub enum TopUpsFilter { + TopUpId(String), + PlayerId(String), + Mempool, + Confirmed, +} +#[derive(Debug, Clone, PartialEq)] pub enum WasmsFilter { AlgorithmId(String), Mempool, @@ -111,6 +118,7 @@ pub trait Context { filter: ProofsFilter, include_data: bool, ) -> ContextResult>; + async fn get_topups(&self, filter: TopUpsFilter) -> ContextResult>; async fn get_wasms(&self, filter: WasmsFilter, include_data: bool) -> ContextResult>; async fn verify_solution( &self, @@ -150,7 +158,7 @@ pub trait Context { &self, benchmark_id: &String, details: BenchmarkDetails, - solution_idxs: HashSet, + solution_nonces: HashSet, ) -> ContextResult<()>; async fn add_precommit_to_mempool( &self, @@ -167,6 +175,11 @@ pub trait Context { benchmark_id: &String, allegation: String, ) -> ContextResult<()>; + async fn add_topup_to_mempool( + &self, + topup_id: &String, + details: TopUpDetails, + ) -> ContextResult<()>; async fn add_wasm_to_mempool( &self, algorithm_id: &String, @@ -202,6 +215,16 @@ pub trait Context { benchmark_id: &String, state: BenchmarkState, ) -> ContextResult<()>; + async fn update_player_state( + &self, + player_id: &String, + state: PlayerState, + ) -> ContextResult<()>; + async fn update_precommit_state( + &self, + benchmark_id: &String, + state: PrecommitState, + ) -> ContextResult<()>; async fn update_proof_state( &self, benchmark_id: &String, @@ -212,6 +235,7 @@ pub trait Context { benchmark_id: &String, state: FraudState, ) -> ContextResult<()>; + async fn update_topup_state(&self, topup_id: &String, state: TopUpState) -> ContextResult<()>; async fn update_player_block_data( &self, player_id: &String, diff --git a/tig-protocol/src/error.rs b/tig-protocol/src/error.rs index 53892fd..5c15ccd 100644 --- a/tig-protocol/src/error.rs +++ b/tig-protocol/src/error.rs @@ -1,4 +1,5 @@ use tig_structs::{config::DifficultyParameter, core::BenchmarkSettings}; +use tig_utils::PreciseNumber; #[derive(Debug, PartialEq)] pub enum ProtocolError { @@ -17,7 +18,7 @@ pub enum ProtocolError { DuplicateProof { benchmark_id: String, }, - DuplicateSubmissionFeeTx { + DuplicateTransaction { tx_hash: String, }, FlaggedAsFraud { @@ -28,6 +29,10 @@ pub enum ProtocolError { min_num_solutions: usize, num_solutions: usize, }, + InsufficientFeeBalance { + fee_paid: PreciseNumber, + available_fee_balance: PreciseNumber, + }, InvalidAlgorithm { algorithm_id: String, }, @@ -50,6 +55,9 @@ pub enum ProtocolError { InvalidMerkleProof { nonce: u64, }, + InvalidNumNonces { + num_nonces: u64, + }, InvalidPrecommit { benchmark_id: String, }, @@ -78,17 +86,17 @@ pub enum ProtocolError { expected_player_id: String, actual_player_id: String, }, - InvalidSubmissionFeeAmount { + InvalidTransactionAmount { expected_amount: String, actual_amount: String, tx_hash: String, }, - InvalidSubmissionFeeReceiver { + InvalidTransactionReceiver { tx_hash: String, expected_receiver: String, actual_receiver: String, }, - InvalidSubmissionFeeSender { + InvalidTransactionSender { tx_hash: String, expected_sender: String, actual_sender: String, @@ -126,7 +134,7 @@ impl std::fmt::Display for ProtocolError { ProtocolError::DuplicateProof { benchmark_id } => { write!(f, "Proof already submitted for benchmark '{}'", benchmark_id) } - ProtocolError::DuplicateSubmissionFeeTx { tx_hash } => write!( + ProtocolError::DuplicateTransaction { tx_hash } => write!( f, "Transaction '{}' is already used", tx_hash @@ -134,6 +142,14 @@ impl std::fmt::Display for ProtocolError { ProtocolError::FlaggedAsFraud { benchmark_id } => { write!(f, "Benchmark '{}' is flagged as fraud", benchmark_id) } + ProtocolError::InsufficientFeeBalance { + fee_paid, + available_fee_balance, + } => write!( + f, + "Insufficient fee balance. Fee to be paid: '{}', Available fee balance: '{}'", + fee_paid, available_fee_balance + ), ProtocolError::InsufficientLifespan => { write!(f, "Benchmark will have no lifespan remaining after submission delay penalty is applied.") } @@ -173,6 +189,11 @@ impl std::fmt::Display for ProtocolError { "Merkle proof for nonce '{}' is invalid", nonce ), + ProtocolError::InvalidNumNonces { num_nonces } => write!( + f, + "Number of nonces '{}' is invalid", + num_nonces + ), ProtocolError::InvalidPrecommit { benchmark_id } => { write!(f, "Precommit '{}' does not exist", benchmark_id) } @@ -221,7 +242,7 @@ impl std::fmt::Display for ProtocolError { "Submission made by the invalid player. Expected: '{}', Actual: '{}'", expected_player_id, actual_player_id ), - ProtocolError::InvalidSubmissionFeeAmount { + ProtocolError::InvalidTransactionAmount { expected_amount, actual_amount, tx_hash, @@ -230,12 +251,12 @@ impl std::fmt::Display for ProtocolError { "Transaction '{}' paid an invalid amount of submission fee. Expected: '{}', Actual: '{}'", tx_hash, expected_amount, actual_amount ), - ProtocolError::InvalidSubmissionFeeReceiver { tx_hash, expected_receiver, actual_receiver } => write!( + ProtocolError::InvalidTransactionReceiver { tx_hash, expected_receiver, actual_receiver } => write!( f, "Transaction '{}' has invalid receiver. Expected: '{}', Actual: '{}'", tx_hash, expected_receiver, actual_receiver ), - ProtocolError::InvalidSubmissionFeeSender { + ProtocolError::InvalidTransactionSender { tx_hash, expected_sender, actual_sender, diff --git a/tig-protocol/src/lib.rs b/tig-protocol/src/lib.rs index b6c0706..dab084c 100644 --- a/tig-protocol/src/lib.rs +++ b/tig-protocol/src/lib.rs @@ -5,6 +5,7 @@ mod submit_algorithm; mod submit_benchmark; mod submit_precommit; mod submit_proof; +mod submit_topup; mod verify_proof; use context::*; pub use error::*; @@ -55,6 +56,10 @@ impl<'a, T: Context> Protocol { submit_proof::execute(&self.ctx, player, benchmark_id, merkle_proofs).await } + pub async fn submit_topup(&self, player: &Player, tx_hash: String) -> ProtocolResult<()> { + submit_topup::execute(&self.ctx, player, tx_hash).await + } + pub async fn verify_proof(&self, benchmark_id: &String) -> ProtocolResult> { verify_proof::execute(&self.ctx, benchmark_id).await } diff --git a/tig-protocol/src/submit_algorithm.rs b/tig-protocol/src/submit_algorithm.rs index 9a497a5..e1a0d13 100644 --- a/tig-protocol/src/submit_algorithm.rs +++ b/tig-protocol/src/submit_algorithm.rs @@ -72,7 +72,7 @@ async fn verify_submission_fee( .first() .is_some() { - return Err(ProtocolError::DuplicateSubmissionFeeTx { + return Err(ProtocolError::DuplicateTransaction { tx_hash: details.tx_hash.clone(), }); } @@ -92,7 +92,7 @@ async fn verify_submission_fee( } })?; if !valid_senders.contains(&transaction.sender) { - return Err(ProtocolError::InvalidSubmissionFeeSender { + return Err(ProtocolError::InvalidTransactionSender { tx_hash: details.tx_hash.clone(), expected_sender: player.id.clone(), actual_sender: transaction.sender.clone(), @@ -100,7 +100,7 @@ async fn verify_submission_fee( } let burn_address = block.config().erc20.burn_address.clone(); if transaction.receiver != burn_address { - return Err(ProtocolError::InvalidSubmissionFeeReceiver { + return Err(ProtocolError::InvalidTransactionReceiver { tx_hash: details.tx_hash.clone(), expected_receiver: burn_address, actual_receiver: transaction.receiver.clone(), @@ -109,7 +109,7 @@ async fn verify_submission_fee( let expected_amount = block.config().algorithm_submissions.submission_fee; if transaction.amount != expected_amount { - return Err(ProtocolError::InvalidSubmissionFeeAmount { + return Err(ProtocolError::InvalidTransactionAmount { tx_hash: details.tx_hash.clone(), expected_amount: jsonify(&expected_amount), actual_amount: jsonify(&transaction.amount), diff --git a/tig-protocol/src/submit_benchmark.rs b/tig-protocol/src/submit_benchmark.rs index a3ea730..f4b1e05 100644 --- a/tig-protocol/src/submit_benchmark.rs +++ b/tig-protocol/src/submit_benchmark.rs @@ -2,7 +2,6 @@ use crate::{context::*, error::*}; use logging_timer::time; use std::collections::HashSet; use tig_structs::core::*; -use tig_utils::*; #[time] pub(crate) async fn execute( @@ -36,8 +35,8 @@ async fn get_precommit_by_id( ctx.get_precommits(PrecommitsFilter::BenchmarkId(benchmark_id.clone())) .await .unwrap_or_else(|e| panic!("get_precommits error: {:?}", e)) - .first() - .map(|x| x.to_owned()) + .pop() + .filter(|p| p.state.is_some()) .ok_or_else(|| ProtocolError::InvalidPrecommit { benchmark_id: benchmark_id.clone(), }) diff --git a/tig-protocol/src/submit_precommit.rs b/tig-protocol/src/submit_precommit.rs index 89911d7..fdf297e 100644 --- a/tig-protocol/src/submit_precommit.rs +++ b/tig-protocol/src/submit_precommit.rs @@ -11,19 +11,21 @@ pub(crate) async fn execute( num_nonces: u64, ) -> ProtocolResult { verify_player_owns_benchmark(player, &settings)?; + verify_num_nonces(num_nonces)?; let block = get_block_by_id(ctx, &settings.block_id).await?; verify_sufficient_lifespan(ctx, &block).await?; let challenge = get_challenge_by_id(ctx, &settings.challenge_id, &block).await?; verify_algorithm(ctx, &settings.algorithm_id, &block).await?; verify_benchmark_settings_are_unique(ctx, &settings).await?; verify_benchmark_difficulty(&settings.difficulty, &challenge, &block)?; - // FIXME verify base_fee + let fee_paid = get_fee_paid(&player, num_nonces, &challenge)?; let benchmark_id = ctx .add_precommit_to_mempool( settings, PrecommitDetails { block_started: block.details.height, num_nonces, + fee_paid, }, ) .await @@ -45,6 +47,14 @@ fn verify_player_owns_benchmark( Ok(()) } +#[time] +fn verify_num_nonces(num_nonces: u64) -> ProtocolResult<()> { + if num_nonces == 0 { + return Err(ProtocolError::InvalidNumNonces { num_nonces }); + } + Ok(()) +} + #[time] async fn verify_sufficient_lifespan(ctx: &T, block: &Block) -> ProtocolResult<()> { let latest_block = ctx @@ -98,8 +108,8 @@ async fn verify_algorithm( .get_algorithms(AlgorithmsFilter::Id(algorithm_id.clone()), None, false) .await .unwrap_or_else(|e| panic!("get_algorithms error: {:?}", e)) - .first() - .is_some() + .pop() + .is_some_and(|a| a.state.is_some()) { return Err(ProtocolError::InvalidAlgorithm { algorithm_id: algorithm_id.clone(), @@ -193,3 +203,25 @@ fn verify_benchmark_difficulty( Ok(()) } + +#[time] +fn get_fee_paid( + player: &Player, + num_nonces: u64, + challenge: &Challenge, +) -> ProtocolResult { + let num_nonces = PreciseNumber::from(num_nonces); + let fee_paid = challenge.block_data().base_fee().clone() + + challenge.block_data().per_nonce_fee().clone() * num_nonces; + if !player + .state + .as_ref() + .is_some_and(|s| *s.available_fee_balance.as_ref().unwrap() >= fee_paid) + { + return Err(ProtocolError::InsufficientFeeBalance { + fee_paid, + available_fee_balance: player.state().available_fee_balance().clone(), + }); + } + Ok(fee_paid) +} diff --git a/tig-protocol/src/submit_proof.rs b/tig-protocol/src/submit_proof.rs index 1db52c4..2368490 100644 --- a/tig-protocol/src/submit_proof.rs +++ b/tig-protocol/src/submit_proof.rs @@ -2,7 +2,7 @@ use crate::{context::*, error::*}; use logging_timer::time; use std::collections::HashSet; use tig_structs::core::*; -use tig_utils::{MerkleBranch, MerkleHash}; +use tig_utils::MerkleHash; #[time] pub(crate) async fn execute( @@ -39,8 +39,8 @@ async fn get_precommit_by_id( ctx.get_precommits(PrecommitsFilter::BenchmarkId(benchmark_id.clone())) .await .unwrap_or_else(|e| panic!("get_precommits error: {:?}", e)) - .first() - .map(|x| x.to_owned()) + .pop() + .filter(|p| p.state.is_some()) .ok_or_else(|| ProtocolError::InvalidPrecommit { benchmark_id: benchmark_id.clone(), }) @@ -54,8 +54,8 @@ async fn get_benchmark_by_id( ctx.get_benchmarks(BenchmarksFilter::Id(benchmark_id.clone()), true) .await .unwrap_or_else(|e| panic!("add_benchmark_to_mempool error: {:?}", e)) - .first() - .map(|x| x.to_owned()) + .pop() + .filter(|b| b.state.is_some()) .ok_or_else(|| ProtocolError::InvalidBenchmark { benchmark_id: benchmark_id.to_string(), }) @@ -114,22 +114,25 @@ fn verify_merkle_proofs( benchmark: &Benchmark, merkle_proofs: &Vec, ) -> ProtocolResult<()> { - let expected_branch_len = (64 - (precommit.details.num_nonces - 1).leading_zeros()) as usize; + let max_branch_len = (64 - (precommit.details.num_nonces - 1).leading_zeros()) as usize; let expected_merkle_root = benchmark.details.merkle_root.clone().unwrap(); for merkle_proof in merkle_proofs.iter() { - if merkle_proof.branch.as_ref().unwrap().0.len() != expected_branch_len { + let branch = merkle_proof.branch.as_ref().unwrap(); + if branch.0.len() > max_branch_len + || branch.0.iter().any(|(d, _)| *d as usize > max_branch_len) + { return Err(ProtocolError::InvalidMerkleProof { nonce: merkle_proof.leaf.nonce.clone(), }); } let output_meta_data = OutputMetaData::from(merkle_proof.leaf.clone()); let hash = MerkleHash::from(output_meta_data); - let actual_merkle_root = merkle_proof + let result = merkle_proof .branch .as_ref() .unwrap() .calc_merkle_root(&hash, merkle_proof.leaf.nonce as usize); - if actual_merkle_root != expected_merkle_root { + if !result.is_ok_and(|actual_merkle_root| actual_merkle_root == expected_merkle_root) { return Err(ProtocolError::InvalidMerkleProof { nonce: merkle_proof.leaf.nonce.clone(), }); diff --git a/tig-protocol/src/submit_topup.rs b/tig-protocol/src/submit_topup.rs new file mode 100644 index 0000000..a9af989 --- /dev/null +++ b/tig-protocol/src/submit_topup.rs @@ -0,0 +1,90 @@ +use crate::{context::*, error::*}; +use logging_timer::time; +use std::collections::HashSet; +use tig_structs::core::*; +use tig_utils::*; + +#[time] +pub(crate) async fn execute( + ctx: &T, + player: &Player, + tx_hash: String, +) -> ProtocolResult<()> { + let topup_amount = verify_topup_tx(ctx, player, &tx_hash).await?; + ctx.add_topup_to_mempool( + &tx_hash, + TopUpDetails { + player_id: player.id.clone(), + amount: topup_amount, + }, + ) + .await + .unwrap_or_else(|e| panic!("add_topup_to_mempool error: {:?}", e)); + Ok(()) +} + +#[time] +async fn verify_topup_tx( + ctx: &T, + player: &Player, + tx_hash: &String, +) -> ProtocolResult { + let block = ctx + .get_block(BlockFilter::Latest, false) + .await + .unwrap_or_else(|e| panic!("get_block error: {:?}", e)) + .expect("No latest block found"); + + if ctx + .get_topups(TopUpsFilter::TopUpId(tx_hash.clone())) + .await + .unwrap_or_else(|e| panic!("get_topups error: {:?}", e)) + .first() + .is_some() + { + return Err(ProtocolError::DuplicateTransaction { + tx_hash: tx_hash.clone(), + }); + } + let mut valid_senders = HashSet::::new(); + valid_senders.insert(player.id.clone()); + if player.details.is_multisig { + let multisig_owners = ctx + .get_multisig_owners(&player.id) + .await + .unwrap_or_else(|e| panic!("get_multisig_owners error: {:?}", e)); + valid_senders.extend(multisig_owners.into_iter()); + } + + let transaction = + ctx.get_transaction(&tx_hash) + .await + .map_err(|_| ProtocolError::InvalidTransaction { + tx_hash: tx_hash.clone(), + })?; + if !valid_senders.contains(&transaction.sender) { + return Err(ProtocolError::InvalidTransactionSender { + tx_hash: tx_hash.clone(), + expected_sender: player.id.clone(), + actual_sender: transaction.sender.clone(), + }); + } + let burn_address = block.config().erc20.burn_address.clone(); + if transaction.receiver != burn_address { + return Err(ProtocolError::InvalidTransactionReceiver { + tx_hash: tx_hash.clone(), + expected_receiver: burn_address, + actual_receiver: transaction.receiver.clone(), + }); + } + + let expected_amount = block.config().precommit_submissions.topup_amount.clone(); + if transaction.amount != expected_amount { + return Err(ProtocolError::InvalidTransactionAmount { + tx_hash: tx_hash.clone(), + expected_amount: jsonify(&expected_amount), + actual_amount: jsonify(&transaction.amount), + }); + } + Ok(expected_amount) +}