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

Merge coinview rule

parent 55c66483
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
......@@ -147,7 +147,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
/// </summary>
/// <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>
protected void UpdateUTXOSet(RuleContext context, Transaction transaction)
protected virtual void UpdateUTXOSet(RuleContext context, Transaction transaction)
{
ChainedHeader index = context.ValidationContext.ChainedHeaderToValidate;
UnspentOutputSet view = (context as UtxoRuleContext).UnspentOutputSet;
......@@ -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.BadTransactionNegativeFee">Thrown if fees sum is negative.</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))
ConsensusErrors.BadTransactionMissingInput.Throw();
......@@ -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="flags">Script verification flags.</param>
/// <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;
......@@ -308,7 +308,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
/// <param name="witness">Witness script.</param>
/// <param name="flags">Script verification flags.</param>
/// <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;
if (!flags.ScriptFlags.HasFlag(ScriptVerify.Witness))
......@@ -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="inputs">Map of previous transactions that have outputs we're spending.</param>
/// <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)
return 0;
......@@ -358,7 +358,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
/// </summary>
/// <param name="transaction">Transaction for which we are computing the cost.</param>
/// <returns>Legacy signature operation cost for transaction.</returns>
private long GetLegacySignatureOperationsCount(Transaction transaction)
protected long GetLegacySignatureOperationsCount(Transaction transaction)
{
long sigOps = 0;
foreach (TxIn txin in transaction.Inputs)
......@@ -375,7 +375,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
/// </summary>
/// <param name="value">The value to be checked.</param>
/// <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));
}
......
using System.Threading.Tasks;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NBitcoin;
using Stratis.Bitcoin.Consensus;
using Stratis.Bitcoin.Consensus.Rules;
using Stratis.Bitcoin.Features.Consensus.Interfaces;
using Stratis.Bitcoin.Utilities;
......@@ -10,7 +12,6 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
/// <summary>
/// Proof of stake override for the coinview rules - BIP68, MaxSigOps and BlockReward checks.
/// </summary>
[ExecutionRule]
public sealed class DeStreamPosCoinviewRule : DeStreamCoinViewRule
{
/// <summary>Provides functionality for checking validity of PoS blocks.</summary>
......@@ -20,37 +21,29 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
private IStakeChain stakeChain;
/// <summary>The consensus of the parent Network.</summary>
private NBitcoin.Consensus consensus;
private IConsensus consensus;
/// <inheritdoc />
public override void Initialize()
{
this.Logger.LogTrace("()");
base.Initialize();
this.consensus = this.Parent.Network.Consensus;
var consensusRules = (PosConsensusRules)this.Parent;
var consensusRules = (PosConsensusRuleEngine)this.Parent;
this.stakeValidator = consensusRules.StakeValidator;
this.stakeChain = consensusRules.StakeChain;
this.Logger.LogTrace("(-)");
}
/// <inheritdoc />
/// <summary>Compute and store the stake proofs.</summary>
public override async Task RunAsync(RuleContext context)
{
this.Logger.LogTrace("()");
this.CheckAndComputeStake(context);
await base.RunAsync(context).ConfigureAwait(false);
var posRuleContext = context as PosRuleContext;
await this.stakeChain.SetAsync(context.ValidationContext.ChainedHeader, posRuleContext.BlockStake).ConfigureAwait(false);
this.Logger.LogTrace("(-)");
await this.stakeChain.SetAsync(context.ValidationContext.ChainedHeaderToValidate, posRuleContext.BlockStake).ConfigureAwait(false);
}
/// <inheritdoc/>
......@@ -62,8 +55,6 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
/// <inheritdoc />
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))
{
var posRuleContext = context as PosRuleContext;
......@@ -87,32 +78,27 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
ConsensusErrors.BadCoinbaseAmount.Throw();
}
}
this.Logger.LogTrace("(-)");
}
/// <inheritdoc />
public override void UpdateCoinView(RuleContext context, Transaction transaction)
{
this.Logger.LogTrace("()");
var posRuleContext = context as PosRuleContext;
UnspentOutputSet view = posRuleContext.UnspentOutputSet;
if (transaction.IsCoinStake)
{
posRuleContext.TotalCoinStakeValueIn = view.GetValueIn(transaction);
posRuleContext.CoinStakePrevOutputs = transaction.Inputs.ToDictionary(txin => txin, txin => view.GetOutputFor(txin));
}
base.UpdateUTXOSet(context, transaction);
this.Logger.LogTrace("(-)");
}
/// <inheritdoc />
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);
if (coins.IsCoinstake)
......@@ -124,8 +110,6 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
ConsensusErrors.BadTransactionPrematureCoinstakeSpending.Throw();
}
}
this.Logger.LogTrace("(-)");
}
/// <summary>
......@@ -136,11 +120,13 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
/// <exception cref="ConsensusErrors.SetStakeEntropyBitFailed">Thrown if failed to set stake entropy bit.</exception>
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;
if (posRuleContext.BlockStake == null)
posRuleContext.BlockStake = BlockStake.Load(context.ValidationContext.BlockToValidate);
BlockStake blockStake = posRuleContext.BlockStake;
// Verify hash target and signature of coinstake tx.
......@@ -180,7 +166,7 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
// Compute stake modifier.
ChainedHeader prevChainedHeader = chainedHeader.Previous;
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)
{
......@@ -190,8 +176,6 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
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);
this.Logger.LogTrace("(-)[OK]");
}
/// <inheritdoc />
......
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NBitcoin;
using Stratis.Bitcoin.Consensus;
using Stratis.Bitcoin.Consensus.Rules;
using Stratis.Bitcoin.Utilities;
namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
{
/// <inheritdoc />
[ExecutionRule]
public sealed class DeStreamPowCoinviewRule : DeStreamCoinViewRule
{
/// <summary>Consensus parameters.</summary>
private NBitcoin.Consensus consensus;
private IConsensus consensus;
/// <inheritdoc />
public override void Initialize()
{
this.Logger.LogTrace("()");
base.Initialize();
this.consensus = this.Parent.Network.Consensus;
this.Logger.LogTrace("(-)");
}
/// <inheritdoc/>
......@@ -34,16 +30,12 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
/// <inheritdoc/>
public override void CheckBlockReward(RuleContext context, Money fees, int height, Block block)
{
this.Logger.LogTrace("()");
Money blockReward = fees + this.GetProofOfWorkReward(height);
if (block.Transactions[0].TotalOut > blockReward)
{
this.Logger.LogTrace("(-)[BAD_COINBASE_AMOUNT]");
ConsensusErrors.BadCoinbaseAmount.Throw();
}
this.Logger.LogTrace("(-)");
}
/// <inheritdoc/>
......@@ -68,6 +60,28 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
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/>
public override void CheckMaturity(UnspentOutputs coins, int spendHeight)
{
......
......@@ -3,7 +3,7 @@
/// <summary>
/// A class that holds consensus errors.
/// </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 HighHash = new ConsensusError("high-hash", "proof of work failed");
......
using Stratis.Bitcoin.Consensus;
namespace Stratis.Bitcoin.Features.Consensus
namespace Stratis.Bitcoin.Consensus
{
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