From 6e8404e1dd0f018eca088a59bb6960972e2f7b02 Mon Sep 17 00:00:00 2001 From: CodingEnthusiast Date: Tue, 31 Dec 2024 10:35:10 +0330 Subject: [PATCH] Refactor API response --- SharpPusher/Models/Response.cs | 21 ++++ SharpPusher/Models/State.cs | 15 +++ SharpPusher/Services/Api.cs | 15 ++- SharpPusher/Services/ErrorCollection.cs | 87 ---------------- SharpPusher/Services/P2P.cs | 14 +-- .../Services/PushServices/BlockCypher.cs | 18 ++-- .../Services/PushServices/BlockchainInfo.cs | 60 ++++++----- .../Services/PushServices/Blockchair.cs | 99 +++++++++---------- SharpPusher/Services/Response.cs | 19 ---- SharpPusher/ViewModels/MainWindowViewModel.cs | 58 ++++++----- SharpPusher/ViewModels/ViewModelBase.cs | 39 -------- 11 files changed, 174 insertions(+), 271 deletions(-) create mode 100644 SharpPusher/Models/Response.cs create mode 100644 SharpPusher/Models/State.cs delete mode 100644 SharpPusher/Services/ErrorCollection.cs delete mode 100644 SharpPusher/Services/Response.cs delete mode 100644 SharpPusher/ViewModels/ViewModelBase.cs diff --git a/SharpPusher/Models/Response.cs b/SharpPusher/Models/Response.cs new file mode 100644 index 0000000..dae6bfa --- /dev/null +++ b/SharpPusher/Models/Response.cs @@ -0,0 +1,21 @@ +// SharpPusher +// Copyright (c) 2017 Coding Enthusiast +// Distributed under the MIT software license, see the accompanying +// file LICENCE or http://www.opensource.org/licenses/mit-license.php. + +namespace SharpPusher.Models +{ + public class Response + { + public bool IsSuccess { get; private set; } = true; + public string Message { get; private set; } = string.Empty; + + private void SetMsg(string msg, bool success) + { + IsSuccess = success; + Message = msg; + } + public void SetMessage(string msg) => SetMsg(msg, true); + public void SetError(string msg) => SetMsg(msg, false); + } +} diff --git a/SharpPusher/Models/State.cs b/SharpPusher/Models/State.cs new file mode 100644 index 0000000..e262b75 --- /dev/null +++ b/SharpPusher/Models/State.cs @@ -0,0 +1,15 @@ +// SharpPusher +// Copyright (c) 2017 Coding Enthusiast +// Distributed under the MIT software license, see the accompanying +// file LICENCE or http://www.opensource.org/licenses/mit-license.php. + +namespace SharpPusher.Models +{ + public enum State + { + Ready, + Broadcasting, + Success, + Failed + } +} diff --git a/SharpPusher/Services/Api.cs b/SharpPusher/Services/Api.cs index 33448e3..c22aca0 100644 --- a/SharpPusher/Services/Api.cs +++ b/SharpPusher/Services/Api.cs @@ -4,6 +4,7 @@ // file LICENCE or http://www.opensource.org/licenses/mit-license.php. using Newtonsoft.Json.Linq; +using SharpPusher.Models; using System; using System.Net.Http; using System.Threading.Tasks; @@ -23,7 +24,7 @@ public abstract class Api /// /// Signed raw transaction in hex format /// Result of broadcasting. - public abstract Task> PushTx(string txHex); + public abstract Task PushTx(string txHex); /// @@ -33,9 +34,9 @@ public abstract class Api /// The JSON key used for making the HttpContent in JSON format. /// Api url to use. /// Result of broadcasting. - protected static async Task> PushTx(string txHex, string jKey, string url) + protected static async Task PushTx(string txHex, string jKey, string url) { - Response resp = new(); + Response resp = new(); using HttpClient client = new(); try @@ -46,12 +47,16 @@ protected static async Task> PushTx(string txHex, string jKey, }; HttpResponseMessage httpResp = await client.PostAsync(url, new StringContent(tx.ToString())); - resp.Result = await httpResp.Content.ReadAsStringAsync(); + if (!httpResp.IsSuccessStatusCode) + { + resp.SetError("API response doesn't indicate success."); + } + resp.SetMessage(await httpResp.Content.ReadAsStringAsync()); } catch (Exception ex) { string errMsg = (ex.InnerException == null) ? ex.Message : ex.Message + " " + ex.InnerException; - resp.Errors.Add(errMsg); + resp.SetError(errMsg); } return resp; diff --git a/SharpPusher/Services/ErrorCollection.cs b/SharpPusher/Services/ErrorCollection.cs deleted file mode 100644 index 40c8423..0000000 --- a/SharpPusher/Services/ErrorCollection.cs +++ /dev/null @@ -1,87 +0,0 @@ -// SharpPusher -// Copyright (c) 2017 Coding Enthusiast -// Distributed under the MIT software license, see the accompanying -// file LICENCE or http://www.opensource.org/licenses/mit-license.php. - -using System.Collections; -using System.Collections.Generic; -using System.Text; - -namespace SharpPusher.Services -{ - public class ErrorCollection : IReadOnlyCollection - { - private readonly List ErrorList = new List(); - - - /// - /// Returns true if there is any errors available. - /// - /// true if there is any error, false if empty. - public bool Any() - { - return ErrorList.Count > 0; - } - - /// - /// Adds one error to the list of errors - /// - /// Error string to add. - internal void Add(string errorMessage) - { - ErrorList.Add(errorMessage); - } - - /// - /// Adds multiple errors to the list of errors - /// - /// List of errors to add. - internal void AddRange(IEnumerable multiError) - { - ErrorList.AddRange(multiError); - } - - /// - /// Clears all errors - /// - internal void Clear() - { - ErrorList.Clear(); - } - - /// - /// Returns a formated string of all the errors. - /// - /// Formatted string of all the errors - public string GetErrors() - { - StringBuilder sb = new StringBuilder(); - foreach (var item in ErrorList) - { - sb.AppendLine("- " + item); - } - return sb.ToString(); - } - - - #region ICommand - - public int Count - { - get { return ErrorList.Count; } - } - - public IEnumerator GetEnumerator() - { - return ErrorList.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)ErrorList).GetEnumerator(); - } - - #endregion - - } -} diff --git a/SharpPusher/Services/P2P.cs b/SharpPusher/Services/P2P.cs index dcdefa5..e1b25a8 100644 --- a/SharpPusher/Services/P2P.cs +++ b/SharpPusher/Services/P2P.cs @@ -8,6 +8,7 @@ using Autarkysoft.Bitcoin.Clients; using Autarkysoft.Bitcoin.P2PNetwork.Messages; using Autarkysoft.Bitcoin.P2PNetwork.Messages.MessagePayloads; +using SharpPusher.Models; using System.Threading.Tasks; namespace SharpPusher.Services @@ -46,8 +47,10 @@ public P2P(bool isMainNet) public override string ApiName => "P2P"; - public override async Task> PushTx(string txHex) + public override async Task PushTx(string txHex) { + Response resp = new(); + Transaction tx = new(txHex); Message msg = new(new TxPayload(tx), netType); @@ -55,16 +58,13 @@ public override async Task> PushTx(string txHex) { DnsSeeds = netType == NetworkType.MainNet ? DnsMain : DnsTest }; - MinimalClient client = new(settings); + using MinimalClient client = new(settings); client.Start(); await Task.Delay(TimeConstants.MilliSeconds.FiveSec); client.Send(msg); - return new Response() - { - Result = $"Transaction was sent to {settings.MaxConnectionCount} nodes. " + - $"Transaction ID: {tx.GetTransactionId()}" - }; + resp.SetMessage($"Transaction was sent to {settings.MaxConnectionCount} nodes. Transaction ID: {tx.GetTransactionId()}"); + return resp; } } } diff --git a/SharpPusher/Services/PushServices/BlockCypher.cs b/SharpPusher/Services/PushServices/BlockCypher.cs index a74c09a..8fe66c1 100644 --- a/SharpPusher/Services/PushServices/BlockCypher.cs +++ b/SharpPusher/Services/PushServices/BlockCypher.cs @@ -4,34 +4,32 @@ // file LICENCE or http://www.opensource.org/licenses/mit-license.php. using Newtonsoft.Json.Linq; +using SharpPusher.Models; using System.Threading.Tasks; namespace SharpPusher.Services.PushServices { public sealed class BlockCypher : Api { - public override string ApiName - { - get { return "BlockCypher"; } - } + public override string ApiName => "BlockCypher"; - public override async Task> PushTx(string txHex) + public override async Task PushTx(string txHex) { - Response resp = await PushTx(txHex, "tx", "https://api.blockcypher.com/v1/bcy/test/txs/push"); - if (resp.Errors.Any()) + Response resp = await PushTx(txHex, "tx", "https://api.blockcypher.com/v1/bcy/test/txs/push"); + if (!resp.IsSuccess) { return resp; } - JObject jResult = JObject.Parse(resp.Result); + JObject jResult = JObject.Parse(resp.Message); if (jResult["error"] != null) { - resp.Errors.Add(jResult["error"].ToString()); + resp.SetError(jResult["error"].ToString()); } else { - resp.Result = "Successfully done. Tx ID: " + jResult["hash"].ToString(); + resp.SetMessage($"Successfully done. Tx ID: {jResult["hash"]}"); } return resp; diff --git a/SharpPusher/Services/PushServices/BlockchainInfo.cs b/SharpPusher/Services/PushServices/BlockchainInfo.cs index 173ed3f..73ac71c 100644 --- a/SharpPusher/Services/PushServices/BlockchainInfo.cs +++ b/SharpPusher/Services/PushServices/BlockchainInfo.cs @@ -5,6 +5,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using SharpPusher.Models; using System; using System.Net.Http; using System.Text; @@ -14,53 +15,48 @@ namespace SharpPusher.Services.PushServices { public sealed class BlockchainInfo : Api { - public override string ApiName - { - get { return "Blockchain.Info"; } - } + public override string ApiName => "Blockchain.Info"; - public async override Task> PushTx(string txHex) + public async override Task PushTx(string txHex) { - Response resp = new Response(); + using HttpClient client = new(); + Response resp = new(); - using (HttpClient client = new HttpClient()) + try { - try - { - client.BaseAddress = new Uri("https://blockchain.info"); + client.BaseAddress = new Uri("https://blockchain.info"); - string json = JsonConvert.SerializeObject(txHex); - string contentType = "application/x-www-form-urlencoded"; - HttpContent httpContent = new MultipartFormDataContent - { - new StringContent(json, Encoding.UTF8, contentType) - }; + string json = JsonConvert.SerializeObject(txHex); + string contentType = "application/x-www-form-urlencoded"; + HttpContent httpContent = new MultipartFormDataContent + { + new StringContent(json, Encoding.UTF8, contentType) + }; - HttpResponseMessage result = await client.PostAsync("pushtx", httpContent); - string sResult = await result.Content.ReadAsStringAsync(); - if (result.IsSuccessStatusCode) + HttpResponseMessage result = await client.PostAsync("pushtx", httpContent); + string sResult = await result.Content.ReadAsStringAsync(); + if (result.IsSuccessStatusCode) + { + if (sResult != null && sResult.StartsWith("{\"error\":")) { - if (sResult != null && sResult.StartsWith("{\"error\":")) - { - JObject jObject = JObject.Parse(sResult); - resp.Errors.Add(jObject["error"].ToString()); - } - else - { - resp.Result = sResult; - } + JObject jObject = JObject.Parse(sResult); + resp.SetError(jObject["error"].ToString()); } else { - resp.Errors.Add(sResult); + resp.SetMessage(sResult); } } - catch (Exception ex) + else { - string errMsg = (ex.InnerException == null) ? ex.Message : ex.Message + " " + ex.InnerException; - resp.Errors.Add(errMsg); + resp.SetError(sResult); } } + catch (Exception ex) + { + string errMsg = (ex.InnerException == null) ? ex.Message : ex.Message + " " + ex.InnerException; + resp.SetError(errMsg); + } return resp; } diff --git a/SharpPusher/Services/PushServices/Blockchair.cs b/SharpPusher/Services/PushServices/Blockchair.cs index 3eed73a..4e22475 100644 --- a/SharpPusher/Services/PushServices/Blockchair.cs +++ b/SharpPusher/Services/PushServices/Blockchair.cs @@ -4,6 +4,7 @@ // file LICENCE or http://www.opensource.org/licenses/mit-license.php. using Newtonsoft.Json.Linq; +using SharpPusher.Models; using System; using System.Collections.Generic; using System.Net; @@ -46,70 +47,68 @@ public enum Chain public override string ApiName => "Blockchair"; - public override async Task> PushTx(string txHex) + public override async Task PushTx(string txHex) { - Response resp = new Response(); + using HttpClient client = new(); + Response resp = new(); - using (HttpClient client = new HttpClient()) + try { - try + string chainName = chain switch { - string chainName = chain switch - { - Chain.BTC => "bitcoin", - Chain.TBTC => "bitcoin/testnet", - Chain.BCH => "bitcoin-cash", - Chain.DOGE => "dogecoin", - Chain.LTC => "litecoin", - Chain.XMR => "monero", - Chain.ADA => "cardano", - Chain.BSV => "bitcoin-sv", - Chain.EOS => "eos", - Chain.ETH => "ethereum", - Chain.ΤETH => "ethereum/testnet", - Chain.XIN => "mixin", - Chain.XLM => "stellar", - Chain.XRP => "ripple", - Chain.XTZ => "tezos", - Chain.DASH => "dash", - Chain.GRS => "groestlcoin", - Chain.ABC => "bitcoin-abc", - Chain.ZEC => "zcash", - _ => throw new ArgumentException("Undefined Chain") - }; + Chain.BTC => "bitcoin", + Chain.TBTC => "bitcoin/testnet", + Chain.BCH => "bitcoin-cash", + Chain.DOGE => "dogecoin", + Chain.LTC => "litecoin", + Chain.XMR => "monero", + Chain.ADA => "cardano", + Chain.BSV => "bitcoin-sv", + Chain.EOS => "eos", + Chain.ETH => "ethereum", + Chain.ΤETH => "ethereum/testnet", + Chain.XIN => "mixin", + Chain.XLM => "stellar", + Chain.XRP => "ripple", + Chain.XTZ => "tezos", + Chain.DASH => "dash", + Chain.GRS => "groestlcoin", + Chain.ABC => "bitcoin-abc", + Chain.ZEC => "zcash", + _ => throw new ArgumentException("Undefined Chain") + }; - string url = $"https://api.blockchair.com/{chainName}/push/transaction"; + string url = $"https://api.blockchair.com/{chainName}/push/transaction"; - var content = new FormUrlEncodedContent(new[] - { + var content = new FormUrlEncodedContent( + [ new KeyValuePair("data", txHex) - }); + ]); - HttpResponseMessage httpResp = await client.PostAsync(url, content); + HttpResponseMessage httpResp = await client.PostAsync(url, content); - string result = await httpResp.Content.ReadAsStringAsync(); - if (httpResp.IsSuccessStatusCode) - { - JObject jResult = JObject.Parse(result); - resp.Result = "Successfully done. Tx ID: " + jResult["data"]["transaction_hash"].ToString(); - } - else if (httpResp.StatusCode == HttpStatusCode.BadRequest) - { - JObject jResult = JObject.Parse(result); - resp.Result = "Bad request: " + jResult["context"]["error"].ToString(); - } - else - { - resp.Errors.Add(result); - } + string result = await httpResp.Content.ReadAsStringAsync(); + if (httpResp.IsSuccessStatusCode) + { + JObject jResult = JObject.Parse(result); + resp.SetMessage($"Successfully done. Tx ID: {jResult["data"]?["transaction_hash"]}"); + } + else if (httpResp.StatusCode == HttpStatusCode.BadRequest) + { + JObject jResult = JObject.Parse(result); + resp.SetError($"Bad request: {jResult["context"]?["error"]}"); } - catch (Exception ex) + else { - string errMsg = (ex.InnerException == null) ? ex.Message : ex.Message + " " + ex.InnerException; - resp.Errors.Add(errMsg); + resp.SetError(result); } } + catch (Exception ex) + { + string errMsg = (ex.InnerException == null) ? ex.Message : ex.Message + " " + ex.InnerException; + resp.SetError(errMsg); + } return resp; } diff --git a/SharpPusher/Services/Response.cs b/SharpPusher/Services/Response.cs deleted file mode 100644 index 0a2097f..0000000 --- a/SharpPusher/Services/Response.cs +++ /dev/null @@ -1,19 +0,0 @@ -// SharpPusher -// Copyright (c) 2017 Coding Enthusiast -// Distributed under the MIT software license, see the accompanying -// file LICENCE or http://www.opensource.org/licenses/mit-license.php. - -namespace SharpPusher.Services -{ - public class Response - { - private readonly ErrorCollection errors = new ErrorCollection(); - - public ErrorCollection Errors - { - get { return errors; } - } - - public T Result { get; set; } - } -} diff --git a/SharpPusher/ViewModels/MainWindowViewModel.cs b/SharpPusher/ViewModels/MainWindowViewModel.cs index 972b9af..b0ed8a7 100644 --- a/SharpPusher/ViewModels/MainWindowViewModel.cs +++ b/SharpPusher/ViewModels/MainWindowViewModel.cs @@ -18,7 +18,7 @@ namespace SharpPusher.ViewModels { - public class MainWindowViewModel : ViewModelBase + public class MainWindowViewModel : InpcBase { public MainWindowViewModel() { @@ -210,12 +210,29 @@ public Api SelectedApi } } + private string _msg = string.Empty; + public string Message + { + get => _msg; + set => SetField(ref _msg, value); + } + + private State _state = State.Ready; + public State CurrentState + { + get => _state; + set => SetField(ref _state, value); + } + [DependsOnProperty(nameof(SelectedNetwork))] - public bool IsCheckTxVisible => SelectedNetwork == Networks.Bitcoin || SelectedNetwork == Networks.BitcoinTestnet || - SelectedNetwork == Networks.BitcoinCash || SelectedNetwork == Networks.BitcoinABC || - SelectedNetwork == Networks.BitcoinSV || SelectedNetwork == Networks.Litecoin || - SelectedNetwork == Networks.Dogecoin; + public bool IsCheckTxVisible => SelectedNetwork is Networks.Bitcoin + or Networks.BitcoinTestnet + or Networks.BitcoinCash + or Networks.BitcoinABC + or Networks.BitcoinSV + or Networks.Litecoin + or Networks.Dogecoin; private bool _checkTx = true; public bool CheckTx @@ -225,7 +242,7 @@ public bool CheckTx } public static string CheckTxToolTip => "Enable to check the transaction hex using Bitcoin.Net library by deserializing " + - "and evaluating all its scripts."; + "and evaluating all its scripts."; private bool _isSending; @@ -246,11 +263,12 @@ public bool IsSending public BindableCommand BroadcastTxCommand { get; private set; } private async void BroadcastTx() { - Errors = string.Empty; + Message = string.Empty; + CurrentState = State.Ready; if (!Base16.TryDecode(RawTx, out byte[] result)) { - Status = "Invalid hex."; + Message = "Invalid hex."; return; } @@ -261,7 +279,7 @@ private async void BroadcastTx() Transaction tx = new(); if (!tx.TryDeserialize(stream, out Errors error)) { - Status = $"Invalid transaction. Error message: {error.Convert()}"; + Message = $"Could not deserialize transaction. Error message:{Environment.NewLine}{error.Convert()}"; return; } @@ -269,7 +287,8 @@ private async void BroadcastTx() { if (!tx.TxInList[i].SigScript.TryEvaluate(ScriptEvalMode.Legacy, out _, out _, out error)) { - Status = $"Invalid input signature script at index {i}. Error message: {error}"; + Message = $"Could not evaluate {(i + 1).ToOrdinal()} input's signature script. " + + $"Error message:{Environment.NewLine}{error}"; return; } } @@ -277,25 +296,20 @@ private async void BroadcastTx() { if (!tx.TxOutList[i].PubScript.TryEvaluate(ScriptEvalMode.Legacy, out _, out _, out error)) { - Status = $"Invalid input pubkey script at index {i}. Error message: {error}"; + Message = $"Could not evaluate {(i + 1).ToOrdinal()} output's pubkey script. " + + $"Error message: {error}"; return; } } } IsSending = true; - Status = "Broadcasting Transaction..."; + CurrentState = State.Broadcasting; + + Response resp = await SelectedApi.PushTx(RawTx); + Message = resp.Message; + CurrentState = resp.IsSuccess ? State.Success : State.Failed; - Response resp = await SelectedApi.PushTx(RawTx); - if (resp.Errors.Any()) - { - Errors = resp.Errors.GetErrors(); - Status = "Finished with error."; - } - else - { - Status = resp.Result; - } IsSending = false; } private bool CanBroadcast() diff --git a/SharpPusher/ViewModels/ViewModelBase.cs b/SharpPusher/ViewModels/ViewModelBase.cs deleted file mode 100644 index 42552b7..0000000 --- a/SharpPusher/ViewModels/ViewModelBase.cs +++ /dev/null @@ -1,39 +0,0 @@ -// SharpPusher -// Copyright (c) 2017 Coding Enthusiast -// Distributed under the MIT software license, see the accompanying -// file LICENCE or http://www.opensource.org/licenses/mit-license.php. - -using SharpPusher.MVVM; - -namespace SharpPusher.ViewModels -{ - public class ViewModelBase : InpcBase - { - /// - /// Used for changing the visibility of error message TextBox. - /// - [DependsOnProperty(nameof(Errors))] - public bool IsErrorMsgVisible => !string.IsNullOrEmpty(Errors); - - - private string _errors = string.Empty; - /// - /// String containing all the errors. - /// - public string Errors - { - get => _errors; - set => SetField(ref _errors, value); - } - - private string _status = string.Empty; - /// - /// Status, showing current action being performed. - /// - public string Status - { - get => _status; - set => SetField(ref _status, value); - } - } -}