public async Task MinerCreateBlockAbsoluteHeightLockedAsync() { var context = new TestContext(); await context.InitializeAsync(); var tx = new Transaction(); tx.AddInput(new TxIn()); tx.AddOutput(new TxOut()); Transaction.LockTimeFlags flags = Transaction.LockTimeFlags.VerifySequence | Transaction.LockTimeFlags.MedianTimePast; int MedianTimeSpan = 11; var prevheights = new List <int>(); prevheights.Add(1); tx.Version = 2; tx.Inputs[0].PrevOut.Hash = context.txFirst[1].GetHash(); tx.Inputs[0].ScriptSig = new Script(OpcodeType.OP_1); tx.Inputs[0].PrevOut.N = 0; tx.Inputs[0].Sequence = new Sequence(TimeSpan.FromMinutes(10)); // txFirst[1] is the 3rd block tx.Outputs[0].Value = context.BLOCKSUBSIDY - context.HIGHFEE; tx.Outputs[0].ScriptPubKey = new Script(OpcodeType.OP_1); tx.LockTime = 0; prevheights[0] = context.baseheight + 2; for (int i = 0; i < MedianTimeSpan; i++) { context.chain.SetTip(new BlockHeader { HashPrevBlock = context.chain.Tip.HashBlock, Time = Utils.DateTimeToUnixTime(context.chain.Tip.GetMedianTimePast()) + 512 }); } SequenceLock locks = (tx.CalculateSequenceLocks(prevheights.ToArray(), context.chain.Tip, flags)); Assert.True(locks.Evaluate(context.chain.Tip)); context = new TestContext(); await context.InitializeAsync(); // absolute height locked tx.Inputs[0].PrevOut.Hash = context.txFirst[2].GetHash(); tx.Inputs[0].Sequence = Sequence.Final - 1; prevheights[0] = context.baseheight + 3; tx.LockTime = context.chain.Tip.Height + 1; context.hash = tx.GetHash(); context.mempool.AddUnchecked(context.hash, context.entry.Time(context.DateTimeProvider.GetTime()).FromTx(tx)); Assert.True(!MempoolValidator.CheckFinalTransaction(context.chain, context.DateTimeProvider, tx, flags)); // Locktime fails Assert.True(this.TestSequenceLocks(context, context.chain.Tip, tx, flags)); // Sequence locks pass context.chain.SetTip(new BlockHeader { HashPrevBlock = context.chain.Tip.HashBlock, Time = Utils.DateTimeToUnixTime(context.chain.Tip.GetMedianTimePast()) + 512 }); context.chain.SetTip(new BlockHeader { HashPrevBlock = context.chain.Tip.HashBlock, Time = Utils.DateTimeToUnixTime(context.chain.Tip.GetMedianTimePast()) + 512 }); Assert.True(tx.IsFinal(context.chain.Tip.GetMedianTimePast(), context.chain.Tip.Height + 2)); // Locktime passes on 2nd block }
public async Task MinerCreateBlockNonFinalTxsInMempoolAsync() { var context = new TestContext(); await context.InitializeAsync(); var tx = context.network.CreateTransaction(); tx.AddInput(new TxIn()); tx.AddOutput(new TxOut()); // non - final txs in mempool (context.DateTimeProvider as DateTimeProviderSet).time = context.ChainIndexer.Tip.Header.Time + 1; //SetMockTime(chainActive.Tip().GetMedianTimePast() + 1); Transaction.LockTimeFlags flags = Transaction.LockTimeFlags.VerifySequence | Transaction.LockTimeFlags.MedianTimePast; // height map var prevheights = new List <int>(); // relative height locked tx.Version = 2; prevheights.Add(1); tx.Inputs[0].PrevOut.Hash = context.txFirst[0].GetHash(); // only 1 transaction tx.Inputs[0].PrevOut.N = 0; tx.Inputs[0].ScriptSig = new Script(OpcodeType.OP_1); tx.Inputs[0].Sequence = new Sequence(context.ChainIndexer.Tip.Height + 1); // txFirst[0] is the 2nd block prevheights[0] = context.baseheight + 1; tx.Outputs[0].Value = context.BLOCKSUBSIDY - context.HIGHFEE; tx.Outputs[0].ScriptPubKey = new Script(OpcodeType.OP_1); tx.LockTime = 0; context.hash = tx.GetHash(); context.mempool.AddUnchecked(context.hash, context.entry.Fee(context.HIGHFEE).Time(context.DateTimeProvider.GetTime()).SpendsCoinbase(true).FromTx(tx)); Assert.True(MempoolValidator.CheckFinalTransaction(context.ChainIndexer, context.DateTimeProvider, tx, flags)); // Locktime passes Assert.True(!this.TestSequenceLocks(context, context.ChainIndexer.Tip, tx, flags)); // Sequence locks fail BlockHeader blockHeader = context.network.Consensus.ConsensusFactory.CreateBlockHeader(); blockHeader.HashPrevBlock = context.ChainIndexer.Tip.HashBlock; blockHeader.Time = Utils.DateTimeToUnixTime(context.ChainIndexer.Tip.GetMedianTimePast()) + 1; context.ChainIndexer.SetTip(blockHeader); blockHeader = context.network.Consensus.ConsensusFactory.CreateBlockHeader(); blockHeader.HashPrevBlock = context.ChainIndexer.Tip.HashBlock; blockHeader.Time = Utils.DateTimeToUnixTime(context.ChainIndexer.Tip.GetMedianTimePast()) + 1; context.ChainIndexer.SetTip(blockHeader); blockHeader = context.network.Consensus.ConsensusFactory.CreateBlockHeader(); blockHeader.HashPrevBlock = context.ChainIndexer.Tip.HashBlock; blockHeader.Time = Utils.DateTimeToUnixTime(context.ChainIndexer.Tip.GetMedianTimePast()) + 1; context.ChainIndexer.SetTip(blockHeader); SequenceLock locks = tx.CalculateSequenceLocks(prevheights.ToArray(), context.ChainIndexer.Tip, flags); Assert.True(locks.Evaluate(context.ChainIndexer.Tip)); // Sequence locks pass on 2nd block }
/// <summary> /// Check if transaction will be BIP 68 final in the next block to be created. /// Simulates calling SequenceLocks() with data from the tip of the current active chain. /// Optionally stores in LockPoints the resulting height and time calculated and the hash /// of the block needed for calculation or skips the calculation and uses the LockPoints /// passed in for evaluation. /// The LockPoints should not be considered valid if CheckSequenceLocks returns false. /// See consensus/consensus.h for flag definitions. /// </summary> /// <param name="network">The blockchain network.</param> /// <param name="tip">Tip of the chain.</param> /// <param name="context">Validation context for the memory pool.</param> /// <param name="flags">Transaction lock time flags.</param> /// <param name="lp">Optional- existing lock points to use, and update during evaluation.</param> /// <param name="useExistingLockPoints">Whether to use the existing lock points during evaluation.</param> /// <returns>Whether sequence lock validated.</returns> /// <seealso cref="SequenceLock.Evaluate(ChainedHeader)"/> public static bool CheckSequenceLocks(Network network, ChainedHeader tip, MempoolValidationContext context, Transaction.LockTimeFlags flags, LockPoints lp = null, bool useExistingLockPoints = false) { Block dummyBlock = network.Consensus.ConsensusFactory.CreateBlock(); dummyBlock.Header.HashPrevBlock = tip.HashBlock; var index = new ChainedHeader(dummyBlock.Header, dummyBlock.GetHash(), tip); // CheckSequenceLocks() uses chainActive.Height()+1 to evaluate // height based locks because when SequenceLocks() is called within // ConnectBlock(), the height of the block *being* // evaluated is what is used. // Thus if we want to know if a transaction can be part of the // *next* block, we need to use one more than chainActive.Height() SequenceLock lockPair; if (useExistingLockPoints) { Guard.Assert(lp != null); lockPair = new SequenceLock(lp.Height, lp.Time); } else { // pcoinsTip contains the UTXO set for chainActive.Tip() var prevheights = new List <int>(); foreach (TxIn txin in context.Transaction.Inputs) { UnspentOutput unspentOutput = context.View.Set.AccessCoins(txin.PrevOut); if (unspentOutput?.Coins == null) { return(false); } if (unspentOutput.Coins.Height == TxMempool.MempoolHeight) { // Assume all mempool transaction confirm in the next block prevheights.Add(tip.Height + 1); } else { prevheights.Add((int)unspentOutput.Coins.Height); } } lockPair = context.Transaction.CalculateSequenceLocks(prevheights.ToArray(), index, flags); if (lp != null) { lp.Height = lockPair.MinHeight; lp.Time = lockPair.MinTime.ToUnixTimeMilliseconds(); // Also store the hash of the block with the highest height of // all the blocks which have sequence locked prevouts. // This hash needs to still be on the chain // for these LockPoint calculations to be valid // Note: It is impossible to correctly calculate a maxInputBlock // if any of the sequence locked inputs depend on unconfirmed txs, // except in the special case where the relative lock time/height // is 0, which is equivalent to no sequence lock. Since we assume // input height of tip+1 for mempool txs and test the resulting // lockPair from CalculateSequenceLocks against tip+1. We know // EvaluateSequenceLocks will fail if there was a non-zero sequence // lock on a mempool input, so we can use the return value of // CheckSequenceLocks to indicate the LockPoints validity int maxInputHeight = 0; foreach (int height in prevheights) { // Can ignore mempool inputs since we'll fail if they had non-zero locks if (height != tip.Height + 1) { maxInputHeight = Math.Max(maxInputHeight, height); } } lp.MaxInputBlock = tip.GetAncestor(maxInputHeight); } } return(lockPair.Evaluate(index)); }
private void CanVerifySequenceLockCore(Sequence[] sequences, int[] prevHeights, int currentHeight, DateTimeOffset first, bool expected, SequenceLock expectedLock) { ConcurrentChain chain = new ConcurrentChain(new BlockHeader() { BlockTime = first }); first = first + TimeSpan.FromMinutes(10); while(currentHeight != chain.Height) { chain.SetTip(new BlockHeader() { BlockTime = first, HashPrevBlock = chain.Tip.HashBlock }); first = first + TimeSpan.FromMinutes(10); } Transaction tx = new Transaction(); tx.Version = 2; for(int i = 0 ; i < sequences.Length ; i++) { TxIn input = new TxIn(); input.Sequence = sequences[i]; tx.Inputs.Add(input); } Assert.Equal(expected, tx.CheckSequenceLocks(prevHeights, chain.Tip)); var actualLock = tx.CalculateSequenceLocks(prevHeights, chain.Tip); Assert.Equal(expectedLock.MinTime, actualLock.MinTime); Assert.Equal(expectedLock.MinHeight, actualLock.MinHeight); }