Commit b5070c9a authored by Sergei Zubov's avatar Sergei Zubov

Add consensus rule for fee checking

Consensus checks that fee is charged from all spent funds (not change)
and is split between miner and DeStream
parent d2b0d085
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
namespace NBitcoin
......@@ -9,5 +9,10 @@ namespace NBitcoin
{
return txInList.Where(p => p.PrevOut.Hash != uint256.Zero);
}
public static IEnumerable<uint> GetChangePointers(this TxInList txInList)
{
return txInList.Where(p => p.PrevOut.Hash == uint256.Zero).Select(p => p.PrevOut.N);
}
}
}
\ No newline at end of file
......@@ -28,6 +28,7 @@ namespace Stratis.Bitcoin.Features.Consensus
/// <param name="network">The network.</param>
public virtual int GetStakeMinConfirmations(int height, Network network)
{
return 0;
if (network.IsTest())
return height < CoinstakeMinConfirmationActivationHeightTestnet ? 10 : 20;
......
......@@ -50,7 +50,7 @@ namespace Stratis.Bitcoin.Features.Consensus
services.AddSingleton<ConsensusController>();
services.AddSingleton<ConsensusStats>();
services.AddSingleton<ConsensusSettings>();
services.AddSingleton<IConsensusRules, PowConsensusRules>();
services.AddSingleton<IConsensusRules, DeStreamPowConsensusRules>();
services
.AddSingleton<IRuleRegistration,
DeStreamPowConsensusRulesRegistration>();
......@@ -93,7 +93,7 @@ namespace Stratis.Bitcoin.Features.Consensus
services.AddSingleton<ConsensusController>();
services.AddSingleton<ConsensusStats>();
services.AddSingleton<ConsensusSettings>();
services.AddSingleton<IConsensusRules, PosConsensusRules>();
services.AddSingleton<IConsensusRules, DeStreamPosConsensusRules>();
services
.AddSingleton<IRuleRegistration,
DeStreamPosConsensusRulesRegistration>();
......@@ -134,7 +134,9 @@ namespace Stratis.Bitcoin.Features.Consensus
// rules that require the store to be loaded (coinview)
new DeStreamLoadCoinviewRule(),
new TransactionDuplicationActivationRule(), // implements BIP30
new PowCoinviewRule() // implements BIP68, MaxSigOps and BlockReward calculation
new PowCoinviewRule(), // implements BIP68, MaxSigOps and BlockReward calculation
new DeStreamBlockFeeRule()
};
}
}
......@@ -177,7 +179,8 @@ namespace Stratis.Bitcoin.Features.Consensus
// rules that require the store to be loaded (coinview)
new DeStreamLoadCoinviewRule(),
new TransactionDuplicationActivationRule(), // implements BIP30
new PosCoinviewRule() // implements BIP68, MaxSigOps and BlockReward calculation
new PosCoinviewRule(), // implements BIP68, MaxSigOps and BlockReward calculation
new DeStreamBlockFeeRule()
};
}
}
......
using System.Collections.Generic;
using NBitcoin;
using Stratis.Bitcoin.Consensus;
namespace Stratis.Bitcoin.Features.Consensus
{
/// <inheritdoc />
public class DeStreamPosRuleContext : PosRuleContext
{
internal DeStreamPosRuleContext()
{
}
public DeStreamPosRuleContext(ValidationContext validationContext, NBitcoin.Consensus consensus,
ChainedHeader consensusTip) : base(validationContext, consensus, consensusTip)
{
}
public List<Script> InputScriptPubKeys { get; set; }
public Money TotalIn { get; set; }
}
/// <inheritdoc />
public class DeStreamPowRuleContext : PowRuleContext
{
internal DeStreamPowRuleContext()
{
}
public DeStreamPowRuleContext(ValidationContext validationContext, NBitcoin.Consensus consensus,
ChainedHeader consensusTip) : base(validationContext, consensus, consensusTip)
{
}
public List<Script> InputScriptPubKeys { get; set; }
public Money TotalIn { get; set; }
}
}
\ No newline at end of file
......@@ -148,7 +148,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)
{
this.Logger.LogTrace("()");
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using Stratis.Bitcoin.Consensus.Rules;
namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
{
[ExecutionRule]
public class DeStreamBlockFeeRule : ConsensusRule
{
public override Task RunAsync(RuleContext context)
{
long actualFee = this.GetActualFee(context.ValidationContext.Block);
long expectedFee;
switch (context)
{
case DeStreamPowRuleContext deStreamPowRuleContext:
expectedFee = this.GetExpectedFee(context.ValidationContext.Block, deStreamPowRuleContext.TotalIn,
deStreamPowRuleContext.InputScriptPubKeys);
break;
case DeStreamPosRuleContext deStreamPosRuleContext:
expectedFee = this.GetExpectedFee(context.ValidationContext.Block, deStreamPosRuleContext.TotalIn,
deStreamPosRuleContext.InputScriptPubKeys);
break;
default:
throw new Exception();
}
if (Math.Abs(actualFee - expectedFee) > Money.CENT)
throw new Exception();
return Task.CompletedTask;
}
private long GetActualFee(Block block)
{
IList<TxOut> outputsToFeeWallet = block.Transactions[BlockStake.IsProofOfStake(block) ? 1 : 0].Outputs
.Where(p =>
this.Parent.Network.DeStreamWallets.Any(q =>
q == p.ScriptPubKey.GetDestinationAddress(this.Parent.Network)?.ToString())).ToList();
if (outputsToFeeWallet.Count() != 1)
throw new Exception();
return outputsToFeeWallet.Single().Value;
}
private long GetExpectedFee(Block block, Money totalIn, ICollection<Script> inputScriptPubKeys)
{
long totalFee = 0;
foreach (Transaction transaction in block.Transactions.Where(p => !p.IsCoinBase && !p.IsCoinStake))
{
IList<Script> changeScriptPubKeys = transaction.Outputs
.Select(q =>
q.ScriptPubKey)
.Intersect(inputScriptPubKeys)
.Concat(transaction.Inputs.GetChangePointers()
.Select(p => transaction.Outputs[p].ScriptPubKey))
.Distinct()
.ToList();
long feeInTransaction = this.GetFeeInTransaction(transaction, totalIn, changeScriptPubKeys);
totalFee += feeInTransaction;
}
return totalFee;
}
private long GetFeeInTransaction(Transaction transaction, Money totalIn,
IEnumerable<Script> changeScriptPubKeys)
{
double feeInTransaction = transaction.Outputs
.Where(p => !changeScriptPubKeys.Contains(p.ScriptPubKey))
.Sum(p => p.Value) * this.Parent.Network.FeeRate;
if (Math.Abs(totalIn.Satoshi - transaction.TotalOut.Satoshi - feeInTransaction) > Money.CENT)
throw new Exception();
return (long) (feeInTransaction * this.Parent.Network.DeStreamFeePart);
}
}
}
\ No newline at end of file
......@@ -75,9 +75,9 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
var txData = new PrecomputedTransactionData(tx);
for (int inputIndex = 0; inputIndex < tx.Inputs.Count; inputIndex++)
{
if(tx.Inputs[inputIndex].PrevOut.Hash == uint256.Zero)
if (tx.Inputs[inputIndex].PrevOut.Hash == uint256.Zero)
continue;
this.Parent.PerformanceCounter.AddProcessedInputs(1);
TxIn input = tx.Inputs[inputIndex];
int inputIndexCopy = inputIndex;
......@@ -87,12 +87,13 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
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);
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);
}
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;
});
......@@ -118,7 +119,9 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
ConsensusErrors.BadTransactionScriptError.Throw();
}
}
else this.Logger.LogTrace("BIP68, SigOp cost, and block reward validation skipped for block at height {0}.", index.Height);
else
this.Logger.LogTrace("BIP68, SigOp cost, and block reward validation skipped for block at height {0}.",
index.Height);
this.Logger.LogTrace("(-)");
}
......@@ -172,17 +175,18 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
this.Logger.LogTrace("(-)");
}
public override long GetTransactionSignatureOperationCost(Transaction transaction, UnspentOutputSet inputs, DeploymentFlags flags)
public override long GetTransactionSignatureOperationCost(Transaction transaction, UnspentOutputSet inputs,
DeploymentFlags flags)
{
long signatureOperationCost = this.GetLegacySignatureOperationsCount(transaction) * this.PowConsensusOptions.WitnessScaleFactor;
long signatureOperationCost = this.GetLegacySignatureOperationsCount(transaction) *
this.PowConsensusOptions.WitnessScaleFactor;
if (transaction.IsCoinBase)
return signatureOperationCost;
if (flags.ScriptFlags.HasFlag(ScriptVerify.P2SH))
{
signatureOperationCost += this.GetP2SHSignatureOperationsCount(transaction, inputs) * this.PowConsensusOptions.WitnessScaleFactor;
}
signatureOperationCost += this.GetP2SHSignatureOperationsCount(transaction, inputs) *
this.PowConsensusOptions.WitnessScaleFactor;
signatureOperationCost += (from t in transaction.Inputs.RemoveChangePointer()
let prevout = inputs.GetOutputFor(t)
......@@ -206,5 +210,31 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
return sigOps;
}
protected override void UpdateUTXOSet(RuleContext context, Transaction transaction)
{
this.Logger.LogTrace("()");
ChainedHeader index = context.ValidationContext.ChainedHeader;
UnspentOutputSet view = (context as UtxoRuleContext).UnspentOutputSet;
switch (context)
{
case DeStreamPowRuleContext deStreamPowRuleContext:
deStreamPowRuleContext.InputScriptPubKeys.AddRange(transaction.Inputs.RemoveChangePointer()
.Select(p => view.GetOutputFor(p).ScriptPubKey));
deStreamPowRuleContext.TotalIn = view.GetValueIn(transaction);
break;
case DeStreamPosRuleContext deStreamPosRuleContext:
deStreamPosRuleContext.InputScriptPubKeys.AddRange(transaction.Inputs.RemoveChangePointer()
.Select(p => view.GetOutputFor(p).ScriptPubKey));
deStreamPosRuleContext.TotalIn = view.GetValueIn(transaction);
break;
}
view.Update(transaction, index.Height);
this.Logger.LogTrace("(-)");
}
}
}
\ No newline at end of file
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NBitcoin;
using Stratis.Bitcoin.Consensus.Rules;
......@@ -18,6 +19,17 @@ namespace Stratis.Bitcoin.Features.Consensus.Rules.CommonRules
// The UTXO set is stored in the context.
this.Logger.LogTrace("Loading UTXO set of the new block.");
utxoRuleContext.UnspentOutputSet = new DeStreamUnspentOutputSet();
switch (utxoRuleContext)
{
case DeStreamPowRuleContext deStreamPowRuleContext:
deStreamPowRuleContext.InputScriptPubKeys = new List<Script>();
break;
case DeStreamPosRuleContext deStreamPosRuleContext:
deStreamPosRuleContext.InputScriptPubKeys = new List<Script>();
break;
}
using (new StopwatchDisposable(o => this.Parent.PerformanceCounter.AddUTXOFetchingTime(o)))
{
uint256[] ids = this.GetIdsToFetch(context.ValidationContext.Block, context.Flags.EnforceBIP30);
......
using Microsoft.Extensions.Logging;
using NBitcoin;
using Stratis.Bitcoin.Base.Deployments;
using Stratis.Bitcoin.BlockPulling;
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.Interfaces;
using Stratis.Bitcoin.Utilities;
namespace Stratis.Bitcoin.Features.Consensus.Rules
{
public class DeStreamPowConsensusRules : PowConsensusRules
{
public DeStreamPowConsensusRules(Network network, ILoggerFactory loggerFactory,
IDateTimeProvider dateTimeProvider, ConcurrentChain chain, NodeDeployments nodeDeployments,
ConsensusSettings consensusSettings, ICheckpoints checkpoints, CoinView utxoSet,
ILookaheadBlockPuller puller) : base(network, loggerFactory, dateTimeProvider, chain, nodeDeployments,
consensusSettings, checkpoints, utxoSet, puller)
{
}
public override RuleContext CreateRuleContext(ValidationContext validationContext, ChainedHeader consensusTip)
{
return new DeStreamPowRuleContext(validationContext, this.Network.Consensus, consensusTip);
}
}
public class DeStreamPosConsensusRules : PosConsensusRules
{
public DeStreamPosConsensusRules(Network network, ILoggerFactory loggerFactory,
IDateTimeProvider dateTimeProvider, ConcurrentChain chain, NodeDeployments nodeDeployments,
ConsensusSettings consensusSettings, ICheckpoints checkpoints, CoinView utxoSet,
ILookaheadBlockPuller puller, IStakeChain stakeChain, IStakeValidator stakeValidator) : base(network,
loggerFactory, dateTimeProvider, chain, nodeDeployments, consensusSettings, checkpoints, utxoSet, puller,
stakeChain, stakeValidator)
{
}
public override RuleContext CreateRuleContext(ValidationContext validationContext, ChainedHeader consensusTip)
{
return new DeStreamPosRuleContext(validationContext, this.Network.Consensus, consensusTip);
}
}
}
\ 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