Article directory
1. Account status stateTrie
Block.Header.Root is stateRoot, which is a PMT tree that stores the current and latest state information of all accounts, such as account balances.
a path is always: sha3(ethereumAddress) and a value is always: rlp(ethereumAccount)
Root is a hash value, you can find the root node of stateTrie in the database through Root, and then obtain the final path to be searched through sha3(ethereumAddress) , and then you can find each account rlp(ethereumAccount) step by step according to the path
Account account balance is divided into two types: account balance and account token balance
type Account struct {
Nonce uint64 //Nonce:账户发起交易的次数
Balance *big.Int //该账户的余额
Root common.Hash //存储树MPT,它是所有合约数据存储的地方
CodeHash []byte //合约代码的Hash值 注:[合约]表示该项仅对合约账户有效
}
Each user corresponds to a StateObject, and the StateObject corresponds to the position in the stateTrie, representing the dynamic change result value of an account
type stateObject struct {
address common.Address
addrHash common.Hash // hash of ethereum address of the account
data Account
db *StateDB
}
1.2 The idea of checking the balance code
-
First get the current block height, and traverse all blocks starting from the genesis block. getBlockNumber()
-
Get the relevant information of a block, get all the transaction TxHash in the block, and traverse it. getBlock()
-
Get the details of a transaction, get the transfer address from and the receiving address to. getTransaction()
-
Determine whether the address is a contract address or an account address. getCode()
-
Get the balance corresponding to the address. getBalance()
-
1.3 Balance inquiry process
-
Query to get the latest block, and then get lastBlock.header.Root
-
First, find out whether there is hot data of stateObject from the local cache. If not, find the corresponding root node in the database according to Root, and then find the corresponding stateObject according to the arrangement of Address in the MPT tree.
-
Find the corresponding Account through stateObject
-
Get the balance in the Account
2. Get account balance
2.1 Code Analysis
Reading the balance of an account is fairly simple. Call the client's BalanceAt method, passing it the account address and optional block number. Setting the block number to nil will return the latest balance.
account := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f")
balance, err := client.BalanceAt(context.Background(), account, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(balance) // 25893180161173005034
Passing the block number allows you to read the account balance at the time of the block. The block number must be of type big.Int.
blockNumber := big.NewInt(5532993)
balance, err := client.BalanceAt(context.Background(), account, blockNumber)
if err != nil {
log.Fatal(err)
}
fmt.Println(balance) // 25729324269165216042
Numbers in Ethereum are handled using the smallest possible units because they are fixed-point precision, in ETH it is wei. To read the ETH value, you have to do the calculation wei/10^18. Since we are dealing with big numbers, we have to import the native Gomath and math/big packages. This is the conversion you do.
fbalance := new(big.Float)
fbalance.SetString(balance.String())
ethValue := new(big.Float).Quo(fbalance, big.NewFloat(math.Pow10(18)))
fmt.Println(ethValue) // 25.729324269165216041
pending balance
Sometimes you want to know what the pending account balance is, for example, after submitting or waiting for a transaction to be confirmed. The client provides a method similar to BalanceAt, named PendingBalanceAt, which receives the account address as a parameter.
pendingBalance, err := client.PendingBalanceAt(context.Background(), account)
fmt.Println(pendingBalance) // 25729324269165216042
2.2 Complete code
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
}
3. Get account token balance
- Get from, to, data parameters
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
}
- Call Ethereum rpc to get the balance
// 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)
}