波場(Tron)監控區塊交易記錄(http 版本,不依賴 sdk)

lijingkun發表於2023-09-06

做專案的時候經常需要透過監控鏈的區塊交易記錄,然後根據交易記錄與使用者的地址進行核對,從而得知使用者地址的充幣和提幣的情況。

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

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)離線生成私鑰和地址 - 筆記

相關文章