文章目录
一、解析区块信息,获取交易回执
1.1 根据块号获取block信息
1.2 解析区块交易信息
1.3 判断TRC20合约入账
1.4 基础信息
1.5 代码实现
二、获取区块信息后,解析交易信息
2.1 区块交易解析流程
2.2 基础信息
2.3 代码实现
一、解析区块信息,获取交易回执
该方式首先获取区块信息,循环判断交易体中的交易类型,如果是trx交易,则格式化from及to地址,如果是trc20交易,则格式化合约地址,如果合约地址是trc20-usdt合约地址(或者自己需要的合约地址),那么根据交易哈希查询交易信息,判断交易信息。
此方式的优点是:交易回执信息是该交易在链上已经执行后的信息,所以根据交易哈希获取交易回执的信息更加准确。
此方式的缺点是:程序需要首先获取区块信息,如果遇到trc20-usdt交易,需要再次调用节点获取该交易的详情。trongrid的频次限制是15次每秒,每天最多50万次。而这样的方式请求很容易达到量级,所以如果此方式最好自己搭建节点。
1.1 根据块号获取block信息
根据区块号从固化块获取block信息: /walletsolidity/getblockbynum
以下块中包含了tron三种交易类型:
TRX交易:transferContract
TRC10交易:TransferAssetContract
TRC20交易:TriggerSmartContract
{
"blockID": "00000000025f10e58f5e6ab2525f96c3722042d880409d2547c102cb9133b17e",
"block_header": {
"raw_data": {
"number": 39784677,
"txTrieRoot": "2e1ec8352d749869c02b470d9091706566ec6390a1789823e27f66789c5ca92a",
"witness_address": "4118e2e1c6cdf4b74b7c1eb84682e503213a174955",
"parentHash": "00000000025f10e431b40dfa61b084572d4aa7c7e7990f8caa3a83873d56be42",
"version": 23,
"timestamp": 1649905374000
},
"witness_signature": "099a82e54fa94425561ddb73344208733cd7e69b616b5c365105aa58f43195203ebc380187851d6a626a565f0c15b260d75d1ebcf6d828e44f091b81ae28de3b01"
},
"transactions": [{
"ret": [{
"contractRet": "SUCCESS"
}],
"signature": ["d3b4f064a18930c961beb0f7990ce2951522018269113291c3603ec2acdf39e16b2affe9eaccbbaa4590340edb355aa2298f899dc01401b08f47216188d62cd200"],
"txID": "53bae0b82f5fabd3495fcdfd94043a36d26bbfc48de057b099117c810bac8081",
"raw_data": {
"contract": [{
"parameter": {
"value": {
"data": "a9059cbb000000000000000000000041706748f118136cd90c44473f405001a2ff56601c000000000000000000000000000000000000000000000000000000000f74a718",
"owner_address": "41a346f2bd7d43a5d90b7c57a18196be96b2840e61",
"contract_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"
},
"type_url": "type.googleapis.com/protocol.TriggerSmartContract"
},
"type": "TriggerSmartContract"
}],
"ref_block_bytes": "10e4",
"ref_block_hash": "31b40dfa61b08457",
"expiration": 1649905432383,
"fee_limit": 10000000,
"timestamp": 1649905371000
},
"raw_data_hex": "0a0210e4220831b40dfa61b0845740bfee9bb082305aae01081f12a9010a31747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e54726967676572536d617274436f6e747261637412740a1541a346f2bd7d43a5d90b7c57a18196be96b2840e61121541a614f803b6fd780986a42c78ec9c7f77e6ded13c2244a9059cbb000000000000000000000041706748f118136cd90c44473f405001a2ff56601c000000000000000000000000000000000000000000000000000000000f74a71870f88e98b08230900180ade204"
}, {
"ret": [{
"contractRet": "SUCCESS"
}],
"signature": ["9174795ca83cf1037c79028bd45cc4d2a1bffa8f08650fb107d220cbfcfdd822694fd6b94b1340a9fa5b599b222eb754f313313b55d22556076a7492ec3577d801"],
"txID": "a71d3bae943b36d30150e8e3723c2b3e705d3816d5406ec46f40fce636b68e82",
"raw_data": {
"contract": [{
"parameter": {
"value": {
"amount": 2400000,
"asset_name": "31303034373238",
"owner_address": "TZC7tSW4sdbEhFZZUja3Y2JqtJZrBTjNUz",
"to_address": "TMxLaCEZokckrak3fY2bsTAA6Wt76u1EyK"
},
"type_url": "type.googleapis.com/protocol.TransferAssetContract"
},
"type": "TransferAssetContract"
}],
"ref_block_bytes": "10d1",
"ref_block_hash": "e5c8f0416ed1ef63",
"expiration": 1649905428000,
"timestamp": 1649905371102
},
"raw_data_hex": "0a0210d12208e5c8f0416ed1ef6340a0cc9bb082305a76080212720a32747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5472616e736665724173736574436f6e7472616374123c0a0731303034373238121541febc080f5a59bc935ac5025a9c7228868776e1e11a1541837769e4d605050f49b241a736df9a299b4cd8b22080be920170de8f98b08230"
}, {
"ret": [{
"contractRet": "SUCCESS"
}],
"signature": ["2e71624e6c6224adece0edc9ff126d313b47b009287f672e73f6620f433a847477ac2dcf406deefd5d48378c945ae07baca43e6c06dbaf22fde162bf342a37e800"],
"txID": "26fb019d24d6af85ad4814a2992f9d3d36f2108e01ce1bba93c24c2358100d65",
"raw_data": {
"contract": [{
"parameter": {
"value": {
"amount": 12799000000,
"owner_address": "TV6MuMXfmLbBqPZvBHdwFsDnQeVfnmiuSi",
"to_address": "TWxFVFMSXnUs1Z38P96yke1msDwDbSFH4a"
},
"type_url": "type.googleapis.com/protocol.TransferContract"
},
"type": "TransferContract"
}],
"ref_block_bytes": "10e4",
"ref_block_hash": "31b40dfa61b08457",
"expiration": 1649905432239,
"fee_limit": 10000000,
"timestamp": 1649905371000
},
"raw_data_hex": "0a0210e4220831b40dfa61b0845740afed9bb082305a69080112650a2d747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5472616e73666572436f6e747261637412340a1541d1c4bb7b2f39aba5707711719b2236b5b605af2e121541e62c4cfc420bfc1e2f55a6b74ed306a374c4ac6a18c0fb84d72f70f88e98b08230900180ade204"
}]
}
1.2 解析区块交易信息
.从block中获取transactions。
.遍历transactions,获取raw_daraw_data.contract.type
.判断是否为TRX入账: raw_data.contract.type == transferContract
3.1 TRX入账: raw_data.contract.type == transferContract
判断contractRet是否为SUCCESS(交易成功为SUCCESS,失败为:REVERT), 检查to地址是否为项目内地址(获取区块信息的接口为固化块接口:/walletsolidity/getblockbynum ,所以不再需要块数确认高度的判断)
3.2 TRC10入账: raw_data.contract.type == TransferAssetContract
3.3 TRC20入账、合约中TRX/TRC10入账:raw_data.contract.type == TriggerSmartContract
1.3 判断TRC20合约入账
.判断交易是否为合约交易
raw_data.contract.type == TriggerSmartContract
.判断交易是否成功
判断data.ret.contractRet是否为SUCCESS(交易成功为SUCCESS,失败为:REVERT)
.判断交易是否为USDT交易
判断data.raw_data.contract.parameter.value.contract_address是否为:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t (当然,该地址也可以修改为你所需要的合约地址)
.查询交易详情
调用:/walletsolidity/gettransactioninfobyid,查询固始化交易的info信息,该接口将返回交易的fee信息,所在区块,合约执行事件log等。
.判断交易是否成功
判断data.receipt.result 是否为 SUCCESS(trc20合约交易成功为SUCCESS,失败为:REVERT)
.判断是否为假充值交易
判断data.receipt.log[0].topics[0]是否为:ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef,以此判断该交易是否是一笔TRC20假充值交易,TRC20交易的transfer 及 transferFrom的keccak256哈希值都是该字符串。
.检查to地址是否为项目内地址
判断data.receipt.log[0].topics[2]地址是否为项目内地址
获取区块信息的接口(/walletsolidity/getblockbynum)及获取交易信息的接口(/walletsolidity/gettransactioninfobyid)都是固化块接口 ,所以不再需要块数确认高度的判断)
.获取交易信息中需要的信息
blockNumber:区块高度
data.receipt.log[0].topics[1]:交易真正的from地址
data.receipt.log[0].topics[2]:交易真正的to地址
data.receipt.log[0].data:交易金额(十六进制整数,实际余额还需要除以合约精度)
1.4 基础信息
.什么是固化块?
刚生产出来的区块处于未确认状态,只有被27个Witness(超级节点)中70%以上(即27 * 70% = 19, 向上取整)的witness"认可"的区块才被认为是不可逆区块,一般称为固化块,此时固化块中包含的交易已经被整个区块链网络确认。对未确认状态区块进行"认可"的方式是Witness在其之后生产后继区块。要强调的是:生产这19个区块的Witness互不相同,并且和生产第103个区块的Witness也不同。
所以,当一个区块变成固化块后,打包在该区块内的交易即被区块链接受。通常一个交易上链后 需要一分钟左右固化。
参考文档:https://cn.developers.tron.network/docs/concensus#%E4%BA%A7%E5%9D%97%E6%9C%BA%E5%88%B6
.交易回执信息中的log中,都是什么信息?
log : 交易中触发的Event列表,对于每一个Event,包含以下三部分内容:
address : 合约地址。为了兼容EVM,虚拟机中的地址为不带前缀0x41的hex格式地址,因此如果要解析log中的地址,需要首先在log中的地址前面加上41,然后再转换为Base58格式。
topics : 事件的主题,包括事件本身和标记为indexed的参数。使用 topics 保存 indexed 参数的原因是:区块链储存一般会选择使用 LevelDB 或 RockDB 一类的 key-value 储存引擎。这些引擎一般都支持 prefix-scan 操作,将indexed参数放到topics中,既可以快速检索 Transfer 事件,又可以快速检索某一特定 toAddress 的 Transfer 事件。
data : 事件的非indexed参数,例如amount。
.LOG解码
将Event log与ABI对照来解码数据:
topics[0]:ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef 是事件本身,该值为keccak256(‘Transfer(address,address,uint256)’)计算后的结果,因此该事件为Transfer事件。事件的keccak256哈希值可以通过tronweb.sha3()计算得到。注意:keccak256的参数为字符串,其中不能有任何空格,否则,计算出的哈希值将不同。
topics[1]:00000000000000000000000079309abcff2cf531070ca9222a1f72c4a5136874 是第一个indexed参数from。这里的地址为去掉前缀0x41的20个字节地址,因此对于这个参数,拿到最后40位数据,再在前面加上41就得到了TRON网络HEX格式地址。
topics[2]:00000000000000000000000081b64b1c09d448d25c9eeb3ee3b8f3348a694c96 是第二个indexed参数to,接收者账户地址,解析同上。
data : 00000000000000000000000000000000000000000000000000000000b2d05e00 是非indexed参数的值,如果存在多个非indexed参数,则按照ABI编码规则依次列出,具体请参考ABI编码规则。对于这个例子,非indexed参数只有一个,即value,转账数额,该数据为16进制数据,将其转为十进制即可。
参考文档:https://cn.developers.tron.network/docs/event
1.5 代码实现
// GetTxInfo 根据交易哈希获取交易回执信息(合约交易)
func (rc RequestChain) GetTxInfo(hash string) (interface{}, error) {
res, err := rc.Client.GetTransactionInfoById(hash)
if err != nil {
logger.Error("GetTxInfo", "step", "GetTransactionInfoById", "err", err.Error())
return "", err
}
txHashInfo := new(types.TransactionsInfo)
if err := json.Unmarshal(res, &txHashInfo); err != nil {
logger.Error("GetTransactionInfoById", "step", "Unmarshal transaction", "err", err.Error())
return "", err
}
//判断是否为成功的trc20交易,如果是,则解析相关地址信息,
//注:trx及trc10交易或未执行成功的trc20合约交易,log为 null
if txHashInfo.Log != nil {
//将交易体中的hex格式合约地址转换为base58格式
txHashInfo.ContractAddress = util.HexToAddress(txHashInfo.ContractAddress)
//判断是否为trc20-usdt合约,如果是,则格式化topics中地址
if txHashInfo.ContractAddress == config.Conf.Tron.ContractAddr {
//将log中的hex格式合约地址转换为base58格式
txHashInfo.Log[0].Address = util.HexToAddress("41"+txHashInfo.Log[0].Address)
//将log中的hex格式from址转换为base58格式
txHashInfo.Log[0].Topics[1] = util.HexToAddress("41"+txHashInfo.Log[0].Topics[1][24:])
//将log中的hex格式to地址转换为base58格式
txHashInfo.Log[0].Topics[2] = util.HexToAddress("41"+txHashInfo.Log[0].Topics[2][24:])
}
}
return txHashInfo, nil
}
二、获取区块信息后,解析交易信息
该方式首先获取区块信息,循环判断交易体中的交易类型,如果是trx交易,则格式化from及to地址,如果是trc20交易,则格式化from地址及合约地址,接下来判断合约地址是trc20-usdt合约地址(或者自己需要的合约地址),再判断合约交易方法,如果是transfer,则格式化to地址及交易金额到对象中,如果是transferFrom,则格式化from地址、to地址及交易金额到对象中
此方式的优点是:每个区块中的交易信息仅需要一次请求,就可以解析所有交易。
此方式的缺点是:因解析的是交易体中的data,所以解析稍复杂,另外data中信息可能会不太准确。除非你知道所所调研的合约的ABI接口。如果需要解析Token的转账,应该使用使用读取事件日志的方式进行。只有成功转账,才会生成事件日志。如果仅仅是解析交易的Input则是不完整的。如:1.交易失败。 2.To是A合约,但A内部实现了自动转账给TokenB。
2.1 区块交易解析流程
.判断是否为TRX交易
如果是TRX交易BlockInfo.Transactions.RawData.Contract[0].Type == "TransferContract ,则将HEX格式的form及to地址修改为base58格式
.判断是否为TRC20交易
如果是TRC20交易BlockInfo.Transactions.RawData.Contract[0].Type == "TriggerSmartContract , 则将HEX格式的form及合约地址修改为base58格式
.判断合约地址是否为TRC20-USDT
判断 BlockInfo.Transactions.RawData.Contract[0].Parameter.Value.ContractAddress 是否为trc20-usdt或者项目中需要的合约地址,如果是则进行以下判断解析
.判断合约交易方法是否为transfer
BlockInfo.Transactions.RawData.Contract[0].Parameter.Value.Data前八位如果是 a9059cbb,那么该合约交易方式使用的是transfer方法,则格式化data信息to地址及交易金额到对象中
(data信息中32至72位为to地址,73至136位为16进制交易金额)
.判断合约交易方法是否为transferFrom
BlockInfo.Transactions.RawData.Contract[0].Parameter.Value.Data前八位如果是 23b872dd,那么该合约交易方式使用的是transferFrom方法,则格式化data信息from
地址、to地址及交易金额到对象中
(data信息中32至72位为from地址,96至136位为to地址,136至200位为16进制交易金额)
2.2 基础信息
.transfer与transferFrom两种交易方法的区别?
transfer:适用于两方转移,发送方希望将一些代币转移给接收方。
Approve + transferFrom:用于3方转移,通常但不一定是交易所,发送方希望授权第二方代表他们转移一些代币。
函数是 transfer 函数的一个方便的替代,使得在去中心化应用程序中有更多的可编程性。与transfer一样,它用于移动代币,但这些代币不一定属于调用合约的人。换句话说,你可以授权某人或另一份合约代表你转移资金。
参考文档:
TRC-20中文文档:https://tronprotocol.github.io/documentation-zh/contracts/trc20/
transfer与transferFrom两种交易方法的区别:https://ethereum.stackexchange.com/questions/46457/send-tokens-using-approve-and-transferfrom-vs-only-transfer
与TRC-20合约方法交互:https://cn.developers.tron.network/docs/trc20-contract-interaction
.data参数解码
data的前四个字节是函数选择器,由Keccak-256得来,无法逆向推导,可以通过两种方法获取函数签名:
如果可以获取到合约ABI,可以计算每个合约函数的选择器,并与data的前四个字节比对来判断函数
由合约生成的合约链上可能没有ABI,合约部署者也可以通过clearAbi接口来清除链上ABI。当无法获取到ABI时,可尝试通过Ethereum Signature Database来查询在数据库中的函数
由上述方法得到,0xa9059cbb是transfer(address,uint256)的函数选择器。每个静态参数的编码占32字节,data中的内容共分三段:
DATA.substring(0,8)是函数选择器
DATA.substring(8,72)是收款人地址 (实际有效的是32-72,8-32全是0)
DATA.substring(72,136)是发送金额
2.3 代码实现
// GetBlockByNumber 根据块号获取区块信息
func (rc RequestChain) GetBlockByNumberInfo(number uint64) (interface{}, error) {
//=========================获取指定区块信息================================================================================
res, err := rc.Client.GetBlockByNumber(number)
if err != nil {
logger.Error("GetBlockByNumber", "step", "GetBlockByNumber", "err", err.Error())
return "", err
}
BlockInfo := new(types.Block)
if err := json.Unmarshal(res, &BlockInfo); err != nil {
logger.Error("GetBlockByNumber", "step", "Unmarshal transaction", "err", err.Error())
return "", err
}
//=========================for循环遍历区块信息中的Transactions==============================================================
//for循环遍历区块信息中的Transactions
ts := BlockInfo.Transactions
for k :=0;k < len(ts);k++ {
// 判断是否为TRX交易,如果是,则将HEX格式的form及to地址修改为base58格式
if ts[k].RawData.Contract[0].Type == "TransferContract"{
ts[k].RawData.Contract[0].Parameter.Value.OwnerAddress = util.HexToAddress(ts[k].RawData.Contract[0].Parameter.Value.OwnerAddress)
ts[k].RawData.Contract[0].Parameter.Value.ToAddress = util.HexToAddress(ts[k].RawData.Contract[0].Parameter.Value.ToAddress)
}
// 判断是否为TRC20交易,如果是,则将HEX格式的form及合约地址修改为base58格式
if ts[k].RawData.Contract[0].Type == "TriggerSmartContract" {
ts[k].RawData.Contract[0].Parameter.Value.OwnerAddress = util.HexToAddress(ts[k].RawData.Contract[0].Parameter.Value.OwnerAddress)
ts[k].RawData.Contract[0].Parameter.Value.ContractAddress = util.HexToAddress(ts[k].RawData.Contract[0].Parameter.Value.ContractAddress)
data := ts[k].RawData.Contract[0].Parameter.Value.Data
//判断合约地址是否为TRC20-USDT,如果是则进行以下流程
if ts[k].RawData.Contract[0].Parameter.Value.ContractAddress == config.Conf.Tron.ContractAddr {
//判断data方法是否为a9059cbb(transfer)方法,如果是则进行以下流程
if data[:8] == "a9059cbb" {
//截取HEX格式的to地址并转为base58后,放到该交易体中的to_address中
to := "41"+ data[32:72]
ts[k].RawData.Contract[0].Parameter.Value.ToAddress = util.HexToAddress(to)
//截取16进制的交易金额后转为10进制的bigint整数,放到该交易体中的amount中
amount := "0x"+ data[73:136]
ts[k].RawData.Contract[0].Parameter.Value.Amount = common.HexToBigInt(amount)
//判断data方法是否为23b872dd(transferFrom)方法,如果是则进行以下流程
}else if data[:8] == "23b872dd" {
//截取HEX格式的from地址并转为base58后,放到该交易体中的owner_address中
from := "41"+ data[32:72]
ts[k].RawData.Contract[0].Parameter.Value.OwnerAddress = util.HexToAddress(from)
//截取HEX格式的to地址并转为base58后,放到该交易体中的to_address中
to := "41"+ data[96:136]
ts[k].RawData.Contract[0].Parameter.Value.ToAddress = util.HexToAddress(to)
//截取16进制的交易金额后转为10进制的bigint整数,放到该交易体中的amount中
amount := "0x"+ data[136:200]
ts[k].RawData.Contract[0].Parameter.Value.Amount = common.HexToBigInt(amount)
}
}
}
}
return BlockInfo, nil
}