/// <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)); } }
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)}"); }
/// <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)); } }
/// <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); }
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); }
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); }
/// <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); }
private bool HasFeeVoteBeenConcluded(InteropConversionRequestFee interopConversionRequestFee) { return(interopConversionRequestFee.FeeVotes.Count >= this.federatedPegSettings.MultiSigM); }
/// <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); }