Commit 314a3e0b authored by Sergei Zubov's avatar Sergei Zubov

Merge coinview rule

parent 55c66483
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Moq;
using NBitcoin;
using NBitcoin.Crypto;
using Stratis.Bitcoin.Base;
using Stratis.Bitcoin.Base.Deployments;
using Stratis.Bitcoin.BlockPulling;
using Stratis.Bitcoin.Configuration.Settings;
using Stratis.Bitcoin.Connection;
using Stratis.Bitcoin.Consensus;
using Stratis.Bitcoin.Consensus.Validators;
using Stratis.Bitcoin.Features.Consensus.CoinViews;
using Stratis.Bitcoin.Features.Consensus.Rules;
using Stratis.Bitcoin.Features.Consensus.Rules.CommonRules;
using Stratis.Bitcoin.Interfaces;
using Stratis.Bitcoin.Tests.Common;
using Stratis.Bitcoin.Utilities;
using Xunit;
namespace Stratis.Bitcoin.Features.Consensus.Tests.Rules.CommonRules
{
/// <summary>
/// Test cases related to the <see cref="PosCoinviewRule"/>.
/// </summary>
public class DeStreamPosCoinViewRuleTests : TestPosConsensusRulesUnitTestBase
{
/// <summary>
/// Creates the consensus manager used by <see cref="PosCoinViewRuleFailsAsync"/>.
/// </summary>
/// <param name="unspentOutputs">The dictionary used to mock up the <see cref="ICoinView"/>.</param>
/// <returns>The constructed consensus manager.</returns>
private async Task<ConsensusManager> CreateConsensusManagerAsync(Dictionary<uint256, UnspentOutputs> unspentOutputs)
{
this.consensusSettings = new ConsensusSettings(Configuration.NodeSettings.Default(this.network));
var initialBlockDownloadState = new InitialBlockDownloadState(this.chainState.Object, this.network, this.consensusSettings, new Checkpoints());
// Register POS consensus rules.
new DeStreamFullNodeBuilderConsensusExtension.PosConsensusRulesRegistration().RegisterRules(this.network.Consensus);
ConsensusRuleEngine consensusRuleEngine = new PosConsensusRuleEngine(this.network, this.loggerFactory.Object, DateTimeProvider.Default,
this.concurrentChain, this.nodeDeployments, this.consensusSettings, this.checkpoints.Object, this.coinView.Object, this.stakeChain.Object,
this.stakeValidator.Object, this.chainState.Object, new InvalidBlockHashStore(this.dateTimeProvider.Object), new Mock<INodeStats>().Object, this.rewindDataIndexStore.Object)
.Register();
var headerValidator = new HeaderValidator(consensusRuleEngine, this.loggerFactory.Object);
var integrityValidator = new IntegrityValidator(consensusRuleEngine, this.loggerFactory.Object);
var partialValidator = new PartialValidator(consensusRuleEngine, this.loggerFactory.Object);
var fullValidator = new FullValidator(consensusRuleEngine, this.loggerFactory.Object);
// Create the chained header tree.
var chainedHeaderTree = new ChainedHeaderTree(this.network, this.loggerFactory.Object, headerValidator, this.checkpoints.Object,
this.chainState.Object, new Mock<IFinalizedBlockInfoRepository>().Object, this.consensusSettings, new InvalidBlockHashStore(new DateTimeProvider()));
// Create consensus manager.
var consensus = new ConsensusManager(chainedHeaderTree, this.network, this.loggerFactory.Object, this.chainState.Object, integrityValidator,
partialValidator, fullValidator, consensusRuleEngine, new Mock<IFinalizedBlockInfoRepository>().Object, new Signals.Signals(),
new Mock<IPeerBanning>().Object, initialBlockDownloadState, this.concurrentChain, new Mock<IBlockPuller>().Object, new Mock<IBlockStore>().Object,
new Mock<IConnectionManager>().Object, new Mock<INodeStats>().Object, new Mock<INodeLifetime>().Object);
// Mock the coinviews "FetchCoinsAsync" method. We will use the "unspentOutputs" dictionary to track spendable outputs.
this.coinView.Setup(d => d.FetchCoinsAsync(It.IsAny<uint256[]>(), It.IsAny<CancellationToken>()))
.Returns((uint256[] txIds, CancellationToken cancel) => Task.Run(() => {
var result = new UnspentOutputs[txIds.Length];
for (int i = 0; i < txIds.Length; i++)
result[i] = unspentOutputs.TryGetValue(txIds[i], out UnspentOutputs unspent) ? unspent : null;
return new FetchCoinsResponse(result, this.concurrentChain.Tip.HashBlock);
}));
// Mock the coinviews "GetTipHashAsync" method.
this.coinView.Setup(d => d.GetTipHashAsync(It.IsAny<CancellationToken>())).Returns(() => Task.Run(() => {
return this.concurrentChain.Tip.HashBlock;
}));
// Since we are mocking the stake validator ensure that GetNextTargetRequired returns something sensible. Otherwise we get the "bad-diffbits" error.
this.stakeValidator.Setup(s => s.GetNextTargetRequired(It.IsAny<IStakeChain>(), It.IsAny<ChainedHeader>(), It.IsAny<IConsensus>(), It.IsAny<bool>()))
.Returns(this.network.Consensus.PowLimit);
// Skip validation of signature in the proven header
this.stakeValidator.Setup(s => s.VerifySignature(It.IsAny<UnspentOutputs>(), It.IsAny<Transaction>(),0, It.IsAny<ScriptVerify>()))
.Returns(true);
// Since we are mocking the stakechain ensure that the Get returns a BlockStake. Otherwise this results in "previous stake is not found".
this.stakeChain.Setup(d => d.Get(It.IsAny<uint256>())).Returns(new BlockStake()
{
Flags = BlockFlag.BLOCK_PROOF_OF_STAKE,
StakeModifierV2 = 0,
StakeTime = (this.concurrentChain.Tip.Header.Time + 60) & ~PosConsensusOptions.StakeTimestampMask
});
// Since we are mocking the chainState ensure that the BlockStoreTip returns a usable value.
this.chainState.Setup(d => d.BlockStoreTip).Returns(this.concurrentChain.Tip);
// Since we are mocking the chainState ensure that the ConsensusTip returns a usable value.
this.chainState.Setup(d => d.ConsensusTip).Returns(this.concurrentChain.Tip);
// Initialize the consensus manager.
await consensus.InitializeAsync(this.concurrentChain.Tip);
return consensus;
}
/// <summary>
/// Tests whether an error is raised when a miner attempts to stake an output which he can't spend with his private key.
/// </summary>
/// <remarks>
/// <para>Create a "previous transaction" with 2 outputs. The first output is sent to miner 2 and the second output is sent to miner 1.
/// Now miner 2 creates a proof of stake block with coinstake transaction which will have two inputs corresponding to both the
/// outputs of the previous transaction. The coinstake transaction will be just two outputs, first is the coinstake marker and
/// the second is normal pay to public key that belongs to miner 2 with value that equals to the sum of the inputs.
/// The testable outcome is whether the consensus engine accepts such a block. Obviously, the test should fail if the block is accepted.
/// </para><para>
/// We use <see cref="ConsensusManager.BlockMinedAsync(Block)"/> to run partial validation and full validation rules and expect that
/// the rules engine will reject the block with the specific error of <see cref="ConsensusErrors.BadTransactionScriptError"/>,
/// which confirms that there was no valid signature on the second input - which corresponds to the output sent to miner 1.
/// </para></remarks>
[Fact]
public async Task PosCoinViewRuleFailsAsync()
{
var unspentOutputs = new Dictionary<uint256, UnspentOutputs>();
ConsensusManager consensusManager = await this.CreateConsensusManagerAsync(unspentOutputs);
// The keys used by miner 1 and miner 2.
var minerKey1 = new Key();
var minerKey2 = new Key();
// The scriptPubKeys (P2PK) of the miners.
Script scriptPubKey1 = minerKey1.PubKey.ScriptPubKey;
Script scriptPubKey2 = minerKey2.PubKey.ScriptPubKey;
// Create the block that we want to validate.
Block block = this.network.Consensus.ConsensusFactory.CreateBlock();
// Add dummy first transaction.
Transaction transaction = this.network.CreateTransaction();
transaction.Inputs.Add(TxIn.CreateCoinbase(this.concurrentChain.Tip.Height + 1));
transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null));
Assert.True(transaction.IsCoinBase);
// Add first transaction to block.
block.Transactions.Add(transaction);
// Create a previous transaction with scriptPubKey outputs.
Transaction prevTransaction = this.network.CreateTransaction();
// Coins sent to miner 2.
prevTransaction.Outputs.Add(new TxOut(Money.COIN * 5_000_000, scriptPubKey2));
// Coins sent to miner 1.
prevTransaction.Outputs.Add(new TxOut(Money.COIN * 10_000_000, scriptPubKey1));
// Record the spendable outputs.
unspentOutputs[prevTransaction.GetHash()] = new UnspentOutputs(1, prevTransaction);
// Create coin stake transaction.
Transaction coinstakeTransaction = this.network.CreateTransaction();
coinstakeTransaction.Inputs.Add(new TxIn(new OutPoint(prevTransaction, 0)));
coinstakeTransaction.Inputs.Add(new TxIn(new OutPoint(prevTransaction, 1)));
// Coinstake marker.
coinstakeTransaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null));
// Normal pay to public key that belongs to the second miner with value that
// equals to the sum of the inputs.
coinstakeTransaction.Outputs.Add(new TxOut(Money.COIN * 15_000_000, scriptPubKey2));
// The second miner signs the first transaction input which requires minerKey2.
// Miner 2 will not have minerKey1 so we leave the second ScriptSig empty/invalid.
new TransactionBuilder(this.network)
.AddKeys(minerKey2)
.AddCoins(new Coin(new OutPoint(prevTransaction, 0), prevTransaction.Outputs[0]))
.SignTransactionInPlace(coinstakeTransaction);
Assert.True(coinstakeTransaction.IsCoinStake);
// Add second transaction to block.
block.Transactions.Add(coinstakeTransaction);
// Finalize the block and add it to the chain.
block.Header.HashPrevBlock = this.concurrentChain.Tip.HashBlock;
block.Header.Time = (this.concurrentChain.Tip.Header.Time + 60) & ~PosConsensusOptions.StakeTimestampMask;
block.Header.Bits = block.Header.GetWorkRequired(this.network, this.concurrentChain.Tip);
block.SetPrivatePropertyValue("BlockSize", 1L);
block.Transactions[0].Time = block.Header.Time;
block.Transactions[1].Time = block.Header.Time;
block.UpdateMerkleRoot();
Assert.True(BlockStake.IsProofOfStake(block));
// Add a signature to the block.
ECDSASignature signature = minerKey2.Sign(block.GetHash());
(block as PosBlock).BlockSignature = new BlockSignature { Signature = signature.ToDER() };
// Execute the rule and check the outcome against what is expected.
ConsensusException error = await Assert.ThrowsAsync<ConsensusException>(async () => await consensusManager.BlockMinedAsync(block));
Assert.Equal(ConsensusErrors.BadTransactionScriptError.Message, error.Message);
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Moq;
using NBitcoin;
using Stratis.Bitcoin.Base;
using Stratis.Bitcoin.Base.Deployments;
using Stratis.Bitcoin.Configuration;
using Stratis.Bitcoin.Configuration.Settings;
using Stratis.Bitcoin.Consensus;
using Stratis.Bitcoin.Consensus.Rules;
using Stratis.Bitcoin.Features.Consensus.CoinViews;
using Stratis.Bitcoin.Features.Consensus.Rules;
using Stratis.Bitcoin.Features.Consensus.Rules.CommonRules;
using Stratis.Bitcoin.Tests.Common;
using Stratis.Bitcoin.Utilities;
using Xunit;
namespace Stratis.Bitcoin.Features.Consensus.Tests.Rules.CommonRules
{
/// <summary>
/// These tests only cover the first part of BIP68 and not the MaxSigOps, coinview update or scripts verify or calculate block rewards
/// </summary>
public class DeStreamPowCoinViewRuleTests
{
private Exception caughtExecption;
private readonly Network network;
private Mock<ILogger> logger;
private const int HeightOfBlockchain = 1;
private RuleContext ruleContext;
private UnspentOutputSet coinView;
private Transaction transactionWithCoinbaseFromPreviousBlock;
private readonly CoinViewRule rule;
public DeStreamPowCoinViewRuleTests()
{
this.network = KnownNetworks.RegTest;
this.rule = new DeStreamPowCoinviewRule();
}
[Fact]
public void RunAsync_ValidatingATransactionThatIsNotCoinBaseButStillHasUnspentOutputsWithoutInput_ThrowsBadTransactionMissingInput()
{
this.GivenACoinbaseTransactionFromAPreviousBlock();
this.AndARuleContext();
this.AndSomeUnspentOutputs();
this.AndATransactionWithNoUnspentOutputsAsInput();
this.WhenExecutingTheRule(this.rule, this.ruleContext);
this.ThenExceptionThrownIs(ConsensusErrors.BadTransactionMissingInput);
}
[Fact]
//NOTE: This is not full coverage of BIP68 bad transaction non final as a block earlier than allowable timestamp is also not allowable under BIP68.
public void RunAsync_ValidatingABlockHeightLowerThanBIP86Allows_ThrowsBadTransactionNonFinal()
{
this.GivenACoinbaseTransactionFromAPreviousBlock();
this.AndARuleContext();
this.AndSomeUnspentOutputs();
this.AndATransactionBlockHeightLowerThanBip68Allows();
this.WhenExecutingTheRule(this.rule, this.ruleContext);
this.ThenExceptionThrownIs(ConsensusErrors.BadTransactionNonFinal);
}
private void AndSomeUnspentOutputs()
{
this.coinView = new UnspentOutputSet();
this.coinView.SetCoins(new UnspentOutputs[0]);
(this.ruleContext as UtxoRuleContext).UnspentOutputSet = this.coinView;
this.coinView.Update(this.transactionWithCoinbaseFromPreviousBlock, 0);
}
private void AndARuleContext()
{
this.ruleContext = new PowRuleContext { };
this.ruleContext.ValidationContext = new ValidationContext();
BlockHeader blockHeader = this.network.Consensus.ConsensusFactory.CreateBlockHeader();
this.ruleContext.ValidationContext.ChainedHeaderToValidate = new ChainedHeader(blockHeader, new uint256("bcd7d5de8d3bcc7b15e7c8e5fe77c0227cdfa6c682ca13dcf4910616f10fdd06"), HeightOfBlockchain);
Block block = this.network.CreateBlock();
block.Transactions = new List<Transaction>();
this.ruleContext.ValidationContext.BlockToValidate = block;
}
protected void WhenExecutingTheRule(ConsensusRuleBase rule, RuleContext ruleContext)
{
try
{
this.logger = new Mock<ILogger>();
rule.Logger = this.logger.Object;
var dateTimeProvider = new DateTimeProvider();
rule.Parent = new PowConsensusRuleEngine(
KnownNetworks.RegTest,
new Mock<ILoggerFactory>().Object,
new Mock<IDateTimeProvider>().Object,
new ConcurrentChain(this.network),
new NodeDeployments(KnownNetworks.RegTest, new ConcurrentChain(this.network)),
new ConsensusSettings(NodeSettings.Default(KnownNetworks.RegTest)), new Mock<ICheckpoints>().Object, new Mock<ICoinView>().Object, new Mock<IChainState>().Object,
new InvalidBlockHashStore(dateTimeProvider),
new NodeStats(dateTimeProvider));
rule.Initialize();
(rule as AsyncConsensusRule).RunAsync(ruleContext).GetAwaiter().GetResult();
}
catch (Exception e)
{
this.caughtExecption = e;
}
}
private void ThenExceptionThrownIs(ConsensusError consensusErrorType)
{
this.caughtExecption.Should().NotBeNull();
this.caughtExecption.Should().BeOfType<ConsensusErrorException>();
var consensusErrorException = (ConsensusErrorException)this.caughtExecption;
consensusErrorException.ConsensusError.Should().Be(consensusErrorType);
}
private void AndATransactionBlockHeightLowerThanBip68Allows()
{
var transaction = new Transaction
{
Inputs = { new TxIn()
{
PrevOut = new OutPoint(this.transactionWithCoinbaseFromPreviousBlock, 0),
Sequence = HeightOfBlockchain + 1, //this sequence being higher triggers the ThrowsBadTransactionNonFinal
} },
Outputs = { new TxOut() },
Version = 2, // So that sequence locks considered (BIP68)
};
this.ruleContext.Flags = new DeploymentFlags() { LockTimeFlags = Transaction.LockTimeFlags.VerifySequence };
this.ruleContext.ValidationContext.BlockToValidate.Transactions.Add(transaction);
}
private void GivenACoinbaseTransactionFromAPreviousBlock()
{
this.transactionWithCoinbaseFromPreviousBlock = new Transaction();
var txIn = new TxIn { PrevOut = new OutPoint() };
this.transactionWithCoinbaseFromPreviousBlock.AddInput(txIn);
this.transactionWithCoinbaseFromPreviousBlock.AddOutput(new TxOut());
}
private void AndATransactionWithNoUnspentOutputsAsInput()
{
this.ruleContext.ValidationContext.BlockToValidate.Transactions.Add(new Transaction { Inputs = { new TxIn() } });
}
}
}
\ No newline at end of file
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Stratis.Bitcoin.BlockPulling; using NBitcoin;
using NBitcoin.Rules;
using Stratis.Bitcoin.Base;
using Stratis.Bitcoin.Builder; using Stratis.Bitcoin.Builder;
using Stratis.Bitcoin.Configuration.Logging; using Stratis.Bitcoin.Configuration.Logging;
using Stratis.Bitcoin.Configuration.Settings;
using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Consensus;
using Stratis.Bitcoin.Consensus.Rules; using Stratis.Bitcoin.Consensus.Rules;
using Stratis.Bitcoin.Features.Consensus.CoinViews; using Stratis.Bitcoin.Features.Consensus.CoinViews;
using Stratis.Bitcoin.Features.Consensus.Interfaces; using Stratis.Bitcoin.Features.Consensus.Interfaces;
using Stratis.Bitcoin.Features.Consensus.ProvenBlockHeaders;
using Stratis.Bitcoin.Features.Consensus.Rules; using Stratis.Bitcoin.Features.Consensus.Rules;
using Stratis.Bitcoin.Features.Consensus.Rules.CommonRules; using Stratis.Bitcoin.Features.Consensus.Rules.CommonRules;
using Stratis.Bitcoin.Features.Consensus.Rules.ProvenHeaderRules;
using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Interfaces;
namespace Stratis.Bitcoin.Features.Consensus namespace Stratis.Bitcoin.Features.Consensus
{ {
/// <summary> /// <summary>
/// A class providing extension methods for <see cref="IFullNodeBuilder" />. /// A class providing extension methods for <see cref="IFullNodeBuilder"/>.
/// </summary> /// </summary>
public static class DeStreamFullNodeBuilderConsensusExtension public static class DeStreamFullNodeBuilderConsensusExtension
{ {
public static IFullNodeBuilder UseDeStreamPowConsensus(this IFullNodeBuilder fullNodeBuilder) public static IFullNodeBuilder UsePowConsensus(this IFullNodeBuilder fullNodeBuilder)
{ {
LoggingConfiguration.RegisterFeatureNamespace<ConsensusFeature>("consensus"); LoggingConfiguration.RegisterFeatureNamespace<PowConsensusFeature>("powconsensus");
LoggingConfiguration.RegisterFeatureClass<ConsensusStats>("bench");
fullNodeBuilder.ConfigureFeature(features => fullNodeBuilder.ConfigureFeature(features =>
{ {
features features
.AddFeature<ConsensusFeature>() .AddFeature<PowConsensusFeature>()
.FeatureServices(services => .FeatureServices(services =>
{ {
// TODO: this should be set on the network build services.AddSingleton<ConsensusOptions, ConsensusOptions>();
fullNodeBuilder.Network.Consensus.Options = new PowConsensusOptions(); services.AddSingleton<DBreezeCoinView>();
services.AddSingleton<ICoinView, CachedCoinView>();
services.AddSingleton<ICheckpoints, Checkpoints>();
services.AddSingleton<NBitcoin.Consensus.ConsensusOptions, PowConsensusOptions>();
services.AddSingleton<DBreezeCoinView, DeStreamDBreezeCoinView>();
services.AddSingleton<CoinView, CachedCoinView>();
services.AddSingleton<LookaheadBlockPuller>()
.AddSingleton<ILookaheadBlockPuller, LookaheadBlockPuller>(provider =>
provider.GetService<LookaheadBlockPuller>());
;
services.AddSingleton<IConsensusLoop, ConsensusLoop>()
.AddSingleton<INetworkDifficulty, ConsensusLoop>(provider =>
provider.GetService<IConsensusLoop>() as ConsensusLoop)
.AddSingleton<IGetUnspentTransaction, ConsensusLoop>(provider =>
provider.GetService<IConsensusLoop>() as ConsensusLoop);
services.AddSingleton<IInitialBlockDownloadState, InitialBlockDownloadState>();
services.AddSingleton<ConsensusController>(); services.AddSingleton<ConsensusController>();
services.AddSingleton<ConsensusStats>(); services.AddSingleton<IConsensusRuleEngine, PowConsensusRuleEngine>();
services.AddSingleton<ConsensusSettings>(); services.AddSingleton<IChainState, ChainState>();
services.AddSingleton<IConsensusRules, DeStreamPowConsensusRules>(); services.AddSingleton<ConsensusQuery>()
services .AddSingleton<INetworkDifficulty, ConsensusQuery>(provider => provider.GetService<ConsensusQuery>())
.AddSingleton<IRuleRegistration, .AddSingleton<IGetUnspentTransaction, ConsensusQuery>(provider => provider.GetService<ConsensusQuery>());
DeStreamPowConsensusRulesRegistration>(); new PowConsensusRulesRegistration().RegisterRules(fullNodeBuilder.Network.Consensus);
}); });
}); });
return fullNodeBuilder; return fullNodeBuilder;
} }
public static IFullNodeBuilder UseDeStreamPosConsensus(this IFullNodeBuilder fullNodeBuilder) public static IFullNodeBuilder UsePosConsensus(this IFullNodeBuilder fullNodeBuilder)
{ {
LoggingConfiguration.RegisterFeatureNamespace<ConsensusFeature>("consensus"); LoggingConfiguration.RegisterFeatureNamespace<PosConsensusFeature>("posconsensus");
LoggingConfiguration.RegisterFeatureClass<ConsensusStats>("bench");
fullNodeBuilder.ConfigureFeature(features => fullNodeBuilder.ConfigureFeature(features =>
{ {
features features
.AddFeature<ConsensusFeature>() .AddFeature<PosConsensusFeature>()
.FeatureServices(services => .FeatureServices(services =>
{ {
fullNodeBuilder.Network.Consensus.Options = new DeStreamPosConsensusOptions(); services.AddSingleton<DBreezeCoinView>();
services.AddSingleton<ICoinView, CachedCoinView>();
services.AddSingleton<ICheckpoints, Checkpoints>(); services.AddSingleton<StakeChainStore>().AddSingleton<IStakeChain, StakeChainStore>(provider => provider.GetService<StakeChainStore>());
services.AddSingleton<DBreezeCoinView, DeStreamDBreezeCoinView>();
services.AddSingleton<CoinView, CachedCoinView>();
services.AddSingleton<LookaheadBlockPuller>()
.AddSingleton<ILookaheadBlockPuller, LookaheadBlockPuller>(provider =>
provider.GetService<LookaheadBlockPuller>());
;
services.AddSingleton<IConsensusLoop, ConsensusLoop>()
.AddSingleton<INetworkDifficulty, ConsensusLoop>(provider =>
provider.GetService<IConsensusLoop>() as ConsensusLoop)
.AddSingleton<IGetUnspentTransaction, ConsensusLoop>(provider =>
provider.GetService<IConsensusLoop>() as ConsensusLoop);
services.AddSingleton<StakeChainStore>()
.AddSingleton<IStakeChain, StakeChainStore>(provider =>
provider.GetService<StakeChainStore>());
services.AddSingleton<IStakeValidator, StakeValidator>(); services.AddSingleton<IStakeValidator, StakeValidator>();
services.AddSingleton<IInitialBlockDownloadState, InitialBlockDownloadState>();
services.AddSingleton<ConsensusController>(); services.AddSingleton<ConsensusController>();
services.AddSingleton<ConsensusStats>(); services.AddSingleton<IRewindDataIndexCache, RewindDataIndexCache>();
services.AddSingleton<ConsensusSettings>(); services.AddSingleton<IConsensusRuleEngine, PosConsensusRuleEngine>();
services.AddSingleton<IConsensusRules, DeStreamPosConsensusRules>(); services.AddSingleton<IChainState, ChainState>();
services services.AddSingleton<ConsensusQuery>()
.AddSingleton<IRuleRegistration, .AddSingleton<INetworkDifficulty, ConsensusQuery>(provider => provider.GetService<ConsensusQuery>())
DeStreamPosConsensusRulesRegistration>(); .AddSingleton<IGetUnspentTransaction, ConsensusQuery>(provider => provider.GetService<ConsensusQuery>());
services.AddSingleton<IProvenBlockHeaderStore, ProvenBlockHeaderStore>();
services.AddSingleton<IProvenBlockHeaderRepository, ProvenBlockHeaderRepository>();
new PosConsensusRulesRegistration().RegisterRules(fullNodeBuilder.Network.Consensus);
}); });
}); });
return fullNodeBuilder; return fullNodeBuilder;
} }
private class DeStreamPowConsensusRulesRegistration : IRuleRegistration public class PowConsensusRulesRegistration : IRuleRegistration
{ {
public IEnumerable<ConsensusRule> GetRules() public void RegisterRules(IConsensus consensus)
{ {
return new List<ConsensusRule> consensus.HeaderValidationRules = new List<IHeaderValidationConsensusRule>()
{ {
new BlockHeaderRule(), new HeaderTimeChecksRule(),
new CheckDifficultyPowRule(),
new BitcoinActivationRule(),
new BitcoinHeaderVersionRule()
};
// rules that are inside the method CheckBlockHeader consensus.IntegrityValidationRules = new List<IIntegrityValidationConsensusRule>()
new CalculateWorkRule(), {
new BlockMerkleRootRule()
};
// rules that are inside the method ContextualCheckBlockHeader consensus.PartialValidationRules = new List<IPartialValidationConsensusRule>()
new CheckpointsRule(), {
new AssumeValidRule(), new SetActivationDeploymentsPartialValidationRule(),
new BlockHeaderPowContextualRule(),
// rules that are inside the method ContextualCheckBlock
new TransactionLocktimeActivationRule(), // implements BIP113 new TransactionLocktimeActivationRule(), // implements BIP113
new CoinbaseHeightActivationRule(), // implements BIP34 new CoinbaseHeightActivationRule(), // implements BIP34
new WitnessCommitmentsRule(), // BIP141, BIP144 new WitnessCommitmentsRule(), // BIP141, BIP144
new BlockSizeRule(), new BlockSizeRule(),
// rules that are inside the method CheckBlock // rules that are inside the method CheckBlock
new BlockMerkleRootRule(),
new EnsureCoinbaseRule(), new EnsureCoinbaseRule(),
new CheckPowTransactionRule(), new CheckPowTransactionRule(),
new CheckSigOpsRule(), new CheckSigOpsRule(),
};
consensus.FullValidationRules = new List<IFullValidationConsensusRule>()
{
new SetActivationDeploymentsFullValidationRule(),
// rules that require the store to be loaded (coinview) // rules that require the store to be loaded (coinview)
new DeStreamLoadCoinviewRule(), new LoadCoinviewRule(),
new DeStreamFundsPreservationRule(),
new TransactionDuplicationActivationRule(), // implements BIP30 new TransactionDuplicationActivationRule(), // implements BIP30
new DeStreamPowCoinviewRule(), // implements BIP68, MaxSigOps and BlockReward calculation new DeStreamPowCoinviewRule(), // implements BIP68, MaxSigOps and BlockReward calculation
new DeStreamBlockFeeRule() new SaveCoinviewRule()
}; };
} }
} }
private class DeStreamPosConsensusRulesRegistration : IRuleRegistration public class PosConsensusRulesRegistration : IRuleRegistration
{
public void RegisterRules(IConsensus consensus)
{ {
public IEnumerable<ConsensusRule> GetRules() consensus.HeaderValidationRules = new List<IHeaderValidationConsensusRule>()
{ {
return new List<ConsensusRule> new HeaderTimeChecksRule(),
new HeaderTimeChecksPosRule(),
new StratisBugFixPosFutureDriftRule(),
new CheckDifficultyPosRule(),
new StratisHeaderVersionRule(),
new ProvenHeaderSizeRule(),
new ProvenHeaderCoinstakeRule()
};
consensus.IntegrityValidationRules = new List<IIntegrityValidationConsensusRule>()
{ {
new BlockHeaderRule(), new BlockMerkleRootRule(),
new PosBlockSignatureRepresentationRule(),
new PosBlockSignatureRule(),
};
// rules that are inside the method CheckBlockHeader consensus.PartialValidationRules = new List<IPartialValidationConsensusRule>()
new CalculateStakeRule(), {
new SetActivationDeploymentsPartialValidationRule(),
// rules that are inside the method ContextualCheckBlockHeader new PosTimeMaskRule(),
new CheckpointsRule(),
new AssumeValidRule(),
new BlockHeaderPowContextualRule(),
new BlockHeaderPosContextualRule(),
// rules that are inside the method ContextualCheckBlock // rules that are inside the method ContextualCheckBlock
new TransactionLocktimeActivationRule(), // implements BIP113 new TransactionLocktimeActivationRule(), // implements BIP113
...@@ -165,24 +157,28 @@ namespace Stratis.Bitcoin.Features.Consensus ...@@ -165,24 +157,28 @@ namespace Stratis.Bitcoin.Features.Consensus
new WitnessCommitmentsRule(), // BIP141, BIP144 new WitnessCommitmentsRule(), // BIP141, BIP144
new BlockSizeRule(), new BlockSizeRule(),
new PosBlockContextRule(), // TODO: this rule needs to be implemented
// rules that are inside the method CheckBlock // rules that are inside the method CheckBlock
new BlockMerkleRootRule(),
new EnsureCoinbaseRule(), new EnsureCoinbaseRule(),
new CheckPowTransactionRule(), new CheckPowTransactionRule(),
new CheckPosTransactionRule(), new CheckPosTransactionRule(),
new CheckSigOpsRule(), new CheckSigOpsRule(),
new PosFutureDriftRule(),
new PosCoinstakeRule(), new PosCoinstakeRule(),
new PosBlockSignatureRule(), };
consensus.FullValidationRules = new List<IFullValidationConsensusRule>()
{
new SetActivationDeploymentsFullValidationRule(),
new CheckDifficultyHybridRule(),
// rules that require the store to be loaded (coinview) // rules that require the store to be loaded (coinview)
new DeStreamLoadCoinviewRule(), new LoadCoinviewRule(),
new DeStreamFundsPreservationRule(),
new TransactionDuplicationActivationRule(), // implements BIP30 new TransactionDuplicationActivationRule(), // implements BIP30
new DeStreamPosCoinviewRule(), // implements BIP68, MaxSigOps and BlockReward calculation new DeStreamPosCoinviewRule(), // implements BIP68, MaxSigOps and BlockReward calculation
new DeStreamBlockFeeRule() // Place the PosColdStakingRule after the PosCoinviewRule to ensure that all input scripts have been evaluated
// and that the "IsColdCoinStake" flag would have been set by the OP_CHECKCOLDSTAKEVERIFY opcode if applicable.
new PosColdStakingRule(),
new SaveCoinviewRule()
}; };
} }
} }
......
...@@ -147,7 +147,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -147,7 +147,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
/// </summary> /// </summary>
/// <param name="context">Context that contains variety of information regarding blocks validation and execution.</param> /// <param name="context">Context that contains variety of information regarding blocks validation and execution.</param>
/// <param name="transaction">Transaction which outputs will be added to the context's <see cref="UnspentOutputSet"/> and which inputs will be removed from it.</param> /// <param name="transaction">Transaction which outputs will be added to the context's <see cref="UnspentOutputSet"/> and which inputs will be removed from it.</param>
protected void UpdateUTXOSet(RuleContext context, Transaction transaction) protected virtual void UpdateUTXOSet(RuleContext context, Transaction transaction)
{ {
ChainedHeader index = context.ValidationContext.ChainedHeaderToValidate; ChainedHeader index = context.ValidationContext.ChainedHeaderToValidate;
UnspentOutputSet view = (context as UtxoRuleContext).UnspentOutputSet; UnspentOutputSet view = (context as UtxoRuleContext).UnspentOutputSet;
...@@ -218,7 +218,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -218,7 +218,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
/// <exception cref="ConsensusErrors.BadTransactionInBelowOut">Thrown if transaction inputs are less then outputs.</exception> /// <exception cref="ConsensusErrors.BadTransactionInBelowOut">Thrown if transaction inputs are less then outputs.</exception>
/// <exception cref="ConsensusErrors.BadTransactionNegativeFee">Thrown if fees sum is negative.</exception> /// <exception cref="ConsensusErrors.BadTransactionNegativeFee">Thrown if fees sum is negative.</exception>
/// <exception cref="ConsensusErrors.BadTransactionFeeOutOfRange">Thrown if fees value is out of range.</exception> /// <exception cref="ConsensusErrors.BadTransactionFeeOutOfRange">Thrown if fees value is out of range.</exception>
public void CheckInputs(Transaction transaction, UnspentOutputSet inputs, int spendHeight) public virtual void CheckInputs(Transaction transaction, UnspentOutputSet inputs, int spendHeight)
{ {
if (!inputs.HaveInputs(transaction)) if (!inputs.HaveInputs(transaction))
ConsensusErrors.BadTransactionMissingInput.Throw(); ConsensusErrors.BadTransactionMissingInput.Throw();
...@@ -280,7 +280,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -280,7 +280,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
/// <param name="inputs">Map of previous transactions that have outputs we're spending.</param> /// <param name="inputs">Map of previous transactions that have outputs we're spending.</param>
/// <param name="flags">Script verification flags.</param> /// <param name="flags">Script verification flags.</param>
/// <returns>Signature operation cost for all transaction's inputs.</returns> /// <returns>Signature operation cost for all transaction's inputs.</returns>
public long GetTransactionSignatureOperationCost(Transaction transaction, UnspentOutputSet inputs, DeploymentFlags flags) public virtual long GetTransactionSignatureOperationCost(Transaction transaction, UnspentOutputSet inputs, DeploymentFlags flags)
{ {
long signatureOperationCost = this.GetLegacySignatureOperationsCount(transaction) * this.ConsensusOptions.WitnessScaleFactor; long signatureOperationCost = this.GetLegacySignatureOperationsCount(transaction) * this.ConsensusOptions.WitnessScaleFactor;
...@@ -308,7 +308,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -308,7 +308,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
/// <param name="witness">Witness script.</param> /// <param name="witness">Witness script.</param>
/// <param name="flags">Script verification flags.</param> /// <param name="flags">Script verification flags.</param>
/// <returns>Signature operation cost for single transaction input.</returns> /// <returns>Signature operation cost for single transaction input.</returns>
private long CountWitnessSignatureOperation(Script scriptPubKey, WitScript witness, DeploymentFlags flags) protected long CountWitnessSignatureOperation(Script scriptPubKey, WitScript witness, DeploymentFlags flags)
{ {
witness = witness ?? WitScript.Empty; witness = witness ?? WitScript.Empty;
if (!flags.ScriptFlags.HasFlag(ScriptVerify.Witness)) if (!flags.ScriptFlags.HasFlag(ScriptVerify.Witness))
...@@ -337,7 +337,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -337,7 +337,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
/// <param name="transaction">Transaction for which we are computing the cost.</param> /// <param name="transaction">Transaction for which we are computing the cost.</param>
/// <param name="inputs">Map of previous transactions that have outputs we're spending.</param> /// <param name="inputs">Map of previous transactions that have outputs we're spending.</param>
/// <returns>Signature operation cost for transaction.</returns> /// <returns>Signature operation cost for transaction.</returns>
private uint GetP2SHSignatureOperationsCount(Transaction transaction, UnspentOutputSet inputs) protected virtual uint GetP2SHSignatureOperationsCount(Transaction transaction, UnspentOutputSet inputs)
{ {
if (transaction.IsCoinBase) if (transaction.IsCoinBase)
return 0; return 0;
...@@ -358,7 +358,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -358,7 +358,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
/// </summary> /// </summary>
/// <param name="transaction">Transaction for which we are computing the cost.</param> /// <param name="transaction">Transaction for which we are computing the cost.</param>
/// <returns>Legacy signature operation cost for transaction.</returns> /// <returns>Legacy signature operation cost for transaction.</returns>
private long GetLegacySignatureOperationsCount(Transaction transaction) protected long GetLegacySignatureOperationsCount(Transaction transaction)
{ {
long sigOps = 0; long sigOps = 0;
foreach (TxIn txin in transaction.Inputs) foreach (TxIn txin in transaction.Inputs)
...@@ -375,7 +375,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -375,7 +375,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
/// </summary> /// </summary>
/// <param name="value">The value to be checked.</param> /// <param name="value">The value to be checked.</param>
/// <returns><c>true</c> if the value is in range. Otherwise <c>false</c>.</returns> /// <returns><c>true</c> if the value is in range. Otherwise <c>false</c>.</returns>
private bool MoneyRange(long value) protected bool MoneyRange(long value)
{ {
return ((value >= 0) && (value <= this.Consensus.MaxMoney)); return ((value >= 0) && (value <= this.Consensus.MaxMoney));
} }
......
...@@ -5,6 +5,7 @@ using System.Threading.Tasks; ...@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NBitcoin; using NBitcoin;
using Stratis.Bitcoin.Base.Deployments; using Stratis.Bitcoin.Base.Deployments;
using Stratis.Bitcoin.Consensus;
using Stratis.Bitcoin.Consensus.Rules; using Stratis.Bitcoin.Consensus.Rules;
using Stratis.Bitcoin.Utilities; using Stratis.Bitcoin.Utilities;
...@@ -17,91 +18,59 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -17,91 +18,59 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
{ {
public override async Task RunAsync(RuleContext context) public override async Task RunAsync(RuleContext context)
{ {
this.Logger.LogTrace("()"); Block block = context.ValidationContext.BlockToValidate;
ChainedHeader index = context.ValidationContext.ChainedHeaderToValidate;
Block block = context.ValidationContext.Block;
ChainedHeader index = context.ValidationContext.ChainedHeader;
DeploymentFlags flags = context.Flags; DeploymentFlags flags = context.Flags;
UnspentOutputSet view = (context as UtxoRuleContext).UnspentOutputSet; UnspentOutputSet view = (context as UtxoRuleContext).UnspentOutputSet;
this.Parent.PerformanceCounter.AddProcessedBlocks(1);
long sigOpsCost = 0; long sigOpsCost = 0;
Money fees = Money.Zero; Money fees = Money.Zero;
var checkInputs = new List<Task<bool>>(); var checkInputs = new List<Task<bool>>();
for (int txIndex = 0; txIndex < block.Transactions.Count; txIndex++) for (int txIndex = 0; txIndex < block.Transactions.Count; txIndex++)
{ {
this.Parent.PerformanceCounter.AddProcessedTransactions(1);
Transaction tx = block.Transactions[txIndex]; Transaction tx = block.Transactions[txIndex];
if (!context.SkipValidation) if (!context.SkipValidation)
{ {
if (!this.IsProtocolTransaction(tx)) if (!tx.IsCoinBase && !view.HaveInputs(tx))
{
if (!view.HaveInputs(tx))
{ {
this.Logger.LogTrace("(-)[BAD_TX_NO_INPUT]"); this.Logger.LogTrace("(-)[BAD_TX_NO_INPUT]");
ConsensusErrors.BadTransactionMissingInput.Throw(); ConsensusErrors.BadTransactionMissingInput.Throw();
} }
var prevheights = new int[tx.Inputs.Count]; if (!this.IsTxFinal(tx, context))
// Check that transaction is BIP68 final.
// BIP68 lock checks (as opposed to nLockTime checks) must
// be in ConnectBlock because they require the UTXO set.
for (int j = 0; j < tx.Inputs.Count; j++)
{
prevheights[j] = tx.Inputs[j].IsChangePointer()
? 0
: (int) view.AccessCoins(tx.Inputs[j].PrevOut.Hash).Height;
}
if (!tx.CheckSequenceLocks(prevheights, index, flags.LockTimeFlags))
{ {
this.Logger.LogTrace("(-)[BAD_TX_NON_FINAL]"); this.Logger.LogTrace("(-)[BAD_TX_NON_FINAL]");
ConsensusErrors.BadTransactionNonFinal.Throw(); ConsensusErrors.BadTransactionNonFinal.Throw();
} }
}
// GetTransactionSignatureOperationCost counts 3 types of sigops: // GetTransactionSignatureOperationCost counts 3 types of sigops:
// * legacy (always), // * legacy (always),AccessCoins(tx.Inputs[j].PrevOut.Hash).Height
// * p2sh (when P2SH enabled in flags and excludes coinbase), // * p2sh (when P2SH enabled in flags and excludes coinbase),
// * witness (when witness enabled in flags and excludes coinbase). // * witness (when witness enabled in flags and excludes coinbase).
sigOpsCost += this.GetTransactionSignatureOperationCost(tx, view, flags); sigOpsCost += this.GetTransactionSignatureOperationCost(tx, view, flags);
if (sigOpsCost > this.PowConsensusOptions.MaxBlockSigopsCost) if (sigOpsCost > this.ConsensusOptions.MaxBlockSigopsCost)
{ {
this.Logger.LogTrace("(-)[BAD_BLOCK_SIG_OPS]"); this.Logger.LogTrace("(-)[BAD_BLOCK_SIG_OPS]");
ConsensusErrors.BadBlockSigOps.Throw(); ConsensusErrors.BadBlockSigOps.Throw();
} }
if (!this.IsProtocolTransaction(tx)) if (!tx.IsCoinBase)
{ {
this.CheckInputs(tx, view, index.Height); this.CheckInputs(tx, view, index.Height);
if (!tx.IsCoinStake)
fees += view.GetValueIn(tx) - tx.TotalOut; fees += view.GetValueIn(tx) - tx.TotalOut;
var txData = new PrecomputedTransactionData(tx); var txData = new PrecomputedTransactionData(tx);
for (int inputIndex = 0; inputIndex < tx.Inputs.Count; inputIndex++) for (int inputIndex = 0; inputIndex < tx.Inputs.Count; inputIndex++)
{ {
this.Parent.PerformanceCounter.AddProcessedInputs(1);
TxIn input = tx.Inputs[inputIndex]; TxIn input = tx.Inputs[inputIndex];
int inputIndexCopy = inputIndex; int inputIndexCopy = inputIndex;
TxOut txout = input.IsChangePointer() TxOut txout = input.IsChangePointer()
? tx.Outputs[input.PrevOut.N] ? tx.Outputs[input.PrevOut.N]
: view.GetOutputFor(input); : view.GetOutputFor(input);
var checkInput = new Task<bool>(() => var checkInput = new Task<bool>(() => this.CheckInput(tx, inputIndexCopy, txout, txData, input, flags));
{
var checker = new TransactionChecker(tx, inputIndexCopy, txout.Value, txData);
var ctx = new ScriptEvaluationContext(this.Parent.Network);
ctx.ScriptVerify = flags.ScriptFlags;
bool verifyScriptResult =
ctx.VerifyScript(input.ScriptSig, txout.ScriptPubKey, checker);
if (verifyScriptResult == false)
{
this.Logger.LogTrace(
"Verify script for transaction '{0}' failed, ScriptSig = '{1}', ScriptPubKey = '{2}', script evaluation error = '{3}'",
tx.GetHash(), input.ScriptSig, txout.ScriptPubKey, ctx.Error);
}
return verifyScriptResult;
});
checkInput.Start(); checkInput.Start();
checkInputs.Add(checkInput); checkInputs.Add(checkInput);
} }
...@@ -136,14 +105,12 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -136,14 +105,12 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
/// <inheritdoc /> /// <inheritdoc />
public override void CheckInputs(Transaction transaction, UnspentOutputSet inputs, int spendHeight) public override void CheckInputs(Transaction transaction, UnspentOutputSet inputs, int spendHeight)
{ {
this.Logger.LogTrace("({0}:{1})", nameof(spendHeight), spendHeight);
if (!inputs.HaveInputs(transaction)) if (!inputs.HaveInputs(transaction))
ConsensusErrors.BadTransactionMissingInput.Throw(); ConsensusErrors.BadTransactionMissingInput.Throw();
Money valueIn = Money.Zero; Money valueIn = Money.Zero;
Money fees = Money.Zero; Money fees = Money.Zero;
foreach (TxIn txIn in transaction.Inputs.RemoveChangePointer()) foreach (var txIn in transaction.Inputs.RemoveChangePointer())
{ {
OutPoint prevout = txIn.PrevOut; OutPoint prevout = txIn.PrevOut;
UnspentOutputs coins = inputs.AccessCoins(prevout.Hash); UnspentOutputs coins = inputs.AccessCoins(prevout.Hash);
...@@ -159,13 +126,15 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -159,13 +126,15 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
} }
} }
if (!transaction.IsProtocolTransaction())
{
if (valueIn < transaction.TotalOut) if (valueIn < transaction.TotalOut)
{ {
this.Logger.LogTrace("(-)[TX_IN_BELOW_OUT]"); this.Logger.LogTrace("(-)[TX_IN_BELOW_OUT]");
ConsensusErrors.BadTransactionInBelowOut.Throw(); ConsensusErrors.BadTransactionInBelowOut.Throw();
} }
// Tally transaction fees. // Check transaction fees.
Money txFee = valueIn - transaction.TotalOut; Money txFee = valueIn - transaction.TotalOut;
if (txFee < 0) if (txFee < 0)
{ {
...@@ -179,29 +148,28 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -179,29 +148,28 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
this.Logger.LogTrace("(-)[BAD_FEE]"); this.Logger.LogTrace("(-)[BAD_FEE]");
ConsensusErrors.BadTransactionFeeOutOfRange.Throw(); ConsensusErrors.BadTransactionFeeOutOfRange.Throw();
} }
}
this.Logger.LogTrace("(-)");
} }
/// <inheritdoc /> /// <inheritdoc />
public override long GetTransactionSignatureOperationCost(Transaction transaction, UnspentOutputSet inputs, public override long GetTransactionSignatureOperationCost(Transaction transaction, UnspentOutputSet inputs,
DeploymentFlags flags) DeploymentFlags flags)
{ {
long signatureOperationCost = this.GetLegacySignatureOperationsCount(transaction) * long signatureOperationCost = this.GetLegacySignatureOperationsCount(transaction) * this.ConsensusOptions.WitnessScaleFactor;
this.PowConsensusOptions.WitnessScaleFactor;
if (transaction.IsCoinBase) if (transaction.IsCoinBase)
return signatureOperationCost; return signatureOperationCost;
if (flags.ScriptFlags.HasFlag(ScriptVerify.P2SH)) if (flags.ScriptFlags.HasFlag(ScriptVerify.P2SH))
{ {
signatureOperationCost += this.GetP2SHSignatureOperationsCount(transaction, inputs) * signatureOperationCost += this.GetP2SHSignatureOperationsCount(transaction, inputs) * this.ConsensusOptions.WitnessScaleFactor;
this.PowConsensusOptions.WitnessScaleFactor;
} }
signatureOperationCost += (from t in transaction.Inputs.RemoveChangePointer() foreach (var txIn in transaction.Inputs.RemoveChangePointer())
let prevout = inputs.GetOutputFor(t) {
select this.CountWitnessSignatureOperation(prevout.ScriptPubKey, t.WitScript, flags)).Sum(); TxOut prevout = inputs.GetOutputFor(txIn);
signatureOperationCost += this.CountWitnessSignatureOperation(prevout.ScriptPubKey, txIn.WitScript, flags);
}
return signatureOperationCost; return signatureOperationCost;
} }
...@@ -213,7 +181,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -213,7 +181,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
return 0; return 0;
uint sigOps = 0; uint sigOps = 0;
foreach (TxIn txIn in transaction.Inputs.RemoveChangePointer()) foreach (var txIn in transaction.Inputs.RemoveChangePointer())
{ {
TxOut prevout = inputs.GetOutputFor(txIn); TxOut prevout = inputs.GetOutputFor(txIn);
if (prevout.ScriptPubKey.IsPayToScriptHash(this.Parent.Network)) if (prevout.ScriptPubKey.IsPayToScriptHash(this.Parent.Network))
...@@ -228,11 +196,9 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -228,11 +196,9 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
{ {
// Saves script pub keys and total amount of spent inputs to context // Saves script pub keys and total amount of spent inputs to context
this.Logger.LogTrace("()"); ChainedHeader index = context.ValidationContext.ChainedHeaderToValidate;
ChainedHeader index = context.ValidationContext.ChainedHeader;
UnspentOutputSet view = (context as UtxoRuleContext).UnspentOutputSet; UnspentOutputSet view = (context as UtxoRuleContext).UnspentOutputSet;
switch (context) switch (context)
{ {
case DeStreamPowRuleContext deStreamPowRuleContext: case DeStreamPowRuleContext deStreamPowRuleContext:
...@@ -242,7 +208,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -242,7 +208,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
deStreamPowRuleContext.TotalIn.Add(transaction.GetHash(), view.GetValueIn(transaction)); deStreamPowRuleContext.TotalIn.Add(transaction.GetHash(), view.GetValueIn(transaction));
break; break;
case DeStreamRuleContext deStreamPosRuleContext: case DeStreamPosRuleContext deStreamPosRuleContext:
deStreamPosRuleContext.InputScriptPubKeys.AddOrReplace(transaction.GetHash(), transaction.Inputs deStreamPosRuleContext.InputScriptPubKeys.AddOrReplace(transaction.GetHash(), transaction.Inputs
.RemoveChangePointer() .RemoveChangePointer()
.Select(p => view.GetOutputFor(p).ScriptPubKey).ToList()); .Select(p => view.GetOutputFor(p).ScriptPubKey).ToList());
...@@ -250,12 +216,10 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -250,12 +216,10 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
break; break;
default: default:
throw new NotSupportedException( throw new NotSupportedException(
$"Rule context must be {nameof(DeStreamPowRuleContext)} or {nameof(DeStreamRuleContext)}"); $"Rule context must be {nameof(DeStreamPowRuleContext)} or {nameof(DeStreamPosRuleContext)}");
} }
view.Update(transaction, index.Height); view.Update(transaction, index.Height);
this.Logger.LogTrace("(-)");
} }
} }
} }
\ No newline at end of file
using System.Threading.Tasks; using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NBitcoin; using NBitcoin;
using Stratis.Bitcoin.Consensus;
using Stratis.Bitcoin.Consensus.Rules; using Stratis.Bitcoin.Consensus.Rules;
using Stratis.Bitcoin.Features.Consensus.Interfaces; using Stratis.Bitcoin.Features.Consensus.Interfaces;
using Stratis.Bitcoin.Utilities; using Stratis.Bitcoin.Utilities;
...@@ -10,7 +12,6 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -10,7 +12,6 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
/// <summary> /// <summary>
/// Proof of stake override for the coinview rules - BIP68, MaxSigOps and BlockReward checks. /// Proof of stake override for the coinview rules - BIP68, MaxSigOps and BlockReward checks.
/// </summary> /// </summary>
[ExecutionRule]
public sealed class DeStreamPosCoinviewRule : DeStreamCoinViewRule public sealed class DeStreamPosCoinviewRule : DeStreamCoinViewRule
{ {
/// <summary>Provides functionality for checking validity of PoS blocks.</summary> /// <summary>Provides functionality for checking validity of PoS blocks.</summary>
...@@ -20,37 +21,29 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -20,37 +21,29 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
private IStakeChain stakeChain; private IStakeChain stakeChain;
/// <summary>The consensus of the parent Network.</summary> /// <summary>The consensus of the parent Network.</summary>
private NBitcoin.Consensus consensus; private IConsensus consensus;
/// <inheritdoc /> /// <inheritdoc />
public override void Initialize() public override void Initialize()
{ {
this.Logger.LogTrace("()");
base.Initialize(); base.Initialize();
this.consensus = this.Parent.Network.Consensus; this.consensus = this.Parent.Network.Consensus;
var consensusRules = (PosConsensusRules)this.Parent; var consensusRules = (PosConsensusRuleEngine)this.Parent;
this.stakeValidator = consensusRules.StakeValidator; this.stakeValidator = consensusRules.StakeValidator;
this.stakeChain = consensusRules.StakeChain; this.stakeChain = consensusRules.StakeChain;
this.Logger.LogTrace("(-)");
} }
/// <inheritdoc /> /// <inheritdoc />
/// <summary>Compute and store the stake proofs.</summary> /// <summary>Compute and store the stake proofs.</summary>
public override async Task RunAsync(RuleContext context) public override async Task RunAsync(RuleContext context)
{ {
this.Logger.LogTrace("()");
this.CheckAndComputeStake(context); this.CheckAndComputeStake(context);
await base.RunAsync(context).ConfigureAwait(false); await base.RunAsync(context).ConfigureAwait(false);
var posRuleContext = context as PosRuleContext; var posRuleContext = context as PosRuleContext;
await this.stakeChain.SetAsync(context.ValidationContext.ChainedHeader, posRuleContext.BlockStake).ConfigureAwait(false); await this.stakeChain.SetAsync(context.ValidationContext.ChainedHeaderToValidate, posRuleContext.BlockStake).ConfigureAwait(false);
this.Logger.LogTrace("(-)");
} }
/// <inheritdoc/> /// <inheritdoc/>
...@@ -62,8 +55,6 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -62,8 +55,6 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
/// <inheritdoc /> /// <inheritdoc />
public override void CheckBlockReward(RuleContext context, Money fees, int height, Block block) public override void CheckBlockReward(RuleContext context, Money fees, int height, Block block)
{ {
this.Logger.LogTrace("({0}:{1},{2}:'{3}')", nameof(fees), fees, nameof(height), height);
if (BlockStake.IsProofOfStake(block)) if (BlockStake.IsProofOfStake(block))
{ {
var posRuleContext = context as PosRuleContext; var posRuleContext = context as PosRuleContext;
...@@ -87,32 +78,27 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -87,32 +78,27 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
ConsensusErrors.BadCoinbaseAmount.Throw(); ConsensusErrors.BadCoinbaseAmount.Throw();
} }
} }
this.Logger.LogTrace("(-)");
} }
/// <inheritdoc /> /// <inheritdoc />
public override void UpdateCoinView(RuleContext context, Transaction transaction) public override void UpdateCoinView(RuleContext context, Transaction transaction)
{ {
this.Logger.LogTrace("()");
var posRuleContext = context as PosRuleContext; var posRuleContext = context as PosRuleContext;
UnspentOutputSet view = posRuleContext.UnspentOutputSet; UnspentOutputSet view = posRuleContext.UnspentOutputSet;
if (transaction.IsCoinStake) if (transaction.IsCoinStake)
{
posRuleContext.TotalCoinStakeValueIn = view.GetValueIn(transaction); posRuleContext.TotalCoinStakeValueIn = view.GetValueIn(transaction);
posRuleContext.CoinStakePrevOutputs = transaction.Inputs.ToDictionary(txin => txin, txin => view.GetOutputFor(txin));
}
base.UpdateUTXOSet(context, transaction); base.UpdateUTXOSet(context, transaction);
this.Logger.LogTrace("(-)");
} }
/// <inheritdoc /> /// <inheritdoc />
public override void CheckMaturity(UnspentOutputs coins, int spendHeight) public override void CheckMaturity(UnspentOutputs coins, int spendHeight)
{ {
this.Logger.LogTrace("({0}:'{1}/{2}',{3}:{4})", nameof(coins), coins.TransactionId, coins.Height, nameof(spendHeight), spendHeight);
base.CheckCoinbaseMaturity(coins, spendHeight); base.CheckCoinbaseMaturity(coins, spendHeight);
if (coins.IsCoinstake) if (coins.IsCoinstake)
...@@ -124,8 +110,6 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -124,8 +110,6 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
ConsensusErrors.BadTransactionPrematureCoinstakeSpending.Throw(); ConsensusErrors.BadTransactionPrematureCoinstakeSpending.Throw();
} }
} }
this.Logger.LogTrace("(-)");
} }
/// <summary> /// <summary>
...@@ -136,11 +120,13 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -136,11 +120,13 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
/// <exception cref="ConsensusErrors.SetStakeEntropyBitFailed">Thrown if failed to set stake entropy bit.</exception> /// <exception cref="ConsensusErrors.SetStakeEntropyBitFailed">Thrown if failed to set stake entropy bit.</exception>
private void CheckAndComputeStake(RuleContext context) private void CheckAndComputeStake(RuleContext context)
{ {
this.Logger.LogTrace("()"); ChainedHeader chainedHeader = context.ValidationContext.ChainedHeaderToValidate;
Block block = context.ValidationContext.BlockToValidate;
ChainedHeader chainedHeader = context.ValidationContext.ChainedHeader;
Block block = context.ValidationContext.Block;
var posRuleContext = context as PosRuleContext; var posRuleContext = context as PosRuleContext;
if (posRuleContext.BlockStake == null)
posRuleContext.BlockStake = BlockStake.Load(context.ValidationContext.BlockToValidate);
BlockStake blockStake = posRuleContext.BlockStake; BlockStake blockStake = posRuleContext.BlockStake;
// Verify hash target and signature of coinstake tx. // Verify hash target and signature of coinstake tx.
...@@ -180,7 +166,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -180,7 +166,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
// Compute stake modifier. // Compute stake modifier.
ChainedHeader prevChainedHeader = chainedHeader.Previous; ChainedHeader prevChainedHeader = chainedHeader.Previous;
BlockStake blockStakePrev = prevChainedHeader == null ? null : this.stakeChain.Get(prevChainedHeader.HashBlock); BlockStake blockStakePrev = prevChainedHeader == null ? null : this.stakeChain.Get(prevChainedHeader.HashBlock);
blockStake.StakeModifierV2 = this.stakeValidator.ComputeStakeModifierV2(prevChainedHeader, blockStakePrev, blockStake.IsProofOfWork() ? chainedHeader.HashBlock : blockStake.PrevoutStake.Hash); blockStake.StakeModifierV2 = this.stakeValidator.ComputeStakeModifierV2(prevChainedHeader, blockStakePrev?.StakeModifierV2, blockStake.IsProofOfWork() ? chainedHeader.HashBlock : blockStake.PrevoutStake.Hash);
} }
else if (chainedHeader.Height == lastCheckpointHeight) else if (chainedHeader.Height == lastCheckpointHeight)
{ {
...@@ -190,8 +176,6 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -190,8 +176,6 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
this.Logger.LogTrace("Last checkpoint stake modifier V2 loaded: '{0}'.", blockStake.StakeModifierV2); this.Logger.LogTrace("Last checkpoint stake modifier V2 loaded: '{0}'.", blockStake.StakeModifierV2);
} }
else this.Logger.LogTrace("POS stake modifier computation skipped for block at height {0} because it is not above last checkpoint block height {1}.", chainedHeader.Height, lastCheckpointHeight); else this.Logger.LogTrace("POS stake modifier computation skipped for block at height {0} because it is not above last checkpoint block height {1}.", chainedHeader.Height, lastCheckpointHeight);
this.Logger.LogTrace("(-)[OK]");
} }
/// <inheritdoc /> /// <inheritdoc />
......
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NBitcoin; using NBitcoin;
using Stratis.Bitcoin.Consensus;
using Stratis.Bitcoin.Consensus.Rules; using Stratis.Bitcoin.Consensus.Rules;
using Stratis.Bitcoin.Utilities; using Stratis.Bitcoin.Utilities;
namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
{ {
/// <inheritdoc /> /// <inheritdoc />
[ExecutionRule]
public sealed class DeStreamPowCoinviewRule : DeStreamCoinViewRule public sealed class DeStreamPowCoinviewRule : DeStreamCoinViewRule
{ {
/// <summary>Consensus parameters.</summary> /// <summary>Consensus parameters.</summary>
private NBitcoin.Consensus consensus; private IConsensus consensus;
/// <inheritdoc /> /// <inheritdoc />
public override void Initialize() public override void Initialize()
{ {
this.Logger.LogTrace("()");
base.Initialize(); base.Initialize();
this.consensus = this.Parent.Network.Consensus; this.consensus = this.Parent.Network.Consensus;
this.Logger.LogTrace("(-)");
} }
/// <inheritdoc/> /// <inheritdoc/>
...@@ -34,16 +30,12 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -34,16 +30,12 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
/// <inheritdoc/> /// <inheritdoc/>
public override void CheckBlockReward(RuleContext context, Money fees, int height, Block block) public override void CheckBlockReward(RuleContext context, Money fees, int height, Block block)
{ {
this.Logger.LogTrace("()");
Money blockReward = fees + this.GetProofOfWorkReward(height); Money blockReward = fees + this.GetProofOfWorkReward(height);
if (block.Transactions[0].TotalOut > blockReward) if (block.Transactions[0].TotalOut > blockReward)
{ {
this.Logger.LogTrace("(-)[BAD_COINBASE_AMOUNT]"); this.Logger.LogTrace("(-)[BAD_COINBASE_AMOUNT]");
ConsensusErrors.BadCoinbaseAmount.Throw(); ConsensusErrors.BadCoinbaseAmount.Throw();
} }
this.Logger.LogTrace("(-)");
} }
/// <inheritdoc/> /// <inheritdoc/>
...@@ -68,6 +60,28 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules ...@@ -68,6 +60,28 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
return subsidy; return subsidy;
} }
/// <inheritdoc />
protected override bool IsTxFinal(Transaction transaction, RuleContext context)
{
if (transaction.IsCoinBase)
return true;
ChainedHeader index = context.ValidationContext.ChainedHeaderToValidate;
UnspentOutputSet view = (context as UtxoRuleContext).UnspentOutputSet;
var prevheights = new int[transaction.Inputs.Count];
// Check that transaction is BIP68 final.
// BIP68 lock checks (as opposed to nLockTime checks) must
// be in ConnectBlock because they require the UTXO set.
for (int i = 0; i < transaction.Inputs.Count; i++)
{
prevheights[i] = (int)view.AccessCoins(transaction.Inputs[i].PrevOut.Hash).Height;
}
return transaction.CheckSequenceLocks(prevheights, index, context.Flags.LockTimeFlags);
}
/// <inheritdoc/> /// <inheritdoc/>
public override void CheckMaturity(UnspentOutputs coins, int spendHeight) public override void CheckMaturity(UnspentOutputs coins, int spendHeight)
{ {
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
/// <summary> /// <summary>
/// A class that holds consensus errors. /// A class that holds consensus errors.
/// </summary> /// </summary>
public static class ConsensusErrors public static partial class ConsensusErrors
{ {
public static readonly ConsensusError InvalidPrevTip = new ConsensusError("invalid-prev-tip", "invalid previous tip"); public static readonly ConsensusError InvalidPrevTip = new ConsensusError("invalid-prev-tip", "invalid previous tip");
public static readonly ConsensusError HighHash = new ConsensusError("high-hash", "proof of work failed"); public static readonly ConsensusError HighHash = new ConsensusError("high-hash", "proof of work failed");
......
using Stratis.Bitcoin.Consensus; namespace Stratis.Bitcoin.Consensus
namespace Stratis.Bitcoin.Features.Consensus
{ {
public static partial class ConsensusErrors public static partial class ConsensusErrors
{ {
......
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