From aa3415337fcc58e5ceea9d89c29cbb86be097cbc Mon Sep 17 00:00:00 2001 From: EchoForge7 Date: Thu, 26 Jun 2025 11:02:31 +0800 Subject: [PATCH] feat: add ignore address type for token transfer monitoring - Add Ignore type to AddressClassification enum - Add IgnoreAddresses configuration option - Update monitoring service to skip metrics for ignore addresses individually - Only skip the specific address metrics, not the entire transaction - Add detailed logging for ignored addresses --- .../Dtos/TransferEventDto.cs | 3 +- .../Options/TokenTransferMonitoringOptions.cs | 5 + .../Service/TokenTransferMonitoringService.cs | 110 ++++++++++++------ src/AElfScanServer.Worker/appsettings.json | 49 +++++++- 4 files changed, 126 insertions(+), 41 deletions(-) diff --git a/src/AElfScanServer.Worker.Core/Dtos/TransferEventDto.cs b/src/AElfScanServer.Worker.Core/Dtos/TransferEventDto.cs index 8ad12942..9554eb0b 100644 --- a/src/AElfScanServer.Worker.Core/Dtos/TransferEventDto.cs +++ b/src/AElfScanServer.Worker.Core/Dtos/TransferEventDto.cs @@ -31,7 +31,8 @@ public enum AddressClassification Normal, Blacklist, ToOnlyMonitored, - LargeAmountOnly + LargeAmountOnly, + Ignore } /// diff --git a/src/AElfScanServer.Worker.Core/Options/TokenTransferMonitoringOptions.cs b/src/AElfScanServer.Worker.Core/Options/TokenTransferMonitoringOptions.cs index e314d74e..598342ee 100644 --- a/src/AElfScanServer.Worker.Core/Options/TokenTransferMonitoringOptions.cs +++ b/src/AElfScanServer.Worker.Core/Options/TokenTransferMonitoringOptions.cs @@ -29,6 +29,11 @@ public class TokenTransferMonitoringOptions /// public List LargeAmountOnlyAddresses { get; set; } = new(); + /// + /// Addresses to ignore completely - no metrics will be recorded for these addresses + /// + public List IgnoreAddresses { get; set; } = new(); + /// /// Minimum USD value threshold for histogram recording, default is 0 /// diff --git a/src/AElfScanServer.Worker.Core/Service/TokenTransferMonitoringService.cs b/src/AElfScanServer.Worker.Core/Service/TokenTransferMonitoringService.cs index ea86e49b..3ec8cf23 100644 --- a/src/AElfScanServer.Worker.Core/Service/TokenTransferMonitoringService.cs +++ b/src/AElfScanServer.Worker.Core/Service/TokenTransferMonitoringService.cs @@ -38,6 +38,7 @@ public class TokenTransferMonitoringService : ITokenTransferMonitoringService, I private readonly HashSet _blacklistAddresses; private readonly HashSet _toOnlyMonitoredAddresses; private readonly HashSet _largeAmountOnlyAddresses; + private readonly HashSet _ignoreAddresses; private readonly IOptionsMonitor _optionsMonitor; public TokenTransferMonitoringService( @@ -62,6 +63,7 @@ public TokenTransferMonitoringService( _blacklistAddresses = new HashSet(_options.BlacklistAddresses, StringComparer.OrdinalIgnoreCase); _toOnlyMonitoredAddresses = new HashSet(_options.ToOnlyMonitoredAddresses, StringComparer.OrdinalIgnoreCase); _largeAmountOnlyAddresses = new HashSet(_options.LargeAmountOnlyAddresses, StringComparer.OrdinalIgnoreCase); + _ignoreAddresses = new HashSet(_options.IgnoreAddresses, StringComparer.OrdinalIgnoreCase); // Initialize histogram with configured buckets _transferUSDEventsHistogram = instrumentationProvider.Meter.CreateHistogram( "aelf_transfer_usd_value", @@ -310,58 +312,88 @@ public void SendTransferMetrics(TransferEventDto transfer) // Determine if this is a high-value transfer once var isHighValue = transfer.UsdValue >= currentOptions.MinUsdValueThreshold; - // Record outbound transaction (from perspective) - var outboundTags = new KeyValuePair[] + // Record outbound transaction (from perspective) - skip if fromAddress is ignore type + if (transfer.FromAddressType != AddressClassification.Ignore && !transfer.FromAddress.IsNullOrEmpty()) { - new("chain_id", transfer.ChainId), - new("symbol", transfer.Symbol), - new("transfer_type", transfer.Type.ToString()), - new("address", transfer.FromAddress), - new("direction", "outbound"), - new("address_type", transfer.FromAddressType.ToString()), - }; - - // Record inbound transaction (to perspective) - var inboundTags = new KeyValuePair[] - { - new("chain_id", transfer.ChainId), - new("symbol", transfer.Symbol), - new("transfer_type", transfer.Type.ToString()), - new("address", transfer.ToAddress), - new("direction", "inbound"), - new("address_type", transfer.ToAddressType.ToString()), - }; - - // Always record counter (for all transfers) + var outboundTags = new KeyValuePair[] + { + new("chain_id", transfer.ChainId), + new("symbol", transfer.Symbol), + new("transfer_type", transfer.Type.ToString()), + new("address", transfer.FromAddress), + new("direction", "outbound"), + new("address_type", transfer.FromAddressType.ToString()), + }; + + // Always record counter + _transferCountsCounter.Add(1, outboundTags); - if (!transfer.FromAddress.IsNullOrEmpty()) + // Only record histogram for high-value transfers + if (isHighValue) + { + _transferUSDEventsHistogram.Record((double)transfer.UsdValue, outboundTags); + } + } + else if (transfer.FromAddressType == AddressClassification.Ignore) { - _transferCountsCounter.Add(1, outboundTags); + _logger.LogInformation("Skipping outbound metrics for transaction {TransactionId} from ignore address {FromAddress}", + transfer.TransactionId, transfer.FromAddress); } - if (!transfer.ToAddress.IsNullOrEmpty() || !IsSystemContractTransfer(transfer.ToAddress)) + // Record inbound transaction (to perspective) - skip if toAddress is ignore type + if (transfer.ToAddressType != AddressClassification.Ignore && + !transfer.ToAddress.IsNullOrEmpty() && + !IsSystemContractTransfer(transfer.ToAddress)) { + var inboundTags = new KeyValuePair[] + { + new("chain_id", transfer.ChainId), + new("symbol", transfer.Symbol), + new("transfer_type", transfer.Type.ToString()), + new("address", transfer.ToAddress), + new("direction", "inbound"), + new("address_type", transfer.ToAddressType.ToString()), + }; + + // Always record counter _transferCountsCounter.Add(1, inboundTags); + + // Only record histogram for high-value transfers + if (isHighValue) + { + _transferUSDEventsHistogram.Record((double)transfer.UsdValue, inboundTags); + } } - // Only record histogram for high-value transfers - if (isHighValue) + else if (transfer.ToAddressType == AddressClassification.Ignore) { - if (!transfer.FromAddress.IsNullOrEmpty()) + _logger.LogInformation("Skipping inbound metrics for transaction {TransactionId} to ignore address {ToAddress}", + transfer.TransactionId, transfer.ToAddress); + } + + // Log transaction processing summary + var recordedMetrics = new List(); + if (transfer.FromAddressType != AddressClassification.Ignore && !transfer.FromAddress.IsNullOrEmpty()) + recordedMetrics.Add("outbound"); + if (transfer.ToAddressType != AddressClassification.Ignore && !transfer.ToAddress.IsNullOrEmpty() && !IsSystemContractTransfer(transfer.ToAddress)) + recordedMetrics.Add("inbound"); + + if (recordedMetrics.Any()) + { + if (isHighValue) { - _transferUSDEventsHistogram.Record((double)transfer.UsdValue, outboundTags); + _logger.LogInformation("Sent transfer metrics ({Directions}) for transaction {TransactionId}, amount {Amount} {Symbol}, USD value {UsdValue}", + string.Join(", ", recordedMetrics), transfer.TransactionId, transfer.Amount, transfer.Symbol, transfer.UsdValue); } - - if (!transfer.ToAddress.IsNullOrEmpty() || !IsSystemContractTransfer(transfer.ToAddress)) + else { - _transferUSDEventsHistogram.Record((double)transfer.UsdValue, inboundTags); + _logger.LogInformation("Sent counter metrics only ({Directions}) for transaction {TransactionId}, amount {Amount} {Symbol}, USD value {UsdValue} below histogram threshold", + string.Join(", ", recordedMetrics), transfer.TransactionId, transfer.Amount, transfer.Symbol, transfer.UsdValue); } - _logger.LogInformation("Sent transfer metrics for transaction {TransactionId}, amount {Amount} {Symbol}, USD value {UsdValue}", - transfer.TransactionId, transfer.Amount, transfer.Symbol, transfer.UsdValue); } else { - _logger.LogInformation("Sent counter metrics only for transaction {TransactionId}, amount {Amount} {Symbol}, USD value {UsdValue} below histogram threshold", - transfer.TransactionId, transfer.Amount, transfer.Symbol, transfer.UsdValue); + _logger.LogInformation("No metrics recorded for transaction {TransactionId} - both addresses are ignore type or system contracts", + transfer.TransactionId); } } catch (Exception ex) @@ -394,7 +426,11 @@ private AddressClassification ClassifyAddress(string address) if (string.IsNullOrEmpty(address)) return AddressClassification.Normal; - // Check blacklist first (highest priority) + // Check ignore addresses first (highest priority) + if (_ignoreAddresses.Contains(address)) + return AddressClassification.Ignore; + + // Check blacklist (second highest priority) if (_blacklistAddresses.Contains(address)) return AddressClassification.Blacklist; diff --git a/src/AElfScanServer.Worker/appsettings.json b/src/AElfScanServer.Worker/appsettings.json index fcf9f34d..684336fc 100644 --- a/src/AElfScanServer.Worker/appsettings.json +++ b/src/AElfScanServer.Worker/appsettings.json @@ -264,12 +264,55 @@ }, "TokenTransferMonitoring": { "EnableMonitoring": true, - "EnableSystemContractFilter":false, + "EnableSystemContractFilter":true, "BlacklistAddresses": [ - "2Ue8S2qAb8dGMrRgcNtjWgBx48Bx3XmNT6UBBKwNLYm5ZpbA"], + "2Ue8S2qAb8dGMrRgcNtjWgBx48Bx3XmNT6UBBKwNLYm5ZpbA", + "kh8TeQiTzr3FWAmNN3fd97fjsNtPVeNr9cxtkdkFf2gyuxXDn", + "B6dvH6p93zMD3rE3zESRVt3gRQg2e8rHw34h1ZJNXxoWNBYjB", + "k9qVGtY5usEhViiacGvXGgNb8wpCEEwFU14w4rxoXAxgZwREF", + "q6w7NcsfLeGSMnNTmXKMnaRUPKSk7yczS4YCd6ZJC4XNTDcb8", + "hUkyUrCNoSuTuduYTbSKCCoHn17fvtX8NYVRNgnhFhpxAyoTt", + "2vJMKR3DqeLffJcjCEquvaR1hTQ2MS7mfpcK3bD8QEiRorDVdG" + ], + "ToOnlyMonitoredAddresses": [ + "2cbyVVSin9aHZzwdjHHGP3HLZxHgo7dKB5S7HdNGaXkwkpvHfT", + "2vGq6qQi9qJSdn5yjhQ7tpez16qQnEKuRCZHoQiUkGBMC34MdT", + "2PPYHTa582wijxchyrniTTWG1SG2QHhV8eLutP4MXRrnUc52uw", + "2imJ6W4ada5wNq735WVEisDfkVgGWDbSxHSyhmwXBBsgKWmMSr", + "2LB3MevN2QSW36ahkFJ6PxEPWFwunpCPQYAWCEXFaNzVwMDrdr", + "nxREyjyKXqzA7aQaK4Nc7EVNL2BWaiFSrDturUQfWwJ4uwW6b", + "eGw4LRJpXWZKcRRuxUi1uhqjJafEpzow4ZzQgnPmRJv7ReGrK", + "2RWf7Aj7Cn2UpZcXVrXLL7iWfhdHz69otYY1YDJ4X158cVNWG2", + "2VeAzekM7bEMisNMyJpRCAARgPcnuFoJVw9uXfWnGa8RX2tnuZ", + "KPFwtmxK48mDjKYTWeYFDfkSmnPNPFrEXAxyJf84jX5ytaGg8", + "29tJaX3k16ZibaXYz2yq9ZbeN4G7cLWJyAucGcSoCsLwhnkAoy", + "2eZtVJvL26DqmKgzQzULK764ihMCoXKTqgK7NzbPBCRRmuBZgz", + "bUxH4xMXHi2yisGd1GCjb81oWVavfXbEeXZ96vNdv4hpBhdeM", + "2PxkDj9tzyNBnAvuGhLVgUPr8bPN4r1DALYVPhYcsjQficvrUV", + "2cvQi5yLSvyni6xdUHXRynesz2nDQCDMBAyFG2QqA6iVSBDvtH", + "5CazTqiizhhDgn9QmPFqHEmJ7EspCRqVoencfCe3r7Qkog3c3", + "2NCCXHS48wyGyjxczJ19qUA4nmPz1HxfUAi359XWvTo4tzdNzM", + "g6LeMdzLcUPccrK97TWYSxri1NxK6ZXJwQjv1VUNf1F8BzfF3", + "2sVo6v1ysTefrFT1bStsoztnCGQHkZEjFK9mSUWjEP6nGnoma1", + "AERrwTnr9LE8Tu682FP1jpUSeJhwAuB8cSgR86SgMGqmeS6ii", + "zaVUYRhCkrurJfMs2EfTUnruJ9qeAbJ4jmsVM7wcaAPcDuKnt", + "FiXvL7pdz9uFYajitRVQw64EVNcu6vXqss3phMkVLTcCCFQ7d" + ], + "LargeAmountOnlyAddresses": [ + "2b7Gf7YqVmjhZXir7uehmZoRwsYo1KNFTo9JDZiiByxPBQS1d8", + "2AJXAXSwyHbKTHQhKFiaYozakUUQDeh3xrHW9FGi3vYDMBjtiS", + "25CkLPA8qwDRGQci2kFg77i6pZXVivvX4DHW78i1B7rPHdBkoK", + "2PwfVguYDmYcpJVPmoH9doEpBgd8L28NCcUDiuq77CzvEWzuKZ", + "2eJ4MnRWFo7YJXB92qj2AF3NWoB3umBggzNLhbGeahkwDYYLAD" + ], + "IgnoreAddresses": [ + "ExampleIgnoreAddress1234567890ABCDEFGHIJKLMNOPQRST", + "TestIgnoreAddress2345678901BCDEFGHIJKLMNOPQRSTUV" + ], + "MinUsdValueThreshold": 0.1, "MonitoredTokens": ["ELF", "USDT", "BTC", "ETH"], "ScanConfig": { - "ChainIds": ["AELF", "tDVW"], + "ChainIds": ["AELF", "tDVV"], "IntervalSeconds": 30, "BatchSize": 1000 }