public async Task RunAsync_ProofOfStakeBlock_TransactionTimestampAfterBlockTimeStamp_ThrowsBlockTimeBeforeTrxConsensusErrorExceptionAsync() { var transaction = this.network.CreateTransaction(); transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); this.ruleContext.ValidationContext.Block.Transactions.Add(transaction); transaction = this.network.CreateTransaction(); transaction.Inputs.Add(new TxIn() { PrevOut = new OutPoint(new uint256(15), 1), ScriptSig = new Script() }); transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); this.ruleContext.ValidationContext.Block.Transactions.Add(transaction); this.ruleContext.ValidationContext.Block.Header.Time = (uint)1483747200; this.ruleContext.ValidationContext.Block.Transactions[1].Time = (uint)1483747201; Assert.True(BlockStake.IsProofOfStake(this.ruleContext.ValidationContext.Block)); ConsensusErrorException exception = await Assert.ThrowsAsync <ConsensusErrorException>(() => this.consensusRules.RegisterRule <PosCoinstakeRule>().RunAsync(this.ruleContext)); Assert.Equal(ConsensusErrors.BlockTimeBeforeTrx, exception.ConsensusError); }
/// <inheritdoc /> protected 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)) { Money stakeReward = block.Transactions[1].TotalOut - context.Stake.TotalCoinStakeValueIn; Money calcStakeReward = fees + this.GetProofOfStakeReward(height); this.logger.LogTrace("Block stake reward is {0}, calculated reward is {1}.", stakeReward, calcStakeReward); if (stakeReward > calcStakeReward) { this.logger.LogTrace("(-)[BAD_COINSTAKE_AMOUNT]"); ConsensusErrors.BadCoinstakeAmount.Throw(); } } else { Money blockReward = fees + this.GetProofOfWorkReward(height); this.logger.LogTrace("Block reward is {0}, calculated reward is {1}.", block.Transactions[0].TotalOut, blockReward); if (block.Transactions[0].TotalOut > blockReward) { this.logger.LogTrace("(-)[BAD_COINBASE_AMOUNT]"); ConsensusErrors.BadCoinbaseAmount.Throw(); } } this.logger.LogTrace("(-)"); }
public void RunAsync_ProofOfStakeBlock_PayToPubKeyScriptPassesBlockSignatureValidation_DoesNotThrowException() { Block block = KnownNetworks.StratisMain.Consensus.ConsensusFactory.CreateBlock(); block.Transactions.Add(KnownNetworks.StratisMain.CreateTransaction()); Transaction transaction = KnownNetworks.StratisMain.CreateTransaction(); transaction.Inputs.Add(new TxIn() { PrevOut = new OutPoint(new uint256(15), 1), ScriptSig = new Script() }); transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); var scriptPubKeyOut = new Script(Op.GetPushOp(this.key.PubKey.ToBytes(true)), OpcodeType.OP_CHECKSIG); transaction.Outputs.Add(new TxOut(Money.Zero, scriptPubKeyOut)); block.Transactions.Add(transaction); ECDSASignature signature = this.key.Sign(block.GetHash()); (block as PosBlock).BlockSignature = new BlockSignature { Signature = signature.ToDER() }; this.ruleContext.ValidationContext.BlockToValidate = block; Assert.True(BlockStake.IsProofOfStake(this.ruleContext.ValidationContext.BlockToValidate)); this.consensusRules.RegisterRule <PosBlockSignatureRule>().Run(this.ruleContext); }
public void RunAsync_ProofOfStakeBlock_CoinStakePayToPubScriptKeyInvalid_ThrowsBadBlockSignatureConsensusErrorException() { Block block = KnownNetworks.StratisMain.Consensus.ConsensusFactory.CreateBlock(); block.Transactions.Add(KnownNetworks.StratisMain.CreateTransaction()); Transaction transaction = KnownNetworks.StratisMain.CreateTransaction(); transaction.Inputs.Add(new TxIn() { PrevOut = new OutPoint(new uint256(15), 1), ScriptSig = new Script() }); // use different key with PayToPubKey script transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); var scriptPubKeyOut = new Script(Op.GetPushOp(new Key().PubKey.ToBytes(true)), OpcodeType.OP_CHECKSIG); transaction.Outputs.Add(new TxOut(Money.Zero, scriptPubKeyOut)); block.Transactions.Add(transaction); ECDSASignature signature = this.key.Sign(block.GetHash()); (block as PosBlock).BlockSignature = new BlockSignature { Signature = signature.ToDER() }; this.ruleContext.ValidationContext.BlockToValidate = block; Assert.True(BlockStake.IsProofOfStake(this.ruleContext.ValidationContext.BlockToValidate)); ConsensusErrorException exception = Assert.Throws <ConsensusErrorException>(() => this.consensusRules.RegisterRule <PosBlockSignatureRule>().Run(this.ruleContext)); Assert.Equal(ConsensusErrors.BadBlockSignature, exception.ConsensusError); }
public async Task RunAsync_ProofOfStakeBlock_OpCountBelowTwo_ThrowsBadBlockSignatureConsensusErrorExceptionAsync() { var block = new Block(); block.Transactions.Add(new Transaction()); var transaction = new Transaction(); transaction.Inputs.Add(new TxIn() { PrevOut = new OutPoint(new uint256(15), 1), ScriptSig = new Script() }); transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); transaction.Outputs.Add(new TxOut(Money.Zero, new Script(new Op() { Code = OpcodeType.OP_RETURN }))); block.Transactions.Add(transaction); ECDSASignature signature = this.key.Sign(block.GetHash()); block.BlockSignatur = new BlockSignature { Signature = signature.ToDER() }; this.ruleContext.BlockValidationContext.Block = block; Assert.True(BlockStake.IsProofOfStake(this.ruleContext.BlockValidationContext.Block)); var exception = await Assert.ThrowsAsync <ConsensusErrorException>(() => this.consensusRules.RegisterRule <PosBlockSignatureRule>().RunAsync(this.ruleContext)); Assert.Equal(ConsensusErrors.BadBlockSignature, exception.ConsensusError); }
public async Task RunAsync_ProofOfStakeBlock_FirstOpInScriptPubKeyNotOP_Return_ThrowsBadBlockSignatureConsensusErrorExceptionAsync() { var block = Network.StratisMain.Consensus.ConsensusFactory.CreateBlock(); block.Transactions.Add(Network.StratisMain.Consensus.ConsensusFactory.CreateTransaction()); var transaction = Network.StratisMain.Consensus.ConsensusFactory.CreateTransaction(); transaction.Inputs.Add(new TxIn() { PrevOut = new OutPoint(new uint256(15), 1), ScriptSig = new Script() }); transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); transaction.Outputs.Add(new TxOut(Money.Zero, new Script(new Op() { Code = OpcodeType.OP_CHECKSIG }))); block.Transactions.Add(transaction); ECDSASignature signature = this.key.Sign(block.GetHash()); (block as PosBlock).BlockSignature = new BlockSignature { Signature = signature.ToDER() }; this.ruleContext.BlockValidationContext.Block = block; Assert.True(BlockStake.IsProofOfStake(this.ruleContext.BlockValidationContext.Block)); var exception = await Assert.ThrowsAsync <ConsensusErrorException>(() => this.consensusRules.RegisterRule <PosBlockSignatureRule>().RunAsync(this.ruleContext)); Assert.Equal(ConsensusErrors.BadBlockSignature, exception.ConsensusError); }
public async Task RunAsync_ProofOfStakeBlock_PayToPubKeyScriptPassesBlockSignatureValidation_DoesNotThrowExceptionAsync() { var block = new Block(); block.Transactions.Add(new Transaction()); var transaction = new Transaction(); transaction.Inputs.Add(new TxIn() { PrevOut = new OutPoint(new uint256(15), 1), ScriptSig = new Script() }); transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); Script scriptPubKeyOut = new Script(Op.GetPushOp(this.key.PubKey.ToBytes(true)), OpcodeType.OP_CHECKSIG); transaction.Outputs.Add(new TxOut(Money.Zero, scriptPubKeyOut)); block.Transactions.Add(transaction); ECDSASignature signature = this.key.Sign(block.GetHash()); block.BlockSignatur = new BlockSignature { Signature = signature.ToDER() }; this.ruleContext.BlockValidationContext.Block = block; Assert.True(BlockStake.IsProofOfStake(this.ruleContext.BlockValidationContext.Block)); await this.consensusRules.RegisterRule <PosBlockSignatureRule>().RunAsync(this.ruleContext); }
public async Task RunAsync_ProofOfStakeBlock_CoinStakePayToPubScriptKeyInvalid_ThrowsBadBlockSignatureConsensusErrorExceptionAsync() { var block = new Block(); block.Transactions.Add(new Transaction()); var transaction = new Transaction(); transaction.Inputs.Add(new TxIn() { PrevOut = new OutPoint(new uint256(15), 1), ScriptSig = new Script() }); // use different key with PayToPubKey script transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); Script scriptPubKeyOut = new Script(Op.GetPushOp(new Key().PubKey.ToBytes(true)), OpcodeType.OP_CHECKSIG); transaction.Outputs.Add(new TxOut(Money.Zero, scriptPubKeyOut)); block.Transactions.Add(transaction); ECDSASignature signature = this.key.Sign(block.GetHash()); block.BlockSignatur = new BlockSignature { Signature = signature.ToDER() }; this.ruleContext.BlockValidationContext.Block = block; Assert.True(BlockStake.IsProofOfStake(this.ruleContext.BlockValidationContext.Block)); var exception = await Assert.ThrowsAsync <ConsensusErrorException>(() => this.consensusRules.RegisterRule <PosBlockSignatureRule>().RunAsync(this.ruleContext)); Assert.Equal(ConsensusErrors.BadBlockSignature, exception.ConsensusError); }
public override Task RunAsync(RuleContext context) { if (context.SkipValidation) { return(Task.CompletedTask); } // Check consistency of ChainedHeader height and the height written in the coinbase tx var newHeight = GetHeightOfBlockToValidateSafe(context); // Get the algorithm of the block we are looking at bool isProofOfStake = BlockStake.IsProofOfStake(context.ValidationContext.BlockToValidate); // Check if there is a rule active, and if so, check if the algorithm is allowed at this height if (this.posConsensusOptions.IsAlgorithmAllowed(isProofOfStake, newHeight)) { // yes, rule passed return(Task.CompletedTask); } // no, this block is not acceptable this.Logger.LogTrace("(-)[BAD-POS-POW-RATCHET-SEQUENCE]"); X1ConsensusErrors.BadPosPowRatchetSequence.Throw(); return(Task.CompletedTask); }
public override void CheckBlockReward(ContextInformation context, Money nFees, ChainedBlock chainedBlock, Block block) { this.logger.LogTrace("({0}:{1},{2}:'{3}')", nameof(nFees), nFees, nameof(chainedBlock), chainedBlock); if (BlockStake.IsProofOfStake(block)) { // proof of stake invalidates previous inputs // and spends the inputs to new outputs with the // additional stake reward, next calculate the // reward does not exceed the consensus rules Money stakeReward = block.Transactions[1].TotalOut - context.Stake.TotalCoinStakeValueIn; Money calcStakeReward = nFees + this.GetProofOfStakeReward(chainedBlock.Height); this.logger.LogTrace("Block stake reward is {0}, calculated reward is {1}.", stakeReward, calcStakeReward); if (stakeReward > calcStakeReward) { this.logger.LogTrace("(-)[BAD_COINSTAKE_AMOUNT]"); ConsensusErrors.BadCoinstakeAmount.Throw(); } } else { Money blockReward = nFees + this.GetProofOfWorkReward(chainedBlock.Height); this.logger.LogTrace("Block reward is {0}, calculated reward is {1}.", block.Transactions[0].TotalOut, blockReward); if (block.Transactions[0].TotalOut > blockReward) { this.logger.LogTrace("(-)[BAD_COINBASE_AMOUNT]"); ConsensusErrors.BadCoinbaseAmount.Throw(); } } this.logger.LogTrace("(-)"); }
public async Task RunAsync_ProofOfStakeBlock_ValidBlock_DoesNotThrowExceptionAsync() { var transaction = this.network.CreateTransaction(); transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); this.ruleContext.ValidationContext.Block.Transactions.Add(transaction); transaction = this.network.CreateTransaction(); transaction.Inputs.Add(new TxIn() { PrevOut = new OutPoint(new uint256(15), 1), ScriptSig = new Script() }); transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); this.ruleContext.ValidationContext.Block.Transactions.Add(transaction); this.ruleContext.ValidationContext.Block.Header.Time = (uint)1483747200; this.ruleContext.ValidationContext.Block.Transactions[0].Time = (uint)1483747200; this.ruleContext.ValidationContext.Block.Transactions[1].Time = (uint)1483747200; Assert.True(BlockStake.IsProofOfStake(this.ruleContext.ValidationContext.Block)); await this.consensusRules.RegisterRule <PosCoinstakeRule>().RunAsync(this.ruleContext); }
public override void CheckBlockReward(ContextInformation context, Money nFees, ChainedBlock chainedBlock, Block block) { if (BlockStake.IsProofOfStake(block)) { // proof of stake invalidates previous inputs // and spends the inputs to new outputs with the // additional stake reward, next calculate the // reward does not exceed the consensus rules var stakeReward = block.Transactions[1].TotalOut - context.Stake.TotalCoinStakeValueIn; var calcStakeReward = nFees + GetProofOfStakeReward(chainedBlock.Height); if (stakeReward > calcStakeReward) { ConsensusErrors.BadCoinstakeAmount.Throw(); } } else { var blockReward = nFees + GetProofOfWorkReward(chainedBlock.Height); if (block.Transactions[0].TotalOut > blockReward) { ConsensusErrors.BadCoinbaseAmount.Throw(); } } }
/// <inheritdoc /> public override void CheckBlockReward(RuleContext context, Money fees, int height, Block block) { if (BlockStake.IsProofOfStake(block)) { var posRuleContext = context as PosRuleContext; Money stakeReward = block.Transactions[1].TotalOut - posRuleContext.TotalCoinStakeValueIn; Money calcStakeReward = fees + this.GetProofOfStakeReward(height); this.Logger.LogTrace("Block stake reward is {0}, calculated reward is {1}.", stakeReward, calcStakeReward); if (stakeReward > calcStakeReward) { this.Logger.LogTrace("(-)[BAD_COINSTAKE_AMOUNT]"); ConsensusErrors.BadCoinstakeAmount.Throw(); } } else { Money blockReward = fees + this.GetProofOfWorkReward(height); this.Logger.LogTrace("Block reward is {0}, calculated reward is {1}.", block.Transactions[0].TotalOut, blockReward); if (block.Transactions[0].TotalOut > blockReward) { this.Logger.LogTrace("(-)[BAD_COINBASE_AMOUNT]"); ConsensusErrors.BadCoinbaseAmount.Throw(); } } }
public async Task RunAsync_ProofOfStakeBlock_ScriptKeyPassesBlockSignatureValidation_DoesNotThrowExceptionAsync() { var block = Network.StratisMain.Consensus.ConsensusFactory.CreateBlock(); block.Transactions.Add(Network.StratisMain.Consensus.ConsensusFactory.CreateTransaction()); var transaction = Network.StratisMain.Consensus.ConsensusFactory.CreateTransaction(); transaction.Inputs.Add(new TxIn() { PrevOut = new OutPoint(new uint256(15), 1), ScriptSig = new Script() }); transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); // push op_return to note external dependancy in front of pay to pubkey script so it does not match pay to pubkey template. Script scriptPubKeyOut = new Script(OpcodeType.OP_RETURN, Op.GetPushOp(this.key.PubKey.ToBytes(true)), OpcodeType.OP_CHECKSIG); transaction.Outputs.Add(new TxOut(Money.Zero, scriptPubKeyOut)); block.Transactions.Add(transaction); ECDSASignature signature = this.key.Sign(block.GetHash()); (block as PosBlock).BlockSignature = new BlockSignature { Signature = signature.ToDER() }; this.ruleContext.BlockValidationContext.Block = block; Assert.True(BlockStake.IsProofOfStake(this.ruleContext.BlockValidationContext.Block)); await this.consensusRules.RegisterRule <PosBlockSignatureRule>().RunAsync(this.ruleContext); }
public void RunAsync_ProofOfStakeBlock_NoOpsInScriptPubKey_ThrowsBadBlockSignatureConsensusErrorException() { Block block = KnownNetworks.StratisMain.Consensus.ConsensusFactory.CreateBlock(); block.Transactions.Add(KnownNetworks.StratisMain.CreateTransaction()); Transaction transaction = KnownNetworks.StratisMain.CreateTransaction(); transaction.Inputs.Add(new TxIn() { PrevOut = new OutPoint(new uint256(15), 1), ScriptSig = new Script() }); transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); transaction.Outputs.Add(new TxOut(Money.Zero, new Script())); block.Transactions.Add(transaction); ECDSASignature signature = this.key.Sign(block.GetHash()); (block as PosBlock).BlockSignature = new BlockSignature { Signature = signature.ToDER() }; this.ruleContext.ValidationContext.BlockToValidate = block; Assert.True(BlockStake.IsProofOfStake(this.ruleContext.ValidationContext.BlockToValidate)); ConsensusErrorException exception = Assert.Throws <ConsensusErrorException>(() => this.consensusRules.RegisterRule <PosBlockSignatureRule>().Run(this.ruleContext)); Assert.Equal(ConsensusErrors.BadBlockSignature, exception.ConsensusError); }
public override void CheckBlock(ContextInformation context) { this.logger.LogTrace("()"); base.CheckBlock(context); Block block = context.BlockResult.Block; // Check timestamp. if (block.Header.Time > this.FutureDrift(this.dateTimeProvider.GetAdjustedTimeAsUnixTimestamp())) { this.logger.LogTrace("(-)[TIME_TOO_FAR]"); ConsensusErrors.BlockTimestampTooFar.Throw(); } if (BlockStake.IsProofOfStake(block)) { // Coinbase output should be empty if proof-of-stake block. if ((block.Transactions[0].Outputs.Count != 1) || !block.Transactions[0].Outputs[0].IsEmpty) { this.logger.LogTrace("(-)[COINBASE_NOT_EMPTY]"); ConsensusErrors.BadStakeBlock.Throw(); } // Second transaction must be coinstake, the rest must not be. if (!block.Transactions[1].IsCoinStake) { this.logger.LogTrace("(-)[NO_COINSTAKE]"); ConsensusErrors.BadStakeBlock.Throw(); } if (block.Transactions.Skip(2).Any(t => t.IsCoinStake)) { this.logger.LogTrace("(-)[MULTIPLE_COINSTAKE]"); ConsensusErrors.BadMultipleCoinstake.Throw(); } } // Check proof-of-stake block signature. if (!CheckBlockSignature(block)) { this.logger.LogTrace("(-)[BAD_SIGNATURE]"); ConsensusErrors.BadBlockSignature.Throw(); } // Check transactions. foreach (Transaction transaction in block.Transactions) { // Check transaction timestamp. if (block.Header.Time < transaction.Time) { this.logger.LogTrace("Block contains transaction with timestamp {0}, which is greater than block's timestamp {1}.", transaction.Time, block.Header.Time); this.logger.LogTrace("(-)[TX_TIME_MISMATCH]"); ConsensusErrors.BlockTimeBeforeTrx.Throw(); } } this.logger.LogTrace("(-)[OK]"); }
/// <summary> /// This helper creates a coin staking block containing a coin staking transaction built according to /// the parameters and with valid first input and output. The block signature is created correctly with the /// private key corresponding to the public key. /// </summary> /// <param name="useCompressedKey">Determines whether the second transaction output will include a compressed /// (versus uncompressed) public key.</param> /// <param name="includeSecondPush">Determines whether the second transaction output will include a small integer /// after the public key.</param> /// <param name="expectFailure">Determines whether we expect failure (versus success).</param> private void ProofOfStakeBlock_CoinStakeTestHelper(bool useCompressedKey, bool includeSecondPush, bool expectFailure) { Block block = KnownNetworks.StratisMain.Consensus.ConsensusFactory.CreateBlock(); // Add a dummy coinbase transaction. block.Transactions.Add(KnownNetworks.StratisMain.CreateTransaction()); // Build a coinstake transaction. Transaction coinStakeTransaction = KnownNetworks.StratisMain.CreateTransaction(); coinStakeTransaction.Inputs.Add(new TxIn() { PrevOut = new OutPoint(new uint256(15), 1), ScriptSig = new Script() }); // First output of coinstake transaction is a special marker. coinStakeTransaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); // Second (unspendable) output. // Depending on the test case use either a compressed public key or an uncompressed public key. var pubKey = useCompressedKey ? this.key.PubKey.Compress() : this.key.PubKey.Decompress(); var opCodes = new List <Op> { OpcodeType.OP_RETURN, Op.GetPushOp(pubKey.ToBytes(true)) }; // Depending on the test case add a second push of some small integer. if (includeSecondPush) { opCodes.Add(Op.GetPushOp(new byte[] { 123 })); } coinStakeTransaction.Outputs.Add(new TxOut(Money.Zero, new Script(opCodes))); // Add the coinstake transaction. block.Transactions.Add(coinStakeTransaction); // Add a signature to the block. ECDSASignature signature = this.key.Sign(block.GetHash()); (block as PosBlock).BlockSignature = new BlockSignature { Signature = signature.ToDER() }; // Execute the PosBlockSignatureRule. this.ruleContext.ValidationContext.BlockToValidate = block; Assert.True(BlockStake.IsProofOfStake(this.ruleContext.ValidationContext.BlockToValidate)); if (expectFailure) { ConsensusErrorException exception = Assert.Throws <ConsensusErrorException>(() => this.consensusRules.RegisterRule <PosBlockSignatureRule>().Run(this.ruleContext)); Assert.Equal(ConsensusErrors.BadBlockSignature, exception.ConsensusError); return; } this.consensusRules.RegisterRule <PosBlockSignatureRule>().Run(this.ruleContext); }
private void OnBlockMined(MineBlockContext context) { this.logger.LogInformation("Mined new {0} block: '{1}'.", BlockStake.IsProofOfStake(context.ChainedHeaderBlock.Block) ? "POS" : "POW", context.ChainedHeaderBlock.ChainedHeader); context.CurrentHeight++; context.Blocks.Add(context.BlockTemplate.Block.GetHash()); context.BlockTemplate = null; }
/// <inheritdoc /> /// <exception cref="ConsensusErrors.BadStakeBlock">The coinbase output (first transaction) is not empty.</exception> /// <exception cref="ConsensusErrors.BadStakeBlock">The second transaction is not a coinstake transaction.</exception> /// <exception cref="ConsensusErrors.BadMultipleCoinstake">There are multiple coinstake tranasctions in the block.</exception> /// <exception cref="ConsensusErrors.BlockTimeBeforeTrx">The block contains a transaction with a timestamp after the block timestamp.</exception> public override Task RunAsync(RuleContext context) { if (context.SkipValidation) { return(Task.CompletedTask); } Block block = context.ValidationContext.BlockToValidate; // Check if the block was produced using POS. if (BlockStake.IsProofOfStake(block)) { // Coinbase output should be empty if proof-of-stake block. if ((block.Transactions[0].Outputs.Count != 1) || (!block.Transactions[0].Outputs[0].IsEmpty)) { if (this.PosParent.Network.Consensus.PosEmptyCoinbase) { this.Logger.LogTrace("(-)[COINBASE_NOT_EMPTY]"); ConsensusErrors.BadStakeBlock.Throw(); } // First output must be empty. if ((!block.Transactions[0].Outputs[0].IsEmpty)) { this.Logger.LogTrace("(-)[COINBASE_NOT_EMPTY]"); ConsensusErrors.BadStakeBlock.Throw(); } // Check that the rest of the outputs are not spendable (op_return) foreach (TxOut txOut in block.Transactions[0].Outputs.Skip(1)) { // Only op_return are allowed in coinbase. if (!txOut.ScriptPubKey.IsUnspendable) { this.Logger.LogTrace("(-)[COINBASE_SPENDABLE]"); ConsensusErrors.BadStakeBlock.Throw(); } } } // Second transaction must be coinstake, the rest must not be. if (!block.Transactions[1].IsCoinStake) { this.Logger.LogTrace("(-)[NO_COINSTAKE]"); ConsensusErrors.BadStakeBlock.Throw(); } if (block.Transactions.Skip(2).Any(t => t.IsCoinStake)) { this.Logger.LogTrace("(-)[MULTIPLE_COINSTAKE]"); ConsensusErrors.BadMultipleCoinstake.Throw(); } } return(Task.CompletedTask); }
/// <inheritdoc /> public override void CheckBlockReward(RuleContext context, Money fees, int height, Block block) { // Currently this rule only applies to PoS blocks if (BlockStake.IsProofOfStake(block)) { var posRuleContext = context as PosRuleContext; Transaction coinstake = block.Transactions[1]; Money stakeReward = coinstake.TotalOut - posRuleContext.TotalCoinStakeValueIn; Money calcStakeReward = fees + this.GetProofOfStakeReward(height); this.Logger.LogDebug("Block stake reward is {0}, calculated reward is {1}.", stakeReward, calcStakeReward); if (stakeReward > calcStakeReward) { this.Logger.LogTrace("(-)[BAD_COINSTAKE_AMOUNT]"); ConsensusErrors.BadCoinstakeAmount.Throw(); } // Compute the total reward amount sent to the reward script. // We only mandate that at least x% of the reward is sent there, there are no other constraints on what gets done with the rest of the reward. Money rewardScriptTotal = Money.Coins(0.0m); foreach (TxOut output in coinstake.Outputs) { // TODO: Double check which rule we have the negative output (and overflow) amount check inside; we assume that has been done before this check if (output.ScriptPubKey == StraxCoinstakeRule.CirrusRewardScript) { rewardScriptTotal += output.Value; } } // It must be CirrusRewardPercentage of the maximum possible reward precisely. // This additionally protects cold staking transactions from over-allocating to the Cirrus reward script at the expense of the non-Cirrus reward. // This means that the hot key can be used for staking by anybody and they will not be able to redirect the non-Cirrus reward to the Cirrus script. // It must additionally not be possible to short-change the Cirrus reward script by deliberately sacrificing part of the overall claimed reward. // TODO: Create a distinct consensus error for this? if ((calcStakeReward * CirrusRewardPercentage / 100) != rewardScriptTotal) { this.Logger.LogTrace("(-)[BAD_COINSTAKE_REWARD_SCRIPT_AMOUNT]"); ConsensusErrors.BadCirrusRewardAmount.Throw(); } // TODO: Perhaps we should limit it to a single output to prevent unnecessary UTXO set bloating } else { Money blockReward = fees + this.GetProofOfWorkReward(height); this.Logger.LogDebug("Block reward is {0}, calculated reward is {1}.", block.Transactions[0].TotalOut, blockReward); if (block.Transactions[0].TotalOut > blockReward) { this.Logger.LogTrace("(-)[BAD_COINBASE_AMOUNT]"); ConsensusErrors.BadCoinbaseAmount.Throw(); } // TODO: Should the reward split apply to blocks in the POW phase of the network too? } }
/// <inheritdoc/> public ChainedHeader GetLastPowPosChainedBlock(IStakeChain stakeChain, ChainedHeader startChainedHeader, bool proofOfStake) { Guard.Assert(startChainedHeader != null); BlockStake blockStake = stakeChain.Get(startChainedHeader.HashBlock); while ((startChainedHeader.Previous != null) && (blockStake.IsProofOfStake() != proofOfStake)) { startChainedHeader = startChainedHeader.Previous; blockStake = stakeChain.Get(startChainedHeader.HashBlock); } return(startChainedHeader); }
public override void CheckBlock(ContextInformation context) { base.CheckBlock(context); var block = context.BlockResult.Block; // Check timestamp if (block.Header.Time > FutureDriftV2(DateTime.UtcNow.Ticks)) { ConsensusErrors.BlockTimestampTooFar.Throw(); } if (BlockStake.IsProofOfStake(block)) { // Coinbase output should be empty if proof-of-stake block if (block.Transactions[0].Outputs.Count != 1 || !block.Transactions[0].Outputs[0].IsEmpty) { ConsensusErrors.BadStakeBlock.Throw(); } // Second transaction must be coinstake, the rest must not be if (!block.Transactions[1].IsCoinStake) { ConsensusErrors.BadStakeBlock.Throw(); } if (block.Transactions.Skip(2).Any(t => t.IsCoinStake)) { ConsensusErrors.BadMultipleCoinstake.Throw(); } } // Check proof-of-stake block signature if (!CheckBlockSignature(block)) { ConsensusErrors.BadBlockSignature.Throw(); } // Check transactions foreach (var transaction in block.Transactions) { // check transaction timestamp if (block.Header.Time < transaction.Time) { ConsensusErrors.BlockTimeBeforeTrx.Throw(); } } }
/// <inheritdoc /> public void BlockModified(ChainedHeader chainTip, Block block) { if (this.network.Consensus.IsProofOfStake) { if (BlockStake.IsProofOfStake(block)) { this.posBlockDefinition.BlockModified(chainTip, block); } else { this.posPowBlockDefinition.BlockModified(chainTip, block); } } this.powBlockDefinition.BlockModified(chainTip, block); }
/// <inheritdoc /> /// <exception cref="ConsensusErrors.BadStakeBlock">The coinbase output (first transaction) is not empty.</exception> /// <exception cref="ConsensusErrors.BadStakeBlock">The second transaction is not a coinstake transaction.</exception> /// <exception cref="ConsensusErrors.BadMultipleCoinstake">There are multiple coinstake tranasctions in the block.</exception> /// <exception cref="ConsensusErrors.BlockTimeBeforeTrx">The block contains a transaction with a timestamp after the block timestamp.</exception> public override Task RunAsync(RuleContext context) { if (context.SkipValidation) { return(Task.CompletedTask); } Block block = context.ValidationContext.BlockToValidate; if (BlockStake.IsProofOfStake(block)) { // Coinbase output should be empty if proof-of-stake block. if ((block.Transactions[0].Outputs.Count != 1) || !block.Transactions[0].Outputs[0].IsEmpty) { this.Logger.LogTrace("(-)[COINBASE_NOT_EMPTY]"); ConsensusErrors.BadStakeBlock.Throw(); } // Second transaction must be coinstake, the rest must not be. if (!block.Transactions[1].IsCoinStake) { this.Logger.LogTrace("(-)[NO_COINSTAKE]"); ConsensusErrors.BadStakeBlock.Throw(); } if (block.Transactions.Skip(2).Any(t => t.IsCoinStake)) { this.Logger.LogTrace("(-)[MULTIPLE_COINSTAKE]"); ConsensusErrors.BadMultipleCoinstake.Throw(); } } // Check transactions. foreach (Transaction transaction in block.Transactions) { // Check transaction timestamp. if (block.Header.Time < transaction.Time) { this.Logger.LogDebug("Block contains transaction with timestamp {0}, which is greater than block's timestamp {1}.", transaction.Time, block.Header.Time); this.Logger.LogTrace("(-)[TX_TIME_MISMATCH]"); ConsensusErrors.BlockTimeBeforeTrx.Throw(); } } return(Task.CompletedTask); }
/// <inheritdoc/> public ChainedHeader GetLastPowPosChainedBlock(IStakeChain stakeChain, ChainedHeader startChainedHeader, bool proofOfStake) { Guard.Assert(startChainedHeader != null); this.logger.LogTrace("({0}:'{1}',{2}:{3})", nameof(startChainedHeader), startChainedHeader, nameof(proofOfStake), proofOfStake); BlockStake blockStake = stakeChain.Get(startChainedHeader.HashBlock); while ((startChainedHeader.Previous != null) && (blockStake.IsProofOfStake() != proofOfStake)) { startChainedHeader = startChainedHeader.Previous; blockStake = stakeChain.Get(startChainedHeader.HashBlock); } this.logger.LogTrace("(-)':{0}'", startChainedHeader); return(startChainedHeader); }
public void CheckAndComputeStake(ContextInformation context) { var pindex = context.BlockResult.ChainedBlock; var block = context.BlockResult.Block; var blockStake = context.Stake.BlockStake; // Verify hash target and signature of coinstake tx if (BlockStake.IsProofOfStake(block)) { var pindexPrev = pindex.Previous; var prevBlockStake = this.stakeChain.Get(pindexPrev.HashBlock); if (prevBlockStake == null) { ConsensusErrors.PrevStakeNull.Throw(); } this.stakeValidator.CheckProofOfStake(context, pindexPrev, prevBlockStake, block.Transactions[1], pindex.Header.Bits.ToCompact()); } // PoW is checked in CheckBlock() if (BlockStake.IsProofOfWork(block)) { context.Stake.HashProofOfStake = pindex.Header.GetPoWHash(); } // TODO: is this the same as chain work? // compute chain trust score //pindexNew.nChainTrust = (pindexNew->pprev ? pindexNew->pprev->nChainTrust : 0) + pindexNew->GetBlockTrust(); // compute stake entropy bit for stake modifier if (!blockStake.SetStakeEntropyBit(blockStake.GetStakeEntropyBit())) { ConsensusErrors.SetStakeEntropyBitFailed.Throw(); } // Record proof hash value blockStake.HashProof = context.Stake.HashProofOfStake; // compute stake modifier this.stakeValidator.ComputeStakeModifier(this.chain, pindex, blockStake); }
public static ChainedBlock GetLastBlockIndex(StakeChain stakeChain, ChainedBlock index, bool proofOfStake) { if (index == null) { throw new ArgumentNullException(nameof(index)); } clogger.LogTrace("({0}:'{1}',{2}:{3})", nameof(index), index, nameof(proofOfStake), proofOfStake); BlockStake blockStake = stakeChain.Get(index.HashBlock); while ((index.Previous != null) && (blockStake.IsProofOfStake() != proofOfStake)) { index = index.Previous; blockStake = stakeChain.Get(index.HashBlock); } clogger.LogTrace("(-)':{0}'", index); return(index); }
public async Task RunAsync_ProofOfStakeBlock_CoinBaseNotEmpty_NoOutputsOnTransaction_ThrowsBadStakeBlockConsensusErrorExceptionAsync() { this.ruleContext.ValidationContext.BlockToValidate.Transactions.Add(new Transaction()); var transaction = this.network.CreateTransaction(); transaction.Inputs.Add(new TxIn() { PrevOut = new OutPoint(new uint256(15), 1), ScriptSig = new Script() }); transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); this.ruleContext.ValidationContext.BlockToValidate.Transactions.Add(transaction); Assert.True(BlockStake.IsProofOfStake(this.ruleContext.ValidationContext.BlockToValidate)); ConsensusErrorException exception = await Assert.ThrowsAsync <ConsensusErrorException>(() => this.consensusRules.RegisterRule <StraxCoinstakeRule>().RunAsync(this.ruleContext)); Assert.Equal(ConsensusErrors.BadStakeBlock, exception.ConsensusError); }
public void RunAsync_ProofOfStakeBlockSignatureEmpty_ThrowsBadBlockSignatureConsensusErrorException() { this.ruleContext.ValidationContext.BlockToValidate = KnownNetworks.StratisMain.Consensus.ConsensusFactory.CreateBlock(); this.ruleContext.ValidationContext.BlockToValidate.Transactions.Add(KnownNetworks.StratisMain.CreateTransaction()); Transaction transaction = KnownNetworks.StratisMain.CreateTransaction(); transaction.Inputs.Add(new TxIn() { PrevOut = new OutPoint(new uint256(15), 1), ScriptSig = new Script() }); transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); this.ruleContext.ValidationContext.BlockToValidate.Transactions.Add(transaction); Assert.True(BlockStake.IsProofOfStake(this.ruleContext.ValidationContext.BlockToValidate)); ConsensusErrorException exception = Assert.Throws <ConsensusErrorException>(() => this.consensusRules.RegisterRule <PosBlockSignatureRule>().Run(this.ruleContext)); Assert.Equal(ConsensusErrors.BadBlockSignature, exception.ConsensusError); }
public async Task RunAsync_ProofOfStakeBlockSignatureEmpty_ThrowsBadBlockSignatureConsensusErrorExceptionAsync() { this.ruleContext.BlockValidationContext.Block = new Block(); this.ruleContext.BlockValidationContext.Block.Transactions.Add(new Transaction()); var transaction = new Transaction(); transaction.Inputs.Add(new TxIn() { PrevOut = new OutPoint(new uint256(15), 1), ScriptSig = new Script() }); transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); transaction.Outputs.Add(new TxOut(Money.Zero, (IDestination)null)); this.ruleContext.BlockValidationContext.Block.Transactions.Add(transaction); Assert.True(BlockStake.IsProofOfStake(this.ruleContext.BlockValidationContext.Block)); var exception = await Assert.ThrowsAsync <ConsensusErrorException>(() => this.consensusRules.RegisterRule <PosBlockSignatureRule>().RunAsync(this.ruleContext)); Assert.Equal(ConsensusErrors.BadBlockSignature, exception.ConsensusError); }