Develop blockchain applications from scratch (12)--Ethereum balance query

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)
}

Guess you like

Origin blog.csdn.net/cljdsc/article/details/122710316