Commit 1b7943dd authored by Sergei Zubov's avatar Sergei Zubov

Merge pos minting

parent f2d2e2e9
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using Moq;
using NBitcoin;
using NBitcoin.BitcoinCore;
using Stratis.Bitcoin.Base;
using Stratis.Bitcoin.Configuration;
using Stratis.Bitcoin.Consensus;
using Stratis.Bitcoin.Features.Consensus;
using Stratis.Bitcoin.Features.Consensus.CoinViews;
using Stratis.Bitcoin.Features.Consensus.Interfaces;
using Stratis.Bitcoin.Features.MemoryPool;
using Stratis.Bitcoin.Features.MemoryPool.Interfaces;
using Stratis.Bitcoin.Features.Miner.Models;
using Stratis.Bitcoin.Features.Miner.Staking;
using Stratis.Bitcoin.Features.Wallet;
using Stratis.Bitcoin.Features.Wallet.Interfaces;
using Stratis.Bitcoin.Interfaces;
using Stratis.Bitcoin.Mining;
using Stratis.Bitcoin.Tests.Common;
using Stratis.Bitcoin.Tests.Common.Logging;
using Stratis.Bitcoin.Tests.Wallet.Common;
using Stratis.Bitcoin.Utilities;
using Xunit;
namespace Stratis.Bitcoin.Features.Miner.Tests
{
public class DeStreamPosMintingTest : LogsTestBase
{
protected PosMinting posMinting;
private readonly Mock<IConsensusManager> consensusManager;
private ConcurrentChain chain;
protected Network network;
private readonly Mock<IDateTimeProvider> dateTimeProvider;
private readonly Mock<IInitialBlockDownloadState> initialBlockDownloadState;
private readonly Mock<INodeLifetime> nodeLifetime;
private readonly Mock<ICoinView> coinView;
private readonly Mock<IStakeChain> stakeChain;
private readonly List<uint256> powBlocks;
private readonly Mock<IStakeValidator> stakeValidator;
private readonly MempoolSchedulerLock mempoolSchedulerLock;
private readonly Mock<ITxMempool> txMempool;
private readonly MinerSettings minerSettings;
private readonly Mock<IWalletManager> walletManager;
private readonly Mock<IAsyncLoopFactory> asyncLoopFactory;
private readonly Mock<ITimeSyncBehaviorState> timeSyncBehaviorState;
private readonly CancellationTokenSource cancellationTokenSource;
public DeStreamPosMintingTest()
{
this.consensusManager = new Mock<IConsensusManager>();
this.network = KnownNetworks.DeStreamMain;
this.network.Consensus.Options = new ConsensusOptions();
this.chain = new ConcurrentChain(this.network);
this.dateTimeProvider = new Mock<IDateTimeProvider>();
this.initialBlockDownloadState = new Mock<IInitialBlockDownloadState>();
this.nodeLifetime = new Mock<INodeLifetime>();
this.coinView = new Mock<ICoinView>();
this.stakeChain = new Mock<IStakeChain>();
this.powBlocks = new List<uint256>();
this.SetupStakeChain();
this.stakeValidator = new Mock<IStakeValidator>();
this.mempoolSchedulerLock = new MempoolSchedulerLock();
this.minerSettings = new MinerSettings(NodeSettings.Default(this.network));
this.txMempool = new Mock<ITxMempool>();
this.walletManager = new Mock<IWalletManager>();
this.asyncLoopFactory = new Mock<IAsyncLoopFactory>();
this.timeSyncBehaviorState = new Mock<ITimeSyncBehaviorState>();
this.cancellationTokenSource = new CancellationTokenSource();
this.nodeLifetime.Setup(n => n.ApplicationStopping).Returns(this.cancellationTokenSource.Token);
this.posMinting = this.InitializePosMinting();
}
[Fact]
public void Stake_StakingLoopNotStarted_StartsStakingLoop()
{
var asyncLoop = new AsyncLoop("PosMining.Stake2", this.FullNodeLogger.Object, token => { return Task.CompletedTask; });
this.asyncLoopFactory.Setup(a => a.Run("PosMining.Stake",
It.IsAny<Func<CancellationToken, Task>>(), It.IsAny<CancellationToken>(),
It.Is<TimeSpan>(t => t.Milliseconds == 500), TimeSpans.Second))
.Returns(asyncLoop)
.Verifiable();
this.posMinting.Stake(new WalletSecret() { WalletName = "wallet1", WalletPassword = "myPassword" });
this.nodeLifetime.Verify();
this.asyncLoopFactory.Verify();
}
[Fact]
public void Stake_StakingLoopThrowsMinerException_AddsErrorToRpcStakingInfoModel()
{
var asyncLoop = new AsyncLoop("PosMining.Stake2", this.FullNodeLogger.Object, token => { return Task.CompletedTask; });
this.asyncLoopFactory.Setup(a => a.Run("PosMining.Stake",
It.IsAny<Func<CancellationToken, Task>>(), It.IsAny<CancellationToken>(),
It.Is<TimeSpan>(t => t.Milliseconds == 500), TimeSpans.Second))
.Callback<string, Func<CancellationToken, Task>, CancellationToken, TimeSpan?, TimeSpan?>((name, func, token, repeat, start) =>
{
func(token);
})
.Returns(asyncLoop)
.Verifiable();
bool isSystemTimeOutOfSyncCalled = false;
this.timeSyncBehaviorState.Setup(c => c.IsSystemTimeOutOfSync)
.Returns(() =>
{
if (!isSystemTimeOutOfSyncCalled)
{
isSystemTimeOutOfSyncCalled = true;
throw new MinerException("Mining error.");
}
this.cancellationTokenSource.Cancel();
throw new InvalidOperationException("End the loop");
});
this.posMinting.Stake(new WalletSecret() { WalletName = "wallet1", WalletPassword = "myPassword" });
asyncLoop.Run();
GetStakingInfoModel model = this.posMinting.GetGetStakingInfoModel();
Assert.Equal("Mining error.", model.Errors);
}
[Fact]
public void Stake_StakingLoopThrowsConsensusErrorException_AddsErrorToRpcStakingInfoModel()
{
var asyncLoop = new AsyncLoop("PosMining.Stake2", this.FullNodeLogger.Object, token => { return Task.CompletedTask; });
this.asyncLoopFactory.Setup(a => a.Run("PosMining.Stake",
It.IsAny<Func<CancellationToken, Task>>(), It.IsAny<CancellationToken>(),
It.Is<TimeSpan>(t => t.Milliseconds == 500), TimeSpans.Second))
.Callback<string, Func<CancellationToken, Task>, CancellationToken, TimeSpan?, TimeSpan?>((name, func, token, repeat, start) =>
{
func(token);
})
.Returns(asyncLoop)
.Verifiable();
bool isSystemTimeOutOfSyncCalled = false;
this.timeSyncBehaviorState.Setup(c => c.IsSystemTimeOutOfSync)
.Returns(() =>
{
if (!isSystemTimeOutOfSyncCalled)
{
isSystemTimeOutOfSyncCalled = true;
throw new ConsensusErrorException(new ConsensusError("15", "Consensus error."));
}
this.cancellationTokenSource.Cancel();
throw new InvalidOperationException("End the loop");
});
this.posMinting.Stake(new WalletSecret() { WalletName = "wallet1", WalletPassword = "myPassword" });
asyncLoop.Run();
GetStakingInfoModel model = this.posMinting.GetGetStakingInfoModel();
Assert.Equal("Consensus error.", model.Errors);
}
[Fact]
public void StopStake_DisposesResources()
{
var asyncLoop = new Mock<IAsyncLoop>();
Func<CancellationToken, Task> stakingLoopFunction = null;
CancellationToken stakingLoopToken = default(CancellationToken);
this.asyncLoopFactory.Setup(a => a.Run("PosMining.Stake",
It.IsAny<Func<CancellationToken, Task>>(), It.IsAny<CancellationToken>(),
It.Is<TimeSpan>(t => t.Milliseconds == 500), TimeSpans.Second))
.Callback<string, Func<CancellationToken, Task>, CancellationToken, TimeSpan?, TimeSpan?>((name, func, token, repeat, start) =>
{
stakingLoopFunction = func;
stakingLoopToken = token;
})
.Returns(asyncLoop.Object)
.Verifiable();
bool isSystemTimeOutOfSyncCalled = false;
this.timeSyncBehaviorState.Setup(c => c.IsSystemTimeOutOfSync)
.Returns(() =>
{
if (!isSystemTimeOutOfSyncCalled)
{
isSystemTimeOutOfSyncCalled = true;
// generates an error in the stakinginfomodel.
throw new MinerException("Mining error.");
}
this.posMinting.StopStake();// stop the staking.
throw new InvalidOperationException("End the loop");
});
this.posMinting.Stake(new WalletSecret() { WalletName = "wallet1", WalletPassword = "myPassword" });
stakingLoopFunction(stakingLoopToken);
stakingLoopFunction(stakingLoopToken);
Assert.True(stakingLoopToken.IsCancellationRequested);
asyncLoop.Verify(a => a.Dispose());
GetStakingInfoModel model = this.posMinting.GetGetStakingInfoModel();
Assert.Null(model.Errors);
Assert.False(model.Enabled);
}
[Fact]
public async Task GenerateBlocksAsync_does_not_use_small_coins()
{
var walletSecret = new WalletSecret() { WalletName = "wallet", WalletPassword = "password" };
var wallet = new Wallet.Wallet()
{
Network = this.network
};
var milliseconds550MinutesAgo = (uint)Math.Max(this.chain.Tip.Header.Time - TimeSpan.FromMinutes(550).Milliseconds, 0);
this.AddAccountWithSpendableOutputs(wallet);
var spendableTransactions = wallet.GetAllSpendableTransactions(CoinType.Stratis, this.chain.Tip.Height, 0).ToList();
this.walletManager.Setup(w => w.GetSpendableTransactionsInWalletForStaking(It.IsAny<string>(), It.IsAny<int>()))
.Returns(spendableTransactions);
var fetchedUtxos = spendableTransactions
.Select(t => new UnspentOutputs(t.Transaction.Id, new Coins()
{
CoinBase = false,
CoinStake = false,
Height = 0,
Outputs = { new TxOut(t.Transaction.Amount ?? Money.Zero, t.Address.ScriptPubKey) },
Time = milliseconds550MinutesAgo,
Version = 1
}))
.ToArray();
var fetchCoinsResponse = new FetchCoinsResponse(fetchedUtxos, this.chain.Tip.HashBlock);
fetchCoinsResponse.UnspentOutputs
.Where(u => u.Outputs.Any(o => o.Value < this.posMinting.MinimumStakingCoinValue)).Should()
.NotBeEmpty("otherwise we are not sure the code actually excludes them");
fetchCoinsResponse.UnspentOutputs
.Where(u => u.Outputs.Any(o => o.Value >= this.posMinting.MinimumStakingCoinValue)).Should()
.NotBeEmpty("otherwise we are not sure the code actually includes them");
this.coinView.Setup(c => c.FetchCoinsAsync(It.IsAny<uint256[]>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(fetchCoinsResponse));
this.consensusManager.Setup(c => c.Tip).Returns(this.chain.Tip);
this.dateTimeProvider.Setup(c => c.GetAdjustedTimeAsUnixTimestamp())
.Returns(this.chain.Tip.Header.Time + 16);
var ct = CancellationToken.None;
var utxoStakeDescriptions = await this.posMinting.GetUtxoStakeDescriptionsAsync(walletSecret, ct);
utxoStakeDescriptions.Select(d => d.TxOut.Value).Where(v => v < this.posMinting.MinimumStakingCoinValue)
.Should().BeEmpty("small coins should not be included");
utxoStakeDescriptions.Select(d => d.TxOut.Value).Where(v => v >= this.posMinting.MinimumStakingCoinValue)
.Should().NotBeEmpty("big enough coins should be included");
var expectedAmounts = spendableTransactions.Select(s => s.Transaction.Amount)
.Where(a => a >= this.posMinting.MinimumStakingCoinValue).ToArray();
utxoStakeDescriptions.Count.Should().Be(expectedAmounts.Length);
utxoStakeDescriptions.Select(d => d.TxOut.Value).Should().Contain(expectedAmounts);
}
private void AddAccountWithSpendableOutputs(Wallet.Wallet wallet)
{
var account = new HdAccount();
account.ExternalAddresses.Add(new HdAddress { Index = 1, Transactions = new List<TransactionData> { new TransactionData { Id = new uint256(15), Index = 0, Amount = this.posMinting.MinimumStakingCoinValue - 1 } } });
account.ExternalAddresses.Add(new HdAddress { Index = 1, Transactions = new List<TransactionData> { new TransactionData { Id = new uint256(16), Index = 0, Amount = this.posMinting.MinimumStakingCoinValue } } });
account.ExternalAddresses.Add(new HdAddress { Index = 2, Transactions = new List<TransactionData> { new TransactionData { Id = new uint256(17), Index = 0, Amount = 2 * Money.COIN } } });
account.ExternalAddresses.Add(new HdAddress { Index = 2, Transactions = new List<TransactionData> { new TransactionData { Id = new uint256(18), Index = 0, Amount = 2 * Money.CENT } } });
account.ExternalAddresses.Add(new HdAddress { Index = 3, Transactions = new List<TransactionData> { new TransactionData { Id = new uint256(19), Index = 0, Amount = 1 * Money.NANO } } });
account.ExternalAddresses.Add(new HdAddress { Index = 4, Transactions = null });
wallet.AccountsRoot.Add(new AccountRoot() { Accounts = new[] { account }, CoinType = CoinType.Stratis });
}
// the difficulty tests are ported from: https://github.com/bitcoin/bitcoin/blob/3e1ee310437f4c93113f6121425beffdc94702c2/src/test/blockchain_tests.cpp
[Fact]
public void GetDifficulty_VeryLowTarget_ReturnsDifficulty()
{
ChainedHeader chainedHeader = CreateChainedBlockWithNBits(this.network, 0x1f111111);
double result = this.posMinting.GetDifficulty(chainedHeader);
Assert.Equal(0.000001, Math.Round(result, 6));
}
[Fact]
public void GetDifficulty_LowTarget_ReturnsDifficulty()
{
ChainedHeader chainedHeader = CreateChainedBlockWithNBits(this.network, 0x1ef88f6f);
double result = this.posMinting.GetDifficulty(chainedHeader);
Assert.Equal(0.000016, Math.Round(result, 6));
}
[Fact]
public void GetDifficulty_MidTarget_ReturnsDifficulty()
{
ChainedHeader chainedHeader = CreateChainedBlockWithNBits(this.network, 0x1df88f6f);
double result = this.posMinting.GetDifficulty(chainedHeader);
Assert.Equal(0.004023, Math.Round(result, 6));
}
[Fact]
public void GetDifficulty_HighTarget_ReturnsDifficulty()
{
ChainedHeader chainedHeader = CreateChainedBlockWithNBits(this.network, 0x1cf88f6f);
double result = this.posMinting.GetDifficulty(chainedHeader);
Assert.Equal(1.029916, Math.Round(result, 6));
}
[Fact]
public void GetDifficulty_VeryHighTarget_ReturnsDifficulty()
{
ChainedHeader chainedHeader = CreateChainedBlockWithNBits(this.network, 0x12345678);
double result = this.posMinting.GetDifficulty(chainedHeader);
Assert.Equal(5913134931067755359633408.0, Math.Round(result, 6));
}
[Fact]
public void GetDifficulty_BlockNull_UsesConsensusLoopTipAndStakeValidator_FindsBlock_ReturnsDifficulty()
{
this.chain = WalletTestsHelpers.GenerateChainWithHeight(3, this.network);
this.consensusManager.Setup(c => c.Tip)
.Returns(this.chain.Tip);
ChainedHeader chainedHeader = CreateChainedBlockWithNBits(this.network, 0x12345678);
this.stakeValidator.Setup(s => s.GetLastPowPosChainedBlock(this.stakeChain.Object, It.Is<ChainedHeader>(c => c.HashBlock == this.chain.Tip.HashBlock), false))
.Returns(chainedHeader);
this.posMinting = this.InitializePosMinting();
double result = this.posMinting.GetDifficulty(null);
Assert.Equal(5913134931067755359633408.0, Math.Round(result, 6));
}
[Fact]
public void GetDifficulty_BlockNull_NoConsensusTip_ReturnsDefaultDifficulty()
{
this.consensusManager.Setup(c => c.Tip)
.Returns((ChainedHeader)null);
double result = this.posMinting.GetDifficulty(null);
Assert.Equal(1, result);
}
[Fact]
public void GetNetworkWeight_NoConsensusLoopTip_ReturnsZero()
{
this.consensusManager.Setup(c => c.Tip)
.Returns((ChainedHeader)null);
double result = this.posMinting.GetNetworkWeight();
Assert.Equal(0, result);
}
[Fact]
public void GetNetworkWeight_UsingConsensusLoop_HavingMoreThan73Blocks_CalculatesNetworkWeightUsingLatestBlocks()
{
this.chain = GenerateChainWithBlockTimeAndHeight(75, this.network, 60, 0x1df88f6f);
this.InitializePosMinting();
this.consensusManager.Setup(c => c.Tip)
.Returns(this.chain.Tip);
double weight = this.posMinting.GetNetworkWeight();
Assert.Equal(4607763.9659653762, weight);
}
[Fact]
public void GetNetworkWeight_UsingConsensusLoop_HavingLessThan73Blocks_CalculatesNetworkWeightUsingLatestBlocks()
{
this.chain = GenerateChainWithBlockTimeAndHeight(50, this.network, 60, 0x1df88f6f);
this.InitializePosMinting();
this.consensusManager.Setup(c => c.Tip)
.Returns(this.chain.Tip);
double weight = this.posMinting.GetNetworkWeight();
Assert.Equal(4701799.9652707893, weight);
}
[Fact]
public void GetNetworkWeight_NonPosBlocksInbetweenPosBlocks_SkipsPowBlocks_CalculatedNetworkWeightUsingLatestBlocks()
{
this.chain = GenerateChainWithBlockTimeAndHeight(73, this.network, 60, 0x1df88f6f);
// the following non-pos blocks should be excluded.
AddBlockToChainWithBlockTimeAndDifficulty(this.chain, 3, 60, 0x12345678, this.network);
foreach (int blockHeight in new int[] { 74, 75, 76 })
{
uint256 blockHash = this.chain.GetBlock(blockHeight).HashBlock;
this.powBlocks.Add(blockHash);
}
this.InitializePosMinting();
this.consensusManager.Setup(c => c.Tip)
.Returns(this.chain.Tip);
double weight = this.posMinting.GetNetworkWeight();
Assert.Equal(4607763.9659653762, weight);
}
[Fact]
public void GetNetworkWeight_UsesLast73Blocks_CalculatedNetworkWeightUsingLatestBlocks()
{
this.chain = GenerateChainWithBlockTimeAndHeight(5, this.network, 60, 0x12345678);
// only the last 72 blocks should be included.
// it skips the first block because it cannot determine it for a single block so we need to add 73.
AddBlockToChainWithBlockTimeAndDifficulty(this.chain, 73, 60, 0x1df88f6f, this.network);
this.InitializePosMinting();
this.consensusManager.Setup(c => c.Tip)
.Returns(this.chain.Tip);
double weight = this.posMinting.GetNetworkWeight();
Assert.Equal(4607763.9659653762, weight);
}
[Fact]
public void CoinstakeAge_BeforeActivation_Testnet()
{
Assert.True(this.WasUtxoSelectedForStaking(KnownNetworks.StratisTest, 1000, 1000 - 8)); // utxo depth is 9, mining block at 10
Assert.False(this.WasUtxoSelectedForStaking(KnownNetworks.StratisTest, 1000, 1000 - 7)); // utxo depth is 8, mining block at 9
}
/// <summary>This is a test of coinstake age softfork activation on testnet.</summary>
/// <remarks><see cref="PosConsensusOptions.GetStakeMinConfirmations"/></remarks>
[Fact]
public void CoinstakeAge_AfterActivation_Testnet()
{
int activationHeight = PosConsensusOptions.CoinstakeMinConfirmationActivationHeightTestnet;
int afterActivationHeight = activationHeight + 1000;
Assert.True(this.WasUtxoSelectedForStaking(KnownNetworks.StratisTest, afterActivationHeight, afterActivationHeight - 18));
Assert.False(this.WasUtxoSelectedForStaking(KnownNetworks.StratisTest, afterActivationHeight, afterActivationHeight - 17));
}
/// <summary>This is a test of coinstake age softfork activation on testnet.</summary>
/// <remarks><see cref="PosConsensusOptions.GetStakeMinConfirmations"/></remarks>
[Fact]
public void CoinstakeAge_AtTheActivation_Testnet()
{
int activationHeight = PosConsensusOptions.CoinstakeMinConfirmationActivationHeightTestnet;
Assert.True(this.WasUtxoSelectedForStaking(KnownNetworks.StratisTest, activationHeight - 2, activationHeight - 10)); // mining block before activation
Assert.True(this.WasUtxoSelectedForStaking(KnownNetworks.StratisTest, activationHeight - 1, activationHeight - 19)); // mining activation block
Assert.False(this.WasUtxoSelectedForStaking(KnownNetworks.StratisTest, activationHeight - 1, activationHeight - 18)); // mining activation block
}
/// <summary>This is a test of coinstake age softfork activation on mainnet.</summary>
/// <remarks><see cref="PosConsensusOptions.GetStakeMinConfirmations"/></remarks>
[Fact]
public void CoinstakeAge_BeforeActivation_Mainnet()
{
Assert.True(this.WasUtxoSelectedForStaking(KnownNetworks.StratisMain, 1000, 1000 - 48)); // utxo depth is 49, mining block at 50
Assert.False(this.WasUtxoSelectedForStaking(KnownNetworks.StratisMain, 1000, 1000 - 47)); // utxo depth is 48, mining block at 49
}
/// <summary>This is a test of coinstake age softfork activation on mainnet.</summary>
/// <remarks><see cref="PosConsensusOptions.GetStakeMinConfirmations"/></remarks>
[Fact]
public void CoinstakeAge_AfterActivation_Mainnet()
{
int activationHeight = PosConsensusOptions.CoinstakeMinConfirmationActivationHeightMainnet;
int afterActivationHeight = activationHeight + 1000;
Assert.True(this.WasUtxoSelectedForStaking(KnownNetworks.StratisMain, afterActivationHeight, afterActivationHeight - 498));
Assert.False(this.WasUtxoSelectedForStaking(KnownNetworks.StratisMain, afterActivationHeight, afterActivationHeight - 497));
}
/// <summary>This is a test of coinstake age softfork activation on mainnet.</summary>
/// <remarks><see cref="PosConsensusOptions.GetStakeMinConfirmations"/></remarks>
[Fact]
public void CoinstakeAge_AtTheActivation_Mainnet()
{
int activationHeight = PosConsensusOptions.CoinstakeMinConfirmationActivationHeightMainnet;
Assert.True(this.WasUtxoSelectedForStaking(KnownNetworks.StratisMain, activationHeight - 2, activationHeight - 50)); // mining block before activation
Assert.True(this.WasUtxoSelectedForStaking(KnownNetworks.StratisMain, activationHeight - 1, activationHeight - 499)); // mining activation block
Assert.False(this.WasUtxoSelectedForStaking(KnownNetworks.StratisMain, activationHeight - 1, activationHeight - 498)); // mining activation block
}
private bool WasUtxoSelectedForStaking(Network network, int chainTipHeight, int utxoHeight)
{
this.network = network;
this.network.Consensus.Options = new PosConsensusOptions();
this.chain = GenerateChainWithBlockTimeAndHeight(2, this.network, 60, 0x1df88f6f);
PosMinting miner = this.InitializePosMinting();
ChainedHeader chainTip = this.chain.Tip;
chainTip.SetPrivatePropertyValue("Height", chainTipHeight);
chainTip.Previous.SetPrivatePropertyValue("Height", utxoHeight);
var descriptions = new List<UtxoStakeDescription>();
var utxoDescription = new UtxoStakeDescription();
utxoDescription.TxOut = new TxOut(new Money(100), new Mock<IDestination>().Object);
utxoDescription.OutPoint = new OutPoint(uint256.One, 0);
utxoDescription.HashBlock = chainTip.Previous.HashBlock;
utxoDescription.UtxoSet = new UnspentOutputs();
utxoDescription.UtxoSet.SetPrivatePropertyValue("Time", chainTip.Header.Time);
descriptions.Add(utxoDescription);
List<UtxoStakeDescription> suitableCoins = miner.GetUtxoStakeDescriptionsSuitableForStakingAsync(descriptions, chainTip, chainTip.Header.Time + 64, long.MaxValue).GetAwaiter().GetResult();
return suitableCoins.Count == 1;
}
private static void AddBlockToChainWithBlockTimeAndDifficulty(ConcurrentChain chain, int blockAmount, int incrementSeconds, uint nbits, Network network)
{
uint256 prevBlockHash = chain.Tip.HashBlock;
uint nonce = RandomUtils.GetUInt32();
DateTime blockTime = Utils.UnixTimeToDateTime(chain.Tip.Header.Time).UtcDateTime;
for (int i = 0; i < blockAmount; i++)
{
Block block = network.Consensus.ConsensusFactory.CreateBlock();
block.AddTransaction(new Transaction());
block.UpdateMerkleRoot();
block.Header.BlockTime = new DateTimeOffset(blockTime);
blockTime = blockTime.AddSeconds(incrementSeconds);
block.Header.HashPrevBlock = prevBlockHash;
block.Header.Nonce = nonce;
block.Header.Bits = new Target(nbits);
chain.SetTip(block.Header);
prevBlockHash = block.GetHash();
}
}
public static ConcurrentChain GenerateChainWithBlockTimeAndHeight(int blockAmount, Network network, int incrementSeconds, uint nbits)
{
var chain = new ConcurrentChain(network);
uint nonce = RandomUtils.GetUInt32();
uint256 prevBlockHash = chain.Genesis.HashBlock;
DateTime blockTime = Utils.UnixTimeToDateTime(chain.Genesis.Header.Time).UtcDateTime;
for (int i = 0; i < blockAmount; i++)
{
Block block = network.Consensus.ConsensusFactory.CreateBlock();
block.AddTransaction(new Transaction());
block.UpdateMerkleRoot();
block.Header.BlockTime = new DateTimeOffset(blockTime);
blockTime = blockTime.AddSeconds(incrementSeconds);
block.Header.HashPrevBlock = prevBlockHash;
block.Header.Nonce = nonce;
block.Header.Bits = new Target(nbits);
chain.SetTip(block.Header);
prevBlockHash = block.GetHash();
}
return chain;
}
private void SetupStakeChain()
{
var callbackBlockId = new uint256();
this.stakeChain.Setup(s => s.Get(It.IsAny<uint256>()))
.Callback<uint256>((b) => { callbackBlockId = b; })
.Returns(() =>
{
var blockStake = new BlockStake();
if (!this.powBlocks.Contains(callbackBlockId))
{
blockStake.Flags = BlockFlag.BLOCK_PROOF_OF_STAKE;
}
return blockStake;
});
}
private PosMinting InitializePosMinting()
{
var posBlockAssembler = new Mock<PosBlockDefinition>(
this.consensusManager.Object,
this.dateTimeProvider.Object,
this.LoggerFactory.Object,
this.txMempool.Object,
this.mempoolSchedulerLock,
this.minerSettings,
this.network,
this.stakeChain.Object,
this.stakeValidator.Object);
posBlockAssembler.Setup(a => a.Build(It.IsAny<ChainedHeader>(), It.IsAny<Script>()))
.Returns(new BlockTemplate(this.network));
var blockBuilder = new MockPosBlockProvider(posBlockAssembler.Object);
return new DeStreamPosMinting(
blockBuilder,
this.consensusManager.Object,
this.chain,
this.network,
this.dateTimeProvider.Object,
this.initialBlockDownloadState.Object,
this.nodeLifetime.Object,
this.coinView.Object,
this.stakeChain.Object,
this.stakeValidator.Object,
this.mempoolSchedulerLock,
this.txMempool.Object,
this.walletManager.Object,
this.asyncLoopFactory.Object,
this.timeSyncBehaviorState.Object,
this.LoggerFactory.Object,
this.minerSettings);
}
private static ChainedHeader CreateChainedBlockWithNBits(Network network, uint bits)
{
BlockHeader blockHeader = network.Consensus.ConsensusFactory.CreateBlockHeader();
blockHeader.Time = 1269211443;
blockHeader.Bits = new Target(bits);
var chainedHeader = new ChainedHeader(blockHeader, blockHeader.GetHash(), 46367);
return chainedHeader;
}
}
}
\ No newline at end of file
......@@ -4,13 +4,14 @@ using Stratis.Bitcoin.Configuration.Logging;
using Stratis.Bitcoin.Features.MemoryPool;
using Stratis.Bitcoin.Features.Miner.Controllers;
using Stratis.Bitcoin.Features.Miner.Interfaces;
using Stratis.Bitcoin.Features.Miner.Staking;
using Stratis.Bitcoin.Features.RPC;
using Stratis.Bitcoin.Features.Wallet;
using Stratis.Bitcoin.Mining;
namespace Stratis.Bitcoin.Features.Miner
{
public static class DeStreamFullNodeBuilderMinerExtension
public static class DeStreamFullNodeBuilderMiningExtension
{
public static IFullNodeBuilder AddDeStreamPowMining(this IFullNodeBuilder fullNodeBuilder)
{
......@@ -28,8 +29,8 @@ namespace Stratis.Bitcoin.Features.Miner
services.AddSingleton<IPowMining, PowMining>();
services.AddSingleton<IBlockProvider, BlockProvider>();
services.AddSingleton<BlockDefinition, DeStreamPowBlockDefinition>();
services.AddSingleton<MinerController>();
services.AddSingleton<MiningRPCController>();
services.AddSingleton<MiningRpcController>();
services.AddSingleton<MiningController>();
services.AddSingleton<MinerSettings>();
});
});
......@@ -57,8 +58,8 @@ namespace Stratis.Bitcoin.Features.Miner
services.AddSingleton<BlockDefinition, DeStreamPowBlockDefinition>();
services.AddSingleton<BlockDefinition, DeStreamPosPowBlockDefinition>();
services.AddSingleton<BlockDefinition, PosBlockDefinition>();
services.AddSingleton<MinerController>();
services.AddSingleton<MiningRPCController>();
services.AddSingleton<MiningRpcController>();
services.AddSingleton<MiningController>();
services.AddSingleton<MinerSettings>();
});
});
......
......@@ -4,10 +4,12 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NBitcoin;
using NBitcoin.BuilderExtensions;
using NBitcoin.DataEncoders;
using NBitcoin.Protocol;
using Stratis.Bitcoin.Base;
using Stratis.Bitcoin.Connection;
using Stratis.Bitcoin.Consensus;
using Stratis.Bitcoin.Features.Consensus;
using Stratis.Bitcoin.Features.Consensus.CoinViews;
using Stratis.Bitcoin.Features.Consensus.Interfaces;
......@@ -19,57 +21,58 @@ using Stratis.Bitcoin.Interfaces;
using Stratis.Bitcoin.Mining;
using Stratis.Bitcoin.Utilities;
namespace Stratis.Bitcoin.Features.Miner
namespace Stratis.Bitcoin.Features.Miner.Staking
{
public class DeStreamPosMinting : PosMinting
{
public DeStreamPosMinting(IBlockProvider blockProvider, IConsensusLoop consensusLoop, ConcurrentChain chain,
Network network, IConnectionManager connectionManager, IDateTimeProvider dateTimeProvider,
IInitialBlockDownloadState initialBlockDownloadState, INodeLifetime nodeLifetime, CoinView coinView,
IStakeChain stakeChain, IStakeValidator stakeValidator, MempoolSchedulerLock mempoolLock,
ITxMempool mempool, IWalletManager walletManager, IAsyncLoopFactory asyncLoopFactory,
ITimeSyncBehaviorState timeSyncBehaviorState, ILoggerFactory loggerFactory) : base(blockProvider,
consensusLoop, chain, network, connectionManager, dateTimeProvider, initialBlockDownloadState, nodeLifetime,
coinView, stakeChain, stakeValidator, mempoolLock, mempool, walletManager, asyncLoopFactory,
timeSyncBehaviorState, loggerFactory)
private DeStreamNetwork DeStreamNetwork
{
get
{
if (!(this.network is DeStreamNetwork))
{
throw new NotSupportedException($"Network must be {nameof(NBitcoin.DeStreamNetwork)}");
}
return (DeStreamNetwork) this.network;
}
}
protected override void CoinstakeWorker(CoinstakeWorkerContext context, ChainedHeader chainTip, Block block,
long minimalAllowedTime, long searchInterval)
{
// Adds empty output for DeStream fees to the end of outputs' array
base.CoinstakeWorker(context, chainTip, block, minimalAllowedTime, searchInterval);
if (context.Result.KernelFoundIndex != context.Index)
{
return;
}
Script deStreamAddressKey = new KeyId(new uint160(Encoders.Base58Check
.DecodeData(this.network.DeStreamWallet)
.DecodeData(this.DeStreamNetwork.DeStreamWallet)
.Skip(this.network.Base58Prefixes[(int) Base58Type.PUBKEY_ADDRESS].Length).ToArray())).ScriptPubKey;
context.CoinstakeContext.CoinstakeTx.AddOutput(new TxOut(Money.Zero, deStreamAddressKey));
}
/// <inheritdoc />
/// <inheritdoc/>
public override async Task<bool> CreateCoinstakeAsync(List<UtxoStakeDescription> utxoStakeDescriptions,
Block block, ChainedHeader chainTip, long searchInterval,
long fees, CoinstakeContext coinstakeContext)
Block block, ChainedHeader chainTip, long searchInterval, long fees, CoinstakeContext coinstakeContext)
{
this.logger.LogTrace("({0}.{1}:{2},{3}:'{4}',{5}:{6},{7}:{8})", nameof(utxoStakeDescriptions),
nameof(utxoStakeDescriptions.Count), utxoStakeDescriptions.Count, nameof(chainTip), chainTip,
nameof(searchInterval), searchInterval, nameof(fees), fees);
// Provides PrepareCoinStakeTransactions with fees amount
int nonEmptyUtxos = utxoStakeDescriptions.Count;
coinstakeContext.CoinstakeTx.Inputs.Clear();
coinstakeContext.CoinstakeTx.Outputs.Clear();
// Mark coinstake transaction.
coinstakeContext.CoinstakeTx.Outputs.Add(new TxOut(Money.Zero, new Script()));
long balance = this.GetMatureBalance(utxoStakeDescriptions).Satoshi;
long balance = (await this.GetMatureBalanceAsync(utxoStakeDescriptions).ConfigureAwait(false)).Satoshi;
if (balance <= this.targetReserveBalance)
{
this.rpcGetStakingInfoModel.Staking = false;
this.rpcGetStakingInfoModel.PauseStaking();
this.logger.LogTrace(
"Total balance of available UTXOs is {0}, which is less than or equal to reserve balance {1}.",
......@@ -79,28 +82,28 @@ namespace Stratis.Bitcoin.Features.Miner
}
// Select UTXOs with suitable depth.
List<UtxoStakeDescription> stakingUtxoDescriptions =
this.GetUtxoStakeDescriptionsSuitableForStaking(utxoStakeDescriptions, chainTip,
coinstakeContext.CoinstakeTx.Time, balance - this.targetReserveBalance);
List<UtxoStakeDescription> stakingUtxoDescriptions = await this
.GetUtxoStakeDescriptionsSuitableForStakingAsync(utxoStakeDescriptions, chainTip,
coinstakeContext.CoinstakeTx.Time, balance - this.targetReserveBalance).ConfigureAwait(false);
if (!stakingUtxoDescriptions.Any())
{
this.rpcGetStakingInfoModel.Staking = false;
this.rpcGetStakingInfoModel.PauseStaking();
this.logger.LogTrace("(-)[NO_SELECTION]:false");
return false;
}
long ourWeight = stakingUtxoDescriptions.Sum(s => s.TxOut.Value);
long expectedTime = StakeValidator.TargetSpacingSeconds * this.networkWeight / ourWeight;
decimal ourPercent = this.networkWeight != 0 ? 100.0m * ourWeight / this.networkWeight : 0;
decimal ourPercent = this.networkWeight != 0
? 100.0m * (decimal) ourWeight / (decimal) this.networkWeight
: 0;
this.logger.LogInformation(
"Node staking with {0} ({1:0.00} % of the network weight {2}), est. time to find new block is {3}.",
new Money(ourWeight), ourPercent, new Money(this.networkWeight), TimeSpan.FromSeconds(expectedTime));
this.rpcGetStakingInfoModel.Staking = true;
this.rpcGetStakingInfoModel.Weight = ourWeight;
this.rpcGetStakingInfoModel.ExpectedTime = expectedTime;
this.rpcGetStakingInfoModel.Errors = null;
this.rpcGetStakingInfoModel.ResumeStaking(ourWeight, expectedTime);
long minimalAllowedTime = chainTip.Header.Time + 1;
this.logger.LogTrace(
......@@ -109,8 +112,7 @@ namespace Stratis.Bitcoin.Features.Miner
// If the time after applying the mask is lower than minimal allowed time,
// it is simply too early for us to mine, there can't be any valid solution.
if ((coinstakeContext.CoinstakeTx.Time & ~BlockHeaderPosContextualRule.StakeTimestampMask) <
minimalAllowedTime)
if ((coinstakeContext.CoinstakeTx.Time & ~PosConsensusOptions.StakeTimestampMask) < minimalAllowedTime)
{
this.logger.LogTrace("(-)[TOO_EARLY_TIME_AFTER_LAST_BLOCK]:false");
return false;
......@@ -155,19 +157,15 @@ namespace Stratis.Bitcoin.Features.Miner
}
this.logger.LogTrace("Worker #{0} found the kernel.", workersResult.KernelFoundIndex);
// Split stake if above threshold.
this.SplitStake(nonEmptyUtxos, chainTip, coinstakeContext.CoinstakeTx.Outputs);
// Input to coinstake transaction.
UtxoStakeDescription coinstakeInput = workersResult.KernelCoin;
// Set output amount.
this.SetOutputAmount(coinstakeContext.CoinstakeTx.Outputs, coinstakeInput.TxOut.Value, fees);
int eventuallyStakableUtxosCount = utxoStakeDescriptions.Count;
Transaction coinstakeTx = this.PrepareCoinStakeTransactions(chainTip.Height, coinstakeContext,
coinstakeInput.TxOut.Value, fees, eventuallyStakableUtxosCount, ourWeight);
// Sign.
if (!this.SignTransactionInput(coinstakeInput, coinstakeContext.CoinstakeTx))
if (!this.SignTransactionInput(coinstakeInput, coinstakeTx))
{
this.logger.LogTrace("(-)[SIGN_FAILED]:false");
return false;
......@@ -177,7 +175,7 @@ namespace Stratis.Bitcoin.Features.Miner
int serializedSize =
coinstakeContext.CoinstakeTx.GetSerializedSize(ProtocolVersion.ALT_PROTOCOL_VERSION,
SerializationType.Network);
if (serializedSize >= MaxBlockSizeGen / 5)
if (serializedSize >= (MaxBlockSizeGen / 5))
{
this.logger.LogTrace("Coinstake size {0} bytes exceeded limit {1} bytes.", serializedSize,
MaxBlockSizeGen / 5);
......@@ -186,51 +184,71 @@ namespace Stratis.Bitcoin.Features.Miner
}
// Successfully generated coinstake.
this.logger.LogTrace("(-):true");
return true;
}
private void SetOutputAmount(TxOutList outputs, long totalOut, long fees)
private Transaction PrepareCoinStakeTransactions(int currentChainHeight, CoinstakeContext coinstakeContext,long totalOut, long fees, int utxosCount, long amountStaked)
{
this.network.SplitFee(fees, out long deStreamFee, out long minerReward);
// Splits fees between miner and DeStream
// Splits miner's output if needed
// Output to DeStream stays in the end of array
this.DeStreamNetwork.SplitFee(fees, out long deStreamFee, out long minerReward);
// Split stake into SplitFactor utxos if above threshold.
bool shouldSplitStake = this.ShouldSplitStake(utxosCount, amountStaked, totalOut, currentChainHeight);
int lastOutputIndex = coinstakeContext.CoinstakeTx.Outputs.Count - 1;
if (outputs.Count == 4)
if (!shouldSplitStake)
{
outputs[1].Value = (totalOut + minerReward) / 2 / Money.CENT * Money.CENT;
outputs[2].Value = totalOut + minerReward - outputs[1].Value;
outputs[3].Value = deStreamFee;
this.logger.LogTrace("Coinstake first output value is {0}, second is {1}, third is {3}.",
outputs[1].Value, outputs[2].Value, outputs[3].Value);
coinstakeContext.CoinstakeTx.Outputs[lastOutputIndex - 1].Value = totalOut + minerReward;
coinstakeContext.CoinstakeTx.Outputs[lastOutputIndex].Value = deStreamFee;
this.logger.LogTrace(
"Coinstake miner output value is {0}, DeStream output value is {1}.",
coinstakeContext.CoinstakeTx.Outputs[lastOutputIndex - 1].Value,
coinstakeContext.CoinstakeTx.Outputs[lastOutputIndex].Value);
this.logger.LogTrace("(-)[NO_SPLIT]:{0}", coinstakeContext.CoinstakeTx);
return coinstakeContext.CoinstakeTx;
}
else
long splitValue = (totalOut + minerReward) / SplitFactor;
long remainder = (totalOut + minerReward) - ((SplitFactor - 1) * splitValue);
coinstakeContext.CoinstakeTx.Outputs[lastOutputIndex - 1].Value = remainder;
for (int i = 0; i < SplitFactor - 1; i++)
{
outputs[1].Value = totalOut + minerReward;
outputs[2].Value = deStreamFee;
this.logger.LogTrace("Coinstake first output value is {0}, second is {1} .", outputs[1].Value,
outputs[2].Value);
}
var split = new TxOut(splitValue,
coinstakeContext.CoinstakeTx.Outputs[lastOutputIndex - 1].ScriptPubKey);
coinstakeContext.CoinstakeTx.Outputs.Insert(coinstakeContext.CoinstakeTx.Outputs.Count - 1, split);
}
private void SplitStake(int nonEmptyUtxos, ChainedHeader chainTip, TxOutList outputs)
{
if (!this.GetSplitStake(nonEmptyUtxos, chainTip)) return;
coinstakeContext.CoinstakeTx.Outputs[coinstakeContext.CoinstakeTx.Outputs.Count - 1].Value = deStreamFee;
this.logger.LogTrace(
"Coinstake output value has been split into {0} outputs of {1} and a remainder of {2}.",
SplitFactor - 1, splitValue, remainder);
this.logger.LogTrace("Coinstake UTXO will be split to two.");
outputs.Insert(2, new TxOut(0, outputs[1].ScriptPubKey));
return coinstakeContext.CoinstakeTx;
}
protected override bool SignTransactionInput(UtxoStakeDescription input, Transaction transaction)
{
this.logger.LogTrace("({0}:'{1}')", nameof(input), input.OutPoint);
// Creates DeStreamTransactionBuilder
bool res = false;
try
{
new DeStreamTransactionBuilder(this.network)
var transactionBuilder = new DeStreamTransactionBuilder(this.network)
.AddKeys(input.Key)
.AddCoins(new Coin(input.OutPoint, input.TxOut))
.SignTransactionInPlace(transaction);
.AddCoins(new Coin(input.OutPoint, input.TxOut));
foreach (BuilderExtension extension in this.walletManager.GetTransactionBuilderExtensionsForStaking())
transactionBuilder.Extensions.Add(extension);
transactionBuilder.SignTransactionInPlace(transaction);
res = true;
}
......@@ -239,8 +257,19 @@ namespace Stratis.Bitcoin.Features.Miner
this.logger.LogDebug("Exception occurred: {0}", e.ToString());
}
this.logger.LogTrace("(-):{0}", res);
return res;
}
public DeStreamPosMinting(IBlockProvider blockProvider, IConsensusManager consensusManager,
ConcurrentChain chain, Network network, IDateTimeProvider dateTimeProvider,
IInitialBlockDownloadState initialBlockDownloadState, INodeLifetime nodeLifetime, ICoinView coinView,
IStakeChain stakeChain, IStakeValidator stakeValidator, MempoolSchedulerLock mempoolLock,
ITxMempool mempool, IWalletManager walletManager, IAsyncLoopFactory asyncLoopFactory,
ITimeSyncBehaviorState timeSyncBehaviorState, ILoggerFactory loggerFactory, MinerSettings minerSettings) :
base(blockProvider, consensusManager, chain, network, dateTimeProvider, initialBlockDownloadState,
nodeLifetime, coinView, stakeChain, stakeValidator, mempoolLock, mempool, walletManager,
asyncLoopFactory, timeSyncBehaviorState, loggerFactory, minerSettings)
{
}
}
}
\ No newline at end of file
......@@ -98,16 +98,16 @@ namespace Stratis.Bitcoin.Features.Miner.Staking
/// <summary>Number of UTXO descriptions that a single worker's task will process.</summary>
/// <remarks>To achieve a good level of parallelism, this should be low enough so that CPU threads are used,
/// but high enough to compensate for tasks' overhead.</remarks>
private const int UtxoStakeDescriptionsPerCoinstakeWorker = 25;
protected const int UtxoStakeDescriptionsPerCoinstakeWorker = 25;
/// <summary>Consumes manager class.</summary>
private readonly IConsensusManager consensusManager;
protected readonly IConsensusManager consensusManager;
/// <summary>Thread safe access to the best chain of block headers (that the node is aware of) from genesis.</summary>
private readonly ConcurrentChain chain;
/// <summary>Specification of the network the node runs on - regtest/testnet/mainnet.</summary>
private readonly Network network;
protected readonly Network network;
/// <summary>Provides date time functionality.</summary>
private readonly IDateTimeProvider dateTimeProvider;
......@@ -128,13 +128,13 @@ namespace Stratis.Bitcoin.Features.Miner.Staking
private readonly IAsyncLoopFactory asyncLoopFactory;
/// <summary>A manager providing operations on wallets.</summary>
private readonly IWalletManager walletManager;
protected readonly IWalletManager walletManager;
/// <summary>Factory for creating loggers.</summary>
private readonly ILoggerFactory loggerFactory;
protected readonly ILoggerFactory loggerFactory;
/// <summary>Instance logger.</summary>
private readonly ILogger logger;
protected readonly ILogger logger;
/// <summary>Loop in which the node attempts to generate new POS blocks by staking coins from its wallet.</summary>
private IAsyncLoop stakingLoop;
......@@ -159,7 +159,7 @@ namespace Stratis.Bitcoin.Features.Miner.Staking
/// Target reserved balance that will not participate in staking.
/// It is possible that less than this amount will be reserved.
/// </summary>
private Money targetReserveBalance;
protected Money targetReserveBalance;
/// <summary>Time in milliseconds between attempts to generate PoS blocks.</summary>
private readonly int minerSleep;
......@@ -179,10 +179,10 @@ namespace Stratis.Bitcoin.Features.Miner.Staking
/// <summary>Information about node's staking for RPC "getstakinginfo" command.</summary>
/// <remarks>This object does not need a synchronized access because there is no execution logic
/// that depends on the reported information.</remarks>
private Models.GetStakingInfoModel rpcGetStakingInfoModel;
protected Models.GetStakingInfoModel rpcGetStakingInfoModel;
/// <summary>Estimation of the total staking weight of all nodes on the network.</summary>
private long networkWeight;
protected long networkWeight;
/// <summary>
/// Timestamp of the last attempt to search for POS solution.
......@@ -576,7 +576,7 @@ namespace Stratis.Bitcoin.Features.Miner.Staking
}
/// <inheritdoc/>
public async Task<bool> CreateCoinstakeAsync(List<UtxoStakeDescription> utxoStakeDescriptions, Block block, ChainedHeader chainTip, long searchInterval, long fees, CoinstakeContext coinstakeContext)
public virtual async Task<bool> CreateCoinstakeAsync(List<UtxoStakeDescription> utxoStakeDescriptions, Block block, ChainedHeader chainTip, long searchInterval, long fees, CoinstakeContext coinstakeContext)
{
coinstakeContext.CoinstakeTx.Inputs.Clear();
coinstakeContext.CoinstakeTx.Outputs.Clear();
......@@ -700,7 +700,7 @@ namespace Stratis.Bitcoin.Features.Miner.Staking
return true;
}
internal Transaction PrepareCoinStakeTransactions(int currentChainHeight, CoinstakeContext coinstakeContext, long coinstakeOutputValue, int utxosCount, long amountStaked)
internal virtual Transaction PrepareCoinStakeTransactions(int currentChainHeight, CoinstakeContext coinstakeContext, long coinstakeOutputValue, int utxosCount, long amountStaked)
{
// Split stake into SplitFactor utxos if above threshold.
bool shouldSplitStake = this.ShouldSplitStake(utxosCount, amountStaked, coinstakeOutputValue, currentChainHeight);
......@@ -742,7 +742,7 @@ namespace Stratis.Bitcoin.Features.Miner.Staking
/// <param name="block">Template of the block that we are trying to mine.</param>
/// <param name="minimalAllowedTime">Minimal valid timestamp for new coinstake transaction.</param>
/// <param name="searchInterval">Length of an unexplored block time space in seconds. It only makes sense to look for a solution within this interval.</param>
private void CoinstakeWorker(CoinstakeWorkerContext context, ChainedHeader chainTip, Block block, long minimalAllowedTime, long searchInterval)
protected virtual void CoinstakeWorker(CoinstakeWorkerContext context, ChainedHeader chainTip, Block block, long minimalAllowedTime, long searchInterval)
{
context.Logger.LogTrace("Going to process {0} UTXOs.", context.utxoStakeDescriptions.Count);
......@@ -867,7 +867,7 @@ namespace Stratis.Bitcoin.Features.Miner.Staking
/// <param name="input">Transaction input.</param>
/// <param name="transaction">Transaction being built.</param>
/// <returns><c>true</c> if the function succeeds, <c>false</c> otherwise.</returns>
private bool SignTransactionInput(UtxoStakeDescription input, Transaction transaction)
protected virtual bool SignTransactionInput(UtxoStakeDescription input, Transaction transaction)
{
bool res = false;
try
......
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Stratis Bitcoin Features Miner</Description>
<AssemblyTitle>Stratis.Bitcoin.Features.Miner</AssemblyTitle>
......@@ -18,19 +17,16 @@
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<CodeAnalysisRuleSet>..\None.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Stratis.Bitcoin.Features.Miner\**" />
<EmbeddedResource Remove="Stratis.Bitcoin.Features.Miner\**" />
<None Remove="Stratis.Bitcoin.Features.Miner\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" />
<PackageReference Include="Tracer.Fody" Version="2.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NBitcoin\NBitcoin.csproj" />
<ProjectReference Include="..\Stratis.Bitcoin.Features.MemoryPool\Stratis.Bitcoin.Features.MemoryPool.csproj" />
......@@ -38,14 +34,12 @@
<ProjectReference Include="..\Stratis.Bitcoin.Features.Wallet\Stratis.Bitcoin.Features.Wallet.csproj" />
<ProjectReference Include="..\Stratis.Bitcoin\Stratis.Bitcoin.csproj" />
</ItemGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<DefineConstants>$(DefineConstants);NETCORE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1701;1702;1705;IDE0008;</NoWarn>
<DocumentationFile></DocumentationFile>
<DocumentationFile>
</DocumentationFile>
</PropertyGroup>
</Project>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment