Ejemplo n.º 1
0
        private int?DetermineApplicableRetrievalHeight(DepositRetrievalType retrievalType, int retrieveFromHeight, out string message)
        {
            message = string.Empty;

            int applicableMaturityHeight;

            if (retrievalType == DepositRetrievalType.Small)
            {
                applicableMaturityHeight = this.consensusTip.Height - this.federatedPegSettings.MinimumConfirmationsSmallDeposits;
            }
            else if (retrievalType == DepositRetrievalType.Normal)
            {
                applicableMaturityHeight = this.consensusTip.Height - this.federatedPegSettings.MinimumConfirmationsNormalDeposits;
            }
            else if (retrievalType == DepositRetrievalType.Distribution)
            {
                applicableMaturityHeight = this.consensusTip.Height - this.federatedPegSettings.MinimumConfirmationsDistributionDeposits;
            }
            else
            {
                applicableMaturityHeight = this.consensusTip.Height - this.federatedPegSettings.MinimumConfirmationsLargeDeposits;
            }

            if (retrieveFromHeight > applicableMaturityHeight)
            {
                message = string.Format("The submitted block height of {0} is not mature enough for '{1}' deposits, blocks below {2} can be returned.", retrieveFromHeight, retrievalType, applicableMaturityHeight);
                return(null);
            }

            this.logger.LogDebug("Blocks will be inspected for '{0}' deposits from height {1}.", retrievalType, applicableMaturityHeight);

            return(applicableMaturityHeight);
        }
Ejemplo n.º 2
0
 public Deposit(uint256 id, DepositRetrievalType retrievalType, Money amount, string targetAddress, int blockNumber, uint256 blockHash)
 {
     this.Id            = id;
     this.RetrievalType = retrievalType;
     this.Amount        = amount;
     this.TargetAddress = targetAddress;
     this.BlockNumber   = blockNumber;
     this.BlockHash     = blockHash;
 }
Ejemplo n.º 3
0
        private List <MaturedBlockDepositsModel> RetrieveDepositsFromHeight(DepositRetrievalType retrievalType, int applicableHeight, int retrieveFromHeight)
        {
            List <ChainedHeader> applicableHeaders = RetrieveApplicableHeaders(applicableHeight, retrieveFromHeight);

            var depositBlockModels = new List <MaturedBlockDepositsModel>();

            // Half of the timeout, we will also need time to convert it to json.
            using (var cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(RestApiClientBase.TimeoutSeconds / 2)))
            {
                for (int headerIndex = 0; headerIndex < applicableHeaders.Count; headerIndex += 100)
                {
                    List <ChainedHeader> currentHeaders = applicableHeaders.GetRange(headerIndex, Math.Min(100, applicableHeaders.Count - headerIndex));

                    var hashes = currentHeaders.Select(h => h.HashBlock).ToList();

                    ChainedHeaderBlock[] blocks = this.consensusManager.GetBlockData(hashes);

                    foreach (ChainedHeaderBlock chainedHeaderBlock in blocks)
                    {
                        if (chainedHeaderBlock?.Block?.Transactions == null)
                        {
                            this.logger.LogDebug(UnableToRetrieveBlockDataFromConsensusMessage, chainedHeaderBlock.ChainedHeader);
                            break;
                        }

                        MaturedBlockDepositsModel depositBlockModel = this.depositExtractor.ExtractBlockDeposits(chainedHeaderBlock, retrievalType);

                        if (depositBlockModel != null && depositBlockModel.Deposits != null)
                        {
                            this.logger.LogDebug("{0} '{1}' deposits extracted at block '{2}'", depositBlockModel.Deposits.Count, retrievalType, chainedHeaderBlock.ChainedHeader);

                            if (depositBlockModel.Deposits.Any())
                            {
                                depositBlockModels.Add(depositBlockModel);
                            }
                        }

                        if (depositBlockModels.Count >= MaturedBlocksSyncManager.MaxBlocksToRequest || depositBlockModels.SelectMany(d => d.Deposits).Count() >= int.MaxValue)
                        {
                            this.logger.LogDebug("Stopping matured blocks collection, thresholds reached; {0}={1}, numberOfDeposits={2}", nameof(depositBlockModels), depositBlockModels.Count, depositBlockModels.SelectMany(d => d.Deposits).Count());
                            break;
                        }

                        if (cancellationToken.IsCancellationRequested)
                        {
                            this.logger.LogDebug("Stopping matured blocks collection, the request is taking too long, sending what has been collected.");
                            break;
                        }
                    }
                }
            }

            return(depositBlockModels);
        }
Ejemplo n.º 4
0
        private void RetrieveDeposits(DepositRetrievalType retrievalType, int retrieveFromHeight, StringBuilder messageBuilder, SerializableResult <List <MaturedBlockDepositsModel> > result)
        {
            var retrieveUpToHeight = DetermineApplicableRetrievalHeight(retrievalType, retrieveFromHeight, out string message);

            if (retrieveUpToHeight == null)
            {
                this.logger.LogDebug(message);
                messageBuilder.AppendLine(message);
            }
            else
            {
                List <MaturedBlockDepositsModel> deposits = RetrieveDepositsFromHeight(retrievalType, retrieveFromHeight, retrieveUpToHeight.Value);
                if (deposits.Any())
                {
                    result.Value.AddRange(deposits);
                }
            }
        }
Ejemplo n.º 5
0
        /// <inheritdoc />
        public IDeposit ExtractDepositFromTransaction(Transaction transaction, int blockHeight, uint256 blockHash, DepositRetrievalType depositRetrievalType)
        {
            // Coinbase transactions can't have deposits.
            if (transaction.IsCoinBase)
            {
                return(null);
            }

            // Deposits have a certain structure.
            if (transaction.Outputs.Count != ExpectedNumberOfOutputsNoChange && transaction.Outputs.Count != ExpectedNumberOfOutputsChange)
            {
                return(null);
            }

            var depositsToMultisig = transaction.Outputs.Where(output =>
                                                               output.ScriptPubKey == this.depositScript &&
                                                               output.Value >= FederatedPegSettings.CrossChainTransferMinimum).ToList();

            if (!depositsToMultisig.Any())
            {
                return(null);
            }

            if (!this.opReturnDataReader.TryGetTargetAddress(transaction, out string targetAddress))
            {
                return(null);
            }

            // Check if this deposit is intended for distribution to the miners. This is identified by a specific destination address in the deposit OP_RETURN.
            // A distribution deposit is otherwise exactly the same as a regular deposit transaction.
            if (targetAddress == StraxCoinstakeRule.CirrusDummyAddress && depositRetrievalType != DepositRetrievalType.Distribution)
            {
                // Distribution transactions are special and take precedence over all the other types.
                return(null);
            }

            this.logger.LogDebug("Processing a received deposit transaction of type {0} with address: {1}. Transaction hash: {2}.", depositRetrievalType, targetAddress, transaction.GetHash());

            return(new Deposit(transaction.GetHash(), depositRetrievalType, depositsToMultisig.Sum(o => o.Value), targetAddress, blockHeight, blockHash));
        }
Ejemplo n.º 6
0
        /// <inheritdoc />
        public IReadOnlyList <IDeposit> ExtractDepositsFromBlock(Block block, int blockHeight, DepositRetrievalType depositRetrievalType)
        {
            var deposits = new List <IDeposit>();

            // If it's an empty block (i.e. only the coinbase transaction is present), there's no deposits inside.
            if (block.Transactions.Count <= 1)
            {
                return(deposits);
            }

            uint256 blockHash = block.GetHash();

            foreach (Transaction transaction in block.Transactions)
            {
                IDeposit deposit = this.ExtractDepositFromTransaction(transaction, blockHeight, blockHash, depositRetrievalType);
                if (deposit == null)
                {
                    continue;
                }

                if (depositRetrievalType == DepositRetrievalType.Small && deposit.Amount <= this.federatedPegSettings.SmallDepositThresholdAmount)
                {
                    deposits.Add(deposit);
                    continue;
                }

                if (depositRetrievalType == DepositRetrievalType.Normal && deposit.Amount > this.federatedPegSettings.SmallDepositThresholdAmount && deposit.Amount <= this.federatedPegSettings.NormalDepositThresholdAmount)
                {
                    deposits.Add(deposit);
                    continue;
                }

                if (depositRetrievalType == DepositRetrievalType.Large && deposit.Amount > this.federatedPegSettings.NormalDepositThresholdAmount)
                {
                    deposits.Add(deposit);
                    continue;
                }

                if (depositRetrievalType == DepositRetrievalType.Distribution)
                {
                    deposits.Add(deposit);
                }
            }

            return(deposits);
        }
Ejemplo n.º 7
0
        /// <inheritdoc />
        public MaturedBlockDepositsModel ExtractBlockDeposits(ChainedHeaderBlock blockToExtractDepositsFrom, DepositRetrievalType depositRetrievalType)
        {
            Guard.NotNull(blockToExtractDepositsFrom, nameof(blockToExtractDepositsFrom));

            var maturedBlockModel = new MaturedBlockInfoModel()
            {
                BlockHash   = blockToExtractDepositsFrom.ChainedHeader.HashBlock,
                BlockHeight = blockToExtractDepositsFrom.ChainedHeader.Height,
                BlockTime   = blockToExtractDepositsFrom.ChainedHeader.Header.Time
            };

            IReadOnlyList <IDeposit> deposits = ExtractDepositsFromBlock(blockToExtractDepositsFrom.Block, blockToExtractDepositsFrom.ChainedHeader.Height, depositRetrievalType);

            return(new MaturedBlockDepositsModel(maturedBlockModel, deposits));
        }
Ejemplo n.º 8
0
        private async Task <bool> ProcessMatureBlockDepositsAsync(SerializableResult <List <MaturedBlockDepositsModel> > matureBlockDeposits)
        {
            // "Value"'s count will be 0 if we are using NewtonSoft's serializer, null if using .Net Core 3's serializer.
            if (matureBlockDeposits.Value.Count == 0)
            {
                this.logger.LogDebug("Considering ourselves fully synced since no blocks were received.");

                // If we've received nothing we assume we are at the tip and should flush.
                // Same mechanic as with syncing headers protocol.
                await this.crossChainTransferStore.SaveCurrentTipAsync().ConfigureAwait(false);

                return(true);
            }

            this.logger.LogInformation("Processing {0} matured blocks.", matureBlockDeposits.Value.Count);

            // Filter out conversion transactions & also log what we've received for diagnostic purposes.
            foreach (MaturedBlockDepositsModel maturedBlockDeposit in matureBlockDeposits.Value)
            {
                var tempDepositList = new List <IDeposit>();

                if (maturedBlockDeposit.Deposits.Count > 0)
                {
                    this.logger.LogDebug("Matured deposit count for block {0} height {1}: {2}.", maturedBlockDeposit.BlockInfo.BlockHash, maturedBlockDeposit.BlockInfo.BlockHeight, maturedBlockDeposit.Deposits.Count);
                }

                foreach (IDeposit potentialConversionTransaction in maturedBlockDeposit.Deposits)
                {
                    // If this is not a conversion transaction then add it immediately to the temporary list.
                    if (potentialConversionTransaction.RetrievalType != DepositRetrievalType.ConversionSmall &&
                        potentialConversionTransaction.RetrievalType != DepositRetrievalType.ConversionNormal &&
                        potentialConversionTransaction.RetrievalType != DepositRetrievalType.ConversionLarge)
                    {
                        tempDepositList.Add(potentialConversionTransaction);
                        continue;
                    }

                    if (this.federatedPegSettings.IsMainChain)
                    {
                        this.logger.LogWarning("Conversion transactions do not get actioned by the main chain.");
                        continue;
                    }

                    var interFluxV2MainChainActivationHeight = ((PoAConsensusOptions)this.network.Consensus.Options).InterFluxV2MainChainActivationHeight;
                    if (interFluxV2MainChainActivationHeight != 0 && maturedBlockDeposit.BlockInfo.BlockHeight < interFluxV2MainChainActivationHeight)
                    {
                        this.logger.LogWarning("Conversion transactions '{0}' will not be processed below the main chain activation height of {1}.", potentialConversionTransaction.Id, interFluxV2MainChainActivationHeight);
                        continue;
                    }

                    this.logger.LogInformation("Conversion transaction '{0}' received.", potentialConversionTransaction.Id);

                    ChainedHeader applicableHeader = null;
                    bool          conversionExists = false;
                    if (this.conversionRequestRepository.Get(potentialConversionTransaction.Id.ToString()) != null)
                    {
                        this.logger.LogWarning("Conversion transaction '{0}' already exists, ignoring.", potentialConversionTransaction.Id);
                        conversionExists = true;
                    }
                    else
                    {
                        // This should ony happen if the conversion does't exist yet.
                        if (!FindApplicableConversionRequestHeader(maturedBlockDeposit, potentialConversionTransaction, out applicableHeader))
                        {
                            continue;
                        }
                    }

                    InteropConversionRequestFee interopConversionRequestFee = await this.conversionRequestFeeService.AgreeFeeForConversionRequestAsync(potentialConversionTransaction.Id.ToString(), maturedBlockDeposit.BlockInfo.BlockHeight).ConfigureAwait(false);

                    // If a dynamic fee could not be determined, create a fallback fee.
                    if (interopConversionRequestFee == null ||
                        (interopConversionRequestFee != null && interopConversionRequestFee.State != InteropFeeState.AgreeanceConcluded))
                    {
                        interopConversionRequestFee.Amount = ConversionRequestFeeService.FallBackFee;
                        this.logger.LogWarning($"A dynamic fee for conversion request '{potentialConversionTransaction.Id}' could not be determined, using a fixed fee of {ConversionRequestFeeService.FallBackFee} STRAX.");
                    }

                    if (Money.Satoshis(interopConversionRequestFee.Amount) >= potentialConversionTransaction.Amount)
                    {
                        this.logger.LogWarning("Conversion transaction '{0}' is no longer large enough to cover the fee.", potentialConversionTransaction.Id);
                        continue;
                    }

                    // We insert the fee distribution as a deposit to be processed, albeit with a special address.
                    // Deposits with this address as their destination will be distributed between the multisig members.
                    // Note that it will be actioned immediately as a matured deposit.
                    this.logger.LogInformation("Adding conversion fee distribution for transaction '{0}' to deposit list.", potentialConversionTransaction.Id);

                    // Instead of being a conversion deposit, the fee distribution is translated to its non-conversion equivalent.
                    DepositRetrievalType depositType = DepositRetrievalType.Small;

                    switch (potentialConversionTransaction.RetrievalType)
                    {
                    case DepositRetrievalType.ConversionSmall:
                        depositType = DepositRetrievalType.Small;
                        break;

                    case DepositRetrievalType.ConversionNormal:
                        depositType = DepositRetrievalType.Normal;
                        break;

                    case DepositRetrievalType.ConversionLarge:
                        depositType = DepositRetrievalType.Large;
                        break;
                    }

                    tempDepositList.Add(new Deposit(potentialConversionTransaction.Id,
                                                    depositType,
                                                    Money.Satoshis(interopConversionRequestFee.Amount),
                                                    this.network.ConversionTransactionFeeDistributionDummyAddress,
                                                    potentialConversionTransaction.TargetChain,
                                                    potentialConversionTransaction.BlockNumber,
                                                    potentialConversionTransaction.BlockHash));

                    if (!conversionExists)
                    {
                        this.logger.LogDebug("Adding conversion request for transaction '{0}' to repository.", potentialConversionTransaction.Id);

                        this.conversionRequestRepository.Save(new ConversionRequest()
                        {
                            RequestId     = potentialConversionTransaction.Id.ToString(),
                            RequestType   = ConversionRequestType.Mint,
                            Processed     = false,
                            RequestStatus = ConversionRequestStatus.Unprocessed,
                            // We do NOT convert to wei here yet. That is done when the minting transaction is submitted on the Ethereum network.
                            Amount             = (ulong)(potentialConversionTransaction.Amount - Money.Satoshis(interopConversionRequestFee.Amount)).Satoshi,
                            BlockHeight        = applicableHeader.Height,
                            DestinationAddress = potentialConversionTransaction.TargetAddress,
                            DestinationChain   = potentialConversionTransaction.TargetChain
                        });
                    }
                }

                maturedBlockDeposit.Deposits = tempDepositList.AsReadOnly();

                // Order all non-conversion deposit transactions in the block deterministically.
                maturedBlockDeposit.Deposits = maturedBlockDeposit.Deposits.OrderBy(x => x.Id, Comparer <uint256> .Create(DeterministicCoinOrdering.CompareUint256)).ToList();

                foreach (IDeposit deposit in maturedBlockDeposit.Deposits)
                {
                    this.logger.LogDebug("Deposit matured: {0}", deposit.ToString());
                }
            }

            // If we received a portion of blocks we can ask for a new portion without any delay.
            RecordLatestMatureDepositsResult result = await this.crossChainTransferStore.RecordLatestMatureDepositsAsync(matureBlockDeposits.Value).ConfigureAwait(false);

            return(!result.MatureDepositRecorded);
        }
Ejemplo n.º 9
0
        /// <inheritdoc />
        public IDeposit ExtractDepositFromTransaction(Transaction transaction, int blockHeight, uint256 blockHash, DepositRetrievalType depositRetrievalType)
        {
            // Coinbases can't have deposits.
            if (transaction.IsCoinBase)
            {
                return(null);
            }

            // Deposits have a certain structure.
            if (transaction.Outputs.Count != ExpectedNumberOfOutputsNoChange && transaction.Outputs.Count != ExpectedNumberOfOutputsChange)
            {
                return(null);
            }

            var depositsToMultisig = transaction.Outputs.Where(output =>
                                                               output.ScriptPubKey == this.depositScript &&
                                                               output.Value >= FederatedPegSettings.CrossChainTransferMinimum).ToList();

            if (!depositsToMultisig.Any())
            {
                return(null);
            }

            if (!this.opReturnDataReader.TryGetTargetAddress(transaction, out string targetAddress))
            {
                return(null);
            }

            this.logger.LogDebug("Processing a received deposit transaction with address: {0}. Transaction hash: {1}.", targetAddress, transaction.GetHash());

            return(new Deposit(transaction.GetHash(), depositRetrievalType, depositsToMultisig.Sum(o => o.Value), targetAddress, blockHeight, blockHash));
        }
Ejemplo n.º 10
0
        /// <inheritdoc />
        public IReadOnlyList <IDeposit> ExtractDepositsFromBlock(Block block, int blockHeight, DepositRetrievalType depositRetrievalType)
        {
            var deposits = new List <IDeposit>();

            // If it's an empty block, there's no deposits inside.
            if (block.Transactions.Count <= 1)
            {
                return(deposits);
            }

            uint256 blockHash = block.GetHash();

            foreach (Transaction transaction in block.Transactions)
            {
                IDeposit deposit = this.ExtractDepositFromTransaction(transaction, blockHeight, blockHash, depositRetrievalType);
                if (deposit != null)
                {
                    if (depositRetrievalType == DepositRetrievalType.Faster && deposit.Amount <= this.federatedPegSettings.FasterDepositThresholdAmount)
                    {
                        deposits.Add(deposit);
                    }

                    if (depositRetrievalType == DepositRetrievalType.Normal && deposit.Amount > this.federatedPegSettings.FasterDepositThresholdAmount)
                    {
                        deposits.Add(deposit);
                    }
                }
            }

            return(deposits);
        }