杰哥的技术杂货铺[1]
一、账户状态 stateTrie
Block.Header.Root 就是 stateRoot,是一棵 PMT 树,存储了所有账户的当前最新的状态信息,比如账户余额。
a path is always: sha3(ethereumAddress) and a value is always: rlp(ethereumAccount) Root 是一个 hash 值,通过 Root 去数据库中可以找到 stateTrie 的根节点,然后通过 sha3(ethereumAddress)得出要最终查找的 path,再根据 path 可以一步步的找到每个账户 rlp(ethereumAccount)
Account 账户余额分为账户余额和账户代币余额两种类型
代码语言:javascript复制type Account struct {
Nonce uint64 //Nonce:账户发起交易的次数
Balance *big.Int //该账户的余额
Root common.Hash //存储树MPT,它是所有合约数据存储的地方
CodeHash []byte //合约代码的Hash值 注:[合约]表示该项仅对合约账户有效
}
每个用户都对应一个 StateObject,StateObject 对应就是在 stateTrie 中的位置,表示一个账户的动态变化结果值
代码语言:javascript复制type stateObject struct {
address common.Address
addrHash common.Hash // hash of ethereum address of the account
data Account
db *StateDB
}
1.2 查询余额代码思路
- 先获取当前的区块高度,并从创世区块开始遍历所有区块。getBlockNumber()
- 获取某一区块的相关信息,得到该区块中的所有交易 TxHash,并遍历。getBlock()
- 获取某一交易的详细信息,得到转账地址 from 和接收地址 to。getTransaction()
- 判断该地址是合约地址还是账号地址。getCode()
- 获取地址对应的余额。getBalance()
- 1.3 余额查询流程
- 查询获取当前最新的区块,然后获取到 lastBlock.header.Root
- 先从本地缓存中查找是否有 stateObject 的热点数据,没有的话则,根据 Root 找到数据库中对应的根节点,然后按照 Address 在 MPT 树中的排列,找到对应的 stateObject
- 通过 stateObject 找到对应的 Account
- 获取到该 Account 里面的余额
二、获取账户余额
2.1 代码解析
读取一个账户的余额相当简单。调用客户端的 BalanceAt 方法,给它传递账户地址和可选的区块号。将区块号设置为 nil 将返回最新的余额。
代码语言:javascript复制account := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f")
balance, err := client.BalanceAt(context.Background(), account, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(balance) // 25893180161173005034
传区块号能让您读取该区块时的账户余额。区块号必须是 big.Int 类型。
代码语言:javascript复制blockNumber := big.NewInt(5532993)
balance, err := client.BalanceAt(context.Background(), account, blockNumber)
if err != nil {
log.Fatal(err)
}
fmt.Println(balance) // 25729324269165216042
以太坊中的数字是使用尽可能小的单位来处理的,因为它们是定点精度,在 ETH 中它是 wei。要读取 ETH 值,您必须做计算 wei/10^18。因为我们正在处理大数,我们得导入原生的 Gomath 和 math/big 包。这是您做的转换。
代码语言:javascript复制fbalance := new(big.Float)
fbalance.SetString(balance.String())
ethValue := new(big.Float).Quo(fbalance, big.NewFloat(math.Pow10(18)))
fmt.Println(ethValue) // 25.729324269165216041
待处理的余额
有时您想知道待处理的账户余额是多少,例如,在提交或等待交易确认后。客户端提供了类似 BalanceAt 的方法,名为 PendingBalanceAt,它接收账户地址作为参数。
代码语言:javascript复制pendingBalance, err := client.PendingBalanceAt(context.Background(), account)
fmt.Println(pendingBalance) // 25729324269165216042
2.2 完整代码
代码语言:javascript复制package main
import (
"context"
"fmt"
"log"
"math"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, err := ethclient.Dial("https://mainnet.infura.io")
if err != nil {
log.Fatal(err)
}
account := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f")
balance, err := client.BalanceAt(context.Background(), account, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(balance) // 25893180161173005034
blockNumber := big.NewInt(5532993)
balanceAt, err := client.BalanceAt(context.Background(), account, blockNumber)
if err != nil {
log.Fatal(err)
}
fmt.Println(balanceAt) // 25729324269165216042
fbalance := new(big.Float)
fbalance.SetString(balanceAt.String())
ethValue := new(big.Float).Quo(fbalance, big.NewFloat(math.Pow10(18)))
fmt.Println(ethValue) // 25.729324269165216041
pendingBalance, err := client.PendingBalanceAt(context.Background(), account)
fmt.Println(pendingBalance) // 25729324269165216042
}
三、获取账户代币余额
- 获取 from、to、data 参数
func (rc *RequestChain) GetCallContractInfo(from, to, data string) (string, error) {
info, err := rc.Client.CallContract(from, to, data)
if err != nil {
logger.Error("GetCallContractInfo", "step", "CallContract", "err", err.Error())
return "", err
}
res, err := common.HexToString(info.(string))
if err != nil {
logger.Error("GetCallContractInfo", "step", "HexToString", "err", err.Error())
return "", err
}
resByte, err := hex.DecodeString(res)
return string(resByte), nil
}
- 调用以太坊 rpc,获取余额
// CallContract 查询合约
func (eth *Http) CallContract(from, to, data string) (interface{}, error) {
tag := "latest"
args = []interface{}{CallMsg{
From: from,
To: to,
Data: data,
}, tag}
params := NewHttpParams("eth_call", args)
resBody, err := eth.rpc.HttpRequest(params)
if err != nil {
return nil, err
}
return eth.ParseJsonRPCResponse(resBody)
}
本系列文章:
从零开发区块链应用(一)--golang 配置文件管理工具 viper[2]
从零开发区块链应用(二)--mysql 安装及数据库表的安装创建[3]
从零开发区块链应用(三)--mysql 初始化及 gorm 框架使用[4]
从零开发区块链应用(四)--自定义业务错误信息[5]
从零开发区块链应用(五)--golang 网络请求[6]
从零开发区块链应用(六)--gin 框架使用[7]
从零开发区块链应用(七)--gin 框架参数获取[8]
从零开发区块链应用(八)--结构体初识[9]
从零开发区块链应用(九)--区块链结构体创建[10]
从零开发区块链应用(十)--golang 协程使用[11]
从零开发区块链应用(十一)--以太坊地址生成[12]
从零开发区块链应用(十二)--以太坊余额查询[13]
从零开发区块链应用(十三)--以太坊区块查询[14]
从零开发区块链应用(十四)--以太坊交易哈希查询[15]
参考资料
[1]
杰哥的技术杂货铺: https://learnblockchain.cn/people/3835
[2]
从零开发区块链应用(一)--golang配置文件管理工具viper: https://learnblockchain.cn/article/3446
[3]
从零开发区块链应用(二)--mysql安装及数据库表的安装创建: https://learnblockchain.cn/article/3447
[4]
从零开发区块链应用(三)--mysql初始化及gorm框架使用: https://learnblockchain.cn/article/3448
[5]
从零开发区块链应用(四)--自定义业务错误信息: https://learnblockchain.cn/article/3449
[6]
从零开发区块链应用(五)--golang网络请求: https://learnblockchain.cn/article/3457
[7]
从零开发区块链应用(六)--gin框架使用: https://learnblockchain.cn/article/3480
[8]
从零开发区块链应用(七)--gin框架参数获取: https://learnblockchain.cn/article/3481
[9]
从零开发区块链应用(八)--结构体初识: https://learnblockchain.cn/article/3482
[10]
从零开发区块链应用(九)--区块链结构体创建: https://learnblockchain.cn/article/3483
[11]
从零开发区块链应用(十)--golang协程使用: https://learnblockchain.cn/article/3484
[12]
从零开发区块链应用(十一)--以太坊地址生成: https://learnblockchain.cn/article/3485
[13]
从零开发区块链应用(十二)--以太坊余额查询: https://learnblockchain.cn/article/3498
[14]
从零开发区块链应用(十三)--以太坊区块查询: https://learnblockchain.cn/article/3499
[15]
从零开发区块链应用(十四)--以太坊交易哈希查询: https://learnblockchain.cn/article/3500