public InteropCoordinationPayload(string requestId, int transactionId, string signature, DestinationChain targetChain)
 {
     this.requestId        = requestId;
     this.transactionId    = transactionId;
     this.signature        = signature;
     this.DestinationChain = targetChain;
 }
        public async Task <IActionResult> MultisigConfirmationsAsync(DestinationChain destinationChain, int transactionId)
        {
            try
            {
                if (!this.ethCompatibleClientProvider.IsChainSupportedAndEnabled(destinationChain))
                {
                    return(this.Json($"{destinationChain} not enabled or supported!"));
                }

                IETHClient client = this.ethCompatibleClientProvider.GetClientForChain(destinationChain);

                List <string> owners = await client.GetOwnersAsync().ConfigureAwait(false);

                var ownersConfirmed = new List <string>();

                foreach (string multisig in owners)
                {
                    bool confirmed = await client.AddressConfirmedTransactionAsync(transactionId, multisig).ConfigureAwait(false);

                    if (confirmed)
                    {
                        ownersConfirmed.Add(multisig);
                    }
                }

                return(this.Json(ownersConfirmed));
            }
            catch (Exception e)
            {
                this.logger.LogError("Exception occurred: {0}", e.ToString());

                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }
        public async Task <IActionResult> MultisigTransactionAsync(DestinationChain destinationChain, int transactionId, bool raw)
        {
            try
            {
                if (!this.ethCompatibleClientProvider.IsChainSupportedAndEnabled(destinationChain))
                {
                    return(this.Json($"{destinationChain} not enabled or supported!"));
                }

                IETHClient client = this.ethCompatibleClientProvider.GetClientForChain(destinationChain);

                if (raw)
                {
                    return(this.Json(await client.GetRawMultisigTransactionAsync(transactionId).ConfigureAwait(false)));
                }

                TransactionDTO transaction = await client.GetMultisigTransactionAsync(transactionId).ConfigureAwait(false);

                var response = new TransactionResponseModel()
                {
                    Destination = transaction.Destination,
                    Value       = transaction.Value.ToString(),
                    Data        = Encoders.Hex.EncodeData(transaction.Data),
                    Executed    = transaction.Executed
                };

                return(this.Json(response));
            }
            catch (Exception e)
            {
                this.logger.LogError("Exception occurred: {0}", e.ToString());

                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }
Exemplo n.º 4
0
        public IActionResult ChangeRequirement(DestinationChain destinationChain, int requirement, int gasPrice)
        {
            try
            {
                if (!this.ethCompatibleClientProvider.IsChainSupportedAndEnabled(destinationChain))
                {
                    return(this.Json($"{destinationChain} not enabled or supported!"));
                }

                IETHClient client = this.ethCompatibleClientProvider.GetClientForChain(destinationChain);

                string data = client.EncodeChangeRequirementParams(requirement);

                ETHInteropSettings settings = this.interopSettings.GetSettingsByChain(destinationChain);

                // TODO: Maybe for convenience the gas price could come from the external API poller
                return(this.Json(client.SubmitTransactionAsync(settings.MultisigWalletAddress, 0, data).GetAwaiter().GetResult()));
            }
            catch (Exception e)
            {
                this.logger.Error("Exception occurred: {0}", e.ToString());

                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }
        /// <inheritdoc />
        public IETHClient GetClientForChain(DestinationChain chain)
        {
            if (!this.supportedChains.ContainsKey(chain))
            {
                throw new NotImplementedException("Provided chain type not supported: " + chain);
            }

            return(this.supportedChains[chain]);
        }
Exemplo n.º 6
0
        /// <summary>
        /// Creates filters that the RPC interfaces uses to listen for events against the desired contract.
        /// In this case the filter is specifically listening for Transfer events emitted by the wrapped strax
        /// contracts deployed on supported chains.
        /// </summary>
        private async Task CreateEventFiltersIfRequired(DestinationChain targetChain, IETHClient chainClient)
        {
            if (this.eventFilterCreationRequired[DestinationChain.ETH])
            {
                // The filter should only be set up once IBD completes.
                await chainClient.CreateTransferEventFilterAsync().ConfigureAwait(false);

                this.eventFilterCreationRequired[targetChain] = false;
            }
        }
        /// <inheritdoc />
        public bool IsChainSupportedAndEnabled(DestinationChain chain)
        {
            bool supported = this.IsChainSupported(chain);

            if (!supported)
            {
                return(false);
            }

            return(this.interopSettings.GetSettingsByChain(chain).InteropEnabled);
        }
Exemplo n.º 8
0
        public ETHInteropSettings GetSettingsByChain(DestinationChain chain)
        {
            switch (chain)
            {
            case DestinationChain.ETH:
            {
                return(this.ETHSettings);
            }

            case DestinationChain.BNB:
            {
                return(this.BNBSettings);
            }
            }

            throw new NotImplementedException("Provided chain type not supported: " + chain);
        }
Exemplo n.º 9
0
        /// <inheritdoc />
        public IDeposit ExtractDepositFromTransaction(Transaction transaction, int blockHeight, uint256 blockHash)
        {
            // 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);
            }

            // Check the common case first.
            bool             conversionTransaction = false;
            DestinationChain targetChain           = DestinationChain.STRAX;

            if (!this.opReturnDataReader.TryGetTargetAddress(transaction, out string targetAddress))
            {
                byte[] opReturnBytes = OpReturnDataReader.SelectBytesContentFromOpReturn(transaction).FirstOrDefault();

                if (opReturnBytes != null && InterFluxOpReturnEncoder.TryDecode(opReturnBytes, out int destinationChain, out targetAddress))
                {
                    targetChain = (DestinationChain)destinationChain;
                }
                else
                {
                    return(null);
                }

                conversionTransaction = true;
            }
        public async Task <IActionResult> BalanceAsync(DestinationChain destinationChain, string account)
        {
            try
            {
                if (!this.ethCompatibleClientProvider.IsChainSupportedAndEnabled(destinationChain))
                {
                    return(this.Json($"{destinationChain} not enabled or supported!"));
                }

                IETHClient client = this.ethCompatibleClientProvider.GetClientForChain(destinationChain);

                return(this.Json((await client.GetErc20BalanceAsync(account).ConfigureAwait(false)).ToString()));
            }
            catch (Exception e)
            {
                this.logger.LogError("Exception occurred: {0}", e.ToString());

                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }
Exemplo n.º 11
0
        public IActionResult ConfirmTransaction(DestinationChain destinationChain, int transactionId, int gasPrice)
        {
            try
            {
                if (!this.ethCompatibleClientProvider.IsChainSupportedAndEnabled(destinationChain))
                {
                    return(this.Json($"{destinationChain} not enabled or supported!"));
                }

                IETHClient client = this.ethCompatibleClientProvider.GetClientForChain(destinationChain);

                return(this.Json(client.ConfirmTransactionAsync(transactionId).GetAwaiter().GetResult()));
            }
            catch (Exception e)
            {
                this.logger.Error("Exception occurred: {0}", e.ToString());

                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }
        public async Task <IActionResult> ConfirmTransactionAsync(DestinationChain destinationChain, int transactionId, int gasPrice)
        {
            try
            {
                if (!this.ethCompatibleClientProvider.IsChainSupportedAndEnabled(destinationChain))
                {
                    return(this.Json($"{destinationChain} not enabled or supported!"));
                }

                IETHClient client = this.ethCompatibleClientProvider.GetClientForChain(destinationChain);

                // TODO: Maybe for convenience the gas price could come from the external API poller
                return(this.Json(await client.ConfirmTransactionAsync(transactionId, gasPrice).ConfigureAwait(false)));
            }
            catch (Exception e)
            {
                this.logger.LogError("Exception occurred: {0}", e.ToString());

                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }
        public async Task <IActionResult> RemoveOwnerAsync(DestinationChain destinationChain, string existingOwnerAddress, int gasPrice)
        {
            try
            {
                if (!this.ethCompatibleClientProvider.IsChainSupportedAndEnabled(destinationChain))
                {
                    return(this.Json($"{destinationChain} not enabled or supported!"));
                }

                IETHClient client = this.ethCompatibleClientProvider.GetClientForChain(destinationChain);
                string     data   = client.EncodeRemoveOwnerParams(existingOwnerAddress);

                ETHInteropSettings settings = this.interopSettings.GetSettingsByChain(destinationChain);

                // TODO: Maybe for convenience the gas price could come from the external API poller
                return(this.Json(await client.SubmitTransactionAsync(settings.MultisigWalletAddress, 0, data, gasPrice).ConfigureAwait(false)));
            }
            catch (Exception e)
            {
                this.logger.LogError("Exception occurred: {0}", e.ToString());

                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }
Exemplo n.º 14
0
        private async Task BroadcastCoordinationAsync(string requestId, BigInteger transactionId, DestinationChain targetChain)
        {
            string signature = this.federationManager.CurrentFederationKey.SignMessage(requestId + ((int)transactionId));

            await this.federatedPegBroadcaster.BroadcastAsync(new InteropCoordinationPayload(requestId, (int)transactionId, signature, targetChain)).ConfigureAwait(false);
        }
Exemplo n.º 15
0
 public static string Encode(DestinationChain destinationChain, string address)
 {
     return(InterFluxPrefix + (int)destinationChain + "_" + address);
 }
 /// <inheritdoc />
 public bool IsChainSupported(DestinationChain chain)
 {
     return(this.supportedChains.ContainsKey(chain));
 }
Exemplo n.º 17
0
        /// <summary>
        /// Retrieves any Transfer events from the logs of the Wrapped Strax contract deployed on specified chain.
        /// Transfers with the zero (0x0000...) address as their destination can be considered to be burn transactions and are saved for processing as withdrawals on the mainchain.
        /// </summary>
        private async Task CheckForContractEventsAsync(DestinationChain targetChain, IETHClient chainClient)
        {
            await this.CreateEventFiltersIfRequired(targetChain, chainClient).ConfigureAwait(false);

            // Check for all Transfer events against the WrappedStrax contract since the last time we checked.
            // In future this could also poll for other events as the need arises.
            List <EventLog <TransferEventDTO> > transferEvents = await chainClient.GetTransferEventsForWrappedStraxAsync().ConfigureAwait(false);

            foreach (EventLog <TransferEventDTO> transferEvent in transferEvents)
            {
                // Will probably never be the case, but check anyway.
                if (string.IsNullOrWhiteSpace(transferEvent.Log.TransactionHash))
                {
                    continue;
                }

                // These could be mints or something else, either way ignore them.
                if (transferEvent.Event.From == ETHClient.ETHClient.ZeroAddress)
                {
                    continue;
                }

                // Transfers can only be burns if they are made with the zero address as the destination.
                if (transferEvent.Event.To != ETHClient.ETHClient.ZeroAddress)
                {
                    continue;
                }

                this.logger.LogInformation("Conversion burn transaction {0} received from contract events, sender {1}.", transferEvent.Log.TransactionHash, transferEvent.Event.From);

                if (this.conversionRequestRepository.Get(transferEvent.Log.TransactionHash) != null)
                {
                    this.logger.LogInformation("Conversion burn transaction {0} already exists, ignoring.", transferEvent.Log.TransactionHash);

                    continue;
                }

                if (transferEvent.Event.Value == BigInteger.Zero)
                {
                    this.logger.LogInformation("Ignoring zero-valued burn transaction {0}.", transferEvent.Log.TransactionHash);

                    continue;
                }

                if (transferEvent.Event.Value < BigInteger.Zero)
                {
                    this.logger.LogInformation("Ignoring negative-valued burn transaction {0}.", transferEvent.Log.TransactionHash);

                    continue;
                }

                this.logger.LogInformation("Conversion burn transaction {0} has value {1}.", transferEvent.Log.TransactionHash, transferEvent.Event.Value);

                // Look up the desired destination address for this account.
                string destinationAddress = await chainClient.GetDestinationAddressAsync(transferEvent.Event.From).ConfigureAwait(false);

                this.logger.LogInformation("Conversion burn transaction {0} has destination address {1}.", transferEvent.Log.TransactionHash, destinationAddress);

                try
                {
                    // Validate that it is a mainchain address here before bothering to add it to the repository.
                    BitcoinAddress.Create(destinationAddress, this.counterChainNetwork);
                }
                catch (Exception)
                {
                    this.logger.LogWarning("Error validating destination address {0} for transaction {1}.", destinationAddress, transferEvent.Log.TransactionHash);

                    continue;
                }

                // Schedule this transaction to be processed at the next block height that is divisible by 10. If the current block height is divisible by 10, add a further 10 to it.
                // In this way, burns will always be scheduled at a predictable future time across the multisig.
                // This is because we cannot predict exactly when each node is polling the Ethereum chain for events.
                ulong blockHeight = (ulong)this.chainIndexer.Tip.Height - ((ulong)this.chainIndexer.Tip.Height % 10) + 10;

                if (blockHeight <= 0)
                {
                    blockHeight = 10;
                }

                this.conversionRequestRepository.Save(new ConversionRequest()
                {
                    RequestId          = transferEvent.Log.TransactionHash,
                    RequestType        = ConversionRequestType.Burn,
                    Processed          = false,
                    RequestStatus      = ConversionRequestStatus.Unprocessed,
                    Amount             = this.ConvertWeiToSatoshi(transferEvent.Event.Value),
                    BlockHeight        = (int)blockHeight,
                    DestinationAddress = destinationAddress,
                    DestinationChain   = targetChain
                });
            }
        }
 public static ConversionRequestPayload Reply(string requestId, int transactionId, string signature, DestinationChain destinationChain)
 {
     return(new ConversionRequestPayload(requestId, transactionId, signature, destinationChain, false));
 }
Exemplo n.º 19
0
 public Deposit(uint256 id, DepositRetrievalType retrievalType, Money amount, string targetAddress, DestinationChain targetChain, int blockNumber, uint256 blockHash)
 {
     this.Id            = id;
     this.RetrievalType = retrievalType;
     this.Amount        = amount;
     this.TargetAddress = targetAddress;
     this.TargetChain   = targetChain;
     this.BlockNumber   = blockNumber;
     this.BlockHash     = blockHash;
 }
 private ConversionRequestPayload(string requestId, int transactionId, string signature, DestinationChain destinationChain, bool isRequesting)
 {
     this.requestId        = requestId;
     this.transactionId    = transactionId;
     this.signature        = signature;
     this.destinationChain = (int)destinationChain;
     this.isRequesting     = isRequesting;
 }