波场(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 TronWallet

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)离线生成私钥和地址 - 笔记

posted @ 2023-09-06 19:21  lijingkun  阅读(1336)  评论(0编辑  收藏  举报