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 }