技术|在Go中测试和部署以太坊智能合约的操作指南

在本文中,我们将逐步研究如何使用Go编程语言测试,部署和与简单的以太坊智能合约进行交互。

网络上有很多教程,详细介绍了如何部署以太坊智能合约并与之交互。 但是这些教程仅以Javascript或Javascript和Go的某种组合来完成。

作为新兴的Gopher和以太坊发烧友,我想使用Go在以太坊上构建,测试,部署和与智能合约进行交互。 但是我找不到简单的逐步指南来帮助我入门。我不得不从不同的来源收集信息,并通过Go Ethereum源代码进行挖掘,以理解并整理所有必要的难题。 在这篇文章中,我将尝试使用我所学到的知识来编写一个易于遵循的分步指南,以期帮助其他人快速使用Go和以太坊进行生产力。

在以下情况下,这篇文章对您最有用:

1. 您熟悉Go编程语言,并且已经设置了Go开发环境。

2. 您对以太坊区块链和相关的智能合约有基本的了解。

3. 您已经接触过用于编写智能合约的Solidity编程语言。

安装Go-Ethereum和相关的开发工具

首先,我们需要为以太坊协议安装Go绑定。假设您已安装Go并正确设置了GOPATH环境变量,则可以通过以下方式获取绑定:

go get -d github.com/ethereum/go-ethereum

一旦我们检查了源代码,就可以继续构建geth(Geth是用于运行基于Go的以太坊节点的完整实现的主要命令行工具)以及所有开发工具:

$ cd $GOPATH/src/github.com/ethereum
$ go install ./...

设置项目结构

我们将编写并部署一个简单的inbox合约。为此让我们首先设置以下目录和文件结构:

# Navigate to your Go src directory. Mine looks like: 
# $GOPATH/src/github.com/sabbas
$ cd $GOPATH/src/github.com/sabbas
$ mkdir -p inbox/contracts
$ touch contracts/inbox_test.go fetch.go update.go deploy.go
$ tree inbox/
inbox/
├── contracts
│   └── inbox_test.go
├── deploy.go
├── fetch.go
└── update.go

我们创建一个名为inbox的新项目文件夹。在此文件夹中,我们创建一个名为contract的包文件夹,该文件夹将包含我们inbox智能合约的Solidity代码及其关联的Go绑定(我们将很快生成)。在智能合约包文件夹中,我们还有一个inbox_test.go文件。该文件将包含我们所有的测试。 还有三个其他文件,分别名为deploy.go,fetch.go和update.go。我们将使用这些附加文件来编写代码以在公共网络上部署以太坊智能合约并与之交互,这些文件现在可以为空。

创建一个简单的以太坊合约

现在我们准备为inbox智能合约编写一些Solidity代码。导航到inbox/contracts文件夹,然后创建一个名为Inbox.sol的文件。

$ tree inbox/
inbox/
└── contracts
    └── Inbox.sol

编辑inbox.sol文件,并为我们的Inbox智能合约加以下Solidity代码行:

pragma solidity ^0.4.17;
contract Inbox {
    string public message;
    function Inbox(string initialMessage) public {
        message = initialMessage;
    }
    function setMessage(string newMessage) public {
        message = newMessage;
    }
}

inbox智能合约非常简单。它具有一个称为message的公共数据变量,该变量保存最新消息字符串的内容。 该智能合约还定义了一个公共setMessage方法,该方法更新消息数据变量的内容。

使用Go中的以太坊合约

现在我们已经在Solidity中定义了inbox智能合约,我们希望能够在Go应用程序中使用此智能合约。更具体地说,我们希望能够将此智能合约部署到以太坊网络上,并可以在Go应用程序中方便地与之交互。Go-Ethereum通过提供一个代码生成器工具使此操作非常简单,该工具可以将Solidity智能合约文件转换为类型安全的go包,我们可以直接从Go应用程序中导入和使用它。 该工具称为abigen,它是在上述go-ethereum设置期间构建和安装的。要使用abigen,请导航到“inbox/contracts”文件夹并执行:

$ abigen -sol inbox.sol -pkg contracts -out inbox.go
$ tree inbox
inbox
└── contracts
    ├── Inbox.sol
    └── inbox.go

我们将要为其生成Go包的Solidity智能合约文件的名称传递给sol命令行参数。我们还为pkg和out命令行参数指定Go软件包名称和输出文件名称。运行abigen会生成inbox.go软件包文件,其中包含Inbox Solidity合同的Go绑定。 一旦有了这些绑定,就可以开始测试inbox智能合约了。

在部署到公共网络之前测试以太坊合约

在将智能合约部署到公共以太坊网络之前,我们要确保其按预期运行。对于inbox智能合约,我们要测试是否可以使用初始消息来部署智能合约,检索此初始消息并稍后更新其值。Go-Ethereum为区块链模拟器提供了一个很好的实用程序,对自动化单元测试非常有帮助。在随后的代码片段中,我们将看到如何使用区块链模拟器应用程序来测试inbox智能合约。

package contracts
import (
"testing"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/core"
"math/big"
)
// Test inbox contract gets deployed correctly
func TestDeployInbox(t *testing.T) {
//Setup simulated block chain
key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key)
alloc := make(core.GenesisAlloc)
alloc[auth.From] = core.GenesisAccount{Balance: big.NewInt(1000000000)}
blockchain := backends.NewSimulatedBackend(alloc)
//Deploy contract
address, _, _, err := DeployInbox(
auth,
blockchain,
"Hello World",
)
// commit all pending transactions
blockchain.Commit()
if err != nil {
t.Fatalf("Failed to deploy the Inbox contract: %v", err)
}
if len(address.Bytes()) == 0 {
t.Error("Expected a valid deployment address. Received empty address byte array instead")
}
}

TestDeployInbox方法通过调用crypto.GenerateKey生成私钥开始。该密钥用于创建交易签名器功能,用于授权模拟区块链中的交易。通过对bind.NewKeyedTransactor的调用,生成了交易签名者函数以及一个我们可以用来进行交易的地址。然后,该地址用于创建一个创始块,该创始块包含具有一些初始余额的单个帐户(通过调用make(core.GenesisAlloc和core.GenesisAccount)。然后该创世块用于为模拟区块链播种。最后我们“ 通过明确地提交所有未决交易并检查inbx智能合约是否已部署到有效地址来进行下一步。

我们可以导航到inbox\contracts并执行go test以确保部署测试通过

go test -v
=== RUN   TestDeployInbox
--- PASS: TestDeployInbox (0.01s)
PASS
ok   github.com/sabbas/inbox/contracts 0.042s

接下来,我们要测试是否在部署inbox智能合约时使用正确的初始消息进行部署。

package contracts
import (
"testing"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/core"
"math/big"
)
//Test initial message gets set up correctly
func TestGetMessage(t *testing.T) {
//Setup simulated block chain
key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key)
alloc := make(core.GenesisAlloc)
alloc[auth.From] = core.GenesisAccount{Balance: big.NewInt(1000000000)}
blockchain := backends.NewSimulatedBackend(alloc)
//Deploy contract
_, _, contract, _ :=DeployInbox(
auth,
blockchain,
"Hello World",
)
// commit all pending transactions
blockchain.Commit()
if got, _ := contract.Message(nil); got != "Hello World" {
t.Errorf("Expected message to be: Hello World. Go: %s", got)
}
}

与TestDeployInbox函数类似,TestGetMessage函数通过设置模拟的区块链开始(模拟的区块链函数可以重构为单独的可重用函数。我在这里避免这样做只是为了提高可读性)。然后它为Inbox Solidity合同调用作为自动生成的Go绑定的一部分而创建的DeployInbox函数。请注意,DeployInbox函数返回一个指向已部署Inbox合同实例的指针作为第三个返回值。我们可以使用此指针与已部署的收件箱合同进行交互(超级酷!)。这正是测试要执行的查询和检查存储在我们刚刚部署的收inbox智能合约实例中的初始消息的方法。

我们可以导航到inbox\contracts并执行go test以确保我们的测试通过

$ go test -v
=== RUN   TestDeployInbox
--- PASS: TestDeployInbox (0.01s)
=== RUN   TestGetMessage
--- PASS: TestGetMessage (0.00s)
PASS
ok   github.com/sabbas/inbox/contracts 0.045s

最后,我们要测试是否可以将已部署的inbox智能合约中的消息数据属性更新为新值。

package contracts
import (
"testing"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/core"
"math/big"
)
// Test message gets updated correctly
func TestSetMessage(t *testing.T) {
//Setup simulated blockchain
key, _ := crypto.GenerateKey()
auth := bind.NewKeyedTransactor(key)
alloc := make(core.GenesisAlloc)
alloc[auth.From] = core.GenesisAccount{Balance: big.NewInt(1000000000)}
blockchain := backends.NewSimulatedBackend(alloc)
//Deploy contract
_, _, contract, _ :=DeployInbox(
auth,
blockchain,
"Hello World",
)
// commit all pending transactions
blockchain.Commit()
contract.SetMessage(&bind.TransactOpts{
From:auth.From,
Signer:auth.Signer,
Value: nil,
}, "Hello from Mars")
blockchain.Commit()
if got, _ := contract.Message(nil); got != "Hello from Mars" {
t.Errorf("Expected message to be: Hello World. Go: %s", got)
}

TestSetMessage函数以我们之前看到的用于设置模拟区块链和部署inbox智能合约的相同样板代码开始。 如前所述,成功调用DeployInbox函数调用将返回指向已部署Inbox智能合约实例的指针,作为第三个返回值。在TestSetMessage函数中,我们使用此智能合约指针通过调用SetMessage函数来更新Inbox合同的消息数据属性。由于SetMessage函数修改了inbox智能合约,因此实际上会生成一个新事务。结果我们传入一个指向填充有交易授权数据的TrasactOpts结构的指针。由于我们不需要与SetMessage调用一起发送任何资金,因此我们将TransactOpts结构的Value属性设置为nil。

我们可以导航到inbox\contracts,然后再运行一次go test,以确保一切正常。

$ go test -v
=== RUN   TestDeployInbox
--- PASS: TestDeployInbox (0.01s)
=== RUN   TestGetMessage
--- PASS: TestGetMessage (0.00s)
=== RUN   TestSetMessage
--- PASS: TestSetMessage (0.01s)
PASS
ok   github.com/sabbas/inbox/contracts 0.051s

一旦我们的本地测试通过,我们的inbox智能合约就可以准备好了。 在下一部分中,我们将研究如何获得Inbox智能合约并将其部署在公共以太坊网络上。

我们将在Rinkeby Test Ethereum公共网络上部署Inbox智能合约。 在以太坊主网络上部署以太坊合约并与之交互需要花费实际金钱,而在我们只是学习或玩耍时,这并不需要。

要在Rinkeby之类的公共网络上部署以太坊合约并与之互动,需要采取以下几项措施:

  • 我们需要在网络上拥有一个有资金(以太币)的帐户

  • 我们需要能够连接到以太坊节点并与之交互。

在以下各节中,我们将依次介绍这些内容。

使用Metamask创建帐户

我们可以直接从之前构建的geth cli创建和管理帐户。但是刚开始时,我发现使用浏览器中的Metamask Chrome扩展程序要简单得多。 安装Metamask并创建一个帐户非常简单。

一旦启动并运行Metamask并创建了一个帐户,我们就要确保将Metamask指向Rinkeby Test网络

向账户注资

Rinkeby网络运行着一个测试水龙头(https://faucet.rinkeby.io/),我们可以使用它来请求以太坊。 但是要从该测试水龙头请求以太币,我们首先需要发布一个仅包含以太坊账户地址的帖子(该账户是我们要将资金转入的账户,我们可以通过单击账户名称旁边的省略号来复制该地址在社交网站上的Metamask扩展中)并提供指向该帖子的链接。

这将需要几秒钟,我们应该会在Metamask扩展程序的帐户中看到资金显示。

连接到以太坊节点

我们可以使用geth在Rinkeby测试网络上启动和管理我们自己的以太坊节点。这既耗费资源和时间,也不总是一个平稳的过程。更好的选择是连接到第三方提供商托管的运行中的以太坊节点。Infura是这样的提供者之一。我们可以注册一个Infura免费帐户。注册后,Infura将向我们发送URL,以连接到运行在不同以太坊网络上的节点。Rinkeby测试网络的URL应该类似于https://rinkeby.infura.io/fYE8qC0WiMx4ZAX4Voff。

为我们的帐户生成加密的JSON密钥

为了通过Go-Ethereum在Rinkeby等公共以太坊网络上部署和交互合约,我们需要为通过上述Metamask创建的账户使用一个加密的json密钥。这是我们要收取的用于部署和与Inbox智能合约进行交互的帐户。

我们可以通过以下方式来生成此JSON密钥:将Metamask中的帐户导出到文件中,然后将其私钥(单击metamask中的帐户名称旁边的省略号以获取导出私钥选项)到文件中,然后通过geth导入。完成导入后,请确保删除私钥文件。

geth account import path/to/private/key/file

上面的命令将在Mac和〜/.ethereum上的〜/Library/Ethereum/keystore中生成一个加密的JSON密钥文件。注意该文件, 在Rinkeby测试网络上部署Inbox智能合约并与之交互时,我们将在下一部分中使用其内容。

最后的边疆

我们终于到了准备将Inbox智能合约部署到Rinkeby网络的地步。首先让我们看一下我们先前创建的deploy.go文件中的代码,然后详细介绍它。

package main
import (
"github.com/ethereum/go-ethereum/ethclient"
"log"
"github.com/sabbas/inbox/contracts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"strings"
"fmt"
"os"
)
const key  = `paste the contents of your JSON key file here`
func main(){
// connect to an ethereum node  hosted by infura
blockchain, err := ethclient.Dial("https://rinkeby.infura.io/fYe8qCnWi6TXZAXOVof9")
if err != nil {
log.Fatalf("Unable to connect to network:%v\n", err)
}
// Get credentials for the account to charge for contract deployments
auth, err := bind.NewTransactor(strings.NewReader(key), "passphrase associated with your JSON key file")
if err != nil {
log.Fatalf("Failed to create authorized transactor: %v", err)
}
address, _, _, _:= contracts.DeployInbox(
auth,
blockchain,
"Hello World",
)
fmt.Printf("Contract pending deploy: 0x%x\n", address)}

我们将ethclient.Dial方法与我们先前生成的Infura url一起使用,以连接到Rinkeby测试网络上的以太坊节点。然后,我们使用bind.NewTransactor实用程序方法从密钥文件的内容(您可以使用geth account list命令检索您的JSON密钥文件在系统上的位置)及其相关密码创建一个授权的事务处理程序,我们通过 geth帐户导入。从那时起,部署智能合约的代码与使用模拟区块链部署inbox智能合约时所见的代码完全相同。最后如果一切顺利,我们将打印inbox智能合约在网络上部署的地址。

go run deploy.go
Contract pending deploy: 0x491c7fd67ac1f0afeceae79447cd98d2a0e6a9ff

挖掘该智能合约并成为区块链的一部分将需要几分钟。我们可以通过以下位置通过etherscan来检查状态:https://rinkeby.etherscan.io/address/ [合约地址]。 就我而言,这是https://rinkeby.etherscan.io/address/0x491c7fd67ac1f0afeceae79447cd98d2a0e6a9ff

一旦它被挖掘并包含在区块链中,我们就可以使用已部署的Inbox智能合约的地址开始与它进行交互。例如我们可以将下面的代码放在上面创建的fetch.go中,以检索初始消息。

package main
import (
"github.com/ethereum/go-ethereum/ethclient"
"log"
"github.com/sabbas/inbox/contracts"
"fmt"
"github.com/ethereum/go-ethereum/common"
)
func main(){
// connect to an ethereum node  hosted by infura
blockchain, err := ethclient.Dial("https://rinkeby.infura.io/fYe8qCnWiM9gh&ZAXOVoff")
if err != nil {
log.Fatalf("Unable to connect to network:%v\n", err)
}
// Create a new instance of the Inbox contract bound to a specific deployed contract
  contract, err :=contracts.NewInbox(common.HexToAddress("0x491c7fd67ac1f0afeceae79447cd98d2a0e6a9ff"), blockchain)
if err != nil {
log.Fatalf("Unable to bind to deployed instance of contract:%v\n")
}
fmt.Println(contract.Message(nil))
}

如前所示,我们对早期生成的Infura url使用ethclient.Dial方法来连接到Rinkeby测试网络上的以太坊节点。 然后我们使用作为Solidity合约的Go绑定的一部分生成的NewInbox方法,将Inbox合约的实例附加到部署在特定地址的Inbox合约。 最后,我们访问智能合约上的Message属性,该属性应输出Hello World。

go run interact.go
Hello World <nil>

我们还可以更新已部署的inbox智能合约上的Message属性。 为此请将下面的代码放在之前创建的update.go文件中。

package main


import (
"github.com/ethereum/go-ethereum/ethclient"
"log"
"github.com/sabbas/inbox/contracts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"strings"
"fmt"
"os"
)
const key  = `paste the contents of your JSON key file here`
func main(){
// connect to an ethereum node  hosted by infura
blockchain, err := ethclient.Dial("https://rinkeby.infura.io/fYe8qCnWi6TXZAXOVof9")
if err != nil {
log.Fatalf("Unable to connect to network:%v\n", err)
}
// Get credentials for the account to charge for contract deployments
auth, err := bind.NewTransactor(strings.NewReader(key), "passphrase associated with your JSON key file")
if err != nil {
log.Fatalf("Failed to create authorized transactor: %v", err)
}
  contract, err :=contracts.NewInbox(common.HexToAddress("0x491c7fd67ac1f0afeceae79447cd98d2a0e6a9ff"), blockchain)
if err != nil {
log.Fatalf("Unable to bind to deployed instance of contract:%v\n")
}
contract.SetMessage(&bind.TransactOpts{
From:auth.From,
Signer:auth.Signer,
Value: nil,
}, "Hello From Mars")
}

回想一下,SetMessage函数修改了inbox合约并实际上生成了一个新事务。结果我们传入一个指向填充有交易授权数据的TrasactOpts结构的指针。同样这笔交易需要几分钟的时间才能被开采并成为区块链的一部分。一旦将其挖掘并包含在区块链中,我们可以按照前面的示例从已部署的智能合约中检索Message属性,以查看更新后的值。

以太坊(Go Ethereum)我们可以做很多事情,但是刚开始可能有点麻烦。 希望这篇文章为您提供了一个起点,从那里您可以进一步探索。

本文来源: 区块链研究实验室 文章作者: 链三丰 我要纠错
声明:本文由入驻金色财经的作者撰写,观点仅代表作者本人,绝不代表金色财经赞同其观点或证实其描述。
提示:投资有风险,入市须谨慎。本资讯不作为投资理财建议。

金色财经 > 区块链 > 技术|在Go中测试和部署以太坊智能合约的操作指南