Example #1
0
        /// <inheritdoc/>
        public async Task <FeeProposalPayload> MultiSigMemberProposedInteropFeeAsync(string requestId, ulong feeAmount, PubKey pubKey)
        {
            using (await this.asyncLockObject.LockAsync().ConfigureAwait(false))
            {
                // If this node does not have the fee proposal, return and wait for the matured blocks sync manager to find and create it.
                InteropConversionRequestFee interopConversionRequestFee = GetInteropConversionRequestFeeLocked(requestId);
                if (interopConversionRequestFee == null)
                {
                    return(null);
                }

                // Check if the incoming proposal has not yet concluded and that the node has not yet proposed it.
                if (!HasFeeProposalBeenConcluded(interopConversionRequestFee) && !interopConversionRequestFee.FeeProposals.Any(p => p.PubKey == pubKey.ToHex()))
                {
                    // Check that the fee proposal is in range.
                    if (!IsFeeWithinAcceptableRange(interopConversionRequestFee.FeeProposals, requestId, feeAmount, pubKey))
                    {
                        return(null);
                    }

                    interopConversionRequestFee.FeeProposals.Add(new InterOpFeeToMultisig()
                    {
                        BlockHeight = interopConversionRequestFee.BlockHeight, PubKey = pubKey.ToHex(), FeeAmount = feeAmount
                    });
                    this.interopRequestKeyValueStore.SaveValueJson(interopConversionRequestFee.RequestId, interopConversionRequestFee, true);

                    this.logger.LogDebug($"Received conversion request proposal '{requestId}' from '{pubKey}', proposing fee of {new Money(feeAmount)}.");
                }

                // This node would have proposed this fee if the InteropConversionRequestFee object exists.
                InterOpFeeToMultisig myProposal = interopConversionRequestFee.FeeProposals.First(p => p.PubKey == this.federationManager.CurrentFederationKey.PubKey.ToHex());
                string signature = this.federationManager.CurrentFederationKey.SignMessage(interopConversionRequestFee.RequestId + myProposal.FeeAmount);
                return(FeeProposalPayload.Reply(interopConversionRequestFee.RequestId, myProposal.FeeAmount, interopConversionRequestFee.BlockHeight, signature));
            }
        }
Example #2
0
        private void ConcludeInteropConversionRequestFee(InteropConversionRequestFee interopConversionRequestFee)
        {
            if (interopConversionRequestFee.State != InteropFeeState.AgreeanceInProgress)
            {
                return;
            }

            foreach (InterOpFeeToMultisig vote in interopConversionRequestFee.FeeVotes)
            {
                this.logger.LogDebug($"Pubkey '{vote.PubKey}' voted for {new Money(vote.FeeAmount)}.");
            }

            // Try and find the majority vote
            IEnumerable <IGrouping <decimal, decimal> > grouped = interopConversionRequestFee.FeeVotes.Select(v => Math.Truncate(Money.Satoshis(v.FeeAmount).ToDecimal(MoneyUnit.BTC))).GroupBy(s => s);
            IGrouping <decimal, decimal> majority = grouped.OrderByDescending(g => g.Count()).First();

            if (majority.Count() >= (this.federatedPegSettings.MultiSigM / 2) + 1)
            {
                interopConversionRequestFee.Amount = Money.Coins(majority.Key);
            }
            else
            {
                interopConversionRequestFee.Amount = FallBackFee;
            }

            interopConversionRequestFee.State = InteropFeeState.AgreeanceConcluded;
            this.interopRequestKeyValueStore.SaveValueJson(interopConversionRequestFee.RequestId, interopConversionRequestFee, true);

            this.logger.LogDebug($"Voting on fee for request id '{interopConversionRequestFee.RequestId}' has concluded, amount: {new Money(interopConversionRequestFee.Amount)}");
        }
Example #3
0
        /// <inheritdoc/>
        public async Task <FeeAgreePayload> MultiSigMemberAgreedOnInteropFeeAsync(string requestId, ulong feeAmount, PubKey pubKey)
        {
            using (await this.asyncLockObject.LockAsync().ConfigureAwait(false))
            {
                // If this node does not have this conversion request fee, return and wait for the matured blocks sync manager to find it.
                InteropConversionRequestFee interopConversionRequestFee = GetInteropConversionRequestFeeLocked(requestId);
                if (interopConversionRequestFee == null || (interopConversionRequestFee != null && interopConversionRequestFee.State == InteropFeeState.ProposalInProgress))
                {
                    return(null);
                }

                // Check if the vote is still in progress and that the incoming node still has to vote on this fee.
                if (!HasFeeVoteBeenConcluded(interopConversionRequestFee) && !interopConversionRequestFee.FeeVotes.Any(p => p.PubKey == pubKey.ToHex()))
                {
                    interopConversionRequestFee.FeeVotes.Add(new InterOpFeeToMultisig()
                    {
                        BlockHeight = interopConversionRequestFee.BlockHeight, PubKey = pubKey.ToHex(), FeeAmount = feeAmount
                    });
                    this.interopRequestKeyValueStore.SaveValueJson(interopConversionRequestFee.RequestId, interopConversionRequestFee, true);

                    this.logger.LogDebug($"Received conversion request fee vote '{requestId}' from '{pubKey} for a fee of {new Money(feeAmount)}.");
                }

                // This node would have voted on this if the InteropConversionRequestFee object exists.
                InterOpFeeToMultisig myVote = interopConversionRequestFee.FeeVotes.FirstOrDefault(p => p.PubKey == this.federationManager.CurrentFederationKey.PubKey.ToHex());
                if (myVote == null)
                {
                    return(null);
                }

                string signature = this.federationManager.CurrentFederationKey.SignMessage(interopConversionRequestFee.RequestId + myVote.FeeAmount);
                return(FeeAgreePayload.Reply(interopConversionRequestFee.RequestId, myVote.FeeAmount, interopConversionRequestFee.BlockHeight, signature));
            }
        }
Example #4
0
        /// <inheritdoc/>
        public async Task <InteropConversionRequestFee> AgreeFeeForConversionRequestAsync(string requestId, int blockHeight)
        {
            // First check if this request is older than max-reorg and if so, ignore.
            // TODO: Find away to ignore old requests...

            InteropConversionRequestFee interopConversionRequestFee = null;

            DateTime lastConversionRequestSync  = this.dateTimeProvider.GetUtcNow();
            DateTime conversionRequestSyncStart = this.dateTimeProvider.GetUtcNow();

            do
            {
                if (conversionRequestSyncStart.AddMinutes(2) <= this.dateTimeProvider.GetUtcNow())
                {
                    this.logger.LogWarning($"A fee for conversion request '{requestId}' failed to reach consensus after 2 minutes, ignoring.");
                    interopConversionRequestFee.State = InteropFeeState.FailRevertToFallback;
                    this.interopRequestKeyValueStore.SaveValueJson(requestId, interopConversionRequestFee);
                    break;
                }

                if (this.nodeLifetime.ApplicationStopping.IsCancellationRequested)
                {
                    break;
                }

                // Execute a small delay to not flood the network with proposal requests.
                if (lastConversionRequestSync.AddMilliseconds(500) > this.dateTimeProvider.GetUtcNow())
                {
                    continue;
                }

                using (await this.asyncLockObject.LockAsync().ConfigureAwait(false))
                {
                    interopConversionRequestFee = CreateInteropConversionRequestFeeLocked(requestId, blockHeight);

                    // If the fee proposal has not concluded then continue until it has.
                    if (interopConversionRequestFee.State == InteropFeeState.ProposalInProgress)
                    {
                        await SubmitProposalForInteropFeeForConversionRequestLockedAsync(interopConversionRequestFee).ConfigureAwait(false);
                    }

                    if (interopConversionRequestFee.State == InteropFeeState.AgreeanceInProgress)
                    {
                        await AgreeOnInteropFeeForConversionRequestLockedAsync(interopConversionRequestFee).ConfigureAwait(false);
                    }

                    if (interopConversionRequestFee.State == InteropFeeState.AgreeanceConcluded)
                    {
                        break;
                    }
                }

                lastConversionRequestSync = this.dateTimeProvider.GetUtcNow();
            } while (true);

            return(interopConversionRequestFee);
        }
Example #5
0
        private InteropConversionRequestFee GetInteropConversionRequestFeeLocked(string requestId)
        {
            InteropConversionRequestFee interopConversionRequest = null;

            byte[] proposalBytes = this.interopRequestKeyValueStore.LoadBytes(requestId);
            if (proposalBytes != null)
            {
                string json = Encoding.ASCII.GetString(proposalBytes);
                interopConversionRequest = Serializer.ToObject <InteropConversionRequestFee>(json);
            }

            return(interopConversionRequest);
        }
Example #6
0
        private InteropConversionRequestFee CreateInteropConversionRequestFeeLocked(string requestId, int blockHeight)
        {
            InteropConversionRequestFee interopConversionRequest = GetInteropConversionRequestFeeLocked(requestId);

            if (interopConversionRequest == null)
            {
                interopConversionRequest = new InteropConversionRequestFee()
                {
                    RequestId = requestId, BlockHeight = blockHeight, State = InteropFeeState.ProposalInProgress
                };
                this.interopRequestKeyValueStore.SaveValueJson(requestId, interopConversionRequest);
                this.logger.LogDebug($"InteropConversionRequestFee object for request '{requestId}' has been created.");
            }

            return(interopConversionRequest);
        }
Example #7
0
        /// <summary>
        /// Submits this node's fee vote. This methods must be called from within <see cref="asyncLockObject"/>.
        /// </summary>
        /// <param name="interopConversionRequestFee">The request we are currently processing.</param>
        /// <returns>The awaited task.</returns>
        private async Task AgreeOnInteropFeeForConversionRequestLockedAsync(InteropConversionRequestFee interopConversionRequestFee)
        {
            if (!HasFeeProposalBeenConcluded(interopConversionRequestFee))
            {
                this.logger.LogError($"Cannot vote on fee proposal for request id '{interopConversionRequestFee.RequestId}' as it hasn't concluded yet.");
                return;
            }

            // Check if this node has already vote on this fee.
            if (!interopConversionRequestFee.FeeVotes.Any(p => p.PubKey == this.federationManager.CurrentFederationKey.PubKey.ToHex()))
            {
                ulong candidateFee = (ulong)interopConversionRequestFee.FeeProposals.Select(s => Convert.ToInt64(s.FeeAmount)).Average();

                var interOpFeeToMultisig = new InterOpFeeToMultisig()
                {
                    BlockHeight = interopConversionRequestFee.BlockHeight, PubKey = this.federationManager.CurrentFederationKey.PubKey.ToHex(), FeeAmount = candidateFee
                };
                interopConversionRequestFee.FeeVotes.Add(interOpFeeToMultisig);
                this.interopRequestKeyValueStore.SaveValueJson(interopConversionRequestFee.RequestId, interopConversionRequestFee, true);

                this.logger.LogDebug($"Creating fee vote for conversion request id '{interopConversionRequestFee.RequestId}' with a fee amount of {new Money(candidateFee)}.");
            }

            this.logger.LogDebug($"{interopConversionRequestFee.FeeVotes.Count} node(s) has voted on a fee for conversion request id '{interopConversionRequestFee.RequestId}'.");

            if (HasFeeVoteBeenConcluded(interopConversionRequestFee))
            {
                ConcludeInteropConversionRequestFee(interopConversionRequestFee);
            }

            // Broadcast this peer's vote to the federation
            InterOpFeeToMultisig myVote = interopConversionRequestFee.FeeVotes.First(p => p.PubKey == this.federationManager.CurrentFederationKey.PubKey.ToHex());
            string signature            = this.federationManager.CurrentFederationKey.SignMessage(interopConversionRequestFee.RequestId + myVote.FeeAmount);

            await this.federatedPegBroadcaster.BroadcastAsync(FeeAgreePayload.Request(interopConversionRequestFee.RequestId, myVote.FeeAmount, interopConversionRequestFee.BlockHeight, signature)).ConfigureAwait(false);
        }
Example #8
0
 private bool HasFeeVoteBeenConcluded(InteropConversionRequestFee interopConversionRequestFee)
 {
     return(interopConversionRequestFee.FeeVotes.Count >= this.federatedPegSettings.MultiSigM);
 }
Example #9
0
        /// <summary>
        /// Submits this node's fee proposal. This methods must be called from within <see cref="asyncLockObject"/>.
        /// </summary>
        /// <param name="interopConversionRequestFee">The request we are currently processing.</param>
        /// <returns>The awaited task.</returns>
        private async Task SubmitProposalForInteropFeeForConversionRequestLockedAsync(InteropConversionRequestFee interopConversionRequestFee)
        {
            // Check if this node has already proposed its fee.
            if (!interopConversionRequestFee.FeeProposals.Any(p => p.PubKey == this.federationManager.CurrentFederationKey.PubKey.ToHex()))
            {
                if (!EstimateConversionTransactionFee(out ulong candidateFee))
                {
                    return;
                }

                interopConversionRequestFee.FeeProposals.Add(new InterOpFeeToMultisig()
                {
                    BlockHeight = interopConversionRequestFee.BlockHeight, PubKey = this.federationManager.CurrentFederationKey.PubKey.ToHex(), FeeAmount = candidateFee
                });
                this.interopRequestKeyValueStore.SaveValueJson(interopConversionRequestFee.RequestId, interopConversionRequestFee, true);

                this.logger.LogDebug($"Adding this node's proposal fee of {candidateFee} for conversion request id '{interopConversionRequestFee.RequestId}'.");
            }

            this.logger.LogDebug($"{interopConversionRequestFee.FeeProposals.Count} node(s) has proposed a fee for conversion request id '{interopConversionRequestFee.RequestId}'.");

            if (HasFeeProposalBeenConcluded(interopConversionRequestFee))
            {
                // Only update the proposal state if it is ProposalInProgress and save it.
                if (interopConversionRequestFee.State == InteropFeeState.ProposalInProgress)
                {
                    interopConversionRequestFee.State = InteropFeeState.AgreeanceInProgress;
                    this.interopRequestKeyValueStore.SaveValueJson(interopConversionRequestFee.RequestId, interopConversionRequestFee, true);

                    IEnumerable <long> values = interopConversionRequestFee.FeeProposals.Select(s => Convert.ToInt64(s.FeeAmount));
                    this.logger.LogDebug($"Proposal fee for request id '{interopConversionRequestFee.RequestId}' has concluded, average amount: {values.Average()}");
                }
            }

            InterOpFeeToMultisig myProposal = interopConversionRequestFee.FeeProposals.First(p => p.PubKey == this.federationManager.CurrentFederationKey.PubKey.ToHex());
            string signature = this.federationManager.CurrentFederationKey.SignMessage(interopConversionRequestFee.RequestId + myProposal.FeeAmount);

            await this.federatedPegBroadcaster.BroadcastAsync(FeeProposalPayload.Request(interopConversionRequestFee.RequestId, myProposal.FeeAmount, interopConversionRequestFee.BlockHeight, signature)).ConfigureAwait(false);
        }