description: Tutorial on how to set up a simulated backend as your client to test your Ethereum application with Go.

Using a Simulated Client

You can use a simulated client for testing your transactions locally quickly and easily, ideal for unit tests. In order to get started we’re going to need an account with some initial ETH in it. To do that first generate an account private key.

  1. privateKey, err := crypto.GenerateKey()
  2. if err != nil {
  3. log.Fatal(err)
  4. }

Then create a NewKeyedTransactor from the accounts/abi/bind package passing the private key.

  1. auth := bind.NewKeyedTransactor(privateKey)

The next step is to create a genesis account and assign it an initial balance. We’ll be using the GenesisAccount type from the core package.

  1. balance := new(big.Int)
  2. balance.SetString("10000000000000000000", 10) // 10 eth in wei
  3. address := auth.From
  4. genesisAlloc := map[common.Address]core.GenesisAccount{
  5. address: {
  6. Balance: balance,
  7. },
  8. }

Now we pass the genesis allocation struct and a configured block gas limit to the NewSimulatedBackend method from the accounts/abi/bind/backends package which will return a new simulated ethereum client.

  1. blockGasLimit := uint64(4712388)
  2. client := backends.NewSimulatedBackend(genesisAlloc, blockGasLimit)

You can use this client as you’d normally would. As an example, we’ll construct a new transaction and broadcast it.

  1. fromAddress := auth.From
  2. nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
  3. if err != nil {
  4. log.Fatal(err)
  5. }
  6. value := big.NewInt(1000000000000000000) // in wei (1 eth)
  7. gasLimit := uint64(21000) // in units
  8. gasPrice, err := client.SuggestGasPrice(context.Background())
  9. if err != nil {
  10. log.Fatal(err)
  11. }
  12. toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
  13. var data []byte
  14. tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)
  15. signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, privateKey)
  16. if err != nil {
  17. log.Fatal(err)
  18. }
  19. err = client.SendTransaction(context.Background(), signedTx)
  20. if err != nil {
  21. log.Fatal(err)
  22. }
  23. fmt.Printf("tx sent: %s\n", signedTx.Hash().Hex()) // tx sent: 0xec3ceb05642c61d33fa6c951b54080d1953ac8227be81e7b5e4e2cfed69eeb51

By now you’re probably wondering when will the transaction actually get mined. Well in order to “mine” it, there’s one additional important thing you must do; call Commit on the client to commit a new mined block.

  1. client.Commit()

Now you can fetch the transaction receipt and see that it was processed.

  1. receipt, err := client.TransactionReceipt(context.Background(), signedTx.Hash())
  2. if err != nil {
  3. log.Fatal(err)
  4. }
  5. if receipt == nil {
  6. log.Fatal("receipt is nil. Forgot to commit?")
  7. }
  8. fmt.Printf("status: %v\n", receipt.Status) // status: 1

So remember that the simulated client allows you to manually mine blocks at your command using the simulated client’s Commit method.

-

Full code

client_simulated.go

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "log"
  6. "math/big"
  7. "github.com/ethereum/go-ethereum/accounts/abi/bind"
  8. "github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
  9. "github.com/ethereum/go-ethereum/common"
  10. "github.com/ethereum/go-ethereum/core"
  11. "github.com/ethereum/go-ethereum/core/types"
  12. "github.com/ethereum/go-ethereum/crypto"
  13. )
  14. func main() {
  15. privateKey, err := crypto.GenerateKey()
  16. if err != nil {
  17. log.Fatal(err)
  18. }
  19. auth := bind.NewKeyedTransactor(privateKey)
  20. balance := new(big.Int)
  21. balance.SetString("10000000000000000000", 10) // 10 eth in wei
  22. address := auth.From
  23. genesisAlloc := map[common.Address]core.GenesisAccount{
  24. address: {
  25. Balance: balance,
  26. },
  27. }
  28. blockGasLimit := uint64(4712388)
  29. client := backends.NewSimulatedBackend(genesisAlloc, blockGasLimit)
  30. fromAddress := auth.From
  31. nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
  32. if err != nil {
  33. log.Fatal(err)
  34. }
  35. value := big.NewInt(1000000000000000000) // in wei (1 eth)
  36. gasLimit := uint64(21000) // in units
  37. gasPrice, err := client.SuggestGasPrice(context.Background())
  38. if err != nil {
  39. log.Fatal(err)
  40. }
  41. toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
  42. var data []byte
  43. tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)
  44. signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, privateKey)
  45. if err != nil {
  46. log.Fatal(err)
  47. }
  48. err = client.SendTransaction(context.Background(), signedTx)
  49. if err != nil {
  50. log.Fatal(err)
  51. }
  52. fmt.Printf("tx sent: %s\n", signedTx.Hash().Hex()) // tx sent: 0xec3ceb05642c61d33fa6c951b54080d1953ac8227be81e7b5e4e2cfed69eeb51
  53. client.Commit()
  54. receipt, err := client.TransactionReceipt(context.Background(), signedTx.Hash())
  55. if err != nil {
  56. log.Fatal(err)
  57. }
  58. if receipt == nil {
  59. log.Fatal("receipt is nil. Forgot to commit?")
  60. }
  61. fmt.Printf("status: %v\n", receipt.Status) // status: 1
  62. }