用Go來做以太坊開發(fā)③交易

交易(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ū)塊中的交易。

完整代碼

blocks.go

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

完整代碼

transactions.go

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

完整代碼

transfer_eth.go

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é).

完整代碼

transfer_tokens.go

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ù)字段,交易列表等等。

完整代碼

block_subscribe.go

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í)如何廣播一個原始交易。


完整代碼

transaction_raw_create.go

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

完整代碼

transaction_raw_sendreate.go

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
}

文章不定期更新,小編微信:grey0805,歡迎交流

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。