/// <summary> /// Processes orphan transactions. /// Executed when receive a new transaction through MempoolBehavior. /// </summary> /// <param name="behavior">Memory pool behavior that received new transaction.</param> /// <param name="tx">The new transaction received.</param> public async Task ProcessesOrphans(MempoolBehavior behavior, Transaction tx) { Queue <OutPoint> vWorkQueue = new Queue <OutPoint>(); List <uint256> vEraseQueue = new List <uint256>(); uint256 trxHash = tx.GetHash(); for (int index = 0; index < tx.Outputs.Count; index++) { vWorkQueue.Enqueue(new OutPoint(trxHash, index)); } // Recursively process any orphan transactions that depended on this one List <ulong> setMisbehaving = new List <ulong>(); while (vWorkQueue.Any()) { // mapOrphanTransactionsByPrev.TryGet() does a .ToList() to take a new collection // of orphans as this collection may be modifed later by anotehr thread List <OrphanTx> itByPrev = await this.MempoolLock.ReadAsync(() => this.mapOrphanTransactionsByPrev.TryGet(vWorkQueue.Dequeue())?.ToList()); if (itByPrev == null) { continue; } foreach (OrphanTx mi in itByPrev) { Transaction orphanTx = mi.Tx; //->second.tx; uint256 orphanHash = orphanTx.GetHash(); ulong fromPeer = mi.NodeId; // (*mi)->second.fromPeer; if (setMisbehaving.Contains(fromPeer)) { continue; } // Use a dummy CValidationState so someone can't setup nodes to counter-DoS based on orphan // resolution (that is, feeding people an invalid transaction based on LegitTxX in order to get // anyone relaying LegitTxX banned) MempoolValidationState stateDummy = new MempoolValidationState(true); if (await this.Validator.AcceptToMemoryPool(stateDummy, orphanTx)) { this.mempoolLogger.LogInformation($"accepted orphan tx {orphanHash}"); await behavior.RelayTransaction(orphanTx.GetHash()); this.signals.SignalTransaction(orphanTx); for (int index = 0; index < orphanTx.Outputs.Count; index++) { vWorkQueue.Enqueue(new OutPoint(orphanHash, index)); } vEraseQueue.Add(orphanHash); } else if (!stateDummy.MissingInputs) { int nDos = 0; if (stateDummy.IsInvalid && nDos > 0) { // Punish peer that gave us an invalid orphan tx //Misbehaving(fromPeer, nDos); setMisbehaving.Add(fromPeer); this.mempoolLogger.LogInformation($"invalid orphan tx {orphanHash}"); } // Has inputs but not accepted to mempool // Probably non-standard or insufficient fee/priority this.mempoolLogger.LogInformation($"removed orphan tx {orphanHash}"); vEraseQueue.Add(orphanHash); if (!orphanTx.HasWitness && !stateDummy.CorruptionPossible) { // Do not use rejection cache for witness transactions or // witness-stripped transactions, as they can have been malleated. // See https://github.com/bitcoin/bitcoin/issues/8279 for details. await this.MempoolLock.WriteAsync(() => this.recentRejects.TryAdd(orphanHash, orphanHash)); } } this.memPool.Check(new MempoolCoinView(this.coinView, this.memPool, this.MempoolLock, this.Validator)); } } foreach (uint256 hash in vEraseQueue) { await this.EraseOrphanTx(hash); } }
/// <summary> /// Processing of the transaction payload message from peer. /// Adds transaction from the transaction payload to the memory pool. /// </summary> /// <param name="peer">Peer sending the message.</param> /// <param name="transactionPayload">The payload for the message.</param> private async Task ProcessTxPayloadAsync(INetworkPeer peer, TxPayload transactionPayload) { // Stop processing the transaction early if we are in blocks only mode. if (this.isBlocksOnlyMode) { this.logger.LogDebug("Transaction sent in violation of protocol from peer '{0}'.", peer.RemoteSocketEndpoint); this.logger.LogTrace("(-)[BLOCKSONLY]"); return; } Transaction trx = transactionPayload.Obj; uint256 trxHash = trx.GetHash(); // add to local filter lock (this.lockObject) { this.filterInventoryKnown.Add(trxHash); } this.logger.LogDebug("Added transaction ID '{0}' to known inventory filter.", trxHash); var state = new MempoolValidationState(true); if (!await this.orphans.AlreadyHaveAsync(trxHash) && await this.validator.AcceptToMemoryPool(state, trx)) { await this.validator.SanityCheck(); this.RelayTransaction(trxHash); this.signals.Publish(new TransactionReceived(trx)); long mmsize = state.MempoolSize; long memdyn = state.MempoolDynamicSize; this.logger.LogInformation("Transaction ID '{0}' accepted to memory pool from peer '{1}' (poolsz {2} txn, {3} kb).", trxHash, peer.RemoteSocketEndpoint, mmsize, memdyn / 1000); await this.orphans.ProcessesOrphansAsync(this, trx); } else if (state.MissingInputs) { this.orphans.ProcessesOrphansMissingInputs(peer, trx); } else { // Do not use rejection cache for witness transactions or // witness-stripped transactions, as they can have been malleated. // See https://github.com/bitcoin/bitcoin/issues/8279 for details. if (!trx.HasWitness && !state.CorruptionPossible) { this.orphans.AddToRecentRejects(trxHash); } // Always relay transactions received from whitelisted peers, even // if they were already in the mempool or rejected from it due // to policy, allowing the node to function as a gateway for // nodes hidden behind it. // // Never relay transactions that we would assign a non-zero DoS // score for, as we expect peers to do the same with us in that // case. if (this.isPeerWhitelistedForRelay) { if (!state.IsInvalid) { this.logger.LogDebug("Force relaying transaction ID '{0}' from whitelisted peer '{1}'.", trxHash, peer.RemoteSocketEndpoint); this.RelayTransaction(trxHash); } else { this.logger.LogDebug("Not relaying invalid transaction ID '{0}' from whitelisted peer '{1}' ({2}).", trxHash, peer.RemoteSocketEndpoint, state); } } } if (state.IsInvalid) { this.logger.LogDebug("Transaction ID '{0}' from peer '{1}' was not accepted. Invalid state of '{2}'.", trxHash, peer.RemoteSocketEndpoint, state); } }
/// <summary> /// Constructs a memory pool exception object. /// Exception message is set from <see cref="MempoolValidationState.ErrorMessage"/>. /// </summary> /// <param name="state">Validation state of the memory pool.</param> public MempoolErrorException(MempoolValidationState state) : base(state.ErrorMessage) { this.ValidationState = state; }
/// <summary> /// Processes orphan transactions. /// Executed when receive a new transaction through MempoolBehavior. /// </summary> /// <param name="behavior">Memory pool behavior that received new transaction.</param> /// <param name="tx">The new transaction received.</param> public async Task ProcessesOrphansAsync(MempoolBehavior behavior, Transaction tx) { var workQueue = new Queue <OutPoint>(); var eraseQueue = new List <uint256>(); uint256 trxHash = tx.GetHash(); for (int index = 0; index < tx.Outputs.Count; index++) { workQueue.Enqueue(new OutPoint(trxHash, index)); } // Recursively process any orphan transactions that depended on this one var setMisbehaving = new List <ulong>(); while (workQueue.Any()) { List <OrphanTx> itByPrev = null; lock (this.lockObject) { List <OrphanTx> prevOrphans = this.mapOrphanTransactionsByPrev.TryGet(workQueue.Dequeue()); if (prevOrphans != null) { // Create a copy of the list so we can manage it outside of the lock. itByPrev = prevOrphans.ToList(); } } if (itByPrev == null) { continue; } foreach (OrphanTx mi in itByPrev) { Transaction orphanTx = mi.Tx; uint256 orphanHash = orphanTx.GetHash(); ulong fromPeer = mi.NodeId; if (setMisbehaving.Contains(fromPeer)) { continue; } // Use a dummy CValidationState so someone can't setup nodes to counter-DoS based on orphan // resolution (that is, feeding people an invalid transaction based on LegitTxX in order to get // anyone relaying LegitTxX banned) var stateDummy = new MempoolValidationState(true); if (await this.Validator.AcceptToMemoryPool(stateDummy, orphanTx)) { this.logger.LogInformation("accepted orphan tx {0}", orphanHash); behavior.RelayTransaction(orphanTx.GetHash()); this.signals.Publish(new TransactionReceived(orphanTx)); for (int index = 0; index < orphanTx.Outputs.Count; index++) { workQueue.Enqueue(new OutPoint(orphanHash, index)); } eraseQueue.Add(orphanHash); } else if (!stateDummy.MissingInputs) { int nDos = 0; if (stateDummy.IsInvalid && nDos > 0) { // Punish peer that gave us an invalid orphan tx //Misbehaving(fromPeer, nDos); setMisbehaving.Add(fromPeer); this.logger.LogInformation("invalid orphan tx {0}", orphanHash); } // Has inputs but not accepted to mempool // Probably non-standard or insufficient fee/priority this.logger.LogInformation("removed orphan tx {0}", orphanHash); eraseQueue.Add(orphanHash); if (!orphanTx.HasWitness && !stateDummy.CorruptionPossible) { // Do not use rejection cache for witness transactions or // witness-stripped transactions, as they can have been malleated. // See https://github.com/bitcoin/bitcoin/issues/8279 for details. this.AddToRecentRejects(orphanHash); } } // TODO: implement sanity checks. //this.memPool.Check(new MempoolCoinView(this.coinView, this.memPool, this.MempoolLock, this.Validator)); } } if (eraseQueue.Count > 0) { lock (this.lockObject) { foreach (uint256 hash in eraseQueue) { this.EraseOrphanTxLock(hash); } } } }
public MempoolValidationContext(Transaction transaction, MempoolValidationState state) { this.Transaction = transaction; this.TransactionHash = transaction.GetHash(); this.State = state; }
private async Task AcceptToMemoryPoolWorker(MempoolValidationState state, Transaction tx, List <uint256> vHashTxnToUncache) { var context = new MempoolValidationContext(tx, state); this.PreMempoolChecks(context); // create the MemPoolCoinView and load relevant utxoset context.View = new MempoolCoinView(this.coinView, this.memPool, this.mempoolScheduler, this); await context.View.LoadView(context.Transaction).ConfigureAwait(false); // adding to the mem pool can only be done sequentially // use the sequential scheduler for that. await this.mempoolScheduler.WriteAsync(() => { // is it already in the memory pool? if (this.memPool.Exists(context.TransactionHash)) { state.Invalid(MempoolErrors.InPool).Throw(); } // Check for conflicts with in-memory transactions this.CheckConflicts(context); this.CheckMempoolCoinView(context); this.CreateMempoolEntry(context, state.AcceptTime); this.CheckSigOps(context); this.CheckFee(context); this.CheckRateLimit(context, state.LimitFree); this.CheckAncestors(context); this.CheckReplacment(context); this.CheckAllInputs(context); // Remove conflicting transactions from the mempool foreach (var it in context.AllConflicting) { this.logger.LogInformation($"replacing tx {it.TransactionHash} with {context.TransactionHash} for {context.ModifiedFees - context.ConflictingFees} BTC additional fees, {context.EntrySize - context.ConflictingSize} delta bytes"); } this.memPool.RemoveStaged(context.AllConflicting, false); // This transaction should only count for fee estimation if // the node is not behind and it is not dependent on any other // transactions in the mempool bool validForFeeEstimation = this.IsCurrentForFeeEstimation() && this.memPool.HasNoInputsOf(tx); // Store transaction in memory this.memPool.AddUnchecked(context.TransactionHash, context.Entry, context.SetAncestors, validForFeeEstimation); // trim mempool and check if tx was trimmed if (!state.OverrideMempoolLimit) { this.LimitMempoolSize(this.nodeArgs.Mempool.MaxMempool * 1000000, this.nodeArgs.Mempool.MempoolExpiry * 60 * 60); if (!this.memPool.Exists(context.TransactionHash)) { state.Fail(MempoolErrors.Full).Throw(); } } // do this here inside the exclusive scheduler for better accuracy // and to avoid springing more concurrent tasks later state.MempoolSize = this.memPool.Size; state.MempoolDynamicSize = this.memPool.DynamicMemoryUsage(); this.PerformanceCounter.SetMempoolSize(state.MempoolSize); this.PerformanceCounter.SetMempoolDynamicSize(state.MempoolDynamicSize); this.PerformanceCounter.AddHitCount(1); }); }
public Task <bool> AcceptToMemoryPool(MempoolValidationState state, Transaction tx) { state.AcceptTime = this.dateTimeProvider.GetTime(); return(this.AcceptToMemoryPoolWithTime(state, tx)); }