From 7893d46c0c5583d18c7fdc70c2231e8c6b1e22ab Mon Sep 17 00:00:00 2001 From: Kevin Loubser Date: Wed, 28 Jun 2023 23:35:08 +0200 Subject: [PATCH] Allocate conversion fees based on confirmation participation --- .../InteropPoller.cs | 9 ++- .../InteropPollerStateMachine.cs | 29 +++++++- .../ConversionConfirmationTracker.cs | 66 +++++++++++++++++++ .../Conversion/ConversionRequestRepository.cs | 13 ++++ .../Distribution/MultiSigMembers.cs | 48 ++++++++++---- .../Distribution/RewardDistributionManager.cs | 27 +++++++- .../FederatedPegFeature.cs | 1 + 7 files changed, 173 insertions(+), 20 deletions(-) create mode 100644 src/Stratis.Features.FederatedPeg/Conversion/ConversionConfirmationTracker.cs diff --git a/src/Stratis.Bitcoin.Features.Interop/InteropPoller.cs b/src/Stratis.Bitcoin.Features.Interop/InteropPoller.cs index fb92a2cf20..3a57a1754a 100644 --- a/src/Stratis.Bitcoin.Features.Interop/InteropPoller.cs +++ b/src/Stratis.Bitcoin.Features.Interop/InteropPoller.cs @@ -79,6 +79,7 @@ public sealed class InteropPoller : IDisposable private readonly ILogger logger; private readonly IMaturedBlocksSyncManager maturedBlocksSyncManager; private readonly IReplenishmentKeyValueStore replenishmentKeyValueStore; + private readonly IConversionConfirmationTracker conversionConfirmationTracker; private readonly Network network; private readonly INodeLifetime nodeLifetime; @@ -118,7 +119,8 @@ public InteropPoller(NodeSettings nodeSettings, INodeStats nodeStats, ICirrusContractClient cirrusClient, IMaturedBlocksSyncManager maturedBlocksSyncManager, - IReplenishmentKeyValueStore replenishmentKeyValueStore) + IReplenishmentKeyValueStore replenishmentKeyValueStore, + IConversionConfirmationTracker conversionConfirmationTracker) { this.interopSettings = interopSettings; this.ethClientProvider = ethClientProvider; @@ -139,6 +141,7 @@ public InteropPoller(NodeSettings nodeSettings, this.keyValueRepository = keyValueRepository; this.maturedBlocksSyncManager = maturedBlocksSyncManager; this.replenishmentKeyValueStore = replenishmentKeyValueStore; + this.conversionConfirmationTracker = conversionConfirmationTracker; this.logger = LogManager.GetCurrentClassLogger(); this.cirrusClient = cirrusClient; @@ -994,7 +997,7 @@ private async Task ProcessMintRequestsAsync() this.logger.Info("There are {0} unprocessed mint requests.", mintRequests.Count); - var stateMachine = new InteropPollerStateMachine(this.logger, this.externalApiPoller, this.conversionRequestCoordinationService, this.federationManager, this.federatedPegBroadcaster); + var stateMachine = new InteropPollerStateMachine(this.logger, this.externalApiPoller, this.conversionRequestCoordinationService, this.federationManager, this.federatedPegBroadcaster, this.conversionConfirmationTracker, this.network); foreach (ConversionRequest request in mintRequests) { @@ -1278,7 +1281,7 @@ private async Task ProcessBurnRequestsAsync() this.logger.Info("There are {0} unprocessed burn requests.", burnRequests.Count); - var stateMachine = new InteropPollerStateMachine(this.logger, this.externalApiPoller, this.conversionRequestCoordinationService, this.federationManager, this.federatedPegBroadcaster); + var stateMachine = new InteropPollerStateMachine(this.logger, this.externalApiPoller, this.conversionRequestCoordinationService, this.federationManager, this.federatedPegBroadcaster, this.conversionConfirmationTracker, this.network); foreach (ConversionRequest request in burnRequests) { diff --git a/src/Stratis.Bitcoin.Features.Interop/InteropPollerStateMachine.cs b/src/Stratis.Bitcoin.Features.Interop/InteropPollerStateMachine.cs index 2d765745b5..de77fbcdb8 100644 --- a/src/Stratis.Bitcoin.Features.Interop/InteropPollerStateMachine.cs +++ b/src/Stratis.Bitcoin.Features.Interop/InteropPollerStateMachine.cs @@ -1,5 +1,8 @@ -using System.Numerics; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; using System.Threading.Tasks; +using NBitcoin; using NLog; using Stratis.Bitcoin.Features.ExternalApi; using Stratis.Bitcoin.Features.Interop.ETHClient; @@ -7,8 +10,10 @@ using Stratis.Bitcoin.Features.Interop.Settings; using Stratis.Bitcoin.Features.PoA; using Stratis.Bitcoin.Features.Wallet; +using Stratis.Bitcoin.Utilities; using Stratis.Features.FederatedPeg.Conversion; using Stratis.Features.FederatedPeg.Coordination; +using Stratis.Features.FederatedPeg.Distribution; using Stratis.Features.FederatedPeg.Interfaces; namespace Stratis.Bitcoin.Features.Interop @@ -23,14 +28,18 @@ public class InteropPollerStateMachine private readonly IConversionRequestCoordinationService conversionRequestCoordinationService; private readonly IFederationManager federationManager; private readonly IFederatedPegBroadcaster federatedPegBroadcaster; + private readonly IConversionConfirmationTracker conversionConfirmationTracker; + private readonly Network network; - public InteropPollerStateMachine(ILogger logger, IExternalApiPoller externalApiPoller, IConversionRequestCoordinationService conversionRequestCoordinationService, IFederationManager federationManager, IFederatedPegBroadcaster federatedPegBroadcaster) + public InteropPollerStateMachine(ILogger logger, IExternalApiPoller externalApiPoller, IConversionRequestCoordinationService conversionRequestCoordinationService, IFederationManager federationManager, IFederatedPegBroadcaster federatedPegBroadcaster, IConversionConfirmationTracker conversionConfirmationTracker, Network network) { this.logger = logger; this.externalApiPoller = externalApiPoller; this.conversionRequestCoordinationService = conversionRequestCoordinationService; this.federationManager = federationManager; this.federatedPegBroadcaster = federatedPegBroadcaster; + this.conversionConfirmationTracker = conversionConfirmationTracker; + this.network = network; } public void Unprocessed(ConversionRequest request, bool originator, IFederationMember designatedMember, bool mintOnCirrus, ContractType contractType) @@ -171,6 +180,22 @@ public async Task VoteFinalisedAsync(ConversionRequest request, IETHClient clien request.RequestStatus = ConversionRequestStatus.Processed; request.Processed = true; + List owners = await clientForDestChain.GetOwnersAsync().ConfigureAwait(false); + + foreach (string owner in owners) + { + bool ownerConfirmed = await clientForDestChain.AddressConfirmedTransactionAsync(transactionId3, owner).ConfigureAwait(false); + + if (ownerConfirmed) + { + PubKey pubKey = this.network.IsTest() ? MultiSigMembers.InteropMultisigAccountsTestNet.First(v => v.Value == owner).Key : MultiSigMembers.InteropMultisigAccountsMainNet.First(v => v.Value == owner).Key; + + this.logger.Debug($"Confirmation tracker: {owner} ({pubKey}) confirmed {request.RequestId}."); + + this.conversionConfirmationTracker.RecordParticipant(request.RequestId, pubKey); + } + } + // We no longer need to track votes for this transaction. this.conversionRequestCoordinationService.RemoveTransaction(request.RequestId); } diff --git a/src/Stratis.Features.FederatedPeg/Conversion/ConversionConfirmationTracker.cs b/src/Stratis.Features.FederatedPeg/Conversion/ConversionConfirmationTracker.cs new file mode 100644 index 0000000000..53582fd0b7 --- /dev/null +++ b/src/Stratis.Features.FederatedPeg/Conversion/ConversionConfirmationTracker.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.Linq; +using NBitcoin; +using Stratis.Bitcoin.Persistence; + +namespace Stratis.Features.FederatedPeg.Conversion +{ + public interface IConversionConfirmationTracker + { + List GetParticipants(string requestId); + + void RecordParticipant(string requestId, PubKey participant); + } + + public class ConversionConfirmationTracker : IConversionConfirmationTracker + { + public const string ConversionConfirmationTrackerKey = "ConversionConfirmationTrackerKey"; + + private readonly IKeyValueRepository keyValueRepository; + + private readonly Dictionary> confirmations; + + private object lockObject = new object(); + + public ConversionConfirmationTracker(IKeyValueRepository keyValueRepository) + { + this.keyValueRepository = keyValueRepository; + + lock (this.lockObject) + { + this.confirmations = this.keyValueRepository.LoadValueJson>>(ConversionConfirmationTrackerKey); + + if (this.confirmations == null) + { + this.confirmations = new Dictionary>(); + this.keyValueRepository.SaveValueJson(ConversionConfirmationTrackerKey, this.confirmations, true); + } + } + } + + public void RecordParticipant(string requestId, PubKey participant) + { + lock (this.lockObject) + { + HashSet currentConfirmations = this.confirmations[requestId]; + + if (currentConfirmations == null) + { + currentConfirmations = new HashSet(); + } + + currentConfirmations.Add(participant); + this.confirmations[requestId] = currentConfirmations; + this.keyValueRepository.SaveValueJson(ConversionConfirmationTrackerKey, this.confirmations, true); + } + } + + public List GetParticipants(string requestId) + { + lock (this.lockObject) + { + return this.confirmations[requestId].ToList(); + } + } + } +} diff --git a/src/Stratis.Features.FederatedPeg/Conversion/ConversionRequestRepository.cs b/src/Stratis.Features.FederatedPeg/Conversion/ConversionRequestRepository.cs index 68e7d205c9..7e41aa4942 100644 --- a/src/Stratis.Features.FederatedPeg/Conversion/ConversionRequestRepository.cs +++ b/src/Stratis.Features.FederatedPeg/Conversion/ConversionRequestRepository.cs @@ -12,6 +12,9 @@ public interface IConversionRequestRepository /// Retrieves with specified id. ConversionRequest Get(string requestId); + /// Retrieves all conversion requests. + List GetAll(bool onlyUnprocessed); + /// Retrieves all mint requests. List GetAllMint(bool onlyUnprocessed); @@ -66,6 +69,16 @@ public ConversionRequest Get(string requestId) return this.KeyValueStore.LoadValue(requestId); } + /// + public List GetAll(bool onlyUnprocessed) + { + List requests = this.KeyValueStore.GetAll(ConversionRequestType.Mint, onlyUnprocessed); + + requests.AddRange(this.KeyValueStore.GetAll(ConversionRequestType.Burn, onlyUnprocessed)); + + return requests; + } + /// public List GetAllMint(bool onlyUnprocessed) { diff --git a/src/Stratis.Features.FederatedPeg/Distribution/MultiSigMembers.cs b/src/Stratis.Features.FederatedPeg/Distribution/MultiSigMembers.cs index 16024c3a7a..24895d0fd2 100644 --- a/src/Stratis.Features.FederatedPeg/Distribution/MultiSigMembers.cs +++ b/src/Stratis.Features.FederatedPeg/Distribution/MultiSigMembers.cs @@ -22,21 +22,38 @@ public static bool IsContractOwner(Network network, PubKey pubKey) /// TODO: Refactor to make this list dynamic. public static readonly List InteropMultisigContractPubKeysMainNet = new List() { - new PubKey("027e793fbf4f6d07de15b0aa8355f88759b8bdf92a9ffb8a65a87fa8ee03baeccd"),// - new PubKey("03e8809be396745434ee8c875089e518a3eef40e31ade81869ce9cbef63484996d"),// - new PubKey("02f40bd4f662ba20629a104115f0ac9ee5eab695716edfe01b240abf56e05797e2"),// - new PubKey("03535a285d0919a9bd71df3b274cecb46e16b78bf50d3bf8b0a3b41028cf8a842d"),// - new PubKey("0317abe6a28cc7af44a46de97e7c6120c1ccec78afb83efe18030f5c36e3016b32"),// - new PubKey("03eb5db0b1703ea7418f0ad20582bf8de0b4105887d232c7724f43f19f14862488"),// - new PubKey("03d8b5580b7ec709c006ef497327db27ea323bd358ca45412171c644214483b74f"),// - new PubKey("0323033679aa439a0388f09f2883bf1ca6f50283b41bfeb6be6ddcc4e420144c16"),// - new PubKey("025cb67811d0922ca77fa33f19c3e5c37961f9639a1f0a116011b9075f6796abcb"),// - new PubKey("028e1d9fd64b84a2ec85fac7185deb2c87cc0dd97270cf2d8adc3aa766dde975a7"),// - new PubKey("036437789fac0ab74cda93d98b519c28608a48ef86c3bd5e8227af606c1e025f61"),// - new PubKey("03f5de5176e29e1e7d518ae76c1e020b1da18b57a3713ac81b16015026e232748e"),// + new PubKey("027e793fbf4f6d07de15b0aa8355f88759b8bdf92a9ffb8a65a87fa8ee03baeccd"), + new PubKey("03e8809be396745434ee8c875089e518a3eef40e31ade81869ce9cbef63484996d"), + new PubKey("02f40bd4f662ba20629a104115f0ac9ee5eab695716edfe01b240abf56e05797e2"), + new PubKey("03535a285d0919a9bd71df3b274cecb46e16b78bf50d3bf8b0a3b41028cf8a842d"), + new PubKey("0317abe6a28cc7af44a46de97e7c6120c1ccec78afb83efe18030f5c36e3016b32"), + new PubKey("03eb5db0b1703ea7418f0ad20582bf8de0b4105887d232c7724f43f19f14862488"), + new PubKey("03d8b5580b7ec709c006ef497327db27ea323bd358ca45412171c644214483b74f"), + new PubKey("0323033679aa439a0388f09f2883bf1ca6f50283b41bfeb6be6ddcc4e420144c16"), + new PubKey("025cb67811d0922ca77fa33f19c3e5c37961f9639a1f0a116011b9075f6796abcb"), + new PubKey("028e1d9fd64b84a2ec85fac7185deb2c87cc0dd97270cf2d8adc3aa766dde975a7"), + new PubKey("036437789fac0ab74cda93d98b519c28608a48ef86c3bd5e8227af606c1e025f61"), + new PubKey("03f5de5176e29e1e7d518ae76c1e020b1da18b57a3713ac81b16015026e232748e"), new PubKey("03a37019d2e010b046ef9d0459e4844a015758007602ddfbdc9702534924a23695") }; + public static readonly Dictionary InteropMultisigAccountsMainNet = new Dictionary() + { + {new PubKey("027e793fbf4f6d07de15b0aa8355f88759b8bdf92a9ffb8a65a87fa8ee03baeccd"), ""}, + {new PubKey("03e8809be396745434ee8c875089e518a3eef40e31ade81869ce9cbef63484996d"), ""}, + {new PubKey("02f40bd4f662ba20629a104115f0ac9ee5eab695716edfe01b240abf56e05797e2"), ""}, + {new PubKey("03535a285d0919a9bd71df3b274cecb46e16b78bf50d3bf8b0a3b41028cf8a842d"), ""}, + {new PubKey("0317abe6a28cc7af44a46de97e7c6120c1ccec78afb83efe18030f5c36e3016b32"), ""}, + {new PubKey("03eb5db0b1703ea7418f0ad20582bf8de0b4105887d232c7724f43f19f14862488"), ""}, + {new PubKey("03d8b5580b7ec709c006ef497327db27ea323bd358ca45412171c644214483b74f"), ""}, + {new PubKey("0323033679aa439a0388f09f2883bf1ca6f50283b41bfeb6be6ddcc4e420144c16"), ""}, + {new PubKey("025cb67811d0922ca77fa33f19c3e5c37961f9639a1f0a116011b9075f6796abcb"), ""}, + {new PubKey("028e1d9fd64b84a2ec85fac7185deb2c87cc0dd97270cf2d8adc3aa766dde975a7"), ""}, + {new PubKey("036437789fac0ab74cda93d98b519c28608a48ef86c3bd5e8227af606c1e025f61"), ""}, + {new PubKey("03f5de5176e29e1e7d518ae76c1e020b1da18b57a3713ac81b16015026e232748e"), ""}, + {new PubKey("03a37019d2e010b046ef9d0459e4844a015758007602ddfbdc9702534924a23695"), ""} + }; + /// /// This is the current set of multisig members that are participating in the multisig contract. /// @@ -47,5 +64,12 @@ public static bool IsContractOwner(Network network, PubKey pubKey) new PubKey("02fc828e06041ae803ab5378b5ec4e0def3d4e331977a69e1b6ef694d67f5c9c13"), // Cirrus 3 new PubKey("02fd4f3197c40d41f9f5478d55844f522744258ca4093b5119571de1a5df1bc653"), // Cirrus 4 }; + + public static readonly Dictionary InteropMultisigAccountsTestNet = new Dictionary() + { + {new PubKey("03cfc06ef56352038e1169deb3b4fa228356e2a54255cf77c271556d2e2607c28c"), ""}, + {new PubKey("02fc828e06041ae803ab5378b5ec4e0def3d4e331977a69e1b6ef694d67f5c9c13"), ""}, + {new PubKey("02fd4f3197c40d41f9f5478d55844f522744258ca4093b5119571de1a5df1bc653"), ""} + }; } } diff --git a/src/Stratis.Features.FederatedPeg/Distribution/RewardDistributionManager.cs b/src/Stratis.Features.FederatedPeg/Distribution/RewardDistributionManager.cs index d5b504be59..ffba1d1191 100644 --- a/src/Stratis.Features.FederatedPeg/Distribution/RewardDistributionManager.cs +++ b/src/Stratis.Features.FederatedPeg/Distribution/RewardDistributionManager.cs @@ -30,6 +30,7 @@ public sealed class RewardDistributionManager : IRewardDistributionManager private readonly ILogger logger; private readonly Network network; private readonly IFederationHistory federationHistory; + private readonly IConversionConfirmationTracker conversionConfirmationTracker; private readonly Dictionary blocksMinedEach = new Dictionary(); private readonly Dictionary commitmentTransactionByHashDictionary = new Dictionary(); @@ -46,7 +47,8 @@ public RewardDistributionManager( ChainIndexer chainIndexer, IConversionRequestRepository conversionRequestRepository, IConsensusManager consensusManager, - IFederationHistory federationHistory) + IFederationHistory federationHistory, + IConversionConfirmationTracker conversionConfirmationTracker) { this.network = network; this.chainIndexer = chainIndexer; @@ -54,6 +56,7 @@ public RewardDistributionManager( this.consensusManager = consensusManager; this.logger = LogManager.GetCurrentClassLogger(); this.federationHistory = federationHistory; + this.conversionConfirmationTracker = conversionConfirmationTracker; this.encoder = new CollateralHeightCommitmentEncoder(); this.epoch = this.network.Consensus.MaxReorgLength == 0 ? DefaultEpoch : (int)this.network.Consensus.MaxReorgLength; @@ -79,8 +82,23 @@ public List DistributeToMultisigNodes(uint256 depositId, Money fee) // For Strax to wStrax transfers and SRC20 to ERC20 the deposit id will match the conversion request id. ConversionRequest conversionRequest = this.conversionRequestRepository.Get(depositId.ToString()); + // We want to apportion fees fairly to active federation members that are confirming InterFlux transfers. + // However, at the time the distribution transaction is constructed the conversion request it relates to may not have been + // confirmed yet. So we use the recent conversion requests that are already processed + IEnumerable requests = this.conversionRequestRepository.GetAll(false).Where(c => c.Processed).OrderByDescending(a => a.BlockHeight).ThenBy(b => b.RequestId).Take(5); + + var participants = new HashSet(); + + foreach (ConversionRequest request in requests) + { + foreach (PubKey participant in this.conversionConfirmationTracker.GetParticipants(request.RequestId)) + { + participants.Add(participant); + } + } + // Start checking if a multisig member mined a block at the conversion deposit block height tip less epoch window blocks. - var startHeight = conversionRequest.BlockHeight - this.epochWindow; + int startHeight = conversionRequest.BlockHeight - this.epochWindow; // Inspect the round of blocks equal to the federation size // and determine if a multisig member mined the block. @@ -88,7 +106,7 @@ public List DistributeToMultisigNodes(uint256 depositId, Money fee) // Look back at least 8 federation sizes to ensure that we collect enough data on the multisig // members that mined. - var inspectionRange = this.federationHistory.GetFederationForBlock(this.chainIndexer.GetHeader(conversionRequest.BlockHeight)).Count * 8; + int inspectionRange = this.federationHistory.GetFederationForBlock(this.chainIndexer.GetHeader(conversionRequest.BlockHeight)).Count * 8; for (int i = inspectionRange; i >= 0; i--) { @@ -105,6 +123,9 @@ public List DistributeToMultisigNodes(uint256 depositId, Money fee) if (!MultiSigMembers.IsContractOwner(this.network, collateralFederationMember.PubKey)) continue; + if (!participants.Contains(collateralFederationMember.PubKey)) + continue; + if (chainedHeader.Block == null) chainedHeader.Block = this.consensusManager.GetBlockData(chainedHeader.HashBlock).Block; diff --git a/src/Stratis.Features.FederatedPeg/FederatedPegFeature.cs b/src/Stratis.Features.FederatedPeg/FederatedPegFeature.cs index a917d33583..4fc8690bda 100644 --- a/src/Stratis.Features.FederatedPeg/FederatedPegFeature.cs +++ b/src/Stratis.Features.FederatedPeg/FederatedPegFeature.cs @@ -321,6 +321,7 @@ public static IFullNodeBuilder AddFederatedPeg(this IFullNodeBuilder fullNodeBui // The reward distribution manager only runs on the side chain. if (!isMainChain) { + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton();