波场(Tron)监控区块交易记录(http 版本,不依赖 sdk)
做项目的时候经常需要通过监控链的区块交易记录,然后根据交易记录与用户的地址进行核对,从而得知用户地址的充币和提币的情况。
var blockNumber = 0; //用来记录当前检查的区块高度
while (true) {
var stopWatch = new Stopwatch();
stopWatch.Start();
try {
string responseString;
if (blockNumber == 0) {
const string url = "https://api.trongrid.io/wallet/getnowblock"; //获取最新区块交易明细
responseString = HttpClientHelper.Get(url);
} else {
const string url = "https://api.trongrid.io/wallet/getblockbynum"; // 指定 blockNumber 获取区块交易明显
var requestBody = new { num = blockNumber + 1 };
responseString = HttpClientHelper.Post(url, JsonConvert.SerializeObject(requestBody), Encoding.UTF8);
}
var responseObject = JsonConvert.DeserializeObject<dynamic>(responseString);
if (responseObject == null) throw new ThreadSleepException();
if (responseObject.blockID == null) throw new ThreadSleepException();
if (responseObject.block_header == null) throw new ThreadSleepException();
blockNumber = (int)responseObject.block_header.raw_data.number;
var blockHash = (string)responseObject.blockID;
var millisecondTimestamp = (long)responseObject.block_header.raw_data.timestamp;
Console.WriteLine($" 区块高度 {blockNumber}\t区块哈希 {blockHash}");
if (responseObject.transactions == null || responseObject.transactions.Count == 0) continue;
var addresses = new List<string>();
foreach (var transaction in responseObject.transactions) {
var ret = transaction.ret;
if (ret == null) continue;
if (ret.Count == 0) continue;
if (ret[0].contractRet == null || ret[0].contractRet != "SUCCESS") continue;
var rawData = transaction.raw_data;
if (rawData == null) continue;
var contracts = rawData.contract;
if (contracts == null) continue;
if (contracts.Count == 0) continue;
var contract = contracts[0];
if (contract == null) continue;
var parameter = contract.parameter;
if (parameter == null) continue;
var value = parameter.value;
if (value == null) continue;
var type = (string)contract.type;
switch (type) {
case "TransferContract": {
if (value.to_address != null && value.asset_name == null) {
// TRX 转出地址
var fromAddress = Base58Encoder.EncodeFromHex((string)value.owner_address, 0x41);
// TRX 转入地址
var toAddress = Base58Encoder.EncodeFromHex((string)value.to_address, 0x41);
// 转账金额,long 类型
var amount = (long)value.amount;
// 转化成 decimal 类型方便业务逻辑处理
var transferAmount = amount / new decimal(1000000);
if (RedisProvider.Instance.KeyExists(fromAddress)) {
// TODO
}
if (RedisProvider.Instance.KeyExists(toAddress)) {
// TODO
}
}
break;
}
case "TriggerSmartContract": {
// 这里监控的是 USDT 合约地址,如果需要监控其他 TRC20 代币,修改合约地址即可
if (value.contract_address != null && (string)value.contract_address == "41a614f803b6fd780986a42c78ec9c7f77e6ded13c") {
var data = (string)value.data;
switch (data[..8]) {
case "a9059cbb": {
// USDT 转出地址
var fromAddress = Base58Encoder.EncodeFromHex((string)value.owner_address, 0x41);
// USDT 转入地址
var toAddress = Base58Encoder.EncodeFromHex(((string)value.data).Substring(8, 64), 0x41);
// 转账金额,long 类型
var amount = Convert.ToInt64(((string)value.data).Substring(72, 64), 16);
// 转化成 decimal 类型方便业务逻辑处理
var transferAmount = amount / new decimal(1000000);
if (RedisProvider.Instance.KeyExists(fromAddress)) {
//TODO
}
if (RedisProvider.Instance.KeyExists(toAddress)) {
//TODO
}
break;
}
}
}
break;
}
case "DelegateResourceContract": {
var receiverAddress = Base58Encoder.EncodeFromHex((string)value.receiver_address, 0x41);
// receiverAddress 是指监控到地址接受到了代理能量
// TODO
break;
}
default: {
continue;
}
}
}
} catch (ThreadSleepException) {
if (stopWatch.ElapsedMilliseconds >= 1000) continue;
Thread.Sleep((int)(1000 - stopWatch.ElapsedMilliseconds));
} catch (Exception exception) {
Console.WriteLine($" {exception}");
LogManager.GetLogger(typeof(Program)).Error(exception);
if (stopWatch.ElapsedMilliseconds >= 1000) continue;
Thread.Sleep((int)(1000 - stopWatch.ElapsedMilliseconds));
}
if (stopWatch.ElapsedMilliseconds >= 2500) continue;
Thread.Sleep((int)(2500 - stopWatch.ElapsedMilliseconds));
}
说明
这段代码运行的时候,首选是获取的最新的区块高度(接口 getnowblock),然后逐个累加(接口 getblockbynum)区块进行检查,核对交易,根据自己的业务逻辑在 TODO 里面进行处理。
我这里使用了 Redis 获取项目里面用户的地址与区块的交易记录进行比对。
其次代码里面使用了 stopWatch 进行区块时间的检查,因为波场是3秒出一个区块,但是由于服务器可能会出现网络故障,这个时候会漏掉区块,跟不上最新的高度,所以每次检查是 2.5秒,这样即使短时间断网,监控程序很快就能追上最新的高度,保证能获取到事实的交易数据。
其他依赖
PM> Install-Package Newtonsoft.Json
PM> Install-Package StackExchange.Redis
PM> Install-Package TronNet.Wallet -Version 1.0.1
HttpClientHelper
请求波场主网需要用到 API-KEY 可以到 https://www.trongrid.io 进行申请
public static class HttpClientHelper {
public static string Get(string url, int timeout = 12000) {
var resp = Get((HttpWebRequest)WebRequest.Create(url), timeout);
using var s = resp.GetResponseStream();
using var sr = new StreamReader(s);
return sr.ReadToEnd();
}
private static HttpWebResponse Get(HttpWebRequest req, int timeout = 12000) {
req.Method = "GET";
req.ContentType = "application/json";
req.Timeout = timeout;
req.Accept = "application/json";
req.Headers.Set("TRON-PRO-API-KEY", "80a8b20f-a917-43a9-a2f1-809fe6eec0d6");
return (HttpWebResponse)req.GetResponse();
}
public static string Post(string url, string requestBody, Encoding encoding, int timeout = 12000) {
var resp = Post((HttpWebRequest)WebRequest.Create(url), requestBody, encoding, timeout);
using var s = resp.GetResponseStream();
using var sr = new StreamReader(s);
return sr.ReadToEnd();
}
private static HttpWebResponse Post(HttpWebRequest req, string requestBody, Encoding encoding, int timeout = 12000) {
var bs = encoding.GetBytes(requestBody);
req.Method = "POST";
req.ContentType = "application/json";
req.ContentLength = bs.Length;
req.Timeout = timeout;
req.Accept = "application/json";
req.Headers.Set("TRON-PRO-API-KEY", "80a8b20f-a917-43a9-a2f1-809fe6eec0d6");
using (var s = req.GetRequestStream()) {
s.Write(bs, 0, bs.Length);
}
return (HttpWebResponse)req.GetResponse();
}
}
RedisProvider
public class RedisProvider {
private readonly IDatabase _database = ConnectionMultiplexer.Connect("127.0.0.1:6379").GetDatabase();
private RedisProvider() { }
public static RedisProvider Instance { get; } = new RedisProvider();
public bool KeyExists(string key) {
return _database.KeyExists(key);
}
public bool StringSet(string key, string value) {
return _database.StringSet(key, value);
}
public string StringGet(string key) {
var value = _database.StringGet(key);
return value.IsNull ? string.Empty : value.ToString();
}
}
public class ThreadSleepException : Exception {
}
其他
波场(Tron)获取钱包TRX、USDT余额和剩余带宽、能量 - 笔记
波场(Tron)钱包设置多签
波场(Tron)网页版(本地)钱包开源
波场(Tron)项目常用工具分享
波场(Tron)离线签名、广播交易 - 笔记
波场(Tron)离线生成私钥和地址 - 笔记