/ Framework / NethereumWorkflow / NethereumInteraction.cs
NethereumInteraction.cs
  1  using System.Numerics;
  2  using BlockchainUtils;
  3  using Logging;
  4  using Nethereum.ABI.FunctionEncoding.Attributes;
  5  using Nethereum.Contracts;
  6  using Nethereum.Model;
  7  using Nethereum.RPC.Eth.Blocks;
  8  using Nethereum.RPC.Eth.DTOs;
  9  using Nethereum.Web3;
 10  using Utils;
 11  
 12  namespace NethereumWorkflow
 13  {
 14      public class NethereumInteraction
 15      {
 16          private readonly BlockCache blockCache;
 17  
 18          private readonly ILog log;
 19          private readonly Web3 web3;
 20  
 21          internal NethereumInteraction(ILog log, Web3 web3, BlockCache blockCache)
 22          {
 23              this.log = log;
 24              this.web3 = web3;
 25              this.blockCache = blockCache;
 26          }
 27  
 28          public string SendEth(string toAddress, Ether eth)
 29          {
 30              var asDecimal = ((decimal)eth.Wei) / (decimal)TokensIntExtensions.WeiPerEth;
 31              return SendEth(toAddress, asDecimal);
 32          }
 33  
 34          public string SendEth(string toAddress, decimal ethAmount)
 35          {
 36              return DebugLogWrap(() =>
 37              {
 38                  var receipt = Time.Wait(web3.Eth.GetEtherTransferService().TransferEtherAndWaitForReceiptAsync(toAddress, ethAmount));
 39                  if (!receipt.Succeeded()) throw new Exception("Unable to send Eth");
 40                  return receipt.TransactionHash;
 41              }, nameof(SendEth));
 42          }
 43  
 44          public BigInteger GetEthBalance()
 45          {
 46              return DebugLogWrap(() =>
 47              {
 48                  return GetEthBalance(web3.TransactionManager.Account.Address);
 49              }, nameof(GetEthBalance));
 50          }
 51  
 52          public BigInteger GetEthBalance(string address)
 53          {
 54              return DebugLogWrap(() =>
 55              {
 56                  var balance = Time.Wait(web3.Eth.GetBalance.SendRequestAsync(address));
 57                  return balance.Value;
 58              }, nameof(GetEthBalance));
 59          }
 60  
 61          public TResult Call<TFunction, TResult>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new()
 62          {
 63              return DebugLogWrap(() =>
 64              {
 65                  var handler = web3.Eth.GetContractQueryHandler<TFunction>();
 66                  return Time.Wait(handler.QueryAsync<TResult>(contractAddress, function));
 67              }, nameof(Call) + "." + typeof(TFunction).ToString());
 68          }
 69  
 70          public TResult Call<TFunction, TResult>(string contractAddress, TFunction function, ulong blockNumber) where TFunction : FunctionMessage, new()
 71          {
 72              return DebugLogWrap(() =>
 73              {
 74                  var handler = web3.Eth.GetContractQueryHandler<TFunction>();
 75                  return Time.Wait(handler.QueryAsync<TResult>(contractAddress, function, new BlockParameter(blockNumber)));
 76              }, nameof(Call) + "." + typeof(TFunction).ToString());
 77          }
 78  
 79          public void Call<TFunction>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new()
 80          {
 81              DebugLogWrap<string>(() =>
 82              {
 83                  var handler = web3.Eth.GetContractQueryHandler<TFunction>();
 84                  Time.Wait(handler.QueryRawAsync(contractAddress, function));
 85                  return string.Empty;
 86              }, nameof(Call) + "." + typeof(TFunction).ToString());
 87          }
 88  
 89          public void Call<TFunction>(string contractAddress, TFunction function, ulong blockNumber) where TFunction : FunctionMessage, new()
 90          {
 91              DebugLogWrap<string>(() =>
 92              {
 93                  var handler = web3.Eth.GetContractQueryHandler<TFunction>();
 94                  var result = Time.Wait(handler.QueryRawAsync(contractAddress, function, new BlockParameter(blockNumber)));
 95                  return string.Empty;
 96              }, nameof(Call) + "." + typeof(TFunction).ToString());
 97          }
 98  
 99          public string SendTransaction<TFunction>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new()
100          {
101              return DebugLogWrap(() =>
102              {
103                  var handler = web3.Eth.GetContractTransactionHandler<TFunction>();
104                  var receipt = Time.Wait(handler.SendRequestAndWaitForReceiptAsync(contractAddress, function));
105                  if (!receipt.Succeeded()) throw new Exception("Unable to perform contract transaction.");
106                  return receipt.TransactionHash;
107              }, nameof(SendTransaction) + "." + typeof(TFunction).ToString());
108          }
109  
110          public Transaction GetTransaction(string transactionHash)
111          {
112              return DebugLogWrap(() =>
113              {
114                  return Time.Wait(web3.Eth.Transactions.GetTransactionByHash.SendRequestAsync(transactionHash));
115              }, nameof(GetTransaction));
116          }
117  
118          public decimal? GetSyncedBlockNumber()
119          {
120              return DebugLogWrap<decimal?>(() =>
121              {
122                  var sync = Time.Wait(web3.Eth.Syncing.SendRequestAsync());
123                  var number = Time.Wait(web3.Eth.Blocks.GetBlockNumber.SendRequestAsync());
124                  var numberOfBlocks = number.ToDecimal();
125                  if (sync.IsSyncing) return null;
126                  return numberOfBlocks;
127              }, nameof(GetTransaction));
128          }
129  
130          public bool IsContractAvailable(string abi, string contractAddress)
131          {
132              return DebugLogWrap(() =>
133              {
134                  try
135                  {
136                      var contract = web3.Eth.GetContract(abi, contractAddress);
137                      return contract != null;
138                  }
139                  catch
140                  {
141                      return false;
142                  }
143              }, nameof(IsContractAvailable));
144          }
145  
146          public List<EventLog<TEvent>> GetEvents<TEvent>(string address, BlockInterval blockRange) where TEvent : IEventDTO, new()
147          {
148              return DebugLogWrap(() =>
149              {
150                  return GetEvents<TEvent>(address, blockRange.From, blockRange.To);
151              }, nameof(GetEvents) + "." + typeof(TEvent).ToString());
152          }
153  
154          public List<EventLog<TEvent>> GetEvents<TEvent>(string address, ulong fromBlockNumber, ulong toBlockNumber) where TEvent : IEventDTO, new()
155          {
156              return DebugLogWrap(() =>
157              {
158                  var logs = new List<FilterLog>();
159                  var p = web3.Processing.Logs.CreateProcessor(
160                      action: logs.Add,
161                      minimumBlockConfirmations: 1,
162                      criteria: l => l.IsLogForEvent<TEvent>()
163                  );
164  
165                  var from = new BlockParameter(fromBlockNumber);
166                  var to = new BlockParameter(toBlockNumber);
167                  var ct = new CancellationTokenSource().Token;
168                  Time.Wait(p.ExecuteAsync(toBlockNumber: to.BlockNumber, cancellationToken: ct, startAtBlockNumberIfNotProcessed: from.BlockNumber));
169  
170                  return logs
171                      .Where(l => l.IsLogForEvent<TEvent>())
172                      .Select(l => l.DecodeEvent<TEvent>())
173                      .ToList();
174              }, nameof(GetEvents) + "." + typeof(TEvent).ToString());
175          }
176  
177          public BlockInterval ConvertTimeRangeToBlockRange(TimeRange timeRange)
178          {
179              return DebugLogWrap(() =>
180              {
181                  if (timeRange.To - timeRange.From < TimeSpan.FromSeconds(1.0))
182                      throw new Exception(nameof(ConvertTimeRangeToBlockRange) + ": Time range too small.");
183  
184                  var wrapper = new Web3Wrapper(web3, log);
185                  var blockTimeFinder = new BlockTimeFinder(blockCache, wrapper, log);
186  
187                  var fromBlock = blockTimeFinder.GetLowestBlockNumberAfter(timeRange.From);
188                  var toBlock = blockTimeFinder.GetHighestBlockNumberBefore(timeRange.To);
189  
190                  if (fromBlock == null || toBlock == null)
191                  {
192                      throw new Exception("Failed to convert time range to block range.");
193                  }
194  
195                  return new BlockInterval(
196                      timeRange: timeRange,
197                      from: fromBlock.Value,
198                      to: toBlock.Value
199                  );
200              }, nameof(ConvertTimeRangeToBlockRange));
201          }
202  
203          public BlockTimeEntry GetBlockForNumber(ulong number)
204          {
205              return DebugLogWrap(() =>
206              {
207                  var wrapper = new Web3Wrapper(web3, log);
208                  var blockTimeFinder = new BlockTimeFinder(blockCache, wrapper, log);
209                  return blockTimeFinder.Get(number);
210              }, nameof(GetBlockForNumber));
211          }
212  
213          public BlockWithTransactions GetBlockWithTransactions(ulong number)
214          {
215              return DebugLogWrap(() =>
216              {
217                  var retry = new Retry(nameof(GetBlockWithTransactions),
218                      maxTimeout: TimeSpan.FromMinutes(1.0),
219                      sleepAfterFail: TimeSpan.FromSeconds(1.0),
220                      onFail: f => { },
221                      failFast: false);
222  
223                  return retry.Run(() => Time.Wait(web3.Eth.Blocks.GetBlockWithTransactionsByNumber.SendRequestAsync(new BlockParameter(number))));
224              }, nameof(GetBlockWithTransactions));
225          }
226  
227          private T DebugLogWrap<T>(Func<T> task, string name = "")
228          {
229              log.Debug($"{name} start...", 1);
230              var result = task();
231              log.Debug($"{name} finished", 1);
232              return result;
233          }
234      }
235  }