/// <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);
            }
        }
Beispiel #3
0
 /// <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);
                    }
                }
            }
        }
Beispiel #5
0
 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));
 }