/// <inheritdoc /> public FetchCoinsResponse FetchCoins(uint256[] txIds, CancellationToken cancellationToken = default) { Guard.NotNull(txIds, nameof(txIds)); using (this.lockobj.LockRead()) { var result = new UnspentOutputs[txIds.Length]; for (var i = 0; i < txIds.Length; i++) { result[i] = this.unspents.TryGet(txIds[i]); if (result[i] != null) { result[i] = result[i].Clone(); } } return(new FetchCoinsResponse(result, this.tipHash)); } }
public void RunRule_ProvenHeadersActive_And_InvalidStakeDepth_StakeDepthErrorIsThrown() { // Setup previous chained header. PosBlock prevPosBlock = new PosBlockBuilder(this.network).Build(); ProvenBlockHeader prevProvenBlockHeader = new ProvenBlockHeaderBuilder(prevPosBlock, this.network).Build(); var previousChainedHeader = new ChainedHeader(prevProvenBlockHeader, prevProvenBlockHeader.GetHash(), null); previousChainedHeader.SetPrivatePropertyValue("Height", this.provenHeadersActivationHeight + 1); // Setup proven header with valid coinstake. PosBlock posBlock = new PosBlockBuilder(this.network).Build(); ProvenBlockHeader provenBlockHeader = new ProvenBlockHeaderBuilder(posBlock, this.network).Build(); provenBlockHeader.HashPrevBlock = prevProvenBlockHeader.GetHash(); provenBlockHeader.Coinstake.Time = provenBlockHeader.Time; // Setup chained header and move it to the height higher than proven header activation height. this.ruleContext.ValidationContext.ChainedHeaderToValidate = new ChainedHeader(provenBlockHeader, provenBlockHeader.GetHash(), previousChainedHeader); this.ruleContext.ValidationContext.ChainedHeaderToValidate.SetPrivatePropertyValue("Height", this.provenHeadersActivationHeight + 2); // Ensure that coinview returns a UTXO with valid outputs. var utxoOneTransaction = new Transaction(); utxoOneTransaction.AddOutput(new TxOut()); var utxoOne = new UnspentOutputs(10, utxoOneTransaction); // Setup coinstake transaction with an invalid stake age. this.coinView .Setup(m => m.FetchCoins(It.IsAny <uint256[]>(), It.IsAny <CancellationToken>())) .Returns(new FetchCoinsResponse(new[] { utxoOne }, posBlock.GetHash())); // Setup stake validator to fail stake age check. this.stakeValidator .Setup(m => m.IsConfirmedInNPrevBlocks(It.IsAny <UnspentOutputs>(), It.IsAny <ChainedHeader>(), It.IsAny <long>())) .Returns(true); // When we run the validation rule, we should hit coinstake depth error. Action ruleValidation = () => this.consensusRules.RegisterRule <ProvenHeaderCoinstakeRule>().Run(this.ruleContext); ruleValidation.Should().Throw <ConsensusErrorException>() .And.ConsensusError .Should().Be(ConsensusErrors.InvalidStakeDepth); }
public async Task GetTxOutAsync_IncludeInMempool_UnspentTransactionFound_VOutNotFound_ReturnsModelAsync() { var txId = new uint256(1243124); Transaction transaction = this.CreateTransaction(); var unspentOutputs = new UnspentOutputs(1, transaction); this.pooledGetUnspentTransaction.Setup(s => s.GetUnspentTransactionAsync(txId)) .ReturnsAsync(unspentOutputs) .Verifiable(); var model = await this.controller.GetTxOutAsync(txId.ToString(), 13, true).ConfigureAwait(false); this.pooledGetUnspentTransaction.Verify(); Assert.Equal(this.chain.Tip.HashBlock, model.BestBlock); Assert.True(model.Coinbase); Assert.Equal(3, model.Confirmations); Assert.Null(model.ScriptPubKey); Assert.Null(model.Value); }
public override Task <FetchCoinsResponse> FetchCoinsAsync(uint256[] txIds) { return(this.session.Execute(() => { using (StopWatch.Instance.Start(o => this.PerformanceCounter.AddQueryTime(o))) { var blockHash = this.GetCurrentHash(); UnspentOutputs[] result = new UnspentOutputs[txIds.Length]; int i = 0; this.PerformanceCounter.AddQueriedEntities(txIds.Length); foreach (var input in txIds) { var coin = this.session.Transaction.Select <byte[], Coins>("Coins", input.ToBytes(false))?.Value; result[i++] = coin == null ? null : new UnspentOutputs(input, coin); } return new FetchCoinsResponse(result, blockHash); } })); }
private UnspentOutputSet GetMockOutputSet() { Transaction transaction = this.network.Consensus.ConsensusFactory.CreateTransaction(); var fakeTxOut = new TxOut { Value = 100_000_000 }; var coins = new UnspentOutputs(new uint(), transaction) { Outputs = new TxOut[] { fakeTxOut } }; var unspentOutputs = new UnspentOutputSet(); unspentOutputs.SetCoins(new[] { coins }); return(unspentOutputs); }
/// <inheritdoc /> public async Task <UnspentOutputs> GetUnspentTransactionAsync(uint256 trxid) { this.logger.LogTrace("({0}:'{1}')", nameof(trxid), trxid); TxMempoolInfo txInfo = await this.InfoAsync(trxid); if (txInfo == null) { this.logger.LogTrace("(-):[TX_IS_NULL]"); return(null); } var memPoolCoinView = new MempoolCoinView(this.coinView, this.memPool, this.MempoolLock, this.Validator); await memPoolCoinView.LoadViewAsync(txInfo.Trx); UnspentOutputs unspentOutputs = memPoolCoinView.GetCoins(trxid); this.logger.LogTrace("(-):{0}.{1}='{2}'", nameof(UnspentOutputs), nameof(UnspentOutputs.TransactionId), unspentOutputs?.TransactionId); return(unspentOutputs); }
/// <summary> /// Checks if provided transaction is a valid coinstake transaction. /// </summary> /// <param name="context">Staking context.</param> /// <param name="prevChainedBlock">Previous chained block.</param> /// <param name="prevBlockStake">Information about previous staked block.</param> /// <param name="transaction">The transaction.</param> /// <param name="headerBits">Chained block's header bits, which define the difficulty target.</param> public void CheckProofOfStake(ContextStakeInformation context, ChainedBlock prevChainedBlock, BlockStake prevBlockStake, Transaction transaction, uint headerBits) { this.logger.LogTrace("({0}:'{1}',{2}.{3}:'{4}',{5}:0x{6:X})", nameof(prevChainedBlock), prevChainedBlock.HashBlock, nameof(prevBlockStake), nameof(prevBlockStake.HashProof), prevBlockStake.HashProof, nameof(headerBits), headerBits); if (!transaction.IsCoinStake) { this.logger.LogTrace("(-)[NO_COINSTAKE]"); ConsensusErrors.NonCoinstake.Throw(); } TxIn txIn = transaction.Inputs[0]; // First try finding the previous transaction in database. FetchCoinsResponse coins = this.coinView.FetchCoinsAsync(new[] { txIn.PrevOut.Hash }).GetAwaiter().GetResult(); if ((coins == null) || (coins.UnspentOutputs.Length != 1)) { ConsensusErrors.ReadTxPrevFailed.Throw(); } ChainedBlock prevBlock = this.chain.GetBlock(coins.BlockHash); UnspentOutputs prevUtxo = coins.UnspentOutputs[0]; // Verify signature. if (!this.VerifySignature(prevUtxo, transaction, 0, ScriptVerify.None)) { this.logger.LogTrace("(-)[BAD_SIGNATURE]"); ConsensusErrors.CoinstakeVerifySignatureFailed.Throw(); } // Min age requirement. if (this.IsConfirmedInNPrevBlocks(prevUtxo, prevChainedBlock, this.consensusOptions.StakeMinConfirmations - 1)) { this.logger.LogTrace("(-)[BAD_STAKE_DEPTH]"); ConsensusErrors.InvalidStakeDepth.Throw(); } this.CheckStakeKernelHash(context, prevChainedBlock, headerBits, prevBlock.Header.Time, prevBlockStake, prevUtxo, txIn.PrevOut, transaction.Time); this.logger.LogTrace("(-)[OK]"); }
/// <inheritdoc /> protected override void ProcessRule(PosRuleContext context, ChainedHeader chainedHeader, ProvenBlockHeader header) { this.CheckCoinstakeIsNotNull(header); this.CheckIfCoinstakeIsTrue(header); this.CheckHeaderAndCoinstakeTimes(header); FetchCoinsResponse coins = this.GetAndValidateCoins(header); UnspentOutputs prevUtxo = this.GetAndValidatePreviousUtxo(coins); this.CheckCoinstakeAgeRequirement(chainedHeader, prevUtxo); this.CheckSignature(header, prevUtxo); this.CheckStakeKernelHash(context, prevUtxo, header, chainedHeader); this.CheckCoinstakeMerkleProof(header); this.CheckHeaderSignatureWithCoinstakeKernel(header, prevUtxo); }
public void MissingOutput_Returns_False() { // Construct transaction. Transaction transaction = this.network.CreateTransaction(); transaction.Inputs.Add(new TxIn()); // Setup coinview to return as if the PrevOut does not exist. var unspentOutputArray = new UnspentOutputs[0]; this.coinView.Setup(x => x.FetchCoinsAsync(It.IsAny <uint256[]>(), default(CancellationToken))) .ReturnsAsync(new FetchCoinsResponse(unspentOutputArray, uint256.Zero)); var blockTxs = new List <Transaction>(); // Retriever fails but doesn't throw exception GetSenderResult result = this.senderRetriever.GetSender(transaction, this.coinView.Object, blockTxs); Assert.False(result.Success); Assert.Equal(result.Error, SenderRetriever.OutputsNotInCoinView); }
public async Task GetTxOutAsync_IncludeInMempool_UnspentTransactionFound_ReturnsModelAsync() { var txId = new uint256(1243124); Transaction transaction = this.CreateTransaction(); var unspentOutputs = new UnspentOutputs(1, transaction); this.pooledGetUnspentTransaction.Setup(s => s.GetUnspentTransactionAsync(txId)) .ReturnsAsync(unspentOutputs) .Verifiable(); this.controller = new FullNodeController(this.LoggerFactory.Object, this.pooledTransaction.Object, this.pooledGetUnspentTransaction.Object, this.getUnspentTransaction.Object, this.networkDifficulty.Object, this.consensusLoop.Object, this.fullNode.Object, this.nodeSettings, this.network, this.chain, this.chainState.Object, this.connectionManager.Object); GetTxOutModel model = await this.controller.GetTxOutAsync(txId.ToString(), 0, true).ConfigureAwait(false); this.pooledGetUnspentTransaction.Verify(); Assert.Equal(this.chain.Tip.HashBlock, model.BestBlock); Assert.True(model.Coinbase); Assert.Equal(3, model.Confirmations); Assert.Equal(new ScriptPubKey(transaction.Outputs[0].ScriptPubKey, this.network).Hex, model.ScriptPubKey.Hex); Assert.Equal(transaction.Outputs[0].Value, model.Value); }
public async Task <IActionResult> GetTxOutAsync([FromQuery] string trxid, uint vout = 0, bool includeMemPool = true) { try { Guard.NotEmpty(trxid, nameof(trxid)); uint256 txid; if (!uint256.TryParse(trxid, out txid)) { throw new ArgumentException(nameof(trxid)); } UnspentOutputs unspentOutputs = null; if (includeMemPool) { unspentOutputs = this.pooledGetUnspentTransaction != null ? await this.pooledGetUnspentTransaction.GetUnspentTransactionAsync(txid).ConfigureAwait(false) : null; } else { unspentOutputs = this.getUnspentTransaction != null ? await this.getUnspentTransaction.GetUnspentTransactionAsync(txid).ConfigureAwait(false) : null; } if (unspentOutputs == null) { return(Json(null)); } return(Json(new GetTxOutModel(unspentOutputs, vout, this.network, this.chainIndexer.Tip))); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
/// <inheritdoc/> public void CheckKernel(ContextStakeInformation context, ChainedBlock prevChainedBlock, uint headerBits, long transactionTime, OutPoint prevout) { this.logger.LogTrace("({0}:'{1}',{2}:0x{3:X},{4}:{5},{6}:'{7}.{8}')", nameof(prevChainedBlock), prevChainedBlock, nameof(headerBits), headerBits, nameof(transactionTime), transactionTime, nameof(prevout), prevout.Hash, prevout.N); FetchCoinsResponse coins = this.coinView.FetchCoinsAsync(new[] { prevout.Hash }).GetAwaiter().GetResult(); if ((coins == null) || (coins.UnspentOutputs.Length != 1)) { this.logger.LogTrace("(-)[READ_PREV_TX_FAILED]"); ConsensusErrors.ReadTxPrevFailed.Throw(); } ChainedBlock prevBlock = this.chain.GetBlock(coins.BlockHash); if (prevBlock == null) { this.logger.LogTrace("(-)[REORG]"); ConsensusErrors.ReadTxPrevFailed.Throw(); } UnspentOutputs prevUtxo = coins.UnspentOutputs[0]; if (this.IsConfirmedInNPrevBlocks(prevUtxo, prevChainedBlock, this.consensusOptions.StakeMinConfirmations - 1)) { this.logger.LogTrace("(-)[LOW_COIN_AGE]"); ConsensusErrors.InvalidStakeDepth.Throw(); } BlockStake prevBlockStake = this.stakeChain.Get(prevChainedBlock.HashBlock); if (prevBlockStake == null) { this.logger.LogTrace("(-)[BAD_STAKE_BLOCK]"); ConsensusErrors.BadStakeBlock.Throw(); } this.CheckStakeKernelHash(context, headerBits, prevBlockStake, prevUtxo, prevout, (uint)transactionTime); }
public async Task GetTxOutAsync_NullVoutandInMempool_PooledUnspentTransactionFound_ReturnsModelVoutZeroAsync() { var txId = new uint256(1243124); Transaction transaction = this.CreateTransaction(); var unspentOutputs = new UnspentOutputs(1, transaction); this.pooledGetUnspentTransaction.Setup(s => s.GetUnspentTransactionAsync(txId)) .ReturnsAsync(unspentOutputs) .Verifiable(); string txid = txId.ToString(); var json = (JsonResult)await this.controller.GetTxOutAsync(txid).ConfigureAwait(false); var resultModel = (GetTxOutModel)json.Value; this.getUnspentTransaction.Verify(); Assert.Equal(this.chain.Tip.HashBlock, resultModel.BestBlock); Assert.True(resultModel.Coinbase); Assert.Equal(3, resultModel.Confirmations); Assert.Equal(new ScriptPubKey(transaction.Outputs[0].ScriptPubKey, this.network).Hex, resultModel.ScriptPubKey.Hex); Assert.Equal(transaction.Outputs[0].Value, resultModel.Value); }
/// <summary> /// Checks if coinstake is spent on another chain. /// </summary> /// <param name="header">The proven block header.</param> /// <param name="context">The POS rule context.</param> /// <returns>The <see cref="UnspentOutputs"> found in a RewindData</returns> private UnspentOutputs CheckIfCoinstakeIsSpentOnAnotherChain(ProvenBlockHeader header, PosRuleContext context) { Transaction coinstake = header.Coinstake; TxIn input = coinstake.Inputs[0]; int?rewindDataIndex = this.PosParent.RewindDataIndexStore.Get(input.PrevOut.Hash, (int)input.PrevOut.N); if (!rewindDataIndex.HasValue) { this.Logger.LogTrace("(-)[NO_REWIND_DATA_INDEX_FOR_INPUT_PREVOUT]"); context.ValidationContext.InsufficientHeaderInformation = true; ConsensusErrors.ReadTxPrevFailedInsufficient.Throw(); } RewindData rewindData = this.PosParent.UtxoSet.GetRewindData(rewindDataIndex.Value).GetAwaiter().GetResult(); UnspentOutputs matchingUnspentUtxo = null; foreach (UnspentOutputs unspent in rewindData.OutputsToRestore) { if (unspent.TransactionId == input.PrevOut.Hash) { if (input.PrevOut.N < unspent.Outputs.Length) { matchingUnspentUtxo = unspent; break; } } } if (matchingUnspentUtxo == null) { this.Logger.LogTrace("(-)[UTXO_NOT_FOUND_IN_REWIND_DATA]"); context.ValidationContext.InsufficientHeaderInformation = true; ConsensusErrors.UtxoNotFoundInRewindData.Throw(); } return(matchingUnspentUtxo); }
/// <see cref="IStakeValidator.CheckStakeKernelHash"/> /// <exception cref="ConsensusException"> /// Throws exception with error <see cref="ConsensusErrors.PrevStakeNull" /> if check fails. /// </exception> private void CheckStakeKernelHash(PosRuleContext context, UnspentOutputs stakingCoins, ProvenBlockHeader header, ChainedHeader chainedHeader) { OutPoint prevOut = this.GetPreviousOut(header); uint transactionTime = header.Coinstake.Time; uint headerBits = chainedHeader.Header.Bits.ToCompact(); uint256 previousStakeModifier = this.GetPreviousStakeModifier(chainedHeader); if (previousStakeModifier == null) { this.Logger.LogTrace("(-)[MODIF_IS_NULL]"); ConsensusErrors.InvalidPreviousProvenHeaderStakeModifier.Throw(); } if (header.Coinstake.IsCoinStake) { this.Logger.LogTrace("Found coinstake checking kernal hash."); this.stakeValidator.CheckStakeKernelHash(context, headerBits, previousStakeModifier, stakingCoins, prevOut, transactionTime); } this.ComputeNextStakeModifier(header, chainedHeader, previousStakeModifier); }
/// <inheritdoc /> public Task <FetchCoinsResponse> FetchCoinsAsync(uint256[] txIds, CancellationToken cancellationToken = default(CancellationToken)) { Task <FetchCoinsResponse> task = Task.Run(() => { this.logger.LogTrace("({0}.{1}:{2})", nameof(txIds), nameof(txIds.Length), txIds?.Length); FetchCoinsResponse res = null; using (DBreeze.Transactions.Transaction transaction = this.dbreeze.GetTransaction()) { transaction.SynchronizeTables("BlockHash", "Coins"); transaction.ValuesLazyLoadingIsOn = false; using (new StopwatchDisposable(o => this.PerformanceCounter.AddQueryTime(o))) { uint256 blockHash = this.GetTipHash(transaction); var result = new UnspentOutputs[txIds.Length]; this.PerformanceCounter.AddQueriedEntities(txIds.Length); int i = 0; foreach (uint256 input in txIds) { Row <byte[], Coins> row = transaction.Select <byte[], Coins>("Coins", input.ToBytes(false)); UnspentOutputs outputs = row.Exists ? new UnspentOutputs(input, row.Value) : null; this.logger.LogTrace("Outputs for '{0}' were {1}.", input, outputs == null ? "NOT loaded" : "loaded"); result[i++] = outputs; } res = new FetchCoinsResponse(result, blockHash); } } this.logger.LogTrace("(-):*.{0}='{1}',*.{2}.{3}={4}", nameof(res.BlockHash), res.BlockHash, nameof(res.UnspentOutputs), nameof(res.UnspentOutputs.Length), res.UnspentOutputs.Length); return(res); }, cancellationToken); return(task); }
/// <see cref="IStakeValidator.CheckStakeKernelHash"/> /// <exception cref="ConsensusException"> /// Throws exception with error <see cref="ConsensusErrors.PrevStakeNull" /> if check fails. /// </exception> private void CheckStakeKernelHash(PosRuleContext context, UnspentOutputs stakingCoins, ProvenBlockHeader header, ChainedHeader chainedHeader) { OutPoint prevOut = this.GetPreviousOut(header); uint transactionTime = header.Coinstake.Time; uint headerBits = chainedHeader.Header.Bits.ToCompact(); uint256 previousStakeModifier = this.GetPreviousStakeModifier(chainedHeader); if (header.Coinstake.IsCoinStake) { this.Logger.LogTrace("Found coinstake checking kernal hash."); var validKernel = this.stakeValidator.CheckStakeKernelHash(context, headerBits, previousStakeModifier, stakingCoins, prevOut, transactionTime); if (!validKernel) { this.Logger.LogTrace("(-)[INVALID_STAKE_HASH_TARGET]"); ConsensusErrors.StakeHashInvalidTarget.Throw(); } } this.ComputeNextStakeModifier(header, chainedHeader, previousStakeModifier); }
/// <summary> /// Gets and validates unspent outputs based of coins fetched from coin view. /// </summary> /// <param name="header">The header.</param> /// <param name="context">Rule context.</param> /// <returns>The validated previous <see cref="UnspentOutputs"/></returns> private UnspentOutputs GetAndValidatePreviousUtxo(ProvenBlockHeader header, PosRuleContext context) { // First try and find the previous trx in the database. TxIn txIn = header.Coinstake.Inputs[0]; UnspentOutputs prevUtxo = null; FetchCoinsResponse coins = this.PosParent.UtxoSet.FetchCoinsAsync(new[] { txIn.PrevOut.Hash }).GetAwaiter().GetResult(); if (coins.UnspentOutputs[0] == null) { // We did not find the previous trx in the database, look in rewind data. prevUtxo = this.CheckIfCoinstakeIsSpentOnAnotherChain(header, context); } else { // The trx was found now check if the UTXO is spent. prevUtxo = coins.UnspentOutputs[0]; TxOut utxo = null; if (txIn.PrevOut.N < prevUtxo.Outputs.Length) { // Check that the size of the outs collection is the same as the expected position of the UTXO // Note the collection will not always represent the original size of the transaction unspent // outputs because when we store outputs do disk the last spent items are removed from the collection. utxo = prevUtxo.Outputs[txIn.PrevOut.N]; } if (utxo == null) { // UTXO is spent so find it in rewind data. prevUtxo = this.CheckIfCoinstakeIsSpentOnAnotherChain(header, context); } } return(prevUtxo); }
public async Task GetTxOutAsync_IncludeInMempool_UnspentTransactionFound_VOutNotFound_ReturnsModelAsync() { var txId = new uint256(1243124); Transaction transaction = this.CreateTransaction(); var unspentOutputs = new UnspentOutputs(1, transaction); this.pooledGetUnspentTransaction.Setup(s => s.GetUnspentTransactionAsync(txId)) .ReturnsAsync(unspentOutputs) .Verifiable(); string txid = txId.ToString(); uint vout = 13; bool includeMemPool = true; var json = (JsonResult)await this.controller.GetTxOutAsync(txid, vout, includeMemPool).ConfigureAwait(false); var resultModel = (GetTxOutModel)json.Value; this.pooledGetUnspentTransaction.Verify(); Assert.Equal(this.chain.Tip.HashBlock, resultModel.BestBlock); Assert.True(resultModel.Coinbase); Assert.Equal(3, resultModel.Confirmations); Assert.Null(resultModel.ScriptPubKey); Assert.Null(resultModel.Value); }
public async Task TestRewindAsync() { uint256 tip = await this.cachedCoinView.GetTipHashAsync(); Assert.Equal(this.concurrentChain.Genesis.HashBlock, tip); int currentHeight = 0; // Create a lot of new coins. List <UnspentOutputs> outputsList = this.CreateOutputsList(currentHeight + 1, 100); await this.SaveChangesAsync(outputsList, new List <TxOut[]>(), currentHeight + 1); currentHeight++; await this.cachedCoinView.FlushAsync(true); uint256 tipAfterOriginalCoinsCreation = await this.cachedCoinView.GetTipHashAsync(); // Collection that will be used as a coinview that we will update in parallel. Needed to verify that actual coinview is ok. List <OutPoint> outPoints = this.ConvertToListOfOutputPoints(outputsList); // Copy of current state to later rewind and verify against it. List <OutPoint> copyOfOriginalOutPoints = new List <OutPoint>(outPoints); List <OutPoint> copyAfterHalfOfAdditions = new List <OutPoint>(); uint256 coinviewTipAfterHalf = null; int addChangesTimes = 500; // Spend some coins in the next N saves. for (int i = 0; i < addChangesTimes; ++i) { uint256 txId = outPoints[this.random.Next(0, outPoints.Count)].Hash; List <OutPoint> txPoints = outPoints.Where(x => x.Hash == txId).ToList(); this.Shuffle(txPoints); List <OutPoint> txPointsToSpend = txPoints.Take(txPoints.Count / 2).ToList(); // First spend in cached coinview FetchCoinsResponse response = await this.cachedCoinView.FetchCoinsAsync(new[] { txId }); Assert.Single(response.UnspentOutputs); UnspentOutputs coins = response.UnspentOutputs[0]; UnspentOutputs unchangedClone = coins.Clone(); foreach (OutPoint outPointToSpend in txPointsToSpend) { coins.Spend(outPointToSpend.N); } // Spend from outPoints. outPoints.RemoveAll(x => txPointsToSpend.Contains(x)); // Save coinview await this.SaveChangesAsync(new List <UnspentOutputs>() { coins }, new List <TxOut[]>() { unchangedClone.Outputs }, currentHeight + 1); currentHeight++; if (i == addChangesTimes / 2) { copyAfterHalfOfAdditions = new List <OutPoint>(outPoints); coinviewTipAfterHalf = await this.cachedCoinView.GetTipHashAsync(); } } await this.ValidateCoinviewIntegrityAsync(outPoints); for (int i = 0; i < addChangesTimes; i++) { await this.cachedCoinView.RewindAsync(); uint256 currentTip = await this.cachedCoinView.GetTipHashAsync(); if (currentTip == coinviewTipAfterHalf) { await this.ValidateCoinviewIntegrityAsync(copyAfterHalfOfAdditions); } } Assert.Equal(tipAfterOriginalCoinsCreation, await this.cachedCoinView.GetTipHashAsync()); await this.ValidateCoinviewIntegrityAsync(copyOfOriginalOutPoints); }
/// <inheritdoc /> public bool CheckStakeKernelHash(PosRuleContext context, uint headerBits, uint256 prevStakeModifier, UnspentOutputs stakingCoins, OutPoint prevout, uint transactionTime) { Guard.NotNull(context, nameof(context)); Guard.NotNull(prevout, nameof(prevout)); Guard.NotNull(stakingCoins, nameof(stakingCoins)); if (transactionTime < stakingCoins.Time) { this.logger.LogDebug("Coinstake transaction timestamp {0} is lower than it's own UTXO timestamp {1}.", transactionTime, stakingCoins.Time); this.logger.LogTrace("(-)[BAD_STAKE_TIME]"); ConsensusErrors.StakeTimeViolation.Throw(); } // Base target. var target = new Target(headerBits).ToBigInteger(); // TODO: Investigate: // The POS protocol should probably put a limit on the max amount that can be staked // not a hard limit but a limit that allow any amount to be staked with a max weight value. // the max weight should not exceed the max uint256 array size (array size = 32). // Weighted target. var valueIn = stakingCoins.Outputs[prevout.N].Value.Satoshi; var weight = BigInteger.ValueOf(valueIn); var weightedTarget = target.Multiply(weight); context.TargetProofOfStake = ToUInt256(weightedTarget); this.logger.LogDebug("POS target is '{0}', weighted target for {1} coins is '{2}'.", ToUInt256(target), valueIn, context.TargetProofOfStake); if (this.legacytimefield == null) { this.legacytimefield = this.network.Consensus.ConsensusFactory.CreateTransaction() is IPosTransactionWithTime; } // Calculate hash. using (var ms = new MemoryStream()) { var serializer = new BitcoinStream(ms, true); serializer.ReadWrite(prevStakeModifier); if (this.legacytimefield == true) // to stay compatible with legacy ODN { serializer.ReadWrite(stakingCoins.Time); } serializer.ReadWrite(prevout.Hash); serializer.ReadWrite(prevout.N); serializer.ReadWrite(transactionTime); context.HashProofOfStake = Hashes.Hash256(ms.ToArray()); } this.logger.LogDebug("Stake modifier V2 is '{0}', hash POS is '{1}'.", prevStakeModifier, context.HashProofOfStake); // Now check if proof-of-stake hash meets target protocol. var hashProofOfStakeTarget = new BigInteger(1, context.HashProofOfStake.ToBytes(false)); if (hashProofOfStakeTarget.CompareTo(weightedTarget) > 0) { this.logger.LogTrace("(-)[TARGET_MISSED]"); return(false); } return(true); }
/// <inheritdoc /> public void SaveChanges(IList <UnspentOutputs> unspentOutputs, IEnumerable <TxOut[]> originalOutputs, uint256 oldBlockHash, uint256 nextBlockHash, int height, List <RewindData> rewindDataList = null) { Guard.NotNull(oldBlockHash, nameof(oldBlockHash)); Guard.NotNull(nextBlockHash, nameof(nextBlockHash)); Guard.NotNull(unspentOutputs, nameof(unspentOutputs)); lock (this.lockobj) { if ((this.blockHash != null) && (oldBlockHash != this.blockHash)) { this.logger.LogTrace("{0}:'{1}'", nameof(this.blockHash), this.blockHash); this.logger.LogTrace("(-)[BLOCKHASH_MISMATCH]"); throw new InvalidOperationException("Invalid oldBlockHash"); } this.blockHeight = height; this.blockHash = nextBlockHash; var rewindData = new RewindData(oldBlockHash); var indexItems = new Dictionary <OutPoint, int>(); foreach (UnspentOutputs unspent in unspentOutputs) { if (!this.cachedUtxoItems.TryGetValue(unspent.TransactionId, out CacheItem cacheItem)) { // This can happen very rarely in the case where we fetch items from // disk but immediately call the Evict method which then removes the cached item(s). this.logger.LogTrace("Outputs of transaction ID '{0}' are not found in cache, creating them.", unspent.TransactionId); FetchCoinsResponse result = this.inner.FetchCoins(new[] { unspent.TransactionId }); UnspentOutputs unspentOutput = result.UnspentOutputs[0]; cacheItem = new CacheItem(); cacheItem.ExistInInner = unspentOutput != null; cacheItem.IsDirty = false; cacheItem.UnspentOutputs = unspentOutput?.Clone(); this.cachedUtxoItems.TryAdd(unspent.TransactionId, cacheItem); this.logger.LogTrace("CacheItem added to the cache during save '{0}'.", cacheItem.UnspentOutputs); } // If cacheItem.UnspentOutputs is null this means the trx was not stored in the disk, // that means the trx (and UTXO) is new and all the UTXOs need to be stored in cache // otherwise we store to cache only the UTXO that have been spent. if (cacheItem.UnspentOutputs != null) { // To handle rewind we'll need to restore the original outputs, // so we clone it and save it in rewind data. UnspentOutputs clone = unspent.Clone(); // We take the original items that are in cache and put them in rewind data. clone.Outputs = cacheItem.UnspentOutputs.Outputs.ToArray(); this.logger.LogTrace("Modifying transaction '{0}' in OutputsToRestore rewind data.", unspent.TransactionId); rewindData.OutputsToRestore.Add(clone); this.logger.LogTrace("Cache item before spend {0}:'{1}'.", nameof(cacheItem.UnspentOutputs), cacheItem.UnspentOutputs); // Now modify the cached items with the mutated data. cacheItem.UnspentOutputs.Spend(unspent); this.logger.LogTrace("Cache item after spend {0}:'{1}'.", nameof(cacheItem.UnspentOutputs), cacheItem.UnspentOutputs); } else { // New trx so it needs to be deleted if a rewind happens. this.logger.LogTrace("Adding transaction '{0}' to TransactionsToRemove rewind data.", unspent.TransactionId); rewindData.TransactionsToRemove.Add(unspent.TransactionId); // Put in the cache the new UTXOs. this.logger.LogTrace("Setting {0} to {1}: '{2}'.", nameof(cacheItem.UnspentOutputs), nameof(unspent), unspent); cacheItem.UnspentOutputs = unspent; } cacheItem.IsDirty = true; if (this.rewindDataIndexCache != null) { for (int i = 0; i < unspent.Outputs.Length; i++) { var key = new OutPoint(unspent.TransactionId, i); indexItems[key] = this.blockHeight; } } // Inner does not need to know pruned unspent that it never saw. if (cacheItem.UnspentOutputs.IsPrunable && !cacheItem.ExistInInner) { this.logger.LogTrace("Outputs of transaction ID '{0}' are prunable and not in underlaying coinview, removing from cache.", unspent.TransactionId); this.cachedUtxoItems.Remove(unspent.TransactionId); } } if (this.rewindDataIndexCache != null && indexItems.Any()) { this.rewindDataIndexCache.Save(indexItems); this.rewindDataIndexCache.Flush(this.blockHeight); } this.cachedRewindDataIndex.Add(this.blockHeight, rewindData); } }
/// <inheritdoc/> public override void CheckMaturity(UnspentOutputs coins, int spendHeight) { base.CheckCoinbaseMaturity(coins, spendHeight); }
/// <summary> /// Contains checks that need to be performed on each input once UTXO data is available. /// </summary> /// <param name="transaction">The transaction that is having its input examined.</param> /// <param name="coins">The unspent output consumed by the input being examined.</param> protected virtual void CheckInputValidity(Transaction transaction, UnspentOutputs coins) { }
protected override void CheckInputValidity(Transaction transaction, UnspentOutputs coins) { return; }
/// <summary> /// Network specific logic that checks the maturity of UTXO sets. /// <para> /// Refer to <see cref="CheckMaturity(UnspentOutputs, int)"/>. /// </para> /// </summary> public abstract void CheckMaturity(UnspentOutputs coins, int spendHeight);
/// <summary> /// Creates the test chain with some default blocks and txs. /// </summary> /// <param name="network">Network to create the chain on.</param> /// <param name="scriptPubKey">Public key to create blocks/txs with.</param> /// <returns>Context object representing the test chain.</returns> public static async Task <ITestChainContext> CreateAsync(Network network, Script scriptPubKey, string dataDir) { var nodeSettings = new NodeSettings(network, args: new string[] { $"-datadir={dataDir}" }); ILoggerFactory loggerFactory = nodeSettings.LoggerFactory; IDateTimeProvider dateTimeProvider = DateTimeProvider.Default; network.Consensus.Options = new ConsensusOptions(); new FullNodeBuilderConsensusExtension.PowConsensusRulesRegistration().RegisterRules(network.Consensus); // Dont check PoW of a header in this test. network.Consensus.HeaderValidationRules.RemoveAll(x => x.GetType() == typeof(CheckDifficultyPowRule)); var consensusSettings = new ConsensusSettings(nodeSettings); var chain = new ConcurrentChain(network); var inMemoryCoinView = new InMemoryCoinView(chain.Tip.HashBlock); var chainState = new ChainState(); var deployments = new NodeDeployments(network, chain); ConsensusRuleEngine consensusRules = new PowConsensusRuleEngine(network, loggerFactory, dateTimeProvider, chain, deployments, consensusSettings, new Checkpoints(), inMemoryCoinView, chainState, new InvalidBlockHashStore(dateTimeProvider), new NodeStats(dateTimeProvider)).Register(); ConsensusManager consensus = ConsensusManagerHelper.CreateConsensusManager(network, dataDir, chainState); var genesis = new ChainedHeader(network.GetGenesis().Header, network.GenesisHash, 0); chainState.BlockStoreTip = genesis; await consensus.InitializeAsync(genesis).ConfigureAwait(false); var blockPolicyEstimator = new BlockPolicyEstimator(new MempoolSettings(nodeSettings), loggerFactory, nodeSettings); var mempool = new TxMempool(dateTimeProvider, blockPolicyEstimator, loggerFactory, nodeSettings); var mempoolLock = new MempoolSchedulerLock(); var minerSettings = new MinerSettings(nodeSettings); // Simple block creation, nothing special yet: var blockDefinition = new PowBlockDefinition(consensus, dateTimeProvider, loggerFactory, mempool, mempoolLock, minerSettings, network, consensusRules); BlockTemplate newBlock = blockDefinition.Build(chain.Tip, scriptPubKey); await consensus.BlockMinedAsync(newBlock.Block); List <BlockInfo> blockinfo = CreateBlockInfoList(); // We can't make transactions until we have inputs therefore, load 100 blocks. var blocks = new List <Block>(); var srcTxs = new List <Transaction>(); for (int i = 0; i < blockinfo.Count; ++i) { Block currentBlock = Block.Load(newBlock.Block.ToBytes(network.Consensus.ConsensusFactory), network); currentBlock.Header.HashPrevBlock = chain.Tip.HashBlock; currentBlock.Header.Version = 1; currentBlock.Header.Time = Utils.DateTimeToUnixTime(chain.Tip.GetMedianTimePast()) + 1; Transaction txCoinbase = network.CreateTransaction(currentBlock.Transactions[0].ToBytes()); txCoinbase.Inputs.Clear(); txCoinbase.Version = 1; txCoinbase.AddInput(new TxIn(new Script(new[] { Op.GetPushOp(blockinfo[i].extraNonce), Op.GetPushOp(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())); currentBlock.Transactions[0] = txCoinbase; if (srcTxs.Count < 4) { srcTxs.Add(currentBlock.Transactions[0]); } currentBlock.UpdateMerkleRoot(); currentBlock.Header.Nonce = blockinfo[i].nonce; chain.SetTip(currentBlock.Header); } // Just to make sure we can still make simple blocks blockDefinition = new PowBlockDefinition(consensus, dateTimeProvider, loggerFactory, mempool, mempoolLock, minerSettings, network, consensusRules); blockDefinition.Build(chain.Tip, scriptPubKey); var mempoolValidator = new MempoolValidator(mempool, mempoolLock, dateTimeProvider, new MempoolSettings(nodeSettings), chain, inMemoryCoinView, loggerFactory, nodeSettings, consensusRules); var outputs = new List <UnspentOutputs>(); foreach (Transaction tx in srcTxs) { var output = new UnspentOutputs(0, tx); outputs.Add(output); } await inMemoryCoinView.SaveChangesAsync(outputs, new List <TxOut[]>(), chain.GetBlock(0).HashBlock, chain.GetBlock(1).HashBlock, chain.GetBlock(0).Height); return(new TestChainContext { MempoolValidator = mempoolValidator, SrcTxs = srcTxs }); }
/// <inheritdoc /> public override Task SaveChangesAsync(IEnumerable <UnspentOutputs> unspentOutputs, IEnumerable <TxOut[]> originalOutputs, uint256 oldBlockHash, uint256 nextBlockHash) { this.logger.LogTrace("({0}.Count():{1},{2}.Count():{3},{4}:'{5}',{6}:'{7}')", nameof(unspentOutputs), unspentOutputs?.Count(), nameof(originalOutputs), originalOutputs?.Count(), nameof(oldBlockHash), oldBlockHash, nameof(nextBlockHash), nextBlockHash); RewindData rewindData = originalOutputs != null ? new RewindData(oldBlockHash) : null; int insertedEntities = 0; List <UnspentOutputs> all = unspentOutputs.ToList(); Dictionary <uint256, TxOut[]> unspentToOriginal = new Dictionary <uint256, TxOut[]>(all.Count); using (new StopwatchDisposable(o => this.PerformanceCounter.AddInsertTime(o))) { if (originalOutputs != null) { IEnumerator <TxOut[]> originalEnumerator = originalOutputs.GetEnumerator(); foreach (UnspentOutputs output in all) { originalEnumerator.MoveNext(); unspentToOriginal.Add(output.TransactionId, originalEnumerator.Current); } } } Task task = Task.Run(() => { this.logger.LogTrace("()"); using (DBreeze.Transactions.Transaction transaction = this.dbreeze.GetTransaction()) { transaction.ValuesLazyLoadingIsOn = false; transaction.SynchronizeTables("BlockHash", "Coins", "Rewind"); transaction.Technical_SetTable_OverwriteIsNotAllowed("Coins"); using (new StopwatchDisposable(o => this.PerformanceCounter.AddInsertTime(o))) { uint256 current = this.GetCurrentHash(transaction); if (current != oldBlockHash) { this.logger.LogTrace("(-)[BLOCKHASH_MISMATCH]"); throw new InvalidOperationException("Invalid oldBlockHash"); } this.SetBlockHash(transaction, nextBlockHash); all.Sort(UnspentOutputsComparer.Instance); foreach (UnspentOutputs coin in all) { this.logger.LogTrace("Outputs of transaction ID '{0}' are {1} and will be {2} to the database.", coin.TransactionId, coin.IsPrunable ? "PRUNABLE" : "NOT PRUNABLE", coin.IsPrunable ? "removed" : "inserted"); if (coin.IsPrunable) { transaction.RemoveKey("Coins", coin.TransactionId.ToBytes(false)); } else { transaction.Insert("Coins", coin.TransactionId.ToBytes(false), coin.ToCoins()); } if (originalOutputs != null) { TxOut[] original = null; unspentToOriginal.TryGetValue(coin.TransactionId, out original); if (original == null) { // This one haven't existed before, if we rewind, delete it. rewindData.TransactionsToRemove.Add(coin.TransactionId); } else { // We'll need to restore the original outputs. UnspentOutputs clone = coin.Clone(); clone._Outputs = original.ToArray(); rewindData.OutputsToRestore.Add(clone); } } } if (rewindData != null) { int nextRewindIndex = this.GetRewindIndex(transaction) + 1; this.logger.LogTrace("Rewind state #{0} created.", nextRewindIndex); transaction.Insert("Rewind", nextRewindIndex, rewindData); } insertedEntities += all.Count; transaction.Commit(); } } this.PerformanceCounter.AddInsertedEntities(insertedEntities); this.logger.LogTrace("(-)"); }); this.logger.LogTrace("(-)"); return(task); }
/// <inheritdoc /> public async Task <FetchCoinsResponse> FetchCoinsAsync(uint256[] txIds, CancellationToken cancellationToken = default(CancellationToken)) { Guard.NotNull(txIds, nameof(txIds)); this.logger.LogTrace("({0}.{1}:{2})", nameof(txIds), nameof(txIds.Length), txIds.Length); FetchCoinsResponse result = null; var outputs = new UnspentOutputs[txIds.Length]; var miss = new List <int>(); var missedTxIds = new List <uint256>(); using (await this.lockobj.LockAsync(cancellationToken).ConfigureAwait(false)) { for (int i = 0; i < txIds.Length; i++) { CacheItem cache; if (!this.unspents.TryGetValue(txIds[i], out cache)) { this.logger.LogTrace("Cache missed for transaction ID '{0}'.", txIds[i]); miss.Add(i); missedTxIds.Add(txIds[i]); } else { this.logger.LogTrace("Cache hit for transaction ID '{0}'.", txIds[i]); outputs[i] = cache.UnspentOutputs == null ? null : cache.UnspentOutputs.IsPrunable ? null : cache.UnspentOutputs.Clone(); } } this.PerformanceCounter.AddMissCount(miss.Count); this.PerformanceCounter.AddHitCount(txIds.Length - miss.Count); } this.logger.LogTrace("{0} cache missed transaction needs to be loaded from underlying CoinView.", missedTxIds.Count); FetchCoinsResponse fetchedCoins = await this.Inner.FetchCoinsAsync(missedTxIds.ToArray(), cancellationToken).ConfigureAwait(false); using (await this.lockobj.LockAsync(cancellationToken).ConfigureAwait(false)) { uint256 innerblockHash = fetchedCoins.BlockHash; if (this.blockHash == null) { Debug.Assert(this.unspents.Count == 0); this.innerBlockHash = innerblockHash; this.blockHash = this.innerBlockHash; } for (int i = 0; i < miss.Count; i++) { int index = miss[i]; UnspentOutputs unspent = fetchedCoins.UnspentOutputs[i]; outputs[index] = unspent; var cache = new CacheItem(); cache.ExistInInner = unspent != null; cache.IsDirty = false; cache.UnspentOutputs = unspent; cache.OriginalOutputs = unspent?.Outputs.ToArray(); this.unspents.TryAdd(txIds[index], cache); } result = new FetchCoinsResponse(outputs, this.blockHash); } int cacheEntryCount = this.CacheEntryCount; if (cacheEntryCount > this.MaxItems) { this.logger.LogTrace("Cache is full now with {0} entries, evicting ...", cacheEntryCount); await this.EvictAsync().ConfigureAwait(false); } this.logger.LogTrace("(-):*.{0}='{1}',*.{2}.{3}={4}", nameof(result.BlockHash), result.BlockHash, nameof(result.UnspentOutputs), nameof(result.UnspentOutputs.Length), result.UnspentOutputs.Length); return(result); }
/// <summary> /// Checks that the stake kernel hash satisfies the target difficulty. /// </summary> /// <param name="context">Staking context.</param> /// <param name="headerBits">Chained block's header bits, which define the difficulty target.</param> /// <param name="prevBlockStake">Information about previous staked block.</param> /// <param name="stakingCoins">Coins that participate in staking.</param> /// <param name="prevout">Information about transaction id and index.</param> /// <param name="transactionTime">Transaction time.</param> /// <remarks> /// Coinstake must meet hash target according to the protocol: /// kernel (input 0) must meet the formula /// <c>hash(stakeModifierV2 + stakingCoins.Time + prevout.Hash + prevout.N + transactionTime) < target * weight</c>. /// This ensures that the chance of getting a coinstake is proportional to the amount of coins one owns. /// <para> /// The reason this hash is chosen is the following: /// <list type="number"> /// <item><paramref name="prevBlockStake.StakeModifierV2"/>: Scrambles computation to make it very difficult to precompute future proof-of-stake.</item> /// <item><paramref name="stakingCoins.Time"/>: Time of the coinstake UTXO. Slightly scrambles computation.</item> /// <item><paramref name="prevout.Hash"/> Hash of stakingCoins UTXO, to reduce the chance of nodes generating coinstake at the same time.</item> /// <item><paramref name="prevout.N"/>: Output number of stakingCoins UTXO, to reduce the chance of nodes generating coinstake at the same time.</item> /// <item><paramref name="transactionTime"/>: Timestamp of the coinstake transaction.</item> /// </list> /// Block or transaction tx hash should not be used here as they can be generated in vast /// quantities so as to generate blocks faster, degrading the system back into a proof-of-work situation. /// </para> /// </remarks> /// <exception cref="ConsensusErrors.StakeTimeViolation">Thrown in case transaction time is lower than it's own UTXO timestamp.</exception> /// <exception cref="ConsensusErrors.StakeHashInvalidTarget">Thrown in case PoS hash doesn't meet target protocol.</exception> private void CheckStakeKernelHash(ContextStakeInformation context, uint headerBits, BlockStake prevBlockStake, UnspentOutputs stakingCoins, OutPoint prevout, uint transactionTime) { this.logger.LogTrace("({0}:{1:X},{2}.{3}:'{4}',{5}:'{6}/{7}',{8}:'{9}',{10}:{11})", nameof(headerBits), headerBits, nameof(prevBlockStake), nameof(prevBlockStake.HashProof), prevBlockStake.HashProof, nameof(stakingCoins), stakingCoins.TransactionId, stakingCoins.Height, nameof(prevout), prevout, nameof(transactionTime), transactionTime); if (transactionTime < stakingCoins.Time) { this.logger.LogTrace("Coinstake transaction timestamp {0} is lower than it's own UTXO timestamp {1}.", transactionTime, stakingCoins.Time); this.logger.LogTrace("(-)[BAD_STAKE_TIME]"); ConsensusErrors.StakeTimeViolation.Throw(); } // Base target. BigInteger target = new Target(headerBits).ToBigInteger(); // TODO: Investigate: // The POS protocol should probably put a limit on the max amount that can be staked // not a hard limit but a limit that allow any amount to be staked with a max weight value. // the max weight should not exceed the max uint256 array size (array size = 32). // Weighted target. long valueIn = stakingCoins.Outputs[prevout.N].Value.Satoshi; BigInteger weight = BigInteger.ValueOf(valueIn); BigInteger weightedTarget = target.Multiply(weight); context.TargetProofOfStake = ToUInt256(weightedTarget); this.logger.LogTrace("POS target is '{0}', weighted target for {1} coins is '{2}'.", ToUInt256(target), valueIn, context.TargetProofOfStake); uint256 stakeModifierV2 = prevBlockStake.StakeModifierV2; // Calculate hash. using (var ms = new MemoryStream()) { var serializer = new BitcoinStream(ms, true); serializer.ReadWrite(stakeModifierV2); serializer.ReadWrite(stakingCoins.Time); serializer.ReadWrite(prevout.Hash); serializer.ReadWrite(prevout.N); serializer.ReadWrite(transactionTime); context.HashProofOfStake = Hashes.Hash256(ms.ToArray()); } this.logger.LogTrace("Stake modifier V2 is '{0}', hash POS is '{1}'.", stakeModifierV2, context.HashProofOfStake); // Now check if proof-of-stake hash meets target protocol. var hashProofOfStakeTarget = new BigInteger(1, context.HashProofOfStake.ToBytes(false)); if (hashProofOfStakeTarget.CompareTo(weightedTarget) > 0) { this.logger.LogTrace("(-)[TARGET_MISSED]"); ConsensusErrors.StakeHashInvalidTarget.Throw(); } this.logger.LogTrace("(-)[OK]"); }