/// <summary> /// Validates that the transaction is the final transaction."/> /// Validated by comparing the transaction vs chain tip. /// If <see cref="CoinViewRule.StandardLocktimeVerifyFlags" /> flag is set then /// use the block time at the end of the block chain for validation. /// Otherwise use the current time for the block time. /// </summary> /// <param name="chainIndexer">Block chain used for computing time-locking on the transaction.</param> /// <param name="dateTimeProvider">Provides the current date and time.</param> /// <param name="tx">The transaction to validate.</param> /// <param name="flags">Flags for time-locking the transaction.</param> /// <returns>Whether the final transaction was valid.</returns> /// <seealso cref="Transaction.IsFinal(DateTimeOffset, int)" /> public static bool CheckFinalTransaction(ChainIndexer chainIndexer, IDateTimeProvider dateTimeProvider, Transaction tx, Transaction.LockTimeFlags flags) { // By convention a negative value for flags indicates that the // current network-enforced consensus rules should be used. In // a future soft-fork scenario that would mean checking which // rules would be enforced for the next block and setting the // appropriate flags. At the present time no soft-forks are // scheduled, so no flags are set. flags = (Transaction.LockTimeFlags)Math.Max((int)flags, (int)Transaction.LockTimeFlags.None); // CheckFinalTx() uses chainActive.Height()+1 to evaluate // nLockTime because when IsFinalTx() is called within // CBlock::AcceptBlock(), 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 call // IsFinalTx() with one more than chainActive.Height(). var blockHeight = chainIndexer.Height + 1; // BIP113 will require that time-locked transactions have nLockTime set to // less than the median time of the previous block they're contained in. // When the next block is created its previous block will be the current // chain tip, so we use that to calculate the median time passed to // IsFinalTx() if LOCKTIME_MEDIAN_TIME_PAST is set. var blockTime = flags.HasFlag(StandardLocktimeVerifyFlags) ? chainIndexer.Tip.Header.BlockTime : DateTimeOffset.FromUnixTimeMilliseconds(dateTimeProvider.GetTime()); return(tx.IsFinal(blockTime, blockHeight)); }
public async Task MinerCreateBlockRelativeTimeLockedAsync() { var context = new TestContext(); await context.InitializeAsync(); var tx = context.network.CreateTransaction(); tx.AddInput(new TxIn()); tx.AddOutput(new TxOut()); Transaction.LockTimeFlags flags = Transaction.LockTimeFlags.VerifySequence | Transaction.LockTimeFlags.MedianTimePast; // height map var prevheights = new List <int>(); prevheights.Add(1); // relative time locked 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; context.hash = tx.GetHash(); context.mempool.AddUnchecked(context.hash, context.entry.Time(context.DateTimeProvider.GetTime()).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 }
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 }
public async Task MinerCreateBlockAbsoluteTimeLockedAsync() { var context = new TestContext(); await context.InitializeAsync(); var tx = context.network.CreateTransaction(); tx.AddInput(new TxIn()); tx.AddOutput(new TxOut()); Transaction.LockTimeFlags flags = Transaction.LockTimeFlags.VerifySequence | Transaction.LockTimeFlags.MedianTimePast; var prevheights = new List <int>(); prevheights.Add(1); tx.Version = 2; tx.Inputs[0].PrevOut.Hash = context.txFirst[3].GetHash(); tx.Inputs[0].ScriptSig = new Script(OpcodeType.OP_1); tx.Inputs[0].PrevOut.N = 0; tx.Outputs[0].Value = context.BLOCKSUBSIDY - context.HIGHFEE; tx.Outputs[0].ScriptPubKey = new Script(OpcodeType.OP_1); // absolute time locked tx.LockTime = context.ChainIndexer.Tip.GetMedianTimePast().AddMinutes(1); tx.Inputs[0].Sequence = Sequence.Final - 1; prevheights[0] = context.baseheight + 4; context.hash = tx.GetHash(); context.mempool.AddUnchecked(context.hash, context.entry.Time(context.DateTimeProvider.GetTime()).FromTx(tx)); Assert.True(!MempoolValidator.CheckFinalTransaction(context.ChainIndexer, context.DateTimeProvider, tx, flags)); // Locktime fails Assert.True(this.TestSequenceLocks(context, context.ChainIndexer.Tip, tx, flags)); // Sequence locks pass BlockHeader blockHeader = context.network.Consensus.ConsensusFactory.CreateBlockHeader(); blockHeader.HashPrevBlock = context.ChainIndexer.Tip.HashBlock; blockHeader.Time = Utils.DateTimeToUnixTime(context.ChainIndexer.Tip.GetMedianTimePast()) + 512; context.ChainIndexer.SetTip(blockHeader); blockHeader = context.network.Consensus.ConsensusFactory.CreateBlockHeader(); blockHeader.HashPrevBlock = context.ChainIndexer.Tip.HashBlock; blockHeader.Time = Utils.DateTimeToUnixTime(context.ChainIndexer.Tip.GetMedianTimePast()) + 512; context.ChainIndexer.SetTip(blockHeader); Assert.True(tx.IsFinal(context.ChainIndexer.Tip.GetMedianTimePast().AddMinutes(2), context.ChainIndexer.Tip.Height + 2)); // Locktime passes 2 min later }
public bool TestSequenceLocks(TestContext testContext, ChainedHeader chainedHeader, Transaction tx, Transaction.LockTimeFlags flags, LockPoints uselock = null) { var context = new MempoolValidationContext(tx, new MempoolValidationState(false)); context.View = new MempoolCoinView(this.network, testContext.cachedCoinView, testContext.mempool, testContext.mempoolLock, null); testContext.mempoolLock.ReadAsync(() => context.View.LoadViewLocked(tx)).GetAwaiter().GetResult(); return(CreateMempoolEntryMempoolRule.CheckSequenceLocks(testContext.network, chainedHeader, context, flags, uselock, false)); }
public bool TestSequenceLocks(TestContext testContext, ChainedBlock chainedBlock, Transaction tx, Transaction.LockTimeFlags flags, LockPoints uselock = null) { var context = new MempoolValidationContext(tx, new MempoolValidationState(false)); context.View = new MempoolCoinView(testContext.cachedCoinView, testContext.mempool, testContext.mempoolLock, null); context.View.LoadViewAsync(tx).GetAwaiter().GetResult(); return(MempoolValidator.CheckSequenceLocks(testContext.network, chainedBlock, context, flags, uselock, false)); }
/// <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)); }
public BIP9DeploymentFlags() { this.ScriptFlags = ScriptVerify.None; this.LockTimeFlags = Transaction.LockTimeFlags.None; }