Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/Stratis.Bitcoin.Features.Interop/InteropPoller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
{
Expand Down
29 changes: 27 additions & 2 deletions src/Stratis.Bitcoin.Features.Interop/InteropPollerStateMachine.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
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;
using Stratis.Bitcoin.Features.Interop.Payloads;
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
Expand All @@ -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)
Expand Down Expand Up @@ -171,6 +180,22 @@ public async Task VoteFinalisedAsync(ConversionRequest request, IETHClient clien
request.RequestStatus = ConversionRequestStatus.Processed;
request.Processed = true;

List<string> 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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<PubKey> 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<string, HashSet<PubKey>> confirmations;

private object lockObject = new object();

public ConversionConfirmationTracker(IKeyValueRepository keyValueRepository)
{
this.keyValueRepository = keyValueRepository;

lock (this.lockObject)
{
this.confirmations = this.keyValueRepository.LoadValueJson<Dictionary<string, HashSet<PubKey>>>(ConversionConfirmationTrackerKey);

if (this.confirmations == null)
{
this.confirmations = new Dictionary<string, HashSet<PubKey>>();
this.keyValueRepository.SaveValueJson(ConversionConfirmationTrackerKey, this.confirmations, true);
}
}
}

public void RecordParticipant(string requestId, PubKey participant)
{
lock (this.lockObject)
{
HashSet<PubKey> currentConfirmations = this.confirmations[requestId];

if (currentConfirmations == null)
{
currentConfirmations = new HashSet<PubKey>();
}

currentConfirmations.Add(participant);
this.confirmations[requestId] = currentConfirmations;
this.keyValueRepository.SaveValueJson(ConversionConfirmationTrackerKey, this.confirmations, true);
}
}

public List<PubKey> GetParticipants(string requestId)
{
lock (this.lockObject)
{
return this.confirmations[requestId].ToList();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ public interface IConversionRequestRepository
/// <summary>Retrieves <see cref="ConversionRequest"/> with specified id.</summary>
ConversionRequest Get(string requestId);

/// <summary>Retrieves all conversion requests.</summary>
List<ConversionRequest> GetAll(bool onlyUnprocessed);

/// <summary>Retrieves all mint requests.</summary>
List<ConversionRequest> GetAllMint(bool onlyUnprocessed);

Expand Down Expand Up @@ -66,6 +69,16 @@ public ConversionRequest Get(string requestId)
return this.KeyValueStore.LoadValue<ConversionRequest>(requestId);
}

/// <inheritdoc />
public List<ConversionRequest> GetAll(bool onlyUnprocessed)
{
List<ConversionRequest> requests = this.KeyValueStore.GetAll(ConversionRequestType.Mint, onlyUnprocessed);

requests.AddRange(this.KeyValueStore.GetAll(ConversionRequestType.Burn, onlyUnprocessed));

return requests;
}

/// <inheritdoc />
public List<ConversionRequest> GetAllMint(bool onlyUnprocessed)
{
Expand Down
48 changes: 36 additions & 12 deletions src/Stratis.Features.FederatedPeg/Distribution/MultiSigMembers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,38 @@ public static bool IsContractOwner(Network network, PubKey pubKey)
/// <remarks>TODO: Refactor to make this list dynamic.</remarks>
public static readonly List<PubKey> InteropMultisigContractPubKeysMainNet = new List<PubKey>()
{
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<PubKey, string> InteropMultisigAccountsMainNet = new Dictionary<PubKey, string>()
{
{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"), ""}
};

/// <summary>
/// This is the current set of multisig members that are participating in the multisig contract.
/// </summary>
Expand All @@ -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<PubKey, string> InteropMultisigAccountsTestNet = new Dictionary<PubKey, string>()
{
{new PubKey("03cfc06ef56352038e1169deb3b4fa228356e2a54255cf77c271556d2e2607c28c"), ""},
{new PubKey("02fc828e06041ae803ab5378b5ec4e0def3d4e331977a69e1b6ef694d67f5c9c13"), ""},
{new PubKey("02fd4f3197c40d41f9f5478d55844f522744258ca4093b5119571de1a5df1bc653"), ""}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Script, long> blocksMinedEach = new Dictionary<Script, long>();
private readonly Dictionary<uint256, Transaction> commitmentTransactionByHashDictionary = new Dictionary<uint256, Transaction>();
Expand All @@ -46,14 +47,16 @@ public RewardDistributionManager(
ChainIndexer chainIndexer,
IConversionRequestRepository conversionRequestRepository,
IConsensusManager consensusManager,
IFederationHistory federationHistory)
IFederationHistory federationHistory,
IConversionConfirmationTracker conversionConfirmationTracker)
{
this.network = network;
this.chainIndexer = chainIndexer;
this.conversionRequestRepository = conversionRequestRepository;
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;
Expand All @@ -79,16 +82,31 @@ public List<Recipient> 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<ConversionRequest> requests = this.conversionRequestRepository.GetAll(false).Where(c => c.Processed).OrderByDescending(a => a.BlockHeight).ThenBy(b => b.RequestId).Take(5);

var participants = new HashSet<PubKey>();

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.
// If so add the list of multisig members to pay.

// 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--)
{
Expand All @@ -105,6 +123,9 @@ public List<Recipient> 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;

Expand Down
1 change: 1 addition & 0 deletions src/Stratis.Features.FederatedPeg/FederatedPegFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IConversionConfirmationTracker, ConversionConfirmationTracker>();
services.AddSingleton<IRewardDistributionManager, RewardDistributionManager>();
services.AddSingleton<ICoinbaseSplitter, PremineCoinbaseSplitter>();
services.AddSingleton<IBlockBufferGenerator, BlockBufferGenerator>();
Expand Down