-
Notifications
You must be signed in to change notification settings - Fork 1k
[Neo Core Fix]Fix https://github.com/neo-project/neo/issues/2862 #3364
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
a037be6
ebc9377
6c27d09
9f13605
7d1afea
883bce9
977226a
3069a21
8893cd7
e397df8
004e5a1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,218 @@ | ||
| // Copyright (C) 2015-2024 The Neo Project. | ||
| // | ||
| // SmartThrottler.cs file belongs to the neo project and is free | ||
| // software distributed under the MIT software license, see the | ||
| // accompanying file LICENSE in the main directory of the | ||
| // repository or http://www.opensource.org/licenses/mit-license.php | ||
| // for more details. | ||
| // | ||
| // Redistribution and use in source and binary forms with or without | ||
| // modifications are permitted. | ||
|
|
||
| using Neo.Network.P2P.Payloads; | ||
| using System; | ||
| using System.Collections.Concurrent; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
|
|
||
| namespace Neo.Ledger; | ||
|
|
||
| /// <summary> | ||
| /// SmartThrottler: Protects Neo blockchain's memory pool from attacks and network congestion | ||
| /// </summary> | ||
| public class SmartThrottler | ||
| { | ||
| private readonly MemoryPool _memoryPool; | ||
| private readonly NeoSystem _system; | ||
| private uint _maxTransactionsPerSecond; | ||
| private int _transactionsThisSecond; | ||
| private DateTime _lastResetTime; | ||
| private readonly object _lock = new(); | ||
| private readonly ConcurrentDictionary<UInt160, int> _senderTransactionCount = new(); | ||
| private readonly int _maxTransactionsPerSender; | ||
| private long _averageFee; | ||
|
|
||
| // Fields for network load estimation | ||
| private readonly Queue<ulong> _recentBlockTimes = new(); | ||
| private const int BlockTimeWindowSize = 20; // Consider last 20 blocks | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move to config |
||
| private ulong _lastBlockTimestamp; | ||
| private int _unconfirmedTxCount; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the SmartThrottler | ||
| /// </summary> | ||
| /// <param name="memoryPool">The memory pool this throttler is associated with</param> | ||
| /// <param name="system">The Neo system</param> | ||
| public SmartThrottler(MemoryPool memoryPool, NeoSystem system) | ||
| { | ||
| _memoryPool = memoryPool; | ||
| _system = system; | ||
| _maxTransactionsPerSecond = (uint)system.Settings.MemPoolSettings.MaxTransactionsPerSecond; | ||
| _lastResetTime = TimeProvider.Current.UtcNow; | ||
| _maxTransactionsPerSender = system.Settings.MemPoolSettings.MaxTransactionsPerSender; | ||
| _lastBlockTimestamp = TimeProvider.Current.UtcNow.ToTimestampMS(); | ||
| _averageFee = CalculateAverageFee(null); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Determines whether a new transaction should be accepted | ||
| /// </summary> | ||
| /// <param name="tx">The transaction to be evaluated</param> | ||
| /// <returns>True if the transaction should be accepted, false otherwise</returns> | ||
| public bool ShouldAcceptTransaction(Transaction tx) | ||
| { | ||
| lock (_lock) | ||
| { | ||
| var now = TimeProvider.Current.UtcNow; | ||
| if (now - _lastResetTime >= TimeSpan.FromSeconds(1)) | ||
| { | ||
| _transactionsThisSecond = 0; | ||
| _lastResetTime = now; | ||
| AdjustThrottling(null); | ||
| _averageFee = CalculateAverageFee(null); | ||
| } | ||
|
|
||
| // Check if we've hit the tx limit and it's not high priority | ||
| if (_transactionsThisSecond >= _maxTransactionsPerSecond && !IsHighPriorityTransaction(tx)) | ||
| return false; | ||
|
|
||
| // Check if sender has reached their tx limit | ||
| if (!CheckSenderLimit(tx.Sender)) | ||
| return false; | ||
|
|
||
| _transactionsThisSecond++; | ||
| _senderTransactionCount.AddOrUpdate(tx.Sender, 1, (_, count) => count + 1); | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Updates the network state after a new block is added | ||
| /// </summary> | ||
| /// <param name="block">The newly added block</param> | ||
| public void UpdateNetworkState(Block block) | ||
| { | ||
| lock (_lock) | ||
| { | ||
| var currentTime = TimeProvider.Current.UtcNow.ToTimestampMS(); | ||
| var blockTime = currentTime - _lastBlockTimestamp; | ||
|
|
||
| _recentBlockTimes.Enqueue(blockTime); | ||
| if (_recentBlockTimes.Count > BlockTimeWindowSize) | ||
| _recentBlockTimes.Dequeue(); | ||
|
|
||
| _lastBlockTimestamp = currentTime; | ||
| _unconfirmedTxCount = _memoryPool.Count; | ||
| _averageFee = CalculateAverageFee(block); | ||
|
|
||
| AdjustThrottling(block); | ||
|
|
||
| // 不再调用 ResetAfterNewBlock() | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Removes a transaction from throttler tracking | ||
| /// </summary> | ||
| /// <param name="tx">The transaction being removed</param> | ||
| public void RemoveTransaction(Transaction tx) | ||
| { | ||
| _senderTransactionCount.AddOrUpdate(tx.Sender, 0, (_, count) => Math.Max(0, count - 1)); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Adjusts throttling parameters based on current network conditions | ||
| /// </summary> | ||
| private void AdjustThrottling(Block block) | ||
| { | ||
| var memoryPoolUtilization = (double)_memoryPool.Count / _system.Settings.MemoryPoolMaxTransactions; | ||
| var networkLoad = EstimateNetworkLoad(block); | ||
|
|
||
| _maxTransactionsPerSecond = CalculateOptimalTps(memoryPoolUtilization, networkLoad, block); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. _optimalMaxTransactionsPerSecond instead of _maxTransactionsPerSecond There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no difference to the code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nomenclature is bad like this, |
||
| } | ||
|
|
||
| /// <summary> | ||
| /// Estimates current network load | ||
| /// </summary> | ||
| /// <returns>An integer between 0 and 100 representing the estimated network load</returns> | ||
| private int EstimateNetworkLoad(Block block) | ||
| { | ||
| var load = 0; | ||
|
|
||
| // 1. Memory pool utilization (30% weight) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These weights does not look like %, they are more than 100 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how could it be more than 100? var memPoolUtilization = (double)_memoryPool.Count / _system.Settings.MemoryPoolMaxTransactions * 100;There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check in the thread below |
||
| var memPoolUtilization = (double)_memoryPool.Count / _system.Settings.MemoryPoolMaxTransactions; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. get this value from previous line before calling the method, it is called just once as it is right now |
||
| load += (int)(memPoolUtilization * 30); | ||
|
|
||
| // 2. Recent block times (30% weight) | ||
| if (_recentBlockTimes.Count > 0) | ||
| { | ||
| var avgBlockTime = _recentBlockTimes.Average(t => (double)t); | ||
| var blockTimeRatio = avgBlockTime / _system.Settings.MillisecondsPerBlock; | ||
| load += (int)(Math.Min(blockTimeRatio, 2) * 30); // Cap at 60 points | ||
|
||
| } | ||
|
|
||
| // 3. Current block transaction count or unconfirmed transaction growth rate (40% weight) | ||
| if (block != null) | ||
| { | ||
| var blockTxRatio = (double)block.Transactions.Length / _system.Settings.MaxTransactionsPerBlock; | ||
| load += (int)(Math.Min(blockTxRatio, 1) * 40); | ||
| } | ||
| else | ||
| { | ||
| var txGrowthRate = (double)_unconfirmedTxCount / _system.Settings.MaxTransactionsPerBlock; | ||
| load += (int)(Math.Min(txGrowthRate, 1) * 40); | ||
| } | ||
|
|
||
| return Math.Min(load, 100); // Ensure load doesn't exceed 100 | ||
|
||
| } | ||
|
|
||
| /// <summary> | ||
| /// Calculates optimal transactions per second | ||
| /// </summary> | ||
| private uint CalculateOptimalTps(double memoryPoolUtilization, int networkLoad, Block block) | ||
| { | ||
| var baseTps = _system.Settings.MaxTransactionsPerBlock * 4; | ||
| var utilizationFactor = 1 - memoryPoolUtilization; | ||
| var loadFactor = 1 - (networkLoad / 100.0); | ||
|
|
||
| // Consider current block's transaction count if available | ||
| var blockFactor = 1.0; | ||
| if (block != null) | ||
| { | ||
| blockFactor = Math.Max(0.5, (double)block.Transactions.Length / _system.Settings.MaxTransactionsPerBlock); | ||
| } | ||
|
|
||
| var optimalTps = (uint)(baseTps * utilizationFactor * loadFactor * blockFactor); | ||
| return Math.Max(optimalTps, _system.Settings.MaxTransactionsPerBlock); // Ensure TPS isn't lower than max transactions per block | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Determines if a transaction is high priority | ||
| /// </summary> | ||
| private bool IsHighPriorityTransaction(Transaction tx) | ||
| { | ||
| // High priority: fee > 3x average | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. committee address? |
||
| return tx.NetworkFee + tx.SystemFee > _averageFee * 3; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Checks if sender has reached their transaction limit | ||
| /// </summary> | ||
| private bool CheckSenderLimit(UInt160 sender) | ||
| { | ||
| return _senderTransactionCount.GetOrAdd(sender, 0) < _maxTransactionsPerSender; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Calculates average fee of transactions in memory pool and new block (if provided) | ||
| /// </summary> | ||
| private long CalculateAverageFee(Block block) | ||
| { | ||
| var transactions = _memoryPool.GetSortedVerifiedTransactions().ToList(); | ||
| if (block != null) | ||
| { | ||
| transactions.AddRange(block.Transactions); // Include transactions from the new block | ||
| } | ||
| return transactions.Count != 0 ? (long)transactions.Average(tx => tx.NetworkFee + tx.SystemFee) : 0; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.