public void MinerCreateBlockCoinbaseMempoolTemplateCreationFails() { using (NodeBuilder builder = NodeBuilder.Create(this)) { CoreNode miner = builder.CreateStratisPowNode(this.network); builder.StartAll(); miner.NotInIBD(); miner.SetDummyMinerSecret(new BitcoinSecret(new Key(), miner.FullNode.Network)); TestHelper.MineBlocks(miner, 1); // Create an invalid coinbase transaction to be added to the mempool. var duplicateCoinbase = this.network.CreateTransaction(); duplicateCoinbase.AddInput(new TxIn()); duplicateCoinbase.AddOutput(new TxOut()); duplicateCoinbase.Inputs[0].PrevOut = new OutPoint(); duplicateCoinbase.Inputs[0].ScriptSig = new Script(OpcodeType.OP_0, OpcodeType.OP_1); duplicateCoinbase.Outputs[0].Value = 0; var txMempoolHelper = new TestMemPoolEntryHelper(); var txMempoolEntry = txMempoolHelper.Fee(Money.CENT).Time(DateTimeProvider.Default.GetTime()).SpendsCoinbase(false).FromTx(duplicateCoinbase); miner.FullNode.NodeService <ITxMempool>().AddUnchecked(duplicateCoinbase.GetHash(), txMempoolEntry); var error = Assert.Throws <ConsensusException>(() => TestHelper.MineBlocks(miner, 1)); Assert.True(error.Message == ConsensusErrors.BadMultipleCoinbase.Message); TestHelper.WaitLoop(() => TestHelper.IsNodeSynced(miner)); Assert.True(miner.FullNode.ConsensusManager().Tip.Height == 1); } }
public void MinerCreateBlockSigopsLimit1000() { using (NodeBuilder builder = NodeBuilder.Create(this)) { CoreNode miner = builder.CreateStratisPowNode(this.network); builder.StartAll(); miner.NotInIBD(); miner.SetDummyMinerSecret(new BitcoinSecret(new Key(), miner.FullNode.Network)); miner.GenerateStratisWithMiner(1); var txMempoolHelper = new TestMemPoolEntryHelper(); // Block sigops > limit: 1000 CHECKMULTISIG + 1 var genesis = this.network.GetGenesis(); var genesisCoinbase = genesis.Transactions[0]; var tx = this.network.CreateTransaction(); tx.AddInput(new TxIn(new OutPoint(genesisCoinbase.GetHash(), 0), new Script(new byte[] { (byte)OpcodeType.OP_0, (byte)OpcodeType.OP_0, (byte)OpcodeType.OP_0, (byte)OpcodeType.OP_NOP, (byte)OpcodeType.OP_CHECKMULTISIG, (byte)OpcodeType.OP_1 }))); // NOTE: OP_NOP is used to force 20 SigOps for the CHECKMULTISIG tx.AddOutput(Money.Coins(50), new Script()); for (int i = 0; i < 1001; ++i) { tx.Outputs[0].Value -= Money.CENT; bool spendsCoinbase = (i == 0); // only first tx spends coinbase // If we don't set the # of sig ops in the CTxMemPoolEntry, template creation fails var txMempoolEntry = txMempoolHelper.Fee(Money.CENT).Time(DateTimeProvider.Default.GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx); miner.FullNode.NodeService <ITxMempool>().AddUnchecked(tx.GetHash(), txMempoolEntry); tx = this.network.CreateTransaction(tx.ToBytes()); tx.Inputs[0].PrevOut.Hash = tx.GetHash(); } var error = Assert.Throws <ConsensusException>(() => miner.GenerateStratisWithMiner(1)); Assert.True(error.Message == ConsensusErrors.BadBlockSigOps.Message); TestHelper.WaitLoop(() => TestHelper.IsNodeSynced(miner)); Assert.True(miner.FullNode.ConsensusManager().Tip.Height == 1); } }
public async Task MinerTestPackageSelectionAsync() { var context = new TestContext(); await context.InitializeAsync(); // Test the ancestor feerate transaction selection. TestMemPoolEntryHelper entry = new TestMemPoolEntryHelper(); // Test that a medium fee transaction will be selected after a higher fee // rate package with a low fee rate parent. Transaction tx = new Transaction(); tx.AddInput(new TxIn(new OutPoint(context.txFirst[0].GetHash(), 0), new Script(OpcodeType.OP_1))); tx.AddOutput(new TxOut(new Money(5000000000L - 1000), new Script())); // This tx has a low fee: 1000 satoshis uint256 hashParentTx = tx.GetHash(); // save this txid for later use context.mempool.AddUnchecked(hashParentTx, entry.Fee(1000).Time(context.DateTimeProvider.GetTime()).SpendsCoinbase(true).FromTx(tx)); // This tx has a medium fee: 10000 satoshis tx = tx.Clone(); tx.Inputs[0].PrevOut.Hash = context.txFirst[1].GetHash(); tx.Outputs[0].Value = 5000000000L - 10000; uint256 hashMediumFeeTx = tx.GetHash(); context.mempool.AddUnchecked(hashMediumFeeTx, entry.Fee(10000).Time(context.DateTimeProvider.GetTime()).SpendsCoinbase(true).FromTx(tx)); // This tx has a high fee, but depends on the first transaction tx = tx.Clone(); tx.Inputs[0].PrevOut.Hash = hashParentTx; tx.Outputs[0].Value = 5000000000L - 1000 - 50000; // 50k satoshi fee uint256 hashHighFeeTx = tx.GetHash(); context.mempool.AddUnchecked(hashHighFeeTx, entry.Fee(50000).Time(context.DateTimeProvider.GetTime()).SpendsCoinbase(false).FromTx(tx)); var pblocktemplate = AssemblerForTest(context).Build(context.chain.Tip, context.scriptPubKey); Assert.True(pblocktemplate.Block.Transactions[1].GetHash() == hashParentTx); Assert.True(pblocktemplate.Block.Transactions[2].GetHash() == hashHighFeeTx); Assert.True(pblocktemplate.Block.Transactions[3].GetHash() == hashMediumFeeTx); // Test that a package below the block min tx fee doesn't get included tx = tx.Clone(); tx.Inputs[0].PrevOut.Hash = hashHighFeeTx; tx.Outputs[0].Value = 5000000000L - 1000 - 50000; // 0 fee uint256 hashFreeTx = tx.GetHash(); context.mempool.AddUnchecked(hashFreeTx, entry.Fee(0).FromTx(tx)); var freeTxSize = tx.GetSerializedSize(); // Calculate a fee on child transaction that will put the package just // below the block min tx fee (assuming 1 child tx of the same size). var feeToUse = blockMinFeeRate.GetFee(2 * freeTxSize) - 1; tx = tx.Clone(); tx.Inputs[0].PrevOut.Hash = hashFreeTx; tx.Outputs[0].Value = 5000000000L - 1000 - 50000 - feeToUse; uint256 hashLowFeeTx = tx.GetHash(); context.mempool.AddUnchecked(hashLowFeeTx, entry.Fee(feeToUse).FromTx(tx)); pblocktemplate = AssemblerForTest(context).Build(context.chain.Tip, context.scriptPubKey); // Verify that the free tx and the low fee tx didn't get selected for (var i = 0; i < pblocktemplate.Block.Transactions.Count; ++i) { Assert.True(pblocktemplate.Block.Transactions[i].GetHash() != hashFreeTx); Assert.True(pblocktemplate.Block.Transactions[i].GetHash() != hashLowFeeTx); } // Test that packages above the min relay fee do get included, even if one // of the transactions is below the min relay fee // Remove the low fee transaction and replace with a higher fee transaction context.mempool.RemoveRecursive(tx); tx = tx.Clone(); tx.Outputs[0].Value -= 2; // Now we should be just over the min relay fee hashLowFeeTx = tx.GetHash(); context.mempool.AddUnchecked(hashLowFeeTx, entry.Fee(feeToUse + 2).FromTx(tx)); pblocktemplate = AssemblerForTest(context).Build(context.chain.Tip, context.scriptPubKey); Assert.True(pblocktemplate.Block.Transactions[4].GetHash() == hashFreeTx); Assert.True(pblocktemplate.Block.Transactions[5].GetHash() == hashLowFeeTx); // Test that transaction selection properly updates ancestor fee // calculations as ancestor transactions get included in a block. // Add a 0-fee transaction that has 2 outputs. tx = tx.Clone(); tx.Inputs[0].PrevOut.Hash = context.txFirst[2].GetHash(); tx.AddOutput(Money.Zero, new Script()); tx.Outputs[0].Value = 5000000000L - 100000000; tx.Outputs[1].Value = 100000000; // 1BTC output uint256 hashFreeTx2 = tx.GetHash(); context.mempool.AddUnchecked(hashFreeTx2, entry.Fee(0).SpendsCoinbase(true).FromTx(tx)); // This tx can't be mined by itself tx = tx.Clone(); tx.Inputs[0].PrevOut.Hash = hashFreeTx2; tx.Outputs.RemoveAt(1); feeToUse = blockMinFeeRate.GetFee(freeTxSize); tx.Outputs[0].Value = 5000000000L - 100000000 - feeToUse; uint256 hashLowFeeTx2 = tx.GetHash(); context.mempool.AddUnchecked(hashLowFeeTx2, entry.Fee(feeToUse).SpendsCoinbase(false).FromTx(tx)); pblocktemplate = AssemblerForTest(context).Build(context.chain.Tip, context.scriptPubKey); // Verify that this tx isn't selected. for (var i = 0; i < pblocktemplate.Block.Transactions.Count; ++i) { Assert.True(pblocktemplate.Block.Transactions[i].GetHash() != hashFreeTx2); Assert.True(pblocktemplate.Block.Transactions[i].GetHash() != hashLowFeeTx2); } // This tx will be mineable, and should cause hashLowFeeTx2 to be selected // as well. tx = tx.Clone(); tx.Inputs[0].PrevOut.N = 1; tx.Outputs[0].Value = 100000000 - 10000; // 10k satoshi fee context.mempool.AddUnchecked(tx.GetHash(), entry.Fee(10000).FromTx(tx)); pblocktemplate = AssemblerForTest(context).Build(context.chain.Tip, context.scriptPubKey); Assert.True(pblocktemplate.Block.Transactions[8].GetHash() == hashLowFeeTx2); }
public async Task InitializeAsync() { this.blockinfo = new List <Blockinfo>(); var lst = blockinfoarr.Cast <long>().ToList(); for (int i = 0; i < lst.Count; i += 2) { this.blockinfo.Add(new Blockinfo { extranonce = (int)lst[i], nonce = (uint)lst[i + 1] }); } // Note that by default, these tests run with size accounting enabled. this.network = Network.Main; var hex = Encoders.Hex.DecodeData("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"); this.scriptPubKey = new Script(new[] { Op.GetPushOp(hex), OpcodeType.OP_CHECKSIG }); this.newBlock = new BlockTemplate(this.network); this.entry = new TestMemPoolEntryHelper(); this.chain = new ConcurrentChain(this.network); this.network.Consensus.Options = new PowConsensusOptions(); IDateTimeProvider dateTimeProvider = DateTimeProvider.Default; this.cachedCoinView = new CachedCoinView(new InMemoryCoinView(this.chain.Tip.HashBlock), dateTimeProvider, new LoggerFactory()); var loggerFactory = new ExtendedLoggerFactory(); loggerFactory.AddConsoleWithFilters(); NodeSettings nodeSettings = new NodeSettings(args: new string[] { "-checkpoints" }); var consensusSettings = new ConsensusSettings().Load(nodeSettings); PowConsensusValidator consensusValidator = new PowConsensusValidator(this.network, new Checkpoints(), dateTimeProvider, loggerFactory); NetworkPeerFactory networkPeerFactory = new NetworkPeerFactory(this.network, dateTimeProvider, loggerFactory, new PayloadProvider().DiscoverPayloads(), new SelfEndpointTracker()); var peerAddressManager = new PeerAddressManager(DateTimeProvider.Default, nodeSettings.DataFolder, loggerFactory, new SelfEndpointTracker()); var peerDiscovery = new PeerDiscovery(new AsyncLoopFactory(loggerFactory), loggerFactory, Network.Main, networkPeerFactory, new NodeLifetime(), nodeSettings, peerAddressManager); var connectionSettings = new ConnectionManagerSettings(); connectionSettings.Load(nodeSettings); var connectionManager = new ConnectionManager(dateTimeProvider, loggerFactory, this.network, networkPeerFactory, nodeSettings, new NodeLifetime(), new NetworkPeerConnectionParameters(), peerAddressManager, new IPeerConnector[] { }, peerDiscovery, connectionSettings); LookaheadBlockPuller blockPuller = new LookaheadBlockPuller(this.chain, connectionManager, new LoggerFactory()); PeerBanning peerBanning = new PeerBanning(connectionManager, loggerFactory, dateTimeProvider, peerAddressManager); NodeDeployments deployments = new NodeDeployments(this.network, this.chain); ConsensusRules consensusRules = new PowConsensusRules(this.network, loggerFactory, dateTimeProvider, this.chain, deployments, consensusSettings, new Checkpoints(), this.cachedCoinView, blockPuller).Register(new FullNodeBuilderConsensusExtension.PowConsensusRulesRegistration()); this.consensus = new ConsensusLoop(new AsyncLoopFactory(loggerFactory), consensusValidator, new NodeLifetime(), this.chain, this.cachedCoinView, blockPuller, new NodeDeployments(this.network, this.chain), loggerFactory, new ChainState(new InvalidBlockHashStore(dateTimeProvider)), connectionManager, dateTimeProvider, new Signals.Signals(), consensusSettings, nodeSettings, peerBanning, consensusRules); await this.consensus.StartAsync(); this.entry.Fee(11); this.entry.Height(11); var date1 = new MemoryPoolTests.DateTimeProviderSet(); date1.time = dateTimeProvider.GetTime(); date1.timeutc = dateTimeProvider.GetUtcNow(); this.DateTimeProvider = date1; this.mempool = new TxMempool(dateTimeProvider, new BlockPolicyEstimator(new MempoolSettings(nodeSettings), new LoggerFactory(), nodeSettings), new LoggerFactory(), nodeSettings); this.mempoolLock = new MempoolSchedulerLock(); // Simple block creation, nothing special yet: this.newBlock = AssemblerForTest(this).Build(this.chain.Tip, this.scriptPubKey); this.chain.SetTip(this.newBlock.Block.Header); await this.consensus.ValidateAndExecuteBlockAsync(new RuleContext(new BlockValidationContext { Block = this.newBlock.Block }, this.network.Consensus, this.consensus.Tip) { CheckPow = false, CheckMerkleRoot = false }); // We can't make transactions until we have inputs // Therefore, load 100 blocks :) this.baseheight = 0; List <Block> blocks = new List <Block>(); this.txFirst = new List <Transaction>(); for (int i = 0; i < this.blockinfo.Count; ++i) { var pblock = this.newBlock.Block.Clone(); // pointer for convenience pblock.Header.HashPrevBlock = this.chain.Tip.HashBlock; pblock.Header.Version = 1; pblock.Header.Time = Utils.DateTimeToUnixTime(this.chain.Tip.GetMedianTimePast()) + 1; Transaction txCoinbase = pblock.Transactions[0].Clone(); txCoinbase.Inputs.Clear(); txCoinbase.Version = 1; txCoinbase.AddInput(new TxIn(new Script(new[] { Op.GetPushOp(this.blockinfo[i].extranonce), Op.GetPushOp(this.chain.Height) }))); // Ignore the (optional) segwit commitment added by CreateNewBlock (as the hardcoded nonces don't account for this) txCoinbase.AddOutput(new TxOut(Money.Zero, new Script())); pblock.Transactions[0] = txCoinbase; if (this.txFirst.Count == 0) { this.baseheight = this.chain.Height; } if (this.txFirst.Count < 4) { this.txFirst.Add(pblock.Transactions[0]); } pblock.UpdateMerkleRoot(); pblock.Header.Nonce = this.blockinfo[i].nonce; this.chain.SetTip(pblock.Header); await this.consensus.ValidateAndExecuteBlockAsync(new RuleContext(new BlockValidationContext { Block = pblock }, this.network.Consensus, this.consensus.Tip) { CheckPow = false, CheckMerkleRoot = false }); blocks.Add(pblock); } // Just to make sure we can still make simple blocks this.newBlock = AssemblerForTest(this).Build(this.chain.Tip, this.scriptPubKey); Assert.NotNull(this.newBlock); }
public async Task InitializeAsync() { this.blockinfo = new List <Blockinfo>(); List <long> lst = blockinfoarr.Cast <long>().ToList(); for (int i = 0; i < lst.Count; i += 2) { this.blockinfo.Add(new Blockinfo { extranonce = (int)lst[i], nonce = (uint)lst[i + 1] }); } // Note that by default, these tests run with size accounting enabled. this.network = KnownNetworks.RegTest; byte[] hex = Encoders.Hex.DecodeData("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"); this.scriptPubKey = new Script(new[] { Op.GetPushOp(hex), OpcodeType.OP_CHECKSIG }); this.entry = new TestMemPoolEntryHelper(); this.ChainIndexer = new ChainIndexer(this.network); this.network.Consensus.Options = new ConsensusOptions(); IDateTimeProvider dateTimeProvider = DateTimeProvider.Default; var loggerFactory = ExtendedLoggerFactory.Create(); var nodeSettings = new NodeSettings(this.network, args: new string[] { "-checkpoints" }); var consensusSettings = new ConsensusSettings(nodeSettings); var inMemoryCoinView = new InMemoryCoinView(new HashHeightPair(this.ChainIndexer.Tip)); var nodeStats = new NodeStats(dateTimeProvider, nodeSettings, new Mock <IVersionProvider>().Object); this.cachedCoinView = new CachedCoinView(this.network, new Checkpoints(), inMemoryCoinView, dateTimeProvider, new LoggerFactory(), nodeStats, consensusSettings); var signals = new Signals.Signals(loggerFactory, null); var asyncProvider = new AsyncProvider(loggerFactory, signals); var connectionSettings = new ConnectionManagerSettings(nodeSettings); var peerAddressManager = new PeerAddressManager(DateTimeProvider.Default, nodeSettings.DataFolder, loggerFactory, new SelfEndpointTracker(loggerFactory, connectionSettings)); var networkPeerFactory = new NetworkPeerFactory(this.network, dateTimeProvider, loggerFactory, new PayloadProvider().DiscoverPayloads(), new SelfEndpointTracker(loggerFactory, connectionSettings), new Mock <IInitialBlockDownloadState>().Object, new ConnectionManagerSettings(nodeSettings), asyncProvider, peerAddressManager); var peerDiscovery = new PeerDiscovery(asyncProvider, loggerFactory, this.network, networkPeerFactory, new NodeLifetime(), nodeSettings, peerAddressManager); var selfEndpointTracker = new SelfEndpointTracker(loggerFactory, connectionSettings); var connectionManager = new ConnectionManager(dateTimeProvider, loggerFactory, this.network, networkPeerFactory, nodeSettings, new NodeLifetime(), new NetworkPeerConnectionParameters(), peerAddressManager, new IPeerConnector[] { }, peerDiscovery, selfEndpointTracker, connectionSettings, new VersionProvider(), new Mock <INodeStats>().Object, asyncProvider, new PayloadProvider()); var peerBanning = new PeerBanning(connectionManager, loggerFactory, dateTimeProvider, peerAddressManager); var deployments = new NodeDeployments(this.network, this.ChainIndexer); var genesis = this.network.GetGenesis(); var chainState = new ChainState() { BlockStoreTip = new ChainedHeader(genesis.Header, genesis.GetHash(), 0) }; var consensusRulesContainer = new ConsensusRulesContainer(); foreach (var ruleType in this.network.Consensus.ConsensusRules.HeaderValidationRules) { consensusRulesContainer.HeaderValidationRules.Add(Activator.CreateInstance(ruleType) as HeaderValidationConsensusRule); } foreach (var ruleType in network.Consensus.ConsensusRules.FullValidationRules) { FullValidationConsensusRule rule = null; if (ruleType == typeof(FlushCoinviewRule)) { rule = new FlushCoinviewRule(new Mock <IInitialBlockDownloadState>().Object); } else { rule = Activator.CreateInstance(ruleType) as FullValidationConsensusRule; } consensusRulesContainer.FullValidationRules.Add(rule); } this.ConsensusRules = new PowConsensusRuleEngine(this.network, loggerFactory, dateTimeProvider, this.ChainIndexer, deployments, consensusSettings, new Checkpoints(), this.cachedCoinView, chainState, new InvalidBlockHashStore(dateTimeProvider), nodeStats, asyncProvider, consensusRulesContainer).SetupRulesEngineParent(); this.consensus = ConsensusManagerHelper.CreateConsensusManager(this.network, chainState: chainState, inMemoryCoinView: inMemoryCoinView, chainIndexer: this.ChainIndexer, consensusRules: this.ConsensusRules); await this.consensus.InitializeAsync(chainState.BlockStoreTip); this.entry.Fee(11); this.entry.Height(11); var dateTimeProviderSet = new DateTimeProviderSet { time = dateTimeProvider.GetTime(), timeutc = dateTimeProvider.GetUtcNow() }; this.DateTimeProvider = dateTimeProviderSet; this.mempool = new TxMempool(dateTimeProvider, new BlockPolicyEstimator(new MempoolSettings(nodeSettings), loggerFactory, nodeSettings), loggerFactory, nodeSettings); this.mempoolLock = new MempoolSchedulerLock(); // We can't make transactions until we have inputs // Therefore, load 100 blocks :) this.baseheight = 0; var blocks = new List <Block>(); this.txFirst = new List <Transaction>(); this.nonce = 0; for (int i = 0; i < this.blockinfo.Count; ++i) { Block block = this.network.CreateBlock(); block.Header.HashPrevBlock = this.consensus.Tip.HashBlock; block.Header.Version = 1; block.Header.Time = Utils.DateTimeToUnixTime(this.ChainIndexer.Tip.GetMedianTimePast()) + 1; Transaction txCoinbase = this.network.CreateTransaction(); txCoinbase.Version = 1; txCoinbase.AddInput(new TxIn(new Script(new[] { Op.GetPushOp(this.blockinfo[i].extranonce), Op.GetPushOp(this.ChainIndexer.Height) }))); // Ignore the (optional) segwit commitment added by CreateNewBlock (as the hardcoded nonces don't account for this) txCoinbase.AddOutput(new TxOut(Money.Zero, new Script())); block.AddTransaction(txCoinbase); if (this.txFirst.Count == 0) { this.baseheight = this.ChainIndexer.Height; } if (this.txFirst.Count < 4) { this.txFirst.Add(block.Transactions[0]); } block.Header.Bits = block.Header.GetWorkRequired(this.network, this.ChainIndexer.Tip); block.UpdateMerkleRoot(); while (!block.CheckProofOfWork()) { block.Header.Nonce = ++this.nonce; } // Serialization sets the BlockSize property. block = Block.Load(block.ToBytes(), this.network.Consensus.ConsensusFactory); var res = await this.consensus.BlockMinedAsync(block); if (res == null) { throw new InvalidOperationException(); } blocks.Add(block); } // Just to make sure we can still make simple blocks this.newBlock = AssemblerForTest(this).Build(this.ChainIndexer.Tip, this.scriptPubKey); Assert.NotNull(this.newBlock); }
public TestContext() { this.blockinfo = new List <Blockinfo>(); var lst = blockinfoarr.Cast <long>().ToList(); for (int i = 0; i < lst.Count; i += 2) { this.blockinfo.Add(new Blockinfo() { extranonce = (int)lst[i], nonce = (uint)lst[i + 1] }); } // Note that by default, these tests run with size accounting enabled. this.network = Network.Main; var hex = Encoders.Hex.DecodeData("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"); this.scriptPubKey = new Script(new[] { Op.GetPushOp(hex), OpcodeType.OP_CHECKSIG }); this.newBlock = new BlockTemplate(); this.entry = new TestMemPoolEntryHelper(); this.chain = new ConcurrentChain(this.network); this.network.Consensus.Options = new PowConsensusOptions(); this.cachedCoinView = new CachedCoinView(new InMemoryCoinView(this.chain.Tip.HashBlock), new LoggerFactory()); this.consensus = new ConsensusLoop(new PowConsensusValidator(this.network), this.chain, this.cachedCoinView, new LookaheadBlockPuller(this.chain, new ConnectionManager(this.network, new NodeConnectionParameters(), new NodeSettings(), new LoggerFactory(), new NodeLifetime()), new LoggerFactory()), new NodeDeployments(this.network)); this.consensus.Initialize(); this.entry.Fee(11); this.entry.Height(11); var date1 = new MemoryPoolTests.DateTimeProviderSet(); date1.time = DateTimeProvider.Default.GetTime(); date1.timeutc = DateTimeProvider.Default.GetUtcNow(); this.date = date1; this.mempool = new TxMempool(new FeeRate(1000), DateTimeProvider.Default, new BlockPolicyEstimator(new FeeRate(1000), NodeSettings.Default(), new LoggerFactory()), new LoggerFactory());; this.mempoolLock = new MempoolAsyncLock(); // Simple block creation, nothing special yet: this.newBlock = AssemblerForTest(this).CreateNewBlock(this.scriptPubKey); this.chain.SetTip(this.newBlock.Block.Header); this.consensus.AcceptBlock(new ContextInformation(new BlockResult { Block = this.newBlock.Block }, this.network.Consensus) { CheckPow = false, CheckMerkleRoot = false }); // We can't make transactions until we have inputs // Therefore, load 100 blocks :) this.baseheight = 0; List <Block> blocks = new List <Block>(); this.txFirst = new List <Transaction>(); for (int i = 0; i < this.blockinfo.Count; ++i) { var pblock = this.newBlock.Block.Clone(); // pointer for convenience pblock.Header.HashPrevBlock = this.chain.Tip.HashBlock; pblock.Header.Version = 1; pblock.Header.Time = Utils.DateTimeToUnixTime(this.chain.Tip.GetMedianTimePast()) + 1; Transaction txCoinbase = pblock.Transactions[0].Clone(); txCoinbase.Inputs.Clear(); txCoinbase.Version = 1; txCoinbase.AddInput(new TxIn(new Script(new[] { Op.GetPushOp(this.blockinfo[i].extranonce), Op.GetPushOp(this.chain.Height) }))); // Ignore the (optional) segwit commitment added by CreateNewBlock (as the hardcoded nonces don't account for this) txCoinbase.AddOutput(new TxOut(Money.Zero, new Script())); pblock.Transactions[0] = txCoinbase; if (this.txFirst.Count == 0) { this.baseheight = this.chain.Height; } if (this.txFirst.Count < 4) { this.txFirst.Add(pblock.Transactions[0]); } pblock.UpdateMerkleRoot(); pblock.Header.Nonce = this.blockinfo[i].nonce; this.chain.SetTip(pblock.Header); this.consensus.AcceptBlock(new ContextInformation(new BlockResult { Block = pblock }, this.network.Consensus) { CheckPow = false, CheckMerkleRoot = false }); blocks.Add(pblock); } // Just to make sure we can still make simple blocks this.newBlock = AssemblerForTest(this).CreateNewBlock(this.scriptPubKey); Assert.NotNull(this.newBlock); }
public void BlockPolicyEstimates() { var dateTimeSet = new MemoryPoolTests.DateTimeProviderSet(); TxMempool mpool = new TxMempool(new FeeRate(1000), DateTimeProvider.Default, new BlockPolicyEstimator(new FeeRate(1000), NodeSettings.Default())); TestMemPoolEntryHelper entry = new TestMemPoolEntryHelper(); Money basefee = new Money(2000); Money deltaFee = new Money(100); List <Money> feeV = new List <Money>(); // Populate vectors of increasing fees for (int j = 0; j < 10; j++) { feeV.Add(basefee * (j + 1)); } // Store the hashes of transactions that have been // added to the mempool by their associate fee // txHashes[j] is populated with transactions either of // fee = basefee * (j+1) List <uint256>[] txHashes = new List <uint256> [10]; for (int i = 0; i < txHashes.Length; i++) { txHashes[i] = new List <uint256>(); } // Create a transaction template Script garbage = new Script(Enumerable.Range(0, 128).Select(i => (byte)1).ToArray()); Transaction txf = new Transaction(); txf.AddInput(new TxIn(garbage)); txf.AddOutput(new TxOut(0L, Script.Empty)); FeeRate baseRate = new FeeRate(basefee, txf.GetVirtualSize()); // Create a fake block List <Transaction> block = new List <Transaction>(); int blocknum = 0; int answerFound; // Loop through 200 blocks // At a decay .998 and 4 fee transactions per block // This makes the tx count about 1.33 per bucket, above the 1 threshold while (blocknum < 200) { for (int j = 0; j < 10; j++) { // For each fee for (int k = 0; k < 4; k++) { // add 4 fee txs var tx = txf.Clone(false); tx.Inputs[0].PrevOut.N = (uint)(10000 * blocknum + 100 * j + k); // make transaction unique uint256 hash = tx.GetHash(); mpool.AddUnchecked(hash, entry.Fee(feeV[j]).Time(dateTimeSet.GetTime()).Priority(0).Height(blocknum).FromTx(tx, mpool)); txHashes[j].Add(hash); } } //Create blocks where higher fee txs are included more often for (int h = 0; h <= blocknum % 10; h++) { // 10/10 blocks add highest fee transactions // 9/10 blocks add 2nd highest and so on until ... // 1/10 blocks add lowest fee transactions while (txHashes[9 - h].Count > 0) { var ptx = mpool.Get(txHashes[9 - h].Last()); if (ptx != null) { block.Add(ptx); } txHashes[9 - h].Remove(txHashes[9 - h].Last()); } } mpool.RemoveForBlock(block, ++blocknum); block.Clear(); if (blocknum == 30) { // At this point we should need to combine 5 buckets to get enough data points // So estimateFee(1,2,3) should fail and estimateFee(4) should return somewhere around // 8*baserate. estimateFee(4) %'s are 100,100,100,100,90 = average 98% Assert.True(mpool.EstimateFee(1) == new FeeRate(0)); Assert.True(mpool.EstimateFee(2) == new FeeRate(0)); Assert.True(mpool.EstimateFee(3) == new FeeRate(0)); Assert.True(mpool.EstimateFee(4).FeePerK < 8 * baseRate.FeePerK + deltaFee); Assert.True(mpool.EstimateFee(4).FeePerK > 8 * baseRate.FeePerK - deltaFee); Assert.True(mpool.EstimateSmartFee(1, out answerFound) == mpool.EstimateFee(4) && answerFound == 4); Assert.True(mpool.EstimateSmartFee(3, out answerFound) == mpool.EstimateFee(4) && answerFound == 4); Assert.True(mpool.EstimateSmartFee(4, out answerFound) == mpool.EstimateFee(4) && answerFound == 4); Assert.True(mpool.EstimateSmartFee(8, out answerFound) == mpool.EstimateFee(8) && answerFound == 8); } } List <Money> origFeeEst = new List <Money>(); // Highest feerate is 10*baseRate and gets in all blocks, // second highest feerate is 9*baseRate and gets in 9/10 blocks = 90%, // third highest feerate is 8*base rate, and gets in 8/10 blocks = 80%, // so estimateFee(1) would return 10*baseRate but is hardcoded to return failure // Second highest feerate has 100% chance of being included by 2 blocks, // so estimateFee(2) should return 9*baseRate etc... for (int i = 1; i < 10; i++) { origFeeEst.Add(mpool.EstimateFee(i).FeePerK); if (i > 2) { // Fee estimates should be monotonically decreasing Assert.True(origFeeEst[i - 1] <= origFeeEst[i - 2]); } int mult = 11 - i; if (i > 1) { Assert.True(origFeeEst[i - 1] < mult * baseRate.FeePerK + deltaFee); Assert.True(origFeeEst[i - 1] > mult * baseRate.FeePerK - deltaFee); } else { Assert.True(origFeeEst[i - 1] == new FeeRate(0).FeePerK); } } // Mine 50 more blocks with no transactions happening, estimates shouldn't change // We haven't decayed the moving average enough so we still have enough data points in every bucket while (blocknum < 250) { mpool.RemoveForBlock(block, ++blocknum); } Assert.True(mpool.EstimateFee(1) == new FeeRate(0)); for (int i = 2; i < 10; i++) { Assert.True(mpool.EstimateFee(i).FeePerK < origFeeEst[i - 1] + deltaFee); Assert.True(mpool.EstimateFee(i).FeePerK > origFeeEst[i - 1] - deltaFee); } // Mine 15 more blocks with lots of transactions happening and not getting mined // Estimates should go up while (blocknum < 265) { for (int j = 0; j < 10; j++) { // For each fee multiple for (int k = 0; k < 4; k++) { // add 4 fee txs var tx = txf.Clone(false); tx.Inputs[0].PrevOut.N = (uint)(10000 * blocknum + 100 * j + k); uint256 hash = tx.GetHash(); mpool.AddUnchecked(hash, entry.Fee(feeV[j]).Time(dateTimeSet.GetTime()).Priority(0).Height(blocknum).FromTx(tx, mpool)); txHashes[j].Add(hash); } } mpool.RemoveForBlock(block, ++blocknum); } for (int i = 1; i < 10; i++) { Assert.True(mpool.EstimateFee(i) == new FeeRate(0) || mpool.EstimateFee(i).FeePerK > origFeeEst[i - 1] - deltaFee); Assert.True(mpool.EstimateSmartFee(i, out answerFound).FeePerK > origFeeEst[answerFound - 1] - deltaFee); } // Mine all those transactions // Estimates should still not be below original for (int j = 0; j < 10; j++) { while (txHashes[j].Count > 0) { var ptx = mpool.Get(txHashes[j].Last()); if (ptx != null) { block.Add(ptx); } txHashes[j].Remove(txHashes[j].Last()); } } mpool.RemoveForBlock(block, 265); block.Clear(); Assert.True(mpool.EstimateFee(1) == new FeeRate(0)); for (int i = 2; i < 10; i++) { Assert.True(mpool.EstimateFee(i).FeePerK > origFeeEst[i - 1] - deltaFee); } // Mine 200 more blocks where everything is mined every block // Estimates should be below original estimates while (blocknum < 465) { for (int j = 0; j < 10; j++) { // For each fee multiple for (int k = 0; k < 4; k++) { // add 4 fee txs var tx = txf.Clone(false); tx.Inputs[0].PrevOut.N = (uint)(10000 * blocknum + 100 * j + k); uint256 hash = tx.GetHash(); mpool.AddUnchecked(hash, entry.Fee(feeV[j]).Time(dateTimeSet.GetTime()).Priority(0).Height(blocknum).FromTx(tx, mpool)); var ptx = mpool.Get(hash); if (ptx != null) { block.Add(ptx); } } } mpool.RemoveForBlock(block, ++blocknum); block.Clear(); } Assert.True(mpool.EstimateFee(1) == new FeeRate(0)); for (int i = 2; i < 10; i++) { Assert.True(mpool.EstimateFee(i).FeePerK < origFeeEst[i - 1] - deltaFee); } // Test that if the mempool is limited, estimateSmartFee won't return a value below the mempool min fee // and that estimateSmartPriority returns essentially an infinite value mpool.AddUnchecked(txf.GetHash(), entry.Fee(feeV[5]).Time(dateTimeSet.GetTime()).Priority(0).Height(blocknum).FromTx(txf, mpool)); // evict that transaction which should set a mempool min fee of minRelayTxFee + feeV[5] mpool.TrimToSize(1); Assert.True(mpool.GetMinFee(1).FeePerK > feeV[5]); for (int i = 1; i < 10; i++) { Assert.True(mpool.EstimateSmartFee(i, out answerFound).FeePerK >= mpool.EstimateFee(i).FeePerK); Assert.True(mpool.EstimateSmartFee(i, out answerFound).FeePerK >= mpool.GetMinFee(1).FeePerK); Assert.True(mpool.EstimateSmartPriority(i, out answerFound) == BlockPolicyEstimator.INF_PRIORITY); } }