private async Task CalculateBlockEffort(PoolConfig pool, Block block, IPayoutHandler handler) { // get share date-range var from = DateTime.MinValue; var to = block.Created; // get last block for pool var lastBlock = cf.Run(con => blockRepo.GetBlockBefore(con, pool.Id, new[] { BlockStatus.Confirmed, BlockStatus.Orphaned, BlockStatus.Pending, }, block.Created)); if (lastBlock != null) { from = lastBlock.Created; } // get combined diff of all shares for block var accumulatedShareDiffForBlock = cf.Run(con => shareRepo.GetAccumulatedShareDifficultyBetweenCreated(con, pool.Id, from, to)); // handler has the final say if (accumulatedShareDiffForBlock.HasValue) { await handler.CalculateBlockEffortAsync(block, accumulatedShareDiffForBlock.Value); } }
private async Task CalculateBlockEffort(PoolConfig pool, Block block, IPayoutHandler handler) { var from = DateTime.MinValue; var to = block.Created; var lastBlock = cf.Run(con => blockRepo.GetBlockBefore(con, pool.Id, new[] { BlockStatus.Confirmed, BlockStatus.Orphaned, BlockStatus.Pending, }, block.Created)); if (lastBlock != null) { from = lastBlock.Created; } var accumulatedShareDiffForBlock = cf.Run(con => shareRepo.GetAccumulatedShareDifficultyBetweenCreated(con, pool.Id, from, to)); if (accumulatedShareDiffForBlock.HasValue) { await handler.CalculateBlockEffortAsync(block, accumulatedShareDiffForBlock.Value); } }
private async Task PayoutPoolBalancesAsync(PoolConfig pool, IPayoutHandler handler) { var poolBalancesOverMinimum = cf.Run(con => balanceRepo.GetPoolBalancesOverThreshold(con, pool.Id, pool.PaymentProcessing.MinimumPayment)); if (poolBalancesOverMinimum.Length > 0) { try { await handler.PayoutAsync(poolBalancesOverMinimum); } catch (Exception ex) { await NotifyPayoutFailureAsync(poolBalancesOverMinimum, pool, ex); throw; } } else { logger.Info(() => $"No balances over configured minimum payout for pool {pool.Id}"); } }
public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, PoolConfig poolConfig, IPayoutHandler payoutHandler, Block block, decimal blockReward) { // calculate rewards var rewards = new Dictionary <string, decimal>(); var shareCutOffDate = CalculateRewards(poolConfig, block, blockReward, rewards); // update balances foreach (var address in rewards.Keys) { var amount = rewards[address]; if (amount > 0) { logger.Info(() => $"Adding {payoutHandler.FormatAmount(amount)} to balance of {address} for block {block.BlockHeight}"); await balanceRepo.AddAmountAsync(con, tx, poolConfig.Id, address, amount, $"Reward for block {block.BlockHeight}"); } } // delete discarded shares if (shareCutOffDate.HasValue) { var cutOffCount = await shareRepo.CountSharesBeforeCreatedAsync(con, tx, poolConfig.Id, shareCutOffDate.Value); if (cutOffCount > 0) { #if !DEBUG logger.Info(() => $"Deleting {cutOffCount} discarded shares before {shareCutOffDate.Value:O}"); await shareRepo.DeleteSharesBeforeCreatedAsync(con, tx, poolConfig.Id, shareCutOffDate.Value); #endif } } }
private async Task UpdatePoolBalancesAsync(PoolConfig pool, IPayoutHandler handler, IPayoutScheme scheme) { // get pending blockRepo for pool var pendingBlocks = await cf.Run(con => blockRepo.GetPendingBlocksForPoolAsync(con, pool.Id)); // classify var updatedBlocks = await handler.ClassifyBlocksAsync(pendingBlocks); if (updatedBlocks.Any()) { foreach (var block in updatedBlocks.OrderBy(x => x.Created)) { logger.Info(() => $"Processing payments for pool {pool.Id}, block {block.BlockHeight}, effort {block.Effort}"); await cf.RunTx(async (con, tx) => { if (!block.Effort.HasValue) // fill block effort if empty { await CalculateBlockEffortAsync(pool, block, handler); } switch (block.Status) { case BlockStatus.Confirmed: // blockchains that do not support block-reward payments via coinbase Tx // must generate balance records for all reward recipients instead var blockReward = await handler.UpdateBlockRewardBalancesAsync(con, tx, block, pool); logger.Info(() => $" --Pool {pool}"); logger.Info(() => $" --Block {block}"); logger.Info(() => $" --Block reward {blockReward}"); logger.Info(() => $" --Con {con}"); logger.Info(() => $" --tx {tx}"); await scheme.UpdateBalancesAsync(con, tx, pool, handler, block, blockReward); await blockRepo.UpdateBlockAsync(con, tx, block); break; case BlockStatus.Orphaned: await blockRepo.UpdateBlockAsync(con, tx, block); break; case BlockStatus.Pending: await blockRepo.UpdateBlockAsync(con, tx, block); break; } }); } } else { logger.Info(() => $"No updated blocks for pool {pool.Id}"); } }
private async Task UpdatePoolBalancesAsync(PoolConfig pool, IPayoutHandler handler, IPayoutScheme scheme) { // get pending blockRepo for pool var pendingBlocks = cf.Run(con => blockRepo.GetPendingBlocksForPool(con, pool.Id)); // classify var updatedBlocks = await handler.ClassifyBlocksAsync(pendingBlocks); if (updatedBlocks.Any()) { foreach (var block in updatedBlocks.OrderBy(x => x.Created)) { logger.Info(() => $"Processing payments for pool {pool.Id}, block {block.BlockHeight}"); await cf.RunTxAsync(async (con, tx) => { // fill block effort if empty if (!block.Effort.HasValue) { await CalculateBlockEffort(pool, block, handler); } switch (block.Status) { case BlockStatus.Confirmed: // blockchains that do not support block-reward payments via coinbase Tx // must generate balance records for all reward recipients instead var blockReward = await handler.UpdateBlockRewardBalancesAsync(con, tx, block, pool); // update share submitter balances through configured payout scheme await scheme.UpdateBalancesAsync(con, tx, pool, handler, block, blockReward); // finally update block status blockRepo.UpdateBlock(con, tx, block); break; case BlockStatus.Orphaned: logger.Info(() => $"Deleting orphaned block {block.BlockHeight} on pool {pool.Id}"); blockRepo.DeleteBlock(con, tx, block); break; case BlockStatus.Pending: blockRepo.UpdateBlock(con, tx, block); break; } }); } } else { logger.Info(() => $"No updated blocks for {pool.Id}"); } }
public ProductsMonitorService(IDateTimeNow dateTimeNow) { _dateTimeNow = dateTimeNow; _modelPortfolioRepository = new ModelPortfolioRepository(); _behaviourFactory = new BehaviourFactory(); _productRepository = new ProductRepository(_modelPortfolioRepository, _behaviourFactory, _dateTimeNow); _rebalancerHandler = new RebalanceHandler(_behaviourFactory); _payoutHandler = new PayoutHandler(_behaviourFactory); _monitorFactory = new MonitorFactory(_dateTimeNow, _rebalancerHandler, _payoutHandler); _monitorHandler = new MonitorHandler(_monitorFactory); }
public Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, PoolConfig poolConfig, IPayoutHandler payoutHandler, Block block, decimal blockReward) { var payoutConfig = poolConfig.PaymentProcessing.PayoutSchemeConfig; // PPLNS window (see https://bitcointalk.org/index.php?topic=39832) var window = payoutConfig?.ToObject <Config>()?.Factor ?? 2.0m; // calculate rewards var shares = new Dictionary <string, double>(); var rewards = new Dictionary <string, decimal>(); var shareCutOffDate = CalculateRewards(poolConfig, window, block, blockReward, shares, rewards); // update balances foreach (var address in rewards.Keys) { var amount = rewards[address]; if (amount > 0) { logger.Info(() => $"Adding {payoutHandler.FormatAmount(amount)} to balance of {address} for {FormatUtil.FormatQuantity(shares[address])} ({shares[address]}) shares for block {block.BlockHeight}"); balanceRepo.AddAmount(con, tx, poolConfig.Id, poolConfig.Coin.Type, address, amount, $"Reward for {FormatUtil.FormatQuantity(shares[address])} shares for block {block.BlockHeight}"); } } // delete discarded shares if (shareCutOffDate.HasValue) { var cutOffCount = shareRepo.CountSharesBeforeCreated(con, tx, poolConfig.Id, shareCutOffDate.Value); if (cutOffCount > 0) { LogDiscardedShares(poolConfig, block, shareCutOffDate.Value); #if !DEBUG logger.Info(() => $"Deleting {cutOffCount} discarded shares before {shareCutOffDate.Value:O}"); shareRepo.DeleteSharesBeforeCreated(con, tx, poolConfig.Id, shareCutOffDate.Value); #endif } } // diagnostics var totalShareCount = shares.Values.ToList().Sum(x => new decimal(x)); var totalRewards = rewards.Values.ToList().Sum(x => x); if (totalRewards > 0) { logger.Info(() => $"{FormatUtil.FormatQuantity((double) totalShareCount)} ({Math.Round(totalShareCount, 2)}) shares contributed to a total payout of {payoutHandler.FormatAmount(totalRewards)} ({totalRewards / blockReward * 100:0.00}% of block reward) to {rewards.Keys.Count} addresses"); } return(Task.FromResult(true)); }
public Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, PoolConfig poolConfig, IPayoutHandler payoutHandler, Block block, decimal blockReward) { var payoutConfig = poolConfig.PaymentProcessing.PayoutSchemeConfig; // PPLNS window (see https://bitcointalk.org/index.php?topic=39832) var window = payoutConfig?.ToObject <Config>()?.Factor ?? 2.0m; // calculate rewards var shares = new Dictionary <string, ulong>(); var rewards = new Dictionary <string, decimal>(); var shareCutOffDate = CalculateRewards(poolConfig, window, block, blockReward, shares, rewards); // update balances foreach (var address in rewards.Keys) { var amount = rewards[address]; if (amount > 0) { logger.Info(() => $"Adding {payoutHandler.FormatAmount(amount)} to balance of {address} for {shares[address]} shares"); balanceRepo.AddAmount(con, tx, poolConfig.Id, poolConfig.Coin.Type, address, amount); } } // delete obsolete shares if (shareCutOffDate.HasValue) { var cutOffCount = shareRepo.CountPoolSharesBefore(con, tx, poolConfig.Id, shareCutOffDate.Value); if (cutOffCount > 0) { logger.Info(() => $"Deleting {cutOffCount} obsolete shares before {shareCutOffDate.Value}"); shareRepo.DeletePoolSharesBefore(con, tx, poolConfig.Id, shareCutOffDate.Value); } //logger.Info(() => $"Shares before {shareCutOffDate.Value} can be deleted"); } // diagnostics var totalShareCount = shares.Values.ToList().Sum(x => new decimal(x)); var totalRewards = rewards.Values.ToList().Sum(x => x); if (totalRewards > 0) { logger.Info(() => $"{totalShareCount} shares contributed to a total payout of {payoutHandler.FormatAmount(totalRewards)} ({totalRewards / blockReward * 100:0.00}% of block reward)"); } return(Task.FromResult(true)); }
public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, PoolConfig poolConfig, IPayoutHandler payoutHandler, Block block, decimal blockReward) { var payoutConfig = poolConfig.PaymentProcessing.PayoutSchemeConfig; var window = payoutConfig?.ToObject <Config>()?.Factor ?? 2.0m; var shares = new Dictionary <string, double>(); var rewards = new Dictionary <string, decimal>(); var shareCutOffDate = await CalculateRewardsAsync(poolConfig, block, blockReward, shares, rewards); // update balances foreach (var address in rewards.Keys) { var amount = rewards[address]; if (amount > 0) { logger.Info(() => $"Adding {payoutHandler.FormatAmount(amount)} to balance of {address} for {FormatUtil.FormatQuantity(shares[address])} ({shares[address]}) shares for block {block.BlockHeight}"); await balanceRepo.AddAmountAsync(con, tx, poolConfig.Id, address, amount, $"Reward for {FormatUtil.FormatQuantity(shares[address])} shares for block {block.BlockHeight}"); } } // delete discarded shares if (shareCutOffDate.HasValue) { var cutOffCount = await shareRepo.CountSharesBeforeCreatedAsync(con, tx, poolConfig.Id, shareCutOffDate.Value); if (cutOffCount > 0) { await LogDiscardedSharesAsync(poolConfig, block, shareCutOffDate.Value); logger.Info(() => $"Deleting {cutOffCount} discarded shares before {shareCutOffDate.Value:O}"); await shareRepo.DeleteSharesBeforeCreatedAsync(con, tx, poolConfig.Id, shareCutOffDate.Value); } } // diagnostics var totalShareCount = shares.Values.ToList().Sum(x => new decimal(x)); var totalRewards = rewards.Values.ToList().Sum(x => x); if (totalRewards > 0) { logger.Info(() => $"{FormatUtil.FormatQuantity((double) totalShareCount)} ({Math.Round(totalShareCount, 2)}) shares contributed to a total payout of {payoutHandler.FormatAmount(totalRewards)} ({totalRewards / blockReward * 100:0.00}% of block reward) to {rewards.Keys.Count} addresses"); } }
private async Task UpdatePoolBalancesAsync(PoolConfig pool, IPayoutHandler handler, IPayoutScheme scheme) { var pendingBlocks = cf.Run(con => blockRepo.GetPendingBlocksForPool(con, pool.Id)); var updatedBlocks = await handler.ClassifyBlocksAsync(pendingBlocks); if (updatedBlocks.Any()) { foreach (var block in updatedBlocks.OrderBy(x => x.Created)) { logger.Info(() => $"Processing payments for pool {pool.Id}, block {block.BlockHeight}"); await cf.RunTxAsync(async (con, tx) => { if (!block.Effort.HasValue) { await CalculateBlockEffort(pool, block, handler); } switch (block.Status) { case BlockStatus.Confirmed: var blockReward = await handler.UpdateBlockRewardBalancesAsync(con, tx, block, pool); await scheme.UpdateBalancesAsync(con, tx, pool, handler, block, blockReward); blockRepo.UpdateBlock(con, tx, block); break; case BlockStatus.Orphaned: case BlockStatus.Pending: blockRepo.UpdateBlock(con, tx, block); break; } }); } } else { logger.Info(() => $"No updated blocks for pool {pool.Id}"); } }
public async Task UpdateBalancesAsync(IDbConnection con, IDbTransaction tx, IMiningPool pool, IPayoutHandler payoutHandler, Block block, decimal blockReward, CancellationToken ct) { var poolConfig = pool.Config; // calculate rewards var rewards = new Dictionary <string, decimal>(); var shareCutOffDate = CalculateRewards(block, blockReward, rewards, ct); // update balances foreach (var address in rewards.Keys) { var amount = rewards[address]; if (amount > 0) { logger.Info(() => $"Adding {payoutHandler.FormatAmount(amount)} to balance of {address} for block {block.BlockHeight}"); await balanceRepo.AddAmountAsync(con, tx, poolConfig.Id, address, amount, $"Reward for block {block.BlockHeight}"); } } // delete discarded shares if (shareCutOffDate.HasValue) { var cutOffCount = await shareRepo.CountSharesByMinerAsync(con, tx, poolConfig.Id, block.Miner, ct); if (cutOffCount > 0) { logger.Info(() => $"Deleting {cutOffCount} discarded shares for {block.Miner}"); await shareRepo.DeleteSharesByMinerAsync(con, tx, poolConfig.Id, block.Miner, ct); } } }
public MonitorFactory(IDateTimeNow dateTimeNow, IRebalanceHandler rebalancerHandler, IPayoutHandler payoutHandler) { _dateTimeNow = dateTimeNow; _rebalancerHandler = rebalancerHandler; _payoutHandler = payoutHandler; }
public PayoutMonitor(IRebalanceHandler rebalancerHandler, IPayoutHandler payoutHandler, ProductBase productBase, IDateTimeNow now) : base(productBase, now) { _rebalancerHandler = rebalancerHandler; _payoutHandler = payoutHandler; }