从零开发区块链应用(十二)--以太坊余额查询

以太坊余额查询

一、账户状态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账户余额分为账户余额和账户代币余额两种类型

type Account struct {
    Nonce    uint64   //Nonce:账户发起交易的次数
    Balance  *big.Int //该账户的余额
    Root     common.Hash //存储树MPT,它是所有合约数据存储的地方
    CodeHash []byte  //合约代码的Hash值 注:[合约]表示该项仅对合约账户有效
}

每个用户都对应一个StateObject,StateObject对应就是在stateTrie中的位置,表示一个账户的动态变化结果值

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将返回最新的余额。

account := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f")
balance, err := client.BalanceAt(context.Background(), account, nil)
if err != nil {
  log.Fatal(err)
}

fmt.Println(balance) // 25893180161173005034

传区块号能让您读取该区块时的账户余额。区块号必须是big.Int类型。

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包。这是您做的转换。

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,它接收账户地址作为参数。

pendingBalance, err := client.PendingBalanceAt(context.Background(), account)
fmt.Println(pendingBalance) // 25729324269165216042

2.2 完整代码

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 从零开发区块链应用(二)--mysql安装及数据库表的安装创建 从零开发区块链应用(三)--mysql初始化及gorm框架使用 从零开发区块链应用(四)--自定义业务错误信息 从零开发区块链应用(五)--golang网络请求 从零开发区块链应用(六)--gin框架使用 从零开发区块链应用(七)--gin框架参数获取 从零开发区块链应用(八)--结构体初识 从零开发区块链应用(九)--区块链结构体创建 从零开发区块链应用(十)--golang协程使用 从零开发区块链应用(十一)--以太坊地址生成 从零开发区块链应用(十二)--以太坊余额查询 从零开发区块链应用(十三)--以太坊区块查询 从零开发区块链应用(十四)--以太坊交易哈希查询 从零开发区块链应用(十五)--以太坊交易匹配查询 从零开发区块链应用(十六)--ETH转账处理


点赞 0
收藏 2
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
杰哥的技术杂货铺
杰哥的技术杂货铺
0x6e60...2aa2
六年区块链开发及运维经验,成都电子科技大学Web3讲师,利用该平台进行技术分享,可私信进行交流沟通