From 0f0e7167ecf03194aa5e9a2a6a08bf1fb016ce76 Mon Sep 17 00:00:00 2001 From: 0xVoronov Date: Fri, 20 Feb 2026 18:49:28 +0300 Subject: [PATCH 01/11] Implemented cycles reward --- Mvkt.Api/Controllers/AccountsController.cs | 4 +- Mvkt.Api/Controllers/DelegatesController.cs | 4 +- Mvkt.Api/Controllers/RewardsController.cs | 22 +- Mvkt.Api/Controllers/RightsController.cs | 4 +- Mvkt.Api/Controllers/VotingController.cs | 6 +- Mvkt.Api/Models/Baking/BakerStats.cs | 64 +++++ Mvkt.Api/Repositories/RewardsRepository.cs | 230 +++++++++++++++--- ...ressAttribute.cs => MvAddressAttribute.cs} | 4 +- Mvkt.Api/appsettings.json | 5 +- ...220100000_BakerCycles_BakerId_CycleDesc.cs | 27 ++ .../Migrations/MvktContextModelSnapshot.cs | 5 +- Mvkt.Data/Models/Baking/BakerCycle.cs | 6 +- Mvkt.Sync/Services/Cache/BlocksCache.cs | 4 +- docker-compose.yml | 4 +- 14 files changed, 331 insertions(+), 58 deletions(-) rename Mvkt.Api/Validation/{TzAddressAttribute.cs => MvAddressAttribute.cs} (78%) create mode 100644 Mvkt.Data/Migrations/20250220100000_BakerCycles_BakerId_CycleDesc.cs diff --git a/Mvkt.Api/Controllers/AccountsController.cs b/Mvkt.Api/Controllers/AccountsController.cs index 90b77e725..f10e3af09 100644 --- a/Mvkt.Api/Controllers/AccountsController.cs +++ b/Mvkt.Api/Controllers/AccountsController.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc; using NSwag.Annotations; using Mvkt.Api.Models; @@ -235,7 +235,7 @@ public async Task>> GetContracts( /// [HttpGet("{address}/delegators")] public async Task>> GetDelegators( - [Required][TzAddress] string address, + [Required][MvAddress] string address, AccountTypeParameter type, Int64Parameter balance, Int32Parameter delegationLevel, diff --git a/Mvkt.Api/Controllers/DelegatesController.cs b/Mvkt.Api/Controllers/DelegatesController.cs index 03bf8cec2..896348d91 100644 --- a/Mvkt.Api/Controllers/DelegatesController.cs +++ b/Mvkt.Api/Controllers/DelegatesController.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc; using Mvkt.Api.Repositories; @@ -90,7 +90,7 @@ public Task GetCount(BoolParameter active) /// Delegate address (starting with mv) /// [HttpGet("{address}")] - public Task GetByAddress([Required][TzAddress] string address) + public Task GetByAddress([Required][MvAddress] string address) { return Accounts.GetDelegate(address); } diff --git a/Mvkt.Api/Controllers/RewardsController.cs b/Mvkt.Api/Controllers/RewardsController.cs index 4e8e515d9..bccd8e991 100644 --- a/Mvkt.Api/Controllers/RewardsController.cs +++ b/Mvkt.Api/Controllers/RewardsController.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc; using NSwag.Annotations; using Mvkt.Api.Models; @@ -28,7 +28,7 @@ public RewardsController(RewardsRepository rewards, StakingRepository staking) /// Baker address /// [HttpGet("bakers/{address}/count")] - public Task GetBakerRewardsCount([Required][TzAddress] string address) + public Task GetBakerRewardsCount([Required][MvAddress] string address) { return Rewards.GetBakerRewardsCount(address); } @@ -49,7 +49,7 @@ public Task GetBakerRewardsCount([Required][TzAddress] string address) /// [HttpGet("bakers/{address}")] public async Task>> GetBakerRewards( - [Required][TzAddress] string address, + [Required][MvAddress] string address, Int32Parameter cycle, SelectParameter select, SortParameter sort, @@ -90,7 +90,7 @@ public async Task>> GetBakerRewards( // deprecated [OpenApiIgnore] [HttpGet("bakers/{address}/{cycle:int}")] - public async Task GetBakerRewardsByCycle([Required][TzAddress] string address, [Min(0)] int cycle, Symbols quote = Symbols.None) + public async Task GetBakerRewardsByCycle([Required][MvAddress] string address, [Min(0)] int cycle, Symbols quote = Symbols.None) { return (await Rewards.GetBakerRewards(address, cycle, null, null, 100, quote)).FirstOrDefault(); } @@ -183,7 +183,7 @@ public async Task GetDelegatorRewardsByCycle([Required][Addres /// Maximum number of delegators to return /// [HttpGet("split/{baker}/{cycle:int}")] - public Task GetRewardSplit([Required][TzAddress] string baker, [Min(0)] int cycle, int offset = 0, [Range(0, 10000)] int limit = 100) + public Task GetRewardSplit([Required][MvAddress] string baker, [Min(0)] int cycle, int offset = 0, [Range(0, 10000)] int limit = 100) { return Rewards.GetRewardSplit(baker, cycle, offset, limit); } @@ -199,7 +199,7 @@ public Task GetRewardSplit([Required][TzAddress] string baker, [Min /// Delegator address /// [HttpGet("split/{baker}/{cycle:int}/{delegator}")] - public Task GetRewardSplitDelegator([Required][TzAddress] string baker, [Min(0)] int cycle, [Required][Address] string delegator) + public Task GetRewardSplitDelegator([Required][MvAddress] string baker, [Min(0)] int cycle, [Required][Address] string delegator) { return Rewards.GetRewardSplitDelegator(baker, cycle, delegator); } @@ -209,15 +209,19 @@ public Task GetRewardSplitDelegator([Required][TzAddress] string /// /// /// Returns aggregated statistics for a baker based on historical rewards data. - /// Includes performance metrics, reliability, luck, total income, fees, and more. + /// If cycle is set, returns stats for that cycle only. Otherwise aggregates over the last cyclesLimit cycles. /// /// Baker address (starting with mv) + /// If set, return stats for this cycle only. Otherwise use cyclesLimit. + /// When cycle is not set: max number of recent cycles to aggregate. Default 10000. /// [HttpGet("bakers/{address}/stats")] public async Task> GetBakerStats( - [Required][TzAddress] string address) + [Required][MvAddress] string address, + [FromQuery] int? cycle = null, + [FromQuery] int cyclesLimit = 10000) { - var stats = await Rewards.GetBakerStats(address); + var stats = await Rewards.GetBakerStats(address, cycle, cyclesLimit); if (stats == null) return NotFound(); diff --git a/Mvkt.Api/Controllers/RightsController.cs b/Mvkt.Api/Controllers/RightsController.cs index c5c2ab318..9414fc663 100644 --- a/Mvkt.Api/Controllers/RightsController.cs +++ b/Mvkt.Api/Controllers/RightsController.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc; using NSwag.Annotations; using Mvkt.Api.Models; @@ -112,7 +112,7 @@ public async Task>> Get( [OpenApiIgnore] [HttpGet("schedule")] public async Task>> GetSchedule( - [Required][TzAddress] string baker, + [Required][MvAddress] string baker, [Required] DateTimeOffset from, [Required] DateTimeOffset to, [Min(0)] int maxRound = 0) diff --git a/Mvkt.Api/Controllers/VotingController.cs b/Mvkt.Api/Controllers/VotingController.cs index ad16f1546..c3f6a2306 100644 --- a/Mvkt.Api/Controllers/VotingController.cs +++ b/Mvkt.Api/Controllers/VotingController.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc; using Mvkt.Api.Models; @@ -219,7 +219,7 @@ public async Task>> GetPeriodVoters( /// Voter address /// [HttpGet("periods/{index:int}/voters/{address}")] - public Task GetPeriodVoter([Min(0)] int index, [Required][TzAddress] string address) + public Task GetPeriodVoter([Min(0)] int index, [Required][MvAddress] string address) { return Voting.GetVoter(index, address); } @@ -259,7 +259,7 @@ public async Task>> GetPeriodVoters( /// Voter address /// [HttpGet("periods/current/voters/{address}")] - public Task GetPeriodVoter([Required][TzAddress] string address) + public Task GetPeriodVoter([Required][MvAddress] string address) { return Voting.GetVoter(State.Current.VotingPeriod, address); } diff --git a/Mvkt.Api/Models/Baking/BakerStats.cs b/Mvkt.Api/Models/Baking/BakerStats.cs index c56bbc0d5..6ca4d68a1 100644 --- a/Mvkt.Api/Models/Baking/BakerStats.cs +++ b/Mvkt.Api/Models/Baking/BakerStats.cs @@ -40,6 +40,70 @@ public class BakerStats /// Total actual rewards received (micro tez) /// public long TotalActualRewards { get; set; } + + /// + /// Cycle if request was for a single cycle; null for lifetime. + /// + public int? Cycle { get; set; } + + /// + /// Number of cycles included in aggregation (1 for single-cycle, else up to cycles param). + /// + public int CyclesUsed { get; set; } + + /// + /// Reward-derived KPIs and efficiency + rewards summary. Null if baker has no cycles. + /// + public BakerStatsKpis Kpis { get; set; } + } + + /// + /// Aggregated KPIs and rewards summary for validator Cycle/Lifetime tabs. + /// + public class BakerStatsKpis + { + #region Reward-derived KPIs + public long TotalIncome { get; set; } + public long ExtraRewards { get; set; } + public long LostRewards { get; set; } + public long SlashedRewards { get; set; } + public long TotalSlashed { get; set; } + public long BlocksBaked { get; set; } + public long BlocksProposed { get; set; } + public long TotalBlockRewards { get; set; } + public long TotalEndorsementRewards { get; set; } + public long EndorsementsMade { get; set; } + public long EndorsementsMissed { get; set; } + /// Expected to delegators + co-stakers (client: pending = ExpectedDistribution - payouts). + public long ExpectedDistribution { get; set; } + #endregion + + #region Efficiency + public double TechnicalReliability { get; set; } + public double MonetaryPerformance { get; set; } + public double FairEfficiency { get; set; } + public double LuckRatio { get; set; } + public long TotalExpectedRewards { get; set; } + public long TotalActualRewards { get; set; } + public int MissedRights { get; set; } + #endregion + + #region Rewards summary + public long TotalRewards { get; set; } + public long TotalBlockFees { get; set; } + public long TotalRevelationRewards { get; set; } + public long MissedBlocks { get; set; } + public long MissedEndorsements { get; set; } + public double PerformanceRate { get; set; } + public double AvgRewardsPerCycle { get; set; } + public long TotalRewardsDelegated { get; set; } + public long TotalRewardsStakedShared { get; set; } + public long TotalRewardsOwnStake { get; set; } + public long TotalEdgeFees { get; set; } + public double DelegatorSharePercent { get; set; } + public double CoStakerSharePercent { get; set; } + public double ValidatorSharePercent { get; set; } + #endregion } } diff --git a/Mvkt.Api/Repositories/RewardsRepository.cs b/Mvkt.Api/Repositories/RewardsRepository.cs index bbaf88f00..cf0b9d678 100644 --- a/Mvkt.Api/Repositories/RewardsRepository.cs +++ b/Mvkt.Api/Repositories/RewardsRepository.cs @@ -1,4 +1,6 @@ -using Dapper; +using System.Diagnostics; +using Dapper; +using Microsoft.Extensions.Logging; using Npgsql; using Mvkt.Api.Models; using Mvkt.Api.Services.Cache; @@ -11,13 +13,15 @@ public class RewardsRepository readonly AccountsCache Accounts; readonly ProtocolsCache Protocols; readonly QuotesCache Quotes; + readonly ILogger Logger; - public RewardsRepository(NpgsqlDataSource dataSource, AccountsCache accounts, ProtocolsCache protocols, QuotesCache quotes) + public RewardsRepository(NpgsqlDataSource dataSource, AccountsCache accounts, ProtocolsCache protocols, QuotesCache quotes, ILogger logger = null) { DataSource = dataSource; Accounts = accounts; Protocols = protocols; Quotes = quotes; + Logger = logger; } #region baker @@ -2185,51 +2189,40 @@ LIMIT 1 #endregion #region baker stats - public async Task GetBakerStats(string address) + public async Task GetBakerStats(string address, int? cycle = null, int cyclesLimit = 10000) { if (await Accounts.GetAsync(address) is not RawDelegate baker) return null; - var rewards = await GetBakerRewards(address, null, null, null, 10000, Symbols.None); - var rewardsList = rewards.ToList(); + var aggTask = GetBakerStatsAggregate(baker.Id, cycle, cyclesLimit); + var aliasTask = Accounts.GetAliasAsync(baker.Id); + await Task.WhenAll(aggTask, aliasTask); - if (!rewardsList.Any()) + var agg = await aggTask; + if (agg == null) return null; - var alias = await Accounts.GetAliasAsync(baker.Id); - - var totalExpectedBlocks = rewardsList.Sum(r => (long)Math.Round(r.ExpectedBlocks)); - var totalBlocks = rewardsList.Sum(r => (long)r.Blocks); - var totalMissedBlocks = rewardsList.Sum(r => (long)r.MissedBlocks); - - var totalExpectedAttestations = rewardsList.Sum(r => (long)Math.Round(r.ExpectedEndorsements)); - var totalAttestations = rewardsList.Sum(r => (long)r.Endorsements); - var totalMissedAttestations = rewardsList.Sum(r => (long)r.MissedEndorsements); - - var totalActualRewards = rewardsList.Sum(r => - r.BlockRewardsDelegated + r.BlockRewardsStakedOwn + r.BlockRewardsStakedEdge + r.BlockRewardsStakedShared + - r.EndorsementRewardsDelegated + r.EndorsementRewardsStakedOwn + r.EndorsementRewardsStakedEdge + r.EndorsementRewardsStakedShared); - - var totalMissedRewards = rewardsList.Sum(r => r.MissedBlockRewards + r.MissedEndorsementRewards); - - var totalExpectedRewards = totalActualRewards + totalMissedRewards; + var alias = await aliasTask; + var totalExpectedRewards = agg.TotalActualRewards + agg.TotalMissedBlockRewards + agg.TotalMissedEndorsementRewards; var luck = totalExpectedRewards > 0 - ? Math.Round((double)totalActualRewards / totalExpectedRewards * 100, 2) + ? Math.Round((double)agg.TotalActualRewards / totalExpectedRewards * 100, 2) : 0.0; - var totalOpportunities = totalBlocks + totalMissedBlocks + totalAttestations + totalMissedAttestations; - var successfulOperations = totalBlocks + totalAttestations; + var totalOpportunities = agg.Blocks + agg.MissedBlocks + agg.Endorsements + agg.MissedEndorsements; + var successfulOperations = agg.Blocks + agg.Endorsements; var performance = totalOpportunities > 0 ? Math.Round((double)successfulOperations / totalOpportunities * 100, 2) : 0.0; - var totalExpectedOperations = totalExpectedBlocks + totalExpectedAttestations; - var totalActualOperations = totalBlocks + totalAttestations; + var totalExpectedOperations = agg.TotalExpectedBlocks + agg.TotalExpectedEndorsements; + var totalActualOperations = agg.Blocks + agg.Endorsements; var reliability = totalExpectedOperations > 0 ? Math.Round((double)totalActualOperations / totalExpectedOperations * 100, 2) : 0.0; + var kpis = MapAggregateToKpis(agg); + return new BakerStats { Alias = alias?.Name, @@ -2237,9 +2230,188 @@ public async Task GetBakerStats(string address) Performance = performance, Reliability = reliability, TotalExpectedRewards = totalExpectedRewards, - TotalActualRewards = totalActualRewards + TotalActualRewards = agg.TotalActualRewards, + Cycle = agg.CyclesUsed == 1 ? agg.SingleCycle : (int?)null, + CyclesUsed = agg.CyclesUsed, + Kpis = kpis }; } + + async Task GetBakerStatsAggregate(int bakerId, int? cycle, int cyclesLimit) + { + const string selectList = """ + COUNT(*) AS "CyclesUsed", + MAX("Cycle") AS "SingleCycle", + ROUND(SUM("ExpectedBlocks"), 2)::bigint AS "TotalExpectedBlocks", + ROUND(SUM("ExpectedEndorsements"), 2)::bigint AS "TotalExpectedEndorsements", + SUM("Blocks") AS "Blocks", + SUM("MissedBlocks") AS "MissedBlocks", + SUM("Endorsements") AS "Endorsements", + SUM("MissedEndorsements") AS "MissedEndorsements", + SUM("BlockRewardsDelegated" + "BlockRewardsStakedOwn" + "BlockRewardsStakedEdge" + "BlockRewardsStakedShared") AS "TotalBlockRewards", + SUM("EndorsementRewardsDelegated" + "EndorsementRewardsStakedOwn" + "EndorsementRewardsStakedEdge" + "EndorsementRewardsStakedShared") AS "TotalEndorsementRewards", + SUM("BlockFees") AS "TotalBlockFees", + SUM("MissedBlockRewards") AS "TotalMissedBlockRewards", + SUM("MissedEndorsementRewards") AS "TotalMissedEndorsementRewards", + SUM("MissedBlockFees") AS "TotalMissedBlockFees", + SUM("NonceRevelationRewardsDelegated" + "NonceRevelationRewardsStakedOwn" + "NonceRevelationRewardsStakedEdge" + "NonceRevelationRewardsStakedShared" + + "VdfRevelationRewardsDelegated" + "VdfRevelationRewardsStakedOwn" + "VdfRevelationRewardsStakedEdge" + "VdfRevelationRewardsStakedShared") AS "TotalRevelationRewards", + SUM("NonceRevelationLosses") AS "TotalNonceRevelationLosses", + SUM("DoubleBakingRewards" + "DoubleEndorsingRewards") AS "TotalAccusationBounties", + SUM("DoubleBakingLostStaked" + "DoubleBakingLostUnstaked" + "DoubleEndorsingLostStaked" + "DoubleEndorsingLostUnstaked") AS "SlashedRewards", + SUM("DoubleBakingLostStaked" + "DoubleBakingLostUnstaked" + "DoubleEndorsingLostStaked" + "DoubleEndorsingLostUnstaked" + + "DoubleBakingLostExternalStaked" + "DoubleBakingLostExternalUnstaked" + "DoubleEndorsingLostExternalStaked" + "DoubleEndorsingLostExternalUnstaked") AS "TotalSlashed", + SUM("BlockRewardsDelegated" + "EndorsementRewardsDelegated" + "BlockRewardsStakedShared" + "EndorsementRewardsStakedShared") AS "ExpectedDistribution", + SUM(CASE WHEN "MissedBlockRewards" > 0 OR "MissedEndorsementRewards" > 0 THEN 1 ELSE 0 END)::int AS "MissedRights", + SUM("BlockRewardsDelegated" + "EndorsementRewardsDelegated") AS "TotalRewardsDelegated", + SUM("BlockRewardsStakedShared" + "EndorsementRewardsStakedShared") AS "TotalRewardsStakedShared", + SUM("BlockRewardsStakedOwn" + "EndorsementRewardsStakedOwn") AS "TotalRewardsOwnStake", + SUM("BlockRewardsStakedEdge" + "EndorsementRewardsStakedEdge") AS "TotalEdgeFees" + """; + + const string cteColumns = """ + "Cycle", "ExpectedBlocks", "ExpectedEndorsements", "Blocks", "MissedBlocks", "Endorsements", "MissedEndorsements", + "BlockRewardsDelegated", "BlockRewardsStakedOwn", "BlockRewardsStakedEdge", "BlockRewardsStakedShared", + "EndorsementRewardsDelegated", "EndorsementRewardsStakedOwn", "EndorsementRewardsStakedEdge", "EndorsementRewardsStakedShared", + "BlockFees", "MissedBlockRewards", "MissedEndorsementRewards", "MissedBlockFees", + "NonceRevelationRewardsDelegated", "NonceRevelationRewardsStakedOwn", "NonceRevelationRewardsStakedEdge", "NonceRevelationRewardsStakedShared", + "VdfRevelationRewardsDelegated", "VdfRevelationRewardsStakedOwn", "VdfRevelationRewardsStakedEdge", "VdfRevelationRewardsStakedShared", + "NonceRevelationLosses", + "DoubleBakingRewards", "DoubleEndorsingRewards", + "DoubleBakingLostStaked", "DoubleBakingLostUnstaked", "DoubleEndorsingLostStaked", "DoubleEndorsingLostUnstaked", + "DoubleBakingLostExternalStaked", "DoubleBakingLostExternalUnstaked", "DoubleEndorsingLostExternalStaked", "DoubleEndorsingLostExternalUnstaked" + """; + + string sql; + object param; + if (cycle.HasValue) + { + sql = $""" + SELECT {selectList} + FROM "BakerCycles" + WHERE "BakerId" = @bakerId AND "Cycle" = @cycle + """; + param = new { bakerId, cycle = cycle.Value }; + } + else + { + sql = $""" + WITH selected_cycles AS ( + SELECT {cteColumns} + FROM "BakerCycles" + WHERE "BakerId" = @bakerId + ORDER BY "Cycle" DESC + LIMIT @limit + ) + SELECT {selectList} + FROM selected_cycles + """; + param = new { bakerId, limit = cyclesLimit }; + } + + await using var db = await DataSource.OpenConnectionAsync(); + var sw = Stopwatch.StartNew(); + var row = await db.QuerySingleOrDefaultAsync(sql, param); + sw.Stop(); + if (Logger?.IsEnabled(LogLevel.Debug) == true) + Logger.LogDebug("GetBakerStatsAggregate: bakerId={BakerId}, cycle={Cycle}, cyclesLimit={CyclesLimit}, cyclesReturned={Cycles}, elapsedMs={ElapsedMs}", + bakerId, cycle, cyclesLimit, row?.CyclesUsed ?? 0, sw.ElapsedMilliseconds); + return row?.CyclesUsed > 0 ? row : null; + } + + static BakerStatsKpis MapAggregateToKpis(BakerStatsAggregate a) + { + var totalBlockAndEndorsement = a.TotalBlockRewards + a.TotalEndorsementRewards; + var totalActualRewards = totalBlockAndEndorsement + a.TotalBlockFees; + var totalMissedRewards = a.TotalMissedBlockRewards + a.TotalMissedEndorsementRewards + a.TotalMissedBlockFees; + var kpisExpectedRewards = totalActualRewards + totalMissedRewards; + var invExpected = kpisExpectedRewards > 0 ? 100.0 / kpisExpectedRewards : 0.0; + var monetaryPerformance = kpisExpectedRewards > 0 ? (double)totalActualRewards * invExpected : 100.0; + var luckRatio = kpisExpectedRewards > 0 ? (double)totalActualRewards / kpisExpectedRewards : 1.0; + + var totalIncome = totalActualRewards + a.TotalRevelationRewards + a.TotalAccusationBounties; + var extraRewards = a.TotalRevelationRewards + a.TotalBlockFees + a.TotalAccusationBounties; + var lostRewards = totalMissedRewards + a.TotalNonceRevelationLosses; + + var totalRewards = totalBlockAndEndorsement; + var totalActivity = a.Blocks + a.Endorsements; + var totalMissedActivity = a.MissedBlocks + a.MissedEndorsements; + var totalOps = totalActivity + totalMissedActivity; + var performanceRate = totalOps > 0 ? (double)totalActivity / totalOps * 100.0 : 100.0; + var avgRewardsPerCycle = a.CyclesUsed > 0 ? (double)totalRewards / a.CyclesUsed : 0.0; + var totalDistributable = a.TotalRewardsDelegated + a.TotalRewardsStakedShared + a.TotalRewardsOwnStake + a.TotalEdgeFees; + var invDistributable = totalDistributable > 0 ? 100.0 / totalDistributable : 0.0; + var delegatorSharePercent = (double)a.TotalRewardsDelegated * invDistributable; + var coStakerSharePercent = (double)a.TotalRewardsStakedShared * invDistributable; + var validatorSharePercent = (double)(a.TotalRewardsOwnStake + a.TotalEdgeFees) * invDistributable; + + return new BakerStatsKpis + { + TotalIncome = totalIncome, + ExtraRewards = extraRewards, + LostRewards = lostRewards, + SlashedRewards = a.SlashedRewards, + TotalSlashed = a.TotalSlashed, + BlocksBaked = a.Blocks, + BlocksProposed = a.Blocks + a.MissedBlocks, + TotalBlockRewards = a.TotalBlockRewards, + TotalEndorsementRewards = a.TotalEndorsementRewards, + EndorsementsMade = a.Endorsements, + EndorsementsMissed = a.MissedEndorsements, + ExpectedDistribution = a.ExpectedDistribution, + TechnicalReliability = Math.Round(monetaryPerformance, 2), + MonetaryPerformance = Math.Round(monetaryPerformance, 2), + FairEfficiency = Math.Round(monetaryPerformance, 2), + LuckRatio = Math.Round(luckRatio, 4), + TotalExpectedRewards = kpisExpectedRewards, + TotalActualRewards = totalActualRewards, + MissedRights = a.MissedRights, + TotalRewards = totalRewards, + TotalBlockFees = a.TotalBlockFees, + TotalRevelationRewards = a.TotalRevelationRewards, + MissedBlocks = a.MissedBlocks, + MissedEndorsements = a.MissedEndorsements, + PerformanceRate = Math.Round(performanceRate, 2), + AvgRewardsPerCycle = Math.Round(avgRewardsPerCycle, 2), + TotalRewardsDelegated = a.TotalRewardsDelegated, + TotalRewardsStakedShared = a.TotalRewardsStakedShared, + TotalRewardsOwnStake = a.TotalRewardsOwnStake, + TotalEdgeFees = a.TotalEdgeFees, + DelegatorSharePercent = Math.Round(delegatorSharePercent, 2), + CoStakerSharePercent = Math.Round(coStakerSharePercent, 2), + ValidatorSharePercent = Math.Round(validatorSharePercent, 2) + }; + } + + sealed class BakerStatsAggregate + { + public int CyclesUsed { get; init; } + public int SingleCycle { get; init; } + public long TotalExpectedBlocks { get; init; } + public long TotalExpectedEndorsements { get; init; } + public long Blocks { get; init; } + public long MissedBlocks { get; init; } + public long Endorsements { get; init; } + public long MissedEndorsements { get; init; } + public long TotalBlockRewards { get; init; } + public long TotalEndorsementRewards { get; init; } + public long TotalBlockFees { get; init; } + public long TotalMissedBlockRewards { get; init; } + public long TotalMissedEndorsementRewards { get; init; } + public long TotalMissedBlockFees { get; init; } + public long TotalRevelationRewards { get; init; } + public long TotalNonceRevelationLosses { get; init; } + public long TotalAccusationBounties { get; init; } + public long SlashedRewards { get; init; } + public long TotalSlashed { get; init; } + public long ExpectedDistribution { get; init; } + public int MissedRights { get; init; } + public long TotalRewardsDelegated { get; init; } + public long TotalRewardsStakedShared { get; init; } + public long TotalRewardsOwnStake { get; init; } + public long TotalEdgeFees { get; init; } + public long TotalActualRewards => TotalBlockRewards + TotalEndorsementRewards + TotalBlockFees; + } #endregion } } diff --git a/Mvkt.Api/Validation/TzAddressAttribute.cs b/Mvkt.Api/Validation/MvAddressAttribute.cs similarity index 78% rename from Mvkt.Api/Validation/TzAddressAttribute.cs rename to Mvkt.Api/Validation/MvAddressAttribute.cs index 8fe4f11ae..560facab3 100644 --- a/Mvkt.Api/Validation/TzAddressAttribute.cs +++ b/Mvkt.Api/Validation/MvAddressAttribute.cs @@ -1,8 +1,8 @@ -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; namespace System.ComponentModel.DataAnnotations { - public sealed class TzAddressAttribute : ValidationAttribute + public sealed class MvAddressAttribute : ValidationAttribute { protected override ValidationResult IsValid(object value, ValidationContext validationContext) { diff --git a/Mvkt.Api/appsettings.json b/Mvkt.Api/appsettings.json index eca5cac0f..ba1182b15 100644 --- a/Mvkt.Api/appsettings.json +++ b/Mvkt.Api/appsettings.json @@ -49,9 +49,8 @@ "Logging": { "LogLevel": { "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information", - "Npgsql.Command": "Warning" + "Npgsql": "Information", + "Mvkt.Api.Repositories.RewardsRepository": "Debug" } }, "Kestrel": { diff --git a/Mvkt.Data/Migrations/20250220100000_BakerCycles_BakerId_CycleDesc.cs b/Mvkt.Data/Migrations/20250220100000_BakerCycles_BakerId_CycleDesc.cs new file mode 100644 index 000000000..c82456e05 --- /dev/null +++ b/Mvkt.Data/Migrations/20250220100000_BakerCycles_BakerId_CycleDesc.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Mvkt.Data.Migrations +{ + /// + public partial class BakerCycles_BakerId_CycleDesc : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(@" + CREATE INDEX IF NOT EXISTS ""IX_BakerCycles_BakerId_Cycle_Desc"" + ON ""BakerCycles"" (""BakerId"", ""Cycle"" DESC); + "); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BakerCycles_BakerId_Cycle_Desc", + table: "BakerCycles"); + } + } +} diff --git a/Mvkt.Data/Migrations/MvktContextModelSnapshot.cs b/Mvkt.Data/Migrations/MvktContextModelSnapshot.cs index 8cc4ddcfe..234ef43c0 100644 --- a/Mvkt.Data/Migrations/MvktContextModelSnapshot.cs +++ b/Mvkt.Data/Migrations/MvktContextModelSnapshot.cs @@ -1,4 +1,4 @@ -// +// using System; using System.Numerics; using System.Text.Json; @@ -850,6 +850,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("BakerId"); + b.HasIndex("BakerId", "Cycle") + .IsDescending(false, true); + b.HasIndex("Cycle", "BakerId"); b.ToTable("BakerCycles"); diff --git a/Mvkt.Data/Models/Baking/BakerCycle.cs b/Mvkt.Data/Models/Baking/BakerCycle.cs index 29b44374c..a137b7a37 100644 --- a/Mvkt.Data/Models/Baking/BakerCycle.cs +++ b/Mvkt.Data/Models/Baking/BakerCycle.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; namespace Mvkt.Data.Models { @@ -98,6 +98,10 @@ public static void BuildBakerCycleModel(this ModelBuilder modelBuilder) modelBuilder.Entity() .HasIndex(x => x.BakerId); + + modelBuilder.Entity() + .HasIndex(x => new { x.BakerId, x.Cycle }) + .IsDescending(false, true); #endregion } } diff --git a/Mvkt.Sync/Services/Cache/BlocksCache.cs b/Mvkt.Sync/Services/Cache/BlocksCache.cs index 207b10b3e..eacdb0f31 100644 --- a/Mvkt.Sync/Services/Cache/BlocksCache.cs +++ b/Mvkt.Sync/Services/Cache/BlocksCache.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -67,7 +67,7 @@ public async Task GetAsync(int level) { if (!CachedBlocks.TryGetValue(level, out var block)) { - block = await Db.Blocks.FirstOrDefaultAsync(x => x.Level == level) + block = await Db.Blocks.OrderBy(x => x.Id).FirstOrDefaultAsync(x => x.Level == level) ?? throw new Exception($"Block #{level} doesn't exist"); Add(block); diff --git a/docker-compose.yml b/docker-compose.yml index 08244208d..a36a8a280 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,7 @@ services: volumes: - postgres:/var/lib/postgresql/data ports: - - 127.0.0.1:5432:5432 + - 127.0.0.1:5832:5432 api: container_name: mvkt-api @@ -24,7 +24,7 @@ services: ConnectionStrings__DefaultConnection: host=db;port=5432;database=${POSTGRES_DB:-mvkt_db};username=${POSTGRES_USER:-mvkt};password=${POSTGRES_PASSWORD:-qwerty};command timeout=${COMMAND_TIMEOUT:-600}; Kestrel__Endpoints__Http__Url: http://0.0.0.0:5000 ports: - - 0.0.0.0:5000:5000 + - 0.0.0.0:5002:5000 sync: container_name: mvkt-sync From 037cd06236f26b2874632ef402eba95ced94b6a0 Mon Sep 17 00:00:00 2001 From: 0xVoronov Date: Fri, 20 Feb 2026 20:51:58 +0300 Subject: [PATCH 02/11] Added migration with index --- ...cs => 20250220100000_BakerCycles_BakerId_CycleDesc.cs.cs} | 5 +++++ 1 file changed, 5 insertions(+) rename Mvkt.Data/Migrations/{20250220100000_BakerCycles_BakerId_CycleDesc.cs => 20250220100000_BakerCycles_BakerId_CycleDesc.cs.cs} (82%) diff --git a/Mvkt.Data/Migrations/20250220100000_BakerCycles_BakerId_CycleDesc.cs b/Mvkt.Data/Migrations/20250220100000_BakerCycles_BakerId_CycleDesc.cs.cs similarity index 82% rename from Mvkt.Data/Migrations/20250220100000_BakerCycles_BakerId_CycleDesc.cs rename to Mvkt.Data/Migrations/20250220100000_BakerCycles_BakerId_CycleDesc.cs.cs index c82456e05..97e18513b 100644 --- a/Mvkt.Data/Migrations/20250220100000_BakerCycles_BakerId_CycleDesc.cs +++ b/Mvkt.Data/Migrations/20250220100000_BakerCycles_BakerId_CycleDesc.cs.cs @@ -1,10 +1,14 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; +using Mvkt.Data; #nullable disable namespace Mvkt.Data.Migrations { /// + [DbContext(typeof(MvktContext))] + [Migration("20250220100000_BakerCycles_BakerId_CycleDesc")] public partial class BakerCycles_BakerId_CycleDesc : Migration { /// @@ -25,3 +29,4 @@ protected override void Down(MigrationBuilder migrationBuilder) } } } + From 621e5470bbb8d1f531efb961e187e7f62d242aeb Mon Sep 17 00:00:00 2001 From: 0xVoronov Date: Fri, 20 Feb 2026 20:52:50 +0300 Subject: [PATCH 03/11] Fixed warning --- ...100000_BakerCycles_BakerId_CycleDesc.cs.cs | 32 ------------------- .../Handlers/Proto20/Commits/BlockCommit.cs | 4 +-- .../Operations/DalPublishCommitmentCommit.cs | 6 ++-- .../MavrykExternalDataProvider.cs | 4 +-- Mvkt.Sync/Services/Quotes/QuotesService.cs | 7 ++-- 5 files changed, 11 insertions(+), 42 deletions(-) delete mode 100644 Mvkt.Data/Migrations/20250220100000_BakerCycles_BakerId_CycleDesc.cs.cs diff --git a/Mvkt.Data/Migrations/20250220100000_BakerCycles_BakerId_CycleDesc.cs.cs b/Mvkt.Data/Migrations/20250220100000_BakerCycles_BakerId_CycleDesc.cs.cs deleted file mode 100644 index 97e18513b..000000000 --- a/Mvkt.Data/Migrations/20250220100000_BakerCycles_BakerId_CycleDesc.cs.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Mvkt.Data; - -#nullable disable - -namespace Mvkt.Data.Migrations -{ - /// - [DbContext(typeof(MvktContext))] - [Migration("20250220100000_BakerCycles_BakerId_CycleDesc")] - public partial class BakerCycles_BakerId_CycleDesc : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql(@" - CREATE INDEX IF NOT EXISTS ""IX_BakerCycles_BakerId_Cycle_Desc"" - ON ""BakerCycles"" (""BakerId"", ""Cycle"" DESC); - "); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_BakerCycles_BakerId_Cycle_Desc", - table: "BakerCycles"); - } - } -} - diff --git a/Mvkt.Sync/Protocols/Handlers/Proto20/Commits/BlockCommit.cs b/Mvkt.Sync/Protocols/Handlers/Proto20/Commits/BlockCommit.cs index ff8be0c9f..b6d7165d4 100644 --- a/Mvkt.Sync/Protocols/Handlers/Proto20/Commits/BlockCommit.cs +++ b/Mvkt.Sync/Protocols/Handlers/Proto20/Commits/BlockCommit.cs @@ -1,4 +1,4 @@ -using System.Text.Json; +using System.Text.Json; namespace Mvkt.Sync.Protocols.Proto20 { @@ -6,7 +6,7 @@ class BlockCommit : Proto19.BlockCommit { public BlockCommit(ProtocolHandler protocol) : base(protocol) { } - public async Task ApplyRewards(JsonElement rawBlock) + public new async Task ApplyRewards(JsonElement rawBlock) { var proposer = Cache.Accounts.GetDelegate(Block.ProposerId); var producer = Cache.Accounts.GetDelegate(Block.ProducerId); diff --git a/Mvkt.Sync/Protocols/Handlers/Proto20/Commits/Operations/DalPublishCommitmentCommit.cs b/Mvkt.Sync/Protocols/Handlers/Proto20/Commits/Operations/DalPublishCommitmentCommit.cs index 139750785..f87301860 100644 --- a/Mvkt.Sync/Protocols/Handlers/Proto20/Commits/Operations/DalPublishCommitmentCommit.cs +++ b/Mvkt.Sync/Protocols/Handlers/Proto20/Commits/Operations/DalPublishCommitmentCommit.cs @@ -1,4 +1,4 @@ -using System.Text.Json; +using System.Text.Json; using Mvkt.Data.Models; using Mvkt.Data.Models.Base; @@ -8,7 +8,7 @@ class DalPublishCommitmentCommit : Proto19.DalPublishCommitmentCommit { public DalPublishCommitmentCommit(ProtocolHandler protocol) : base(protocol) { } - public async Task Apply(Block block, JsonElement op, JsonElement content) + public new async Task Apply(Block block, JsonElement op, JsonElement content) { #region init var sender = await Cache.Accounts.GetAsync(content.RequiredString("source")) as User; @@ -82,7 +82,7 @@ public async Task Apply(Block block, JsonElement op, JsonElement content) Db.DalPublishCommitmentOps.Add(operation); } - public async Task Revert(Block block, DalPublishCommitmentOperation operation) + public new async Task Revert(Block block, DalPublishCommitmentOperation operation) { var sender = await Cache.Accounts.GetAsync(operation.SenderId) as User; var senderDelegate = sender as Data.Models.Delegate ?? Cache.Accounts.GetDelegate(sender.DelegateId); diff --git a/Mvkt.Sync/Services/Quotes/Providers/MavrykExternalData/MavrykExternalDataProvider.cs b/Mvkt.Sync/Services/Quotes/Providers/MavrykExternalData/MavrykExternalDataProvider.cs index 0e14ab835..f0efd5d12 100644 --- a/Mvkt.Sync/Services/Quotes/Providers/MavrykExternalData/MavrykExternalDataProvider.cs +++ b/Mvkt.Sync/Services/Quotes/Providers/MavrykExternalData/MavrykExternalDataProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; @@ -87,7 +87,7 @@ public async Task FillQuotes(IEnumerable quotes, IQuote last) return quotes.Count(); } - async Task?> GetQuotes(DateTime from, DateTime to) + async Task> GetQuotes(DateTime from, DateTime to) { try { diff --git a/Mvkt.Sync/Services/Quotes/QuotesService.cs b/Mvkt.Sync/Services/Quotes/QuotesService.cs index 139ff79fe..b4edb814f 100644 --- a/Mvkt.Sync/Services/Quotes/QuotesService.cs +++ b/Mvkt.Sync/Services/Quotes/QuotesService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -36,7 +36,7 @@ public QuotesService(MvktContext db, CacheService cache, IQuoteProvider provider Logger = logger; } - public async Task Init() + public Task Init() { Logger.LogInformation("Quote provider: {ProviderName} ({Mode})", Provider.GetType().Name, Config.Async ? "Async" : "Sync"); @@ -46,6 +46,7 @@ public async Task Init() var totalMissed = state.Level - state.QuoteLevel; Logger.LogInformation("{TotalMissed} quotes missed. QuotesSyncService will sync them in background.", totalMissed); } + return Task.CompletedTask; } public async Task SyncBatch() @@ -275,7 +276,7 @@ void UpdateAppState(AppState state, Quote last) state.QuoteGbp = last.Gbp; } - IQuote? LastQuote(AppState state) + IQuote LastQuote(AppState state) { if (state.QuoteLevel < 0) return null; From 070e0c67c2fdf1a0da13bc39bce98e491f2ecd7c Mon Sep 17 00:00:00 2001 From: 0xVoronov Date: Fri, 20 Feb 2026 20:53:03 +0300 Subject: [PATCH 04/11] Added migration with index --- ...220100000_BakerCycles_BakerId_CycleDesc.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Mvkt.Data/Migrations/20250220100000_BakerCycles_BakerId_CycleDesc.cs diff --git a/Mvkt.Data/Migrations/20250220100000_BakerCycles_BakerId_CycleDesc.cs b/Mvkt.Data/Migrations/20250220100000_BakerCycles_BakerId_CycleDesc.cs new file mode 100644 index 000000000..5273686d9 --- /dev/null +++ b/Mvkt.Data/Migrations/20250220100000_BakerCycles_BakerId_CycleDesc.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Mvkt.Data; + +#nullable disable + +namespace Mvkt.Data.Migrations +{ + /// + [DbContext(typeof(MvktContext))] + [Migration("20250220100000_BakerCycles_BakerId_CycleDesc")] + public partial class BakerCycles_BakerId_CycleDesc : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(@" + CREATE INDEX IF NOT EXISTS ""IX_BakerCycles_BakerId_Cycle_Desc"" + ON ""BakerCycles"" (""BakerId"", ""Cycle"" DESC); + "); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BakerCycles_BakerId_Cycle_Desc", + table: "BakerCycles"); + } + } +} From 65c10ce47cf7719e436588203ac1b73a15428b1c Mon Sep 17 00:00:00 2001 From: 0xVoronov Date: Fri, 20 Feb 2026 21:35:43 +0300 Subject: [PATCH 05/11] Implemented new tests --- Mvkt.Api.Tests/Api/TestRewardsQueries.cs | 51 ++++++++++++++++------ Mvkt.Api/Repositories/RewardsRepository.cs | 4 +- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/Mvkt.Api.Tests/Api/TestRewardsQueries.cs b/Mvkt.Api.Tests/Api/TestRewardsQueries.cs index 07e38d910..dc7a1acc8 100644 --- a/Mvkt.Api.Tests/Api/TestRewardsQueries.cs +++ b/Mvkt.Api.Tests/Api/TestRewardsQueries.cs @@ -39,25 +39,48 @@ public async Task TestBakerStats() var res = await Client.GetJsonAsync($"/v1/rewards/bakers/{Settings.Baker}/stats"); Assert.True(res is DJsonObject); - + Assert.True((double)res.luck >= 0); Assert.True((double)res.performance >= 0 && (double)res.performance <= 100); Assert.True((double)res.reliability >= 0 && (double)res.reliability <= 100); Assert.True((long)res.totalExpectedRewards >= 0); Assert.True((long)res.totalActualRewards >= 0); - - if (res.apy != null) - { - Assert.True(res.apy is DJsonObject); - - Assert.True((double)res.apy.ownStakeApy >= 0); - Assert.True((double)res.apy.externalStakeApy >= 0); - Assert.True((double)res.apy.delegationApy >= 0); - - Assert.True((double)res.apy.ownStakeApy < 1000); - Assert.True((double)res.apy.externalStakeApy < 1000); - Assert.True((double)res.apy.delegationApy < 1000); - } + + Assert.True(res.apy is DJsonObject); + Assert.True(((double)res.apy.ownStakeApy >= 0 && (double)res.apy.externalStakeApy >= 0 && (double)res.apy.delegationApy >= 0)); + Assert.True(((double)res.apy.ownStakeApy < 1000 && (double)res.apy.externalStakeApy < 1000 && (double)res.apy.delegationApy < 1000)); + + Assert.True((int)res.cyclesUsed > 0); + Assert.True(res.kpis is DJsonObject); + } + + [Fact] + public async Task TestBakerStatsWithCycle() + { + var res = await Client.GetJsonAsync($"/v1/rewards/bakers/{Settings.Baker}/stats?cycle={Settings.Cycle}"); + + Assert.True(res is DJsonObject); + var cyclesUsed = res.cyclesUsed != null ? (int)res.cyclesUsed : 0; + Assert.True(cyclesUsed == 1); + Assert.True((int)res.cycle == Settings.Cycle); + Assert.True(res.kpis is DJsonObject); + } + + [Fact] + public async Task TestBakerStatsWithCyclesLimit() + { + var res = await Client.GetJsonAsync($"/v1/rewards/bakers/{Settings.Baker}/stats?cyclesLimit=5"); + + Assert.True(res is DJsonObject); + Assert.True((int)res.cyclesUsed == 5); + Assert.Null(res.cycle); + } + + [Fact] + public async Task TestBakerStatsInvalidBakerAddress() + { + var response = await Client.GetAsync("/v1/rewards/bakers/mv111111111111111111111111111111111111/stats"); + Assert.Equal(System.Net.HttpStatusCode.BadRequest, response.StatusCode); } [Fact] diff --git a/Mvkt.Api/Repositories/RewardsRepository.cs b/Mvkt.Api/Repositories/RewardsRepository.cs index cf0b9d678..0a88a9b26 100644 --- a/Mvkt.Api/Repositories/RewardsRepository.cs +++ b/Mvkt.Api/Repositories/RewardsRepository.cs @@ -2242,8 +2242,8 @@ async Task GetBakerStatsAggregate(int bakerId, int? cycle, const string selectList = """ COUNT(*) AS "CyclesUsed", MAX("Cycle") AS "SingleCycle", - ROUND(SUM("ExpectedBlocks"), 2)::bigint AS "TotalExpectedBlocks", - ROUND(SUM("ExpectedEndorsements"), 2)::bigint AS "TotalExpectedEndorsements", + ROUND(SUM("ExpectedBlocks")::numeric, 2)::bigint AS "TotalExpectedBlocks", + ROUND(SUM("ExpectedEndorsements")::numeric, 2)::bigint AS "TotalExpectedEndorsements", SUM("Blocks") AS "Blocks", SUM("MissedBlocks") AS "MissedBlocks", SUM("Endorsements") AS "Endorsements", From 9456d4d6db8a578067ad97749e76a5a9753a28e7 Mon Sep 17 00:00:00 2001 From: 0xVoronov Date: Fri, 20 Feb 2026 21:43:01 +0300 Subject: [PATCH 06/11] Implement cache response --- Mvkt.Api/Controllers/RewardsController.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Mvkt.Api/Controllers/RewardsController.cs b/Mvkt.Api/Controllers/RewardsController.cs index bccd8e991..87a0bf59c 100644 --- a/Mvkt.Api/Controllers/RewardsController.cs +++ b/Mvkt.Api/Controllers/RewardsController.cs @@ -1,8 +1,9 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc; using NSwag.Annotations; using Mvkt.Api.Models; using Mvkt.Api.Repositories; +using Mvkt.Api.Services; namespace Mvkt.Api.Controllers { @@ -12,11 +13,13 @@ public class RewardsController : ControllerBase { private readonly RewardsRepository Rewards; private readonly StakingRepository Staking; + private readonly ResponseCacheService ResponseCache; - public RewardsController(RewardsRepository rewards, StakingRepository staking) + public RewardsController(RewardsRepository rewards, StakingRepository staking, ResponseCacheService responseCache) { Rewards = rewards; Staking = staking; + ResponseCache = responseCache; } /// @@ -221,13 +224,19 @@ public async Task> GetBakerStats( [FromQuery] int? cycle = null, [FromQuery] int cyclesLimit = 10000) { + var query = ResponseCacheService.BuildKey(Request.Path.Value, ("cycle", cycle), ("cyclesLimit", cyclesLimit)); + + if (ResponseCache.TryGet(query, out var cached)) + return this.Bytes(cached); + var stats = await Rewards.GetBakerStats(address, cycle, cyclesLimit); if (stats == null) return NotFound(); stats.Apy = await Staking.GetBakerApy(address); - return Ok(stats); + cached = ResponseCache.Set(query, stats); + return this.Bytes(cached); } } } From c69dc38507cd1a89f477de5a533011f28bb0fa94 Mon Sep 17 00:00:00 2001 From: 0xVoronov Date: Mon, 23 Feb 2026 08:09:09 +0300 Subject: [PATCH 07/11] Updated docker file --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a36a8a280..08244208d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,7 @@ services: volumes: - postgres:/var/lib/postgresql/data ports: - - 127.0.0.1:5832:5432 + - 127.0.0.1:5432:5432 api: container_name: mvkt-api @@ -24,7 +24,7 @@ services: ConnectionStrings__DefaultConnection: host=db;port=5432;database=${POSTGRES_DB:-mvkt_db};username=${POSTGRES_USER:-mvkt};password=${POSTGRES_PASSWORD:-qwerty};command timeout=${COMMAND_TIMEOUT:-600}; Kestrel__Endpoints__Http__Url: http://0.0.0.0:5000 ports: - - 0.0.0.0:5002:5000 + - 0.0.0.0:5000:5000 sync: container_name: mvkt-sync From a0301e6e6bc0d88c5166dfd32e96e3dbfce01be5 Mon Sep 17 00:00:00 2001 From: 0xVoronov Date: Mon, 23 Feb 2026 08:10:46 +0300 Subject: [PATCH 08/11] Removed changes, kepp it for future refact --- Mvkt.Sync/Services/Cache/BlocksCache.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mvkt.Sync/Services/Cache/BlocksCache.cs b/Mvkt.Sync/Services/Cache/BlocksCache.cs index eacdb0f31..207b10b3e 100644 --- a/Mvkt.Sync/Services/Cache/BlocksCache.cs +++ b/Mvkt.Sync/Services/Cache/BlocksCache.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -67,7 +67,7 @@ public async Task GetAsync(int level) { if (!CachedBlocks.TryGetValue(level, out var block)) { - block = await Db.Blocks.OrderBy(x => x.Id).FirstOrDefaultAsync(x => x.Level == level) + block = await Db.Blocks.FirstOrDefaultAsync(x => x.Level == level) ?? throw new Exception($"Block #{level} doesn't exist"); Add(block); From 5a63bd61f1f4a15a10f715d730ef8ec88016c947 Mon Sep 17 00:00:00 2001 From: 0xVoronov Date: Mon, 23 Feb 2026 08:28:25 +0300 Subject: [PATCH 09/11] Simplified aggregated tasks --- Mvkt.Api/Repositories/RewardsRepository.cs | 102 ++++++++++----------- 1 file changed, 49 insertions(+), 53 deletions(-) diff --git a/Mvkt.Api/Repositories/RewardsRepository.cs b/Mvkt.Api/Repositories/RewardsRepository.cs index 0a88a9b26..965a6c4d2 100644 --- a/Mvkt.Api/Repositories/RewardsRepository.cs +++ b/Mvkt.Api/Repositories/RewardsRepository.cs @@ -2194,34 +2194,30 @@ public async Task GetBakerStats(string address, int? cycle = null, i if (await Accounts.GetAsync(address) is not RawDelegate baker) return null; - var aggTask = GetBakerStatsAggregate(baker.Id, cycle, cyclesLimit); - var aliasTask = Accounts.GetAliasAsync(baker.Id); - await Task.WhenAll(aggTask, aliasTask); - - var agg = await aggTask; - if (agg == null) + var totals = await GetBakerStatsTotals(baker.Id, cycle, cyclesLimit); + if (totals == null) return null; - var alias = await aliasTask; + var alias = await Accounts.GetAliasAsync(baker.Id); - var totalExpectedRewards = agg.TotalActualRewards + agg.TotalMissedBlockRewards + agg.TotalMissedEndorsementRewards; + var totalExpectedRewards = totals.TotalActualRewards + totals.TotalMissedBlockRewards + totals.TotalMissedEndorsementRewards; var luck = totalExpectedRewards > 0 - ? Math.Round((double)agg.TotalActualRewards / totalExpectedRewards * 100, 2) + ? Math.Round((double)totals.TotalActualRewards / totalExpectedRewards * 100, 2) : 0.0; - var totalOpportunities = agg.Blocks + agg.MissedBlocks + agg.Endorsements + agg.MissedEndorsements; - var successfulOperations = agg.Blocks + agg.Endorsements; + var totalOpportunities = totals.Blocks + totals.MissedBlocks + totals.Endorsements + totals.MissedEndorsements; + var successfulOperations = totals.Blocks + totals.Endorsements; var performance = totalOpportunities > 0 ? Math.Round((double)successfulOperations / totalOpportunities * 100, 2) : 0.0; - var totalExpectedOperations = agg.TotalExpectedBlocks + agg.TotalExpectedEndorsements; - var totalActualOperations = agg.Blocks + agg.Endorsements; + var totalExpectedOperations = totals.TotalExpectedBlocks + totals.TotalExpectedEndorsements; + var totalActualOperations = totals.Blocks + totals.Endorsements; var reliability = totalExpectedOperations > 0 ? Math.Round((double)totalActualOperations / totalExpectedOperations * 100, 2) : 0.0; - var kpis = MapAggregateToKpis(agg); + var kpis = MapTotalsToKpis(totals); return new BakerStats { @@ -2230,14 +2226,14 @@ public async Task GetBakerStats(string address, int? cycle = null, i Performance = performance, Reliability = reliability, TotalExpectedRewards = totalExpectedRewards, - TotalActualRewards = agg.TotalActualRewards, - Cycle = agg.CyclesUsed == 1 ? agg.SingleCycle : (int?)null, - CyclesUsed = agg.CyclesUsed, + TotalActualRewards = totals.TotalActualRewards, + Cycle = totals.CyclesUsed == 1 ? totals.SingleCycle : (int?)null, + CyclesUsed = totals.CyclesUsed, Kpis = kpis }; } - async Task GetBakerStatsAggregate(int bakerId, int? cycle, int cyclesLimit) + async Task GetBakerStatsTotals(int bakerId, int? cycle, int cyclesLimit) { const string selectList = """ COUNT(*) AS "CyclesUsed", @@ -2311,79 +2307,79 @@ FROM selected_cycles await using var db = await DataSource.OpenConnectionAsync(); var sw = Stopwatch.StartNew(); - var row = await db.QuerySingleOrDefaultAsync(sql, param); + var row = await db.QuerySingleOrDefaultAsync(sql, param); sw.Stop(); if (Logger?.IsEnabled(LogLevel.Debug) == true) - Logger.LogDebug("GetBakerStatsAggregate: bakerId={BakerId}, cycle={Cycle}, cyclesLimit={CyclesLimit}, cyclesReturned={Cycles}, elapsedMs={ElapsedMs}", + Logger.LogDebug("GetBakerStatsTotals: bakerId={BakerId}, cycle={Cycle}, cyclesLimit={CyclesLimit}, cyclesReturned={Cycles}, elapsedMs={ElapsedMs}", bakerId, cycle, cyclesLimit, row?.CyclesUsed ?? 0, sw.ElapsedMilliseconds); return row?.CyclesUsed > 0 ? row : null; } - static BakerStatsKpis MapAggregateToKpis(BakerStatsAggregate a) + static BakerStatsKpis MapTotalsToKpis(BakerStatsTotals totals) { - var totalBlockAndEndorsement = a.TotalBlockRewards + a.TotalEndorsementRewards; - var totalActualRewards = totalBlockAndEndorsement + a.TotalBlockFees; - var totalMissedRewards = a.TotalMissedBlockRewards + a.TotalMissedEndorsementRewards + a.TotalMissedBlockFees; + var totalBlockAndEndorsement = totals.TotalBlockRewards + totals.TotalEndorsementRewards; + var totalActualRewards = totalBlockAndEndorsement + totals.TotalBlockFees; + var totalMissedRewards = totals.TotalMissedBlockRewards + totals.TotalMissedEndorsementRewards + totals.TotalMissedBlockFees; var kpisExpectedRewards = totalActualRewards + totalMissedRewards; var invExpected = kpisExpectedRewards > 0 ? 100.0 / kpisExpectedRewards : 0.0; var monetaryPerformance = kpisExpectedRewards > 0 ? (double)totalActualRewards * invExpected : 100.0; var luckRatio = kpisExpectedRewards > 0 ? (double)totalActualRewards / kpisExpectedRewards : 1.0; - var totalIncome = totalActualRewards + a.TotalRevelationRewards + a.TotalAccusationBounties; - var extraRewards = a.TotalRevelationRewards + a.TotalBlockFees + a.TotalAccusationBounties; - var lostRewards = totalMissedRewards + a.TotalNonceRevelationLosses; + var totalIncome = totalActualRewards + totals.TotalRevelationRewards + totals.TotalAccusationBounties; + var extraRewards = totals.TotalRevelationRewards + totals.TotalBlockFees + totals.TotalAccusationBounties; + var lostRewards = totalMissedRewards + totals.TotalNonceRevelationLosses; var totalRewards = totalBlockAndEndorsement; - var totalActivity = a.Blocks + a.Endorsements; - var totalMissedActivity = a.MissedBlocks + a.MissedEndorsements; + var totalActivity = totals.Blocks + totals.Endorsements; + var totalMissedActivity = totals.MissedBlocks + totals.MissedEndorsements; var totalOps = totalActivity + totalMissedActivity; var performanceRate = totalOps > 0 ? (double)totalActivity / totalOps * 100.0 : 100.0; - var avgRewardsPerCycle = a.CyclesUsed > 0 ? (double)totalRewards / a.CyclesUsed : 0.0; - var totalDistributable = a.TotalRewardsDelegated + a.TotalRewardsStakedShared + a.TotalRewardsOwnStake + a.TotalEdgeFees; + var avgRewardsPerCycle = totals.CyclesUsed > 0 ? (double)totalRewards / totals.CyclesUsed : 0.0; + var totalDistributable = totals.TotalRewardsDelegated + totals.TotalRewardsStakedShared + totals.TotalRewardsOwnStake + totals.TotalEdgeFees; var invDistributable = totalDistributable > 0 ? 100.0 / totalDistributable : 0.0; - var delegatorSharePercent = (double)a.TotalRewardsDelegated * invDistributable; - var coStakerSharePercent = (double)a.TotalRewardsStakedShared * invDistributable; - var validatorSharePercent = (double)(a.TotalRewardsOwnStake + a.TotalEdgeFees) * invDistributable; + var delegatorSharePercent = (double)totals.TotalRewardsDelegated * invDistributable; + var coStakerSharePercent = (double)totals.TotalRewardsStakedShared * invDistributable; + var validatorSharePercent = (double)(totals.TotalRewardsOwnStake + totals.TotalEdgeFees) * invDistributable; return new BakerStatsKpis { TotalIncome = totalIncome, ExtraRewards = extraRewards, LostRewards = lostRewards, - SlashedRewards = a.SlashedRewards, - TotalSlashed = a.TotalSlashed, - BlocksBaked = a.Blocks, - BlocksProposed = a.Blocks + a.MissedBlocks, - TotalBlockRewards = a.TotalBlockRewards, - TotalEndorsementRewards = a.TotalEndorsementRewards, - EndorsementsMade = a.Endorsements, - EndorsementsMissed = a.MissedEndorsements, - ExpectedDistribution = a.ExpectedDistribution, + SlashedRewards = totals.SlashedRewards, + TotalSlashed = totals.TotalSlashed, + BlocksBaked = totals.Blocks, + BlocksProposed = totals.Blocks + totals.MissedBlocks, + TotalBlockRewards = totals.TotalBlockRewards, + TotalEndorsementRewards = totals.TotalEndorsementRewards, + EndorsementsMade = totals.Endorsements, + EndorsementsMissed = totals.MissedEndorsements, + ExpectedDistribution = totals.ExpectedDistribution, TechnicalReliability = Math.Round(monetaryPerformance, 2), MonetaryPerformance = Math.Round(monetaryPerformance, 2), FairEfficiency = Math.Round(monetaryPerformance, 2), LuckRatio = Math.Round(luckRatio, 4), TotalExpectedRewards = kpisExpectedRewards, TotalActualRewards = totalActualRewards, - MissedRights = a.MissedRights, + MissedRights = totals.MissedRights, TotalRewards = totalRewards, - TotalBlockFees = a.TotalBlockFees, - TotalRevelationRewards = a.TotalRevelationRewards, - MissedBlocks = a.MissedBlocks, - MissedEndorsements = a.MissedEndorsements, + TotalBlockFees = totals.TotalBlockFees, + TotalRevelationRewards = totals.TotalRevelationRewards, + MissedBlocks = totals.MissedBlocks, + MissedEndorsements = totals.MissedEndorsements, PerformanceRate = Math.Round(performanceRate, 2), AvgRewardsPerCycle = Math.Round(avgRewardsPerCycle, 2), - TotalRewardsDelegated = a.TotalRewardsDelegated, - TotalRewardsStakedShared = a.TotalRewardsStakedShared, - TotalRewardsOwnStake = a.TotalRewardsOwnStake, - TotalEdgeFees = a.TotalEdgeFees, + TotalRewardsDelegated = totals.TotalRewardsDelegated, + TotalRewardsStakedShared = totals.TotalRewardsStakedShared, + TotalRewardsOwnStake = totals.TotalRewardsOwnStake, + TotalEdgeFees = totals.TotalEdgeFees, DelegatorSharePercent = Math.Round(delegatorSharePercent, 2), CoStakerSharePercent = Math.Round(coStakerSharePercent, 2), ValidatorSharePercent = Math.Round(validatorSharePercent, 2) }; } - sealed class BakerStatsAggregate + sealed class BakerStatsTotals { public int CyclesUsed { get; init; } public int SingleCycle { get; init; } From 1fad10c31eceac2a6194f99b911a0772b5601e90 Mon Sep 17 00:00:00 2001 From: 0xVoronov Date: Mon, 23 Feb 2026 08:35:42 +0300 Subject: [PATCH 10/11] Removed map method for simplification --- Mvkt.Api/Repositories/RewardsRepository.cs | 118 +++++++++------------ 1 file changed, 51 insertions(+), 67 deletions(-) diff --git a/Mvkt.Api/Repositories/RewardsRepository.cs b/Mvkt.Api/Repositories/RewardsRepository.cs index 965a6c4d2..c2a264102 100644 --- a/Mvkt.Api/Repositories/RewardsRepository.cs +++ b/Mvkt.Api/Repositories/RewardsRepository.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using Dapper; using Microsoft.Extensions.Logging; using Npgsql; @@ -2217,7 +2217,20 @@ public async Task GetBakerStats(string address, int? cycle = null, i ? Math.Round((double)totalActualOperations / totalExpectedOperations * 100, 2) : 0.0; - var kpis = MapTotalsToKpis(totals); + var totalBlockAndEndorsement = totals.TotalBlockRewards + totals.TotalEndorsementRewards; + var kpisTotalActualRewards = totalBlockAndEndorsement + totals.TotalBlockFees; + var totalMissedRewards = totals.TotalMissedBlockRewards + totals.TotalMissedEndorsementRewards + totals.TotalMissedBlockFees; + var kpisExpectedRewards = kpisTotalActualRewards + totalMissedRewards; + var invExpected = kpisExpectedRewards > 0 ? 100.0 / kpisExpectedRewards : 0.0; + var monetaryPerformance = kpisExpectedRewards > 0 ? (double)kpisTotalActualRewards * invExpected : 100.0; + var luckRatio = kpisExpectedRewards > 0 ? (double)kpisTotalActualRewards / kpisExpectedRewards : 1.0; + var totalActivity = totals.Blocks + totals.Endorsements; + var totalMissedActivity = totals.MissedBlocks + totals.MissedEndorsements; + var totalOps = totalActivity + totalMissedActivity; + var performanceRate = totalOps > 0 ? (double)totalActivity / totalOps * 100.0 : 100.0; + var avgRewardsPerCycle = totals.CyclesUsed > 0 ? (double)totalBlockAndEndorsement / totals.CyclesUsed : 0.0; + var totalDistributable = totals.TotalRewardsDelegated + totals.TotalRewardsStakedShared + totals.TotalRewardsOwnStake + totals.TotalEdgeFees; + var invDistributable = totalDistributable > 0 ? 100.0 / totalDistributable : 0.0; return new BakerStats { @@ -2229,7 +2242,42 @@ public async Task GetBakerStats(string address, int? cycle = null, i TotalActualRewards = totals.TotalActualRewards, Cycle = totals.CyclesUsed == 1 ? totals.SingleCycle : (int?)null, CyclesUsed = totals.CyclesUsed, - Kpis = kpis + Kpis = new BakerStatsKpis + { + TotalIncome = kpisTotalActualRewards + totals.TotalRevelationRewards + totals.TotalAccusationBounties, + ExtraRewards = totals.TotalRevelationRewards + totals.TotalBlockFees + totals.TotalAccusationBounties, + LostRewards = totalMissedRewards + totals.TotalNonceRevelationLosses, + SlashedRewards = totals.SlashedRewards, + TotalSlashed = totals.TotalSlashed, + BlocksBaked = totals.Blocks, + BlocksProposed = totals.Blocks + totals.MissedBlocks, + TotalBlockRewards = totals.TotalBlockRewards, + TotalEndorsementRewards = totals.TotalEndorsementRewards, + EndorsementsMade = totals.Endorsements, + EndorsementsMissed = totals.MissedEndorsements, + ExpectedDistribution = totals.ExpectedDistribution, + TechnicalReliability = Math.Round(monetaryPerformance, 2), + MonetaryPerformance = Math.Round(monetaryPerformance, 2), + FairEfficiency = Math.Round(monetaryPerformance, 2), + LuckRatio = Math.Round(luckRatio, 4), + TotalExpectedRewards = kpisExpectedRewards, + TotalActualRewards = kpisTotalActualRewards, + MissedRights = totals.MissedRights, + TotalRewards = totalBlockAndEndorsement, + TotalBlockFees = totals.TotalBlockFees, + TotalRevelationRewards = totals.TotalRevelationRewards, + MissedBlocks = totals.MissedBlocks, + MissedEndorsements = totals.MissedEndorsements, + PerformanceRate = Math.Round(performanceRate, 2), + AvgRewardsPerCycle = Math.Round(avgRewardsPerCycle, 2), + TotalRewardsDelegated = totals.TotalRewardsDelegated, + TotalRewardsStakedShared = totals.TotalRewardsStakedShared, + TotalRewardsOwnStake = totals.TotalRewardsOwnStake, + TotalEdgeFees = totals.TotalEdgeFees, + DelegatorSharePercent = Math.Round((double)totals.TotalRewardsDelegated * invDistributable, 2), + CoStakerSharePercent = Math.Round((double)totals.TotalRewardsStakedShared * invDistributable, 2), + ValidatorSharePercent = Math.Round((double)(totals.TotalRewardsOwnStake + totals.TotalEdgeFees) * invDistributable, 2) + } }; } @@ -2315,70 +2363,6 @@ FROM selected_cycles return row?.CyclesUsed > 0 ? row : null; } - static BakerStatsKpis MapTotalsToKpis(BakerStatsTotals totals) - { - var totalBlockAndEndorsement = totals.TotalBlockRewards + totals.TotalEndorsementRewards; - var totalActualRewards = totalBlockAndEndorsement + totals.TotalBlockFees; - var totalMissedRewards = totals.TotalMissedBlockRewards + totals.TotalMissedEndorsementRewards + totals.TotalMissedBlockFees; - var kpisExpectedRewards = totalActualRewards + totalMissedRewards; - var invExpected = kpisExpectedRewards > 0 ? 100.0 / kpisExpectedRewards : 0.0; - var monetaryPerformance = kpisExpectedRewards > 0 ? (double)totalActualRewards * invExpected : 100.0; - var luckRatio = kpisExpectedRewards > 0 ? (double)totalActualRewards / kpisExpectedRewards : 1.0; - - var totalIncome = totalActualRewards + totals.TotalRevelationRewards + totals.TotalAccusationBounties; - var extraRewards = totals.TotalRevelationRewards + totals.TotalBlockFees + totals.TotalAccusationBounties; - var lostRewards = totalMissedRewards + totals.TotalNonceRevelationLosses; - - var totalRewards = totalBlockAndEndorsement; - var totalActivity = totals.Blocks + totals.Endorsements; - var totalMissedActivity = totals.MissedBlocks + totals.MissedEndorsements; - var totalOps = totalActivity + totalMissedActivity; - var performanceRate = totalOps > 0 ? (double)totalActivity / totalOps * 100.0 : 100.0; - var avgRewardsPerCycle = totals.CyclesUsed > 0 ? (double)totalRewards / totals.CyclesUsed : 0.0; - var totalDistributable = totals.TotalRewardsDelegated + totals.TotalRewardsStakedShared + totals.TotalRewardsOwnStake + totals.TotalEdgeFees; - var invDistributable = totalDistributable > 0 ? 100.0 / totalDistributable : 0.0; - var delegatorSharePercent = (double)totals.TotalRewardsDelegated * invDistributable; - var coStakerSharePercent = (double)totals.TotalRewardsStakedShared * invDistributable; - var validatorSharePercent = (double)(totals.TotalRewardsOwnStake + totals.TotalEdgeFees) * invDistributable; - - return new BakerStatsKpis - { - TotalIncome = totalIncome, - ExtraRewards = extraRewards, - LostRewards = lostRewards, - SlashedRewards = totals.SlashedRewards, - TotalSlashed = totals.TotalSlashed, - BlocksBaked = totals.Blocks, - BlocksProposed = totals.Blocks + totals.MissedBlocks, - TotalBlockRewards = totals.TotalBlockRewards, - TotalEndorsementRewards = totals.TotalEndorsementRewards, - EndorsementsMade = totals.Endorsements, - EndorsementsMissed = totals.MissedEndorsements, - ExpectedDistribution = totals.ExpectedDistribution, - TechnicalReliability = Math.Round(monetaryPerformance, 2), - MonetaryPerformance = Math.Round(monetaryPerformance, 2), - FairEfficiency = Math.Round(monetaryPerformance, 2), - LuckRatio = Math.Round(luckRatio, 4), - TotalExpectedRewards = kpisExpectedRewards, - TotalActualRewards = totalActualRewards, - MissedRights = totals.MissedRights, - TotalRewards = totalRewards, - TotalBlockFees = totals.TotalBlockFees, - TotalRevelationRewards = totals.TotalRevelationRewards, - MissedBlocks = totals.MissedBlocks, - MissedEndorsements = totals.MissedEndorsements, - PerformanceRate = Math.Round(performanceRate, 2), - AvgRewardsPerCycle = Math.Round(avgRewardsPerCycle, 2), - TotalRewardsDelegated = totals.TotalRewardsDelegated, - TotalRewardsStakedShared = totals.TotalRewardsStakedShared, - TotalRewardsOwnStake = totals.TotalRewardsOwnStake, - TotalEdgeFees = totals.TotalEdgeFees, - DelegatorSharePercent = Math.Round(delegatorSharePercent, 2), - CoStakerSharePercent = Math.Round(coStakerSharePercent, 2), - ValidatorSharePercent = Math.Round(validatorSharePercent, 2) - }; - } - sealed class BakerStatsTotals { public int CyclesUsed { get; init; } From 5d6c1d5c73093cf6b5f9b010ea5e74edd4c296d5 Mon Sep 17 00:00:00 2001 From: 0xVoronov Date: Wed, 4 Mar 2026 07:09:23 +0300 Subject: [PATCH 11/11] Revert configs --- Mvkt.Api/appsettings.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Mvkt.Api/appsettings.json b/Mvkt.Api/appsettings.json index ba1182b15..eca5cac0f 100644 --- a/Mvkt.Api/appsettings.json +++ b/Mvkt.Api/appsettings.json @@ -49,8 +49,9 @@ "Logging": { "LogLevel": { "Default": "Information", - "Npgsql": "Information", - "Mvkt.Api.Repositories.RewardsRepository": "Debug" + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information", + "Npgsql.Command": "Warning" } }, "Kestrel": {