Skip to content
This repository has been archived by the owner on Aug 16, 2021. It is now read-only.

[3.0.8.0] PoA mining fix #4090

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
6 changes: 5 additions & 1 deletion src/Stratis.Bitcoin.Features.PoA.Tests/PoATestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class PoATestsBase
protected readonly IFederationManager federationManager;
protected readonly VotingManager votingManager;
protected readonly Mock<IPollResultExecutor> resultExecutorMock;
protected readonly Mock<IConsensusManager> consensusManager;
protected readonly ISignals signals;
protected readonly DBreezeSerializer dBreezeSerializer;
protected readonly ChainState chainState;
Expand All @@ -52,7 +53,10 @@ public PoATestsBase(TestPoANetwork network = null)
this.consensusSettings = new ConsensusSettings(NodeSettings.Default(this.network));

this.federationManager = CreateFederationManager(this, this.network, this.loggerFactory, this.signals);
this.slotsManager = new SlotsManager(this.network, this.federationManager, this.loggerFactory);

this.consensusManager = new Mock<IConsensusManager>();
this.consensusManager.Setup(x => x.Tip).Returns(new ChainedHeader(new BlockHeader(), 0, 0));
this.slotsManager = new SlotsManager(this.network, this.federationManager, this.consensusManager.Object, this.loggerFactory);

this.poaHeaderValidator = new PoABlockHeaderValidator(this.loggerFactory);
this.asyncProvider = new AsyncProvider(this.loggerFactory, this.signals, new Mock<INodeLifetime>().Object);
Expand Down
34 changes: 29 additions & 5 deletions src/Stratis.Bitcoin.Features.PoA.Tests/SlotsManagerTests.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using Moq;
using NBitcoin;
using Stratis.Bitcoin.Configuration;
using Stratis.Bitcoin.Configuration.Logging;
using Stratis.Bitcoin.Consensus;
using Stratis.Bitcoin.Tests.Common;
using Xunit;

Expand All @@ -12,16 +14,18 @@ public class SlotsManagerTests
{
private ISlotsManager slotsManager;
private TestPoANetwork network;
private PoAConsensusOptions consensusOptions;
private IFederationManager federationManager;
private readonly PoAConsensusOptions consensusOptions;
private readonly IFederationManager federationManager;
private Mock<IConsensusManager> consensusManagerMock;

public SlotsManagerTests()
{
this.network = new TestPoANetwork();
this.consensusOptions = this.network.ConsensusOptions;

this.federationManager = PoATestsBase.CreateFederationManager(this);
this.slotsManager = new SlotsManager(this.network, this.federationManager, new LoggerFactory());
this.consensusManagerMock = new Mock<IConsensusManager>();
this.slotsManager = new SlotsManager(this.network, this.federationManager, this.consensusManagerMock.Object, new LoggerFactory());
}

[Fact]
Expand Down Expand Up @@ -61,9 +65,10 @@ public void GetMiningTimestamp()
this.network = new TestPoANetwork(new List<PubKey>() { tool.GeneratePrivateKey().PubKey, key.PubKey, tool.GeneratePrivateKey().PubKey });

IFederationManager fedManager = PoATestsBase.CreateFederationManager(this, this.network, new ExtendedLoggerFactory(), new Signals.Signals(new LoggerFactory(), null));
this.slotsManager = new SlotsManager(this.network, fedManager, new LoggerFactory());
this.consensusManagerMock.Setup(x => x.Tip).Returns(new ChainedHeader(new BlockHeader(), 0, 0));
this.slotsManager = new SlotsManager(this.network, fedManager, this.consensusManagerMock.Object, new LoggerFactory());

List<IFederationMember> federationMembers = this.federationManager.GetFederationMembers();
List<IFederationMember> federationMembers = fedManager.GetFederationMembers();
uint roundStart = this.consensusOptions.TargetSpacingSeconds * (uint)federationMembers.Count * 5;

fedManager.SetPrivatePropertyValue(typeof(FederationManagerBase), nameof(IFederationManager.CurrentFederationKey), key);
Expand All @@ -77,6 +82,25 @@ public void GetMiningTimestamp()
Assert.Equal(roundStart + this.consensusOptions.TargetSpacingSeconds, this.slotsManager.GetMiningTimestamp(roundStart - this.consensusOptions.TargetSpacingSeconds + 1));

Assert.True(this.slotsManager.IsValidTimestamp(this.slotsManager.GetMiningTimestamp(roundStart - 5)));

uint thisTurnTimestamp = roundStart + this.consensusOptions.TargetSpacingSeconds;
uint nextTurnTimestamp = thisTurnTimestamp + this.consensusOptions.TargetSpacingSeconds * (uint)federationMembers.Count;

// If we are past our last timestamp's turn, always give us the NEXT timestamp.
uint justPastOurTurnTime = thisTurnTimestamp + (this.consensusOptions.TargetSpacingSeconds / 2) + 1;
Assert.Equal(nextTurnTimestamp, this.slotsManager.GetMiningTimestamp(justPastOurTurnTime));

// If we are only just past our last timestamp, but still in the "range" and we haven't mined a block yet, get THIS turn's timestamp.
Assert.Equal(thisTurnTimestamp, this.slotsManager.GetMiningTimestamp(thisTurnTimestamp + 1));

// If we are only just past our last timestamp, but we've already mined a block there, then get the NEXT turn's timestamp.
this.consensusManagerMock.Setup(x => x.Tip).Returns(new ChainedHeader(new BlockHeader
{
Time = thisTurnTimestamp
}, 0, 0));
this.slotsManager = new SlotsManager(this.network, fedManager, this.consensusManagerMock.Object, new LoggerFactory());
Assert.Equal(nextTurnTimestamp, this.slotsManager.GetMiningTimestamp(thisTurnTimestamp + 1));

}
}
}
14 changes: 9 additions & 5 deletions src/Stratis.Bitcoin.Features.PoA/SlotsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@ public class SlotsManager : ISlotsManager

private readonly IFederationManager federationManager;

private readonly IConsensusManager consensusManager;

private readonly ILogger logger;

public SlotsManager(Network network, IFederationManager federationManager, ILoggerFactory loggerFactory)
public SlotsManager(Network network, IFederationManager federationManager, IConsensusManager consensusManager, ILoggerFactory loggerFactory)
{
Guard.NotNull(network, nameof(network));
this.federationManager = Guard.NotNull(federationManager, nameof(federationManager));

this.consensusManager = consensusManager;
this.consensusOptions = (network as PoANetwork).ConsensusOptions;
this.logger = loggerFactory.CreateLogger(this.GetType().FullName);
}
Expand Down Expand Up @@ -83,9 +85,11 @@ public uint GetMiningTimestamp(uint currentTime)
uint roundStartTimestamp = (currentTime / roundTime) * roundTime;
uint nextTimestampForMining = roundStartTimestamp + slotIndex * this.consensusOptions.TargetSpacingSeconds;

// We already passed our slot in this round.
// Get timestamp of our slot from next round.
if (nextTimestampForMining < currentTime)
// Check if we have missed our turn for this round.
// We still consider ourselves "in a turn" if we are in the first half of the turn and we haven't mined there yet.
// This might happen when starting the node for the first time or if there was a problem when mining.
if (currentTime > nextTimestampForMining + (this.consensusOptions.TargetSpacingSeconds / 2) // We are closer to the next turn than our own
|| this.consensusManager.Tip.Header.Time == nextTimestampForMining) // We have already mined in that slot
{
// Get timestamp for next round.
nextTimestampForMining = roundStartTimestamp + roundTime + slotIndex * this.consensusOptions.TargetSpacingSeconds;
Expand Down