交易(Transaction)
這些部分將討論如何使用go-ethereumethclient包在以太坊上查詢和發(fā)送交易。注意這里的交易transaction 是指廣義的對以太坊狀態(tài)的更改,它既可以指具體的以太幣轉(zhuǎn)賬,代幣的轉(zhuǎn)賬,或者其他對智能合約的創(chuàng)建或者調(diào)用。而不僅僅是傳統(tǒng)意義的買賣交易。
查詢區(qū)塊
正如我們所見,您可以有兩種方式查詢區(qū)塊信息。
區(qū)塊頭
您可以調(diào)用客戶端的HeadByNumber
來返回有關(guān)一個區(qū)塊的頭信息。若您傳入nil
,它將返回最新的區(qū)塊頭。
header, err := client.HeaderByNumber(context.Background(), nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(header.Number.String()) // 5671744
完整區(qū)塊
調(diào)用客戶端的BlockByNumber
方法來獲得完整區(qū)塊。您可以讀取該區(qū)塊的所有內(nèi)容和元數(shù)據(jù),例如,區(qū)塊號,區(qū)塊時間戳,區(qū)塊摘要,區(qū)塊難度以及交易列表等等。
blockNumber := big.NewInt(5671744)
block, err := client.BlockByNumber(context.Background(), blockNumber)
if err != nil {
log.Fatal(err)
}
fmt.Println(block.Number().Uint64()) // 5671744
fmt.Println(block.Time().Uint64()) // 1527211625
fmt.Println(block.Difficulty().Uint64()) // 3217000136609065
fmt.Println(block.Hash().Hex()) // 0x9e8751ebb5069389b855bba72d94902cc385042661498a415979b7b6ee9ba4b9
fmt.Println(len(block.Transactions())) // 144
調(diào)用Transaction
只返回一個區(qū)塊的交易數(shù)目。
count, err := client.TransactionCount(context.Background(), block.Hash())
if err != nil {
log.Fatal(err)
}
fmt.Println(count) // 144
在下個章節(jié),我們將學(xué)習(xí)查詢區(qū)塊中的交易。
完整代碼
package main
import (
"context"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, err := ethclient.Dial("https://mainnet.infura.io")
if err != nil {
log.Fatal(err)
}
header, err := client.HeaderByNumber(context.Background(), nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(header.Number.String()) // 5671744
blockNumber := big.NewInt(5671744)
block, err := client.BlockByNumber(context.Background(), blockNumber)
if err != nil {
log.Fatal(err)
}
fmt.Println(block.Number().Uint64()) // 5671744
fmt.Println(block.Time().Uint64()) // 1527211625
fmt.Println(block.Difficulty().Uint64()) // 3217000136609065
fmt.Println(block.Hash().Hex()) // 0x9e8751ebb5069389b855bba72d94902cc385042661498a415979b7b6ee9ba4b9
fmt.Println(len(block.Transactions())) // 144
count, err := client.TransactionCount(context.Background(), block.Hash())
if err != nil {
log.Fatal(err)
}
fmt.Println(count) // 144
}
查詢交易
在上個章節(jié) 我們學(xué)習(xí)了如何在給定區(qū)塊編號的情況下讀取塊及其所有數(shù)據(jù)。 我們可以通過調(diào)用Transactions
方法來讀取塊中的事務(wù),該方法返回一個Transaction
類型的列表。 然后,重復(fù)遍歷集合并獲取有關(guān)事務(wù)的任何信息就變得簡單了。
for _, tx := range block.Transactions() {
fmt.Println(tx.Hash().Hex()) // 0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2
fmt.Println(tx.Value().String()) // 10000000000000000
fmt.Println(tx.Gas()) // 105000
fmt.Println(tx.GasPrice().Uint64()) // 102000000000
fmt.Println(tx.Nonce()) // 110644
fmt.Println(tx.Data()) // []
fmt.Println(tx.To().Hex()) // 0x55fE59D8Ad77035154dDd0AD0388D09Dd4047A8e
}
為了讀取發(fā)送方的地址,我們需要在事務(wù)上調(diào)用AsMessage
,它返回一個Message
類型,其中包含一個返回sender(from)地址的函數(shù)。 AsMessage
方法需要EIP155簽名者,這個我們從客戶端拿到鏈ID。
chainID, err := client.NetworkID(context.Background())
if err != nil {
log.Fatal(err)
}
if msg, err := tx.AsMessage(types.NewEIP155Signer(chainID)); err != nil {
fmt.Println(msg.From().Hex()) // 0x0fD081e3Bb178dc45c0cb23202069ddA57064258
}
每個事務(wù)都有一個收據(jù),其中包含執(zhí)行事務(wù)的結(jié)果,例如任何返回值和日志,以及為“1”(成功)或“0”(失敗)的事件結(jié)果狀態(tài)。
receipt, err := client.TransactionReceipt(context.Background(), tx.Hash())
if err != nil {
log.Fatal(err)
}
fmt.Println(receipt.Status) // 1
fmt.Println(receipt.Logs) // ...
在不獲取塊的情況下遍歷事務(wù)的另一種方法是調(diào)用客戶端的TransactionInBlock
方法。 此方法僅接受塊哈希和塊內(nèi)事務(wù)的索引值。 您可以調(diào)用TransactionCount
來了解塊中有多少個事務(wù)。
blockHash := common.HexToHash("0x9e8751ebb5069389b855bba72d94902cc385042661498a415979b7b6ee9ba4b9")
count, err := client.TransactionCount(context.Background(), blockHash)
if err != nil {
log.Fatal(err)
}
for idx := uint(0); idx < count; idx++ {
tx, err := client.TransactionInBlock(context.Background(), blockHash, idx)
if err != nil {
log.Fatal(err)
}
fmt.Println(tx.Hash().Hex()) // 0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2
}
您還可以使用TransactionByHash
在給定具體事務(wù)哈希值的情況下直接查詢單個事務(wù)。
txHash := common.HexToHash("0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2")
tx, isPending, err := client.TransactionByHash(context.Background(), txHash)
if err != nil {
log.Fatal(err)
}
fmt.Println(tx.Hash().Hex()) // 0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2
fmt.Println(isPending) // false
完整代碼
package main
import (
"context"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, err := ethclient.Dial("https://mainnet.infura.io")
if err != nil {
log.Fatal(err)
}
blockNumber := big.NewInt(5671744)
block, err := client.BlockByNumber(context.Background(), blockNumber)
if err != nil {
log.Fatal(err)
}
for _, tx := range block.Transactions() {
fmt.Println(tx.Hash().Hex()) // 0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2
fmt.Println(tx.Value().String()) // 10000000000000000
fmt.Println(tx.Gas()) // 105000
fmt.Println(tx.GasPrice().Uint64()) // 102000000000
fmt.Println(tx.Nonce()) // 110644
fmt.Println(tx.Data()) // []
fmt.Println(tx.To().Hex()) // 0x55fE59D8Ad77035154dDd0AD0388D09Dd4047A8e
chainID, err := client.NetworkID(context.Background())
if err != nil {
log.Fatal(err)
}
if msg, err := tx.AsMessage(types.NewEIP155Signer(chainID)); err == nil {
fmt.Println(msg.From().Hex()) // 0x0fD081e3Bb178dc45c0cb23202069ddA57064258
}
receipt, err := client.TransactionReceipt(context.Background(), tx.Hash())
if err != nil {
log.Fatal(err)
}
fmt.Println(receipt.Status) // 1
}
blockHash := common.HexToHash("0x9e8751ebb5069389b855bba72d94902cc385042661498a415979b7b6ee9ba4b9")
count, err := client.TransactionCount(context.Background(), blockHash)
if err != nil {
log.Fatal(err)
}
for idx := uint(0); idx < count; idx++ {
tx, err := client.TransactionInBlock(context.Background(), blockHash, idx)
if err != nil {
log.Fatal(err)
}
fmt.Println(tx.Hash().Hex()) // 0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2
}
txHash := common.HexToHash("0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2")
tx, isPending, err := client.TransactionByHash(context.Background(), txHash)
if err != nil {
log.Fatal(err)
}
fmt.Println(tx.Hash().Hex()) // 0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2
fmt.Println(isPending) // false
}
ETH轉(zhuǎn)賬
轉(zhuǎn)賬以太幣ETH
在本課程中,您將學(xué)習(xí)如何將ETH從一個帳戶轉(zhuǎn)移到另一個帳戶。如果您已熟悉以太坊,那么您就知道如何交易包括您打算轉(zhuǎn)賬的以太幣數(shù)量量,燃氣限額,燃氣價格,一個隨機數(shù)(nonce),接收地址以及可選擇性的添加的數(shù)據(jù)。 在廣告發(fā)送到網(wǎng)絡(luò)之前,必須使用發(fā)送方的私鑰對該交易進行簽名。
假設(shè)您已經(jīng)連接了客戶端,下一步就是加載您的私鑰。
privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19")
if err != nil {
log.Fatal(err)
}
之后我們需要獲得帳戶的隨機數(shù)(nonce)。 每筆交易都需要一個nonce。 根據(jù)定義,nonce是僅使用一次的數(shù)字。 如果是發(fā)送交易的新帳戶,則該隨機數(shù)將為“0”。 來自帳戶的每個新事務(wù)都必須具有前一個nonce增加1的nonce。很難對所有nonce進行手動跟蹤,于是ethereum客戶端提供一個幫助方法PendingNonceAt
,它將返回你應(yīng)該使用的下一個nonce。
該函數(shù)需要我們發(fā)送的帳戶的公共地址 - 這個我們可以從私鑰派生。
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
log.Fatal("error casting public key to ECDSA")
}
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
接下來我們可以讀取我們應(yīng)該用于帳戶交易的隨機數(shù)。
nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
log.Fatal(err)
}
下一步是設(shè)置我們將要轉(zhuǎn)移的ETH數(shù)量。 但是我們必須將ETH以太轉(zhuǎn)換為wei,因為這是以太坊區(qū)塊鏈所使用的。 以太網(wǎng)支持最多18個小數(shù)位,因此1個ETH為1加18個零。 這里有一個小工具可以幫助您在ETH和wei之間進行轉(zhuǎn)換: https://etherconverter.online
value := big.NewInt(1000000000000000000) // in wei (1 eth)
ETH轉(zhuǎn)賬的燃氣應(yīng)設(shè)上限為“21000”單位。
gasLimit := uint64(21000) // in units
燃氣價格必須以wei為單位設(shè)定。 在撰寫本文時,將在一個區(qū)塊中比較快的打包交易的燃氣價格為30 gwei。
gasPrice := big.NewInt(30000000000) // in wei (30 gwei)
然而,燃氣價格總是根據(jù)市場需求和用戶愿意支付的價格而波動的,因此對燃氣價格進行硬編碼有時并不理想。 go-ethereum客戶端提供SuggestGasPrice
函數(shù),用于根據(jù)'x'個先前塊來獲得平均燃氣價格。
gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatal(err)
}
接下來我們弄清楚我們將ETH發(fā)送給誰。
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
現(xiàn)在我們最終可以通過導(dǎo)入go-ethereumcore/types
包并調(diào)用NewTransaction
來生成我們的未簽名以太坊事務(wù),這個函數(shù)需要接收nonce,地址,值,燃氣上限值,燃氣價格和可選發(fā)的數(shù)據(jù)。 發(fā)送ETH的數(shù)據(jù)字段為“nil”。 在與智能合約進行交互時,我們將使用數(shù)據(jù)字段,僅僅轉(zhuǎn)賬以太幣是不需要數(shù)據(jù)字段的。
tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, nil)
下一步是使用發(fā)件人的私鑰對事務(wù)進行簽名。 為此,我們調(diào)用SignTx
方法,該方法接受一個未簽名的事務(wù)和我們之前構(gòu)造的私鑰。 SignTx
方法需要EIP155簽名者,這個也需要我們先從客戶端拿到鏈ID。
chainID, err := client.NetworkID(context.Background())
if err != nil {
log.Fatal(err)
}
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
if err != nil {
log.Fatal(err)
}
現(xiàn)在我們終于準備通過在客戶端上調(diào)用“SendTransaction”來將已簽名的事務(wù)廣播到整個網(wǎng)絡(luò)。
err = client.SendTransaction(context.Background(), signedTx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("tx sent: %s", signedTx.Hash().Hex()) // tx sent: 0x77006fcb3938f648e2cc65bafd27dec30b9bfbe9df41f78498b9c8b7322a249e
然后你可以去Etherscan看交易的確認過程: https://rinkeby.etherscan.io/tx/0x77006fcb3938f648e2cc65bafd27dec30b9bfbe9df41f78498b9c8b7322a249e
完整代碼
package main
import (
"context"
"crypto/ecdsa"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, err := ethclient.Dial("https://rinkeby.infura.io")
if err != nil {
log.Fatal(err)
}
privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19")
if err != nil {
log.Fatal(err)
}
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
log.Fatal("error casting public key to ECDSA")
}
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
log.Fatal(err)
}
value := big.NewInt(1000000000000000000) // in wei (1 eth)
gasLimit := uint64(21000) // in units
gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatal(err)
}
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
var data []byte
tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)
chainID, err := client.NetworkID(context.Background())
if err != nil {
log.Fatal(err)
}
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
if err != nil {
log.Fatal(err)
}
err = client.SendTransaction(context.Background(), signedTx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("tx sent: %s", signedTx.Hash().Hex())
}
代幣的轉(zhuǎn)賬
本節(jié)將向你介紹如何轉(zhuǎn)移ERC-20代幣。了解如何轉(zhuǎn)移非ERC-20兼容的其他類型的代幣請查閱智能合約的章節(jié) 來了解如何與智能合約交互。
假設(shè)您已連接客戶端,加載私鑰并配置燃氣價格,下一步是設(shè)置具體的交易數(shù)據(jù)字段。 如果你完全不明白我剛講的這些,請先復(fù)習(xí) 以太幣轉(zhuǎn)賬的章節(jié)。
代幣傳輸不需要傳輸ETH,因此將交易“值”設(shè)置為“0”。
value := big.NewInt(0)
先將您要發(fā)送代幣的地址存儲在變量中。
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
現(xiàn)在輪到有趣的部分。 我們需要弄清楚交易的 data 部分。 這意味著我們需要找出我們將要調(diào)用的智能合約函數(shù)名,以及函數(shù)將接收的輸入。 然后我們使用函數(shù)名的keccak-256哈希來檢索 方法ID,它是前8個字符(4個字節(jié))。 然后,我們附加我們發(fā)送的地址,并附加我們打算轉(zhuǎn)賬的代幣數(shù)量。 這些輸入需要256位長(32字節(jié))并填充左側(cè)。 方法ID不需填充。
為了演示,我創(chuàng)造了一個新的代幣(HelloToken HTN),這個可以用代幣工廠服務(wù)來完成https://tokenfactory.surge.sh, 代幣我部署到了Rinkeby測試網(wǎng)。
讓我們將代幣合約地址分配給變量。
tokenAddress := common.HexToAddress("0x28b149020d2152179873ec60bed6bf7cd705775d")
函數(shù)名將是傳遞函數(shù)的名稱,即ERC-20規(guī)范中的transfer
和參數(shù)類型。 第一個參數(shù)類型是address
(令牌的接收者),第二個類型是uint256
(要發(fā)送的代幣數(shù)量)。 不需要沒有空格和參數(shù)名稱。 我們還需要用字節(jié)切片格式。
transferFnSignature := []byte("transfer(address,uint256)")
我們現(xiàn)在將從go-ethereum導(dǎo)入crypto/sha3
包以生成函數(shù)簽名的Keccak256哈希。 然后我們只使用前4個字節(jié)來獲取方法ID。
hash := sha3.NewKeccak256()
hash.Write(transferFnSignature)
methodID := hash.Sum(nil)[:4]
fmt.Println(hexutil.Encode(methodID)) // 0xa9059cbb
接下來,我們需要將給我們發(fā)送代幣的地址左填充到32字節(jié)。
paddedAddress := common.LeftPadBytes(toAddress.Bytes(), 32)
fmt.Println(hexutil.Encode(paddedAddress)) // 0x0000000000000000000000004592d8f8d7b001e72cb26a73e4fa1806a51ac79d
接下來我們確定要發(fā)送多少個代幣,在這個例子里是1,000個,并且我們需要在big.Int
中格式化為wei。
amount := new(big.Int)
amount.SetString("1000000000000000000000", 10) // 1000 tokens
代幣量也需要左填充到32個字節(jié)。
paddedAmount := common.LeftPadBytes(amount.Bytes(), 32)
fmt.Println(hexutil.Encode(paddedAmount)) // 0x00000000000000000000000000000000000000000000003635c9adc5dea00000
接下來我們只需將方法ID,填充后的地址和填后的轉(zhuǎn)賬量,接到將成為我們數(shù)據(jù)字段的字節(jié)片。
var data []byte
data = append(data, methodID...)
data = append(data, paddedAddress...)
data = append(data, paddedAmount...)
燃氣上限制將取決于交易數(shù)據(jù)的大小和智能合約必須執(zhí)行的計算步驟。 幸運的是,客戶端提供了EstimateGas
方法,它可以為我們估算所需的燃氣量。 這個函數(shù)從ethereum
包中獲取CallMsg
結(jié)構(gòu),我們在其中指定數(shù)據(jù)和地址。 它將返回我們估算的完成交易所需的估計燃氣上限。
gasLimit, err := client.EstimateGas(context.Background(), ethereum.CallMsg{
To: &toAddress,
Data: data,
})
if err != nil {
log.Fatal(err)
}
fmt.Println(gasLimit) // 23256
接下來我們需要做的是構(gòu)建交易事務(wù)類型,這類似于您在ETH轉(zhuǎn)賬部分中看到的,除了to字段將是代幣智能合約地址。 這個常讓人困惑。我們還必須在調(diào)用中包含0 ETH的值字段和剛剛生成的數(shù)據(jù)字節(jié)。
tx := types.NewTransaction(nonce, tokenAddress, value, gasLimit, gasPrice, data)
下一步是使用發(fā)件人的私鑰對事務(wù)進行簽名。 SignTx
方法需要EIP155簽名器(EIP155 signer),這需要我們從客戶端拿到鏈ID。
chainID, err := client.NetworkID(context.Background())
if err != nil {
log.Fatal(err)
}
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
if err != nil {
log.Fatal(err)
}
最后廣播交易。
err = client.SendTransaction(context.Background(), signedTx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("tx sent: %s", signedTx.Hash().Hex()) // tx sent: 0xa56316b637a94c4cc0331c73ef26389d6c097506d581073f927275e7a6ece0bc
你可以去Etherscan看交易的確認過程: https://rinkeby.etherscan.io/tx/0xa56316b637a94c4cc0331c73ef26389d6c097506d581073f927275e7a6ece0bc
要了解更多如何加載ERC20智能合約并與之互動的內(nèi)容,可以查看ERC20代幣的智能合約章節(jié).
完整代碼
package main
import (
"context"
"crypto/ecdsa"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, err := ethclient.Dial("https://rinkeby.infura.io")
if err != nil {
log.Fatal(err)
}
privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19")
if err != nil {
log.Fatal(err)
}
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
log.Fatal("error casting public key to ECDSA")
}
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
log.Fatal(err)
}
value := big.NewInt(0) // in wei (0 eth)
gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatal(err)
}
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
tokenAddress := common.HexToAddress("0x28b149020d2152179873ec60bed6bf7cd705775d")
transferFnSignature := []byte("transfer(address,uint256)")
hash := sha3.NewKeccak256()
hash.Write(transferFnSignature)
methodID := hash.Sum(nil)[:4]
fmt.Println(hexutil.Encode(methodID)) // 0xa9059cbb
paddedAddress := common.LeftPadBytes(toAddress.Bytes(), 32)
fmt.Println(hexutil.Encode(paddedAddress)) // 0x0000000000000000000000004592d8f8d7b001e72cb26a73e4fa1806a51ac79d
amount := new(big.Int)
amount.SetString("1000000000000000000000", 10) // 1000 tokens
paddedAmount := common.LeftPadBytes(amount.Bytes(), 32)
fmt.Println(hexutil.Encode(paddedAmount)) // 0x00000000000000000000000000000000000000000000003635c9adc5dea00000
var data []byte
data = append(data, methodID...)
data = append(data, paddedAddress...)
data = append(data, paddedAmount...)
gasLimit, err := client.EstimateGas(context.Background(), ethereum.CallMsg{
To: &toAddress,
Data: data,
})
if err != nil {
log.Fatal(err)
}
fmt.Println(gasLimit) // 23256
tx := types.NewTransaction(nonce, tokenAddress, value, gasLimit, gasPrice, data)
chainID, err := client.NetworkID(context.Background())
if err != nil {
log.Fatal(err)
}
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
if err != nil {
log.Fatal(err)
}
err = client.SendTransaction(context.Background(), signedTx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("tx sent: %s", signedTx.Hash().Hex()) // tx sent: 0xa56316b637a94c4cc0331c73ef26389d6c097506d581073f927275e7a6ece0bc
}
監(jiān)聽新區(qū)塊
訂閱新區(qū)塊
在本節(jié)中,我們將討論如何設(shè)置訂閱以便在新區(qū)塊被開采時獲取事件。首先,我們需要一個支持websocket RPC的以太坊服務(wù)提供者。在示例中,我們將使用infura 的websocket端點。
client, err := ethclient.Dial("wss://ropsten.infura.io/ws")
if err != nil {
log.Fatal(err)
}
接下來,我們將創(chuàng)建一個新的通道,用于接收最新的區(qū)塊頭。
headers := make(chan *types.Header)
現(xiàn)在我們調(diào)用客戶端的SubscribeNewHead
方法,它接收我們剛創(chuàng)建的區(qū)塊頭通道,該方法將返回一個訂閱對象。
sub, err := client.SubscribeNewHead(context.Background(), headers)
if err != nil {
log.Fatal(err)
}
訂閱將推送新的區(qū)塊頭事件到我們的通道,因此我們可以使用一個select語句來監(jiān)聽新消息。訂閱對象還包括一個error通道,該通道將在訂閱失敗時發(fā)送消息。
for {
select {
case err := <-sub.Err():
log.Fatal(err)
case header := <-headers:
fmt.Println(header.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7f
}
}
要獲得該區(qū)塊的完整內(nèi)容,我們可以將區(qū)塊頭的摘要傳遞給客戶端的BlockByHash
函數(shù)。
block, err := client.BlockByHash(context.Background(), header.Hash())
if err != nil {
log.Fatal(err)
}
fmt.Println(block.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7f
fmt.Println(block.Number().Uint64()) // 3477413
fmt.Println(block.Time().Uint64()) // 1529525947
fmt.Println(block.Nonce()) // 130524141876765836
fmt.Println(len(block.Transactions())) // 7
正如您所見,您可以讀取整個區(qū)塊的元數(shù)據(jù)字段,交易列表等等。
完整代碼
package main
import (
"context"
"fmt"
"log"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, err := ethclient.Dial("wss://ropsten.infura.io/ws")
if err != nil {
log.Fatal(err)
}
headers := make(chan *types.Header)
sub, err := client.SubscribeNewHead(context.Background(), headers)
if err != nil {
log.Fatal(err)
}
for {
select {
case err := <-sub.Err():
log.Fatal(err)
case header := <-headers:
fmt.Println(header.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7f
block, err := client.BlockByHash(context.Background(), header.Hash())
if err != nil {
log.Fatal(err)
}
fmt.Println(block.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7f
fmt.Println(block.Number().Uint64()) // 3477413
fmt.Println(block.Time().Uint64()) // 1529525947
fmt.Println(block.Nonce()) // 130524141876765836
fmt.Println(len(block.Transactions())) // 7
}
}
}
創(chuàng)建裸交易
構(gòu)建原始交易(Raw Transaction)
如果你看過上個章節(jié), 那么你知道如何加載你的私鑰來簽名交易。 我們現(xiàn)在假設(shè)你知道如何做到這一點,現(xiàn)在你想讓原始交易數(shù)據(jù)能夠在以后廣播它。
首先構(gòu)造事務(wù)對象并對其進行簽名,例如:
tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
if err != nil {
log.Fatal(err)
}
現(xiàn)在,在我們以原始字節(jié)格式獲取事務(wù)之前,我們需要初始化一個types.Transactions
類型,并將簽名后的交易作為第一個值。
ts := types.Transactions{signedTx}
這樣做的原因是因為Transactions
類型提供了一個GetRlp
方法,用于以RLP編碼格式返回事務(wù)。 RLP是以太坊用于序列化對象的特殊編碼方法。 結(jié)果是原始字節(jié)。
rawTxBytes := ts.GetRlp(0)
最后,我們可以非常輕松地將原始字節(jié)轉(zhuǎn)換為十六進制字符串。
rawTxHex := hex.EncodeToString(rawTxBytes)
fmt.Printf(rawTxHex)
// f86d8202b38477359400825208944592d8f8d7b001e72cb26a73e4fa1806a51ac79d880de0b6b3a7640000802ba0699ff162205967ccbabae13e07cdd4284258d46ec1051a70a51be51ec2bc69f3a04e6944d508244ea54a62ebf9a72683eeadacb73ad7c373ee542f1998147b220e
接下來,你就可以廣播原始交易數(shù)據(jù)。在下一章 我們將學(xué)習(xí)如何廣播一個原始交易。
完整代碼
package main
import (
"context"
"crypto/ecdsa"
"encoding/hex"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, err := ethclient.Dial("https://rinkeby.infura.io")
if err != nil {
log.Fatal(err)
}
privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19")
if err != nil {
log.Fatal(err)
}
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
log.Fatal("error casting public key to ECDSA")
}
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
log.Fatal(err)
}
value := big.NewInt(1000000000000000000) // in wei (1 eth)
gasLimit := uint64(21000) // in units
gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatal(err)
}
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
var data []byte
tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)
chainID, err := client.NetworkID(context.Background())
if err != nil {
log.Fatal(err)
}
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
if err != nil {
log.Fatal(err)
}
ts := types.Transactions{signedTx}
rawTxBytes := ts.GetRlp(0)
rawTxHex := hex.EncodeToString(rawTxBytes)
fmt.Printf(rawTxHex) // f86...772
}
發(fā)送裸交易
發(fā)送原始交易事務(wù)
在上個章節(jié)中 我們學(xué)會了如何創(chuàng)建原始事務(wù)。 現(xiàn)在,我們將學(xué)習(xí)如何將其廣播到以太坊網(wǎng)絡(luò),以便最終被處理和被礦工打包到區(qū)塊。
首先將原始事務(wù)十六進制解碼為字節(jié)格式。
rawTx := "f86d8202b28477359400825208944592d8f8d7b001e72cb26a73e4fa1806a51ac79d880de0b6b3a7640000802ca05924bde7ef10aa88db9c66dd4f5fb16b46dff2319b9968be983118b57bb50562a001b24b31010004f13d9a26b320845257a6cfc2bf819a3d55e3fc86263c5f0772"
rawTxBytes, err := hex.DecodeString(rawTx)
接下來初始化一個新的types.Transaction
指針并從go-ethereumrlp
包中調(diào)用DecodeBytes
,將原始事務(wù)字節(jié)和指針傳遞給以太坊事務(wù)類型。 RLP是以太坊用于序列化和反序列化數(shù)據(jù)的編碼方法。
tx := new(types.Transaction)
rlp.DecodeBytes(rawTxBytes, &tx)
現(xiàn)在,我們可以使用我們的以太坊客戶端輕松地廣播交易。
err := client.SendTransaction(context.Background(), tx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("tx sent: %s", tx.Hash().Hex()) // tx sent: 0xc429e5f128387d224ba8bed6885e86525e14bfdc2eb24b5e9c3351a1176fd81f
然后你可以去Etherscan看交易的確認過程: https://rinkeby.etherscan.io/tx/0xc429e5f128387d224ba8bed6885e86525e14bfdc2eb24b5e9c3351a1176fd81f
完整代碼
package main
import (
"context"
"encoding/hex"
"fmt"
"log"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rlp"
)
func main() {
client, err := ethclient.Dial("https://rinkeby.infura.io")
if err != nil {
log.Fatal(err)
}
rawTx := "f86d8202b28477359400825208944592d8f8d7b001e72cb26a73e4fa1806a51ac79d880de0b6b3a7640000802ca05924bde7ef10aa88db9c66dd4f5fb16b46dff2319b9968be983118b57bb50562a001b24b31010004f13d9a26b320845257a6cfc2bf819a3d55e3fc86263c5f0772"
rawTxBytes, err := hex.DecodeString(rawTx)
tx := new(types.Transaction)
rlp.DecodeBytes(rawTxBytes, &tx)
err = client.SendTransaction(context.Background(), tx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("tx sent: %s", tx.Hash().Hex()) // tx sent: 0xc429e5f128387d224ba8bed6885e86525e14bfdc2eb24b5e9c3351a1176fd81f
}