/// <summary> /// https://github.com/bitcoin-sv-specs/brfc-misc/tree/master/feespec /// </summary> /// <returns></returns> public async Task <FeeQuote> GetFeeQuote() { var feeQuote = _lastFeeQuote; if (feeQuote?.ExpiryTime > DateTime.UtcNow) { return(feeQuote); } var url = $"{_baseUrl}/mapi/feeQuote"; var json = await _httpClient.GetStringAsync(url); var e = JsonConvert.DeserializeObject <Envelope>(json); var verified = e.VerifySignature(ref _pubKey); feeQuote = JsonConvert.DeserializeObject <FeeQuote>(e.Payload); if (!verified && feeQuote.MinerId != null) { throw new InvalidOperationException("Miner did not verify."); } _lastFeeQuote = feeQuote; return(feeQuote); }
public async Task <ActionResult <PolicyQuoteViewModelGet> > GetPolicyQuote() { if (!IdentityProviderStore.GetUserAndIssuer(User, Request.Headers, out var identity)) { return(Unauthorized("Incorrectly formatted token")); } logger.LogInformation($"Get PolicyQuote for user { ((identity == null) ? "/" : identity.ToString())} ..."); FeeQuote feeQuote = feeQuoteRepository.GetCurrentFeeQuoteByIdentity(identity); if (feeQuote == null) { logger.LogInformation($"There are no active policyQuotes."); return(NotFound()); } var policyQuoteViewModelGet = new PolicyQuoteViewModelGet(feeQuote, callbackIPAddressesArray) { Timestamp = clock.UtcNow(), }; return(await FillFeeQuoteViewModelWithInfo(policyQuoteViewModelGet)); }
public FeeQuoteViewModelGet(FeeQuote feeQuote) { ApiVersion = Const.MERCHANT_API_VERSION; Fees = (from fee in feeQuote.Fees select new FeeViewModelGet(fee)).ToArray(); // Other fields are initialized from BlockCHainInfo and MinerId }
public FeeQuoteViewModelCreate(FeeQuote feeQuote) { Id = feeQuote.Id; CreatedAt = feeQuote.CreatedAt; ValidFrom = feeQuote.ValidFrom; Identity = feeQuote.Identity; IdentityProvider = feeQuote.IdentityProvider; Fees = (from fee in feeQuote.Fees select new FeeViewModelCreate(fee)).ToArray(); }
public FeeQuoteConfigViewModelGet(FeeQuote feeQuote) { Id = feeQuote.Id; CreatedAt = feeQuote.CreatedAt; Identity = feeQuote.Identity; IdentityProvider = feeQuote.IdentityProvider; ValidFrom = feeQuote.ValidFrom; Policies = feeQuote.PoliciesDict; Fees = (from fee in feeQuote.Fees select new FeeViewModelGet(fee)).ToArray(); }
public FeeQuoteViewModelGet(FeeQuote feeQuote, string[] urls) { ApiVersion = Const.MERCHANT_API_VERSION; Fees = (from fee in feeQuote.Fees select new FeeViewModelGet(fee)).ToArray(); if (urls != null) { Callbacks = (from url in urls select new CallbackViewModelGet(url)).ToArray(); } // Other fields are initialized from BlockChainInfo and MinerId }
public ActionResult <FeeQuoteConfigViewModelGet> GetFeeQuoteById(long id) { logger.LogInformation($"Get FeeQuote {id}"); FeeQuote feeQuote = feeQuoteRepository.GetFeeQuoteById(id); if (feeQuote == null) { return(NotFound()); } var feeQuoteViewModelGet = new FeeQuoteConfigViewModelGet(feeQuote); return(Ok(feeQuoteViewModelGet)); }
protected void InsertFeeQuote(UserAndIssuer userAndIssuer = null) { using (MockedClock.NowIs(DateTime.UtcNow.AddMinutes(-1))) { var feeQuote = new FeeQuote { Id = 1, CreatedAt = MockedClock.UtcNow, ValidFrom = MockedClock.UtcNow, Identity = userAndIssuer?.Identity, IdentityProvider = userAndIssuer?.IdentityProvider, Fees = new[] { new Fee { FeeType = Const.FeeType.Standard, MiningFee = new FeeAmount { Satoshis = 500, Bytes = 1000, FeeAmountType = Const.AmountType.MiningFee }, RelayFee = new FeeAmount { Satoshis = 250, Bytes = 1000, FeeAmountType = Const.AmountType.RelayFee }, }, new Fee { FeeType = Const.FeeType.Data, MiningFee = new FeeAmount { Satoshis = 500, Bytes = 1000, FeeAmountType = Const.AmountType.MiningFee }, RelayFee = new FeeAmount { Satoshis = 250, Bytes = 1000, FeeAmountType = Const.AmountType.RelayFee }, }, } }; if (FeeQuoteRepository.InsertFeeQuoteAsync(feeQuote).Result == null) { throw new Exception("Can not insert test fee quote"); } } }
public async Task <ActionResult <FeeQuoteViewModelGet> > GetFeeQuote() { if (!IdentityProviderStore.GetUserAndIssuer(User, Request.Headers, out var identity)) { return(Unauthorized("Incorrectly formatted token")); } logger.LogInformation($"Get FeeQuote for user { ((identity == null) ? "/" : identity.ToString() )} ..."); FeeQuote feeQuote = feeQuoteRepository.GetCurrentFeeQuoteByIdentity(identity); if (feeQuote == null) { logger.LogInformation($"There are no active feeQuotes."); return(NotFound()); } var feeQuoteViewModelGet = new FeeQuoteViewModelGet(feeQuote) { Timestamp = clock.UtcNow(), }; feeQuoteViewModelGet.ExpiryTime = feeQuoteViewModelGet.Timestamp.Add(TimeSpan.FromMinutes(quoteExpiryMinutes)); var info = blockChainInfo.GetInfo(); feeQuoteViewModelGet.MinerId = await minerId.GetCurrentMinerIdAsync(); feeQuoteViewModelGet.CurrentHighestBlockHash = info.BestBlockHash; feeQuoteViewModelGet.CurrentHighestBlockHeight = info.BestBlockHeight; logger.LogInformation($"Returning feeQuote with ExpiryTime: {feeQuoteViewModelGet.ExpiryTime}."); return(await SignIfRequiredAsync(feeQuoteViewModelGet, feeQuoteViewModelGet.MinerId)); }
public Task <FeeQuote> InsertFeeQuoteAsync(FeeQuote feeQuote) { throw new NotImplementedException(); }
/// <summary> /// Check if specified transaction meets fee policy /// </summary> /// <param name="txBytesLength">Transactions</param> /// <param name="sumPrevOuputs">result of CollectPreviousOutputs. If previous outputs are not found there, the node is consulted</param> /// <returns></returns> public (bool okToMine, bool okToRely) CheckFees(Transaction transaction, long txBytesLength, Money sumPrevOuputs, FeeQuote feeQuote) { // This could leave some og the bytes unparsed. In this case we would charge for bytes at the ned of // the stream that will not be published to the blockchain, but this is sender's problem. Money sumNewOuputs = Money.Zero; long dataBytes = 0; foreach (var output in transaction.Outputs) { sumNewOuputs += output.Value; if (output.Value < 0L) { throw new ExceptionWithSafeErrorMessage("Negative inputs are not allowed"); } if (IsDataOuput(output)) { dataBytes += output.ScriptPubKey.Length; } } long actualFee = (sumPrevOuputs - sumNewOuputs).Satoshi; long normalBytes = txBytesLength - dataBytes; long feesRequiredMining = 0; long feesRequiredRelay = 0; foreach (var fee in feeQuote.Fees) { if (fee.FeeType == "standard") { feesRequiredMining += (normalBytes * fee.MiningFee.Satoshis) / fee.MiningFee.Bytes; feesRequiredRelay += (normalBytes * fee.RelayFee.Satoshis) / fee.RelayFee.Bytes; } else if (fee.FeeType == "data") { feesRequiredMining += (dataBytes * fee.MiningFee.Satoshis) / fee.MiningFee.Bytes; feesRequiredRelay += (dataBytes * fee.RelayFee.Satoshis) / fee.RelayFee.Bytes; } } bool okToMine = actualFee >= feesRequiredMining; bool okToRelay = actualFee >= feesRequiredRelay; return(okToMine, okToRelay); }
public PolicyQuoteViewModelGet(FeeQuote feeQuote, string[] urls) : base(feeQuote, urls) { Policies = feeQuote.PoliciesDict; // Other fields are initialized from BlockChainInfo and MinerId }