如何使用Go与Ethereum智能合约进行互动

812 阅读6分钟

在这篇文章中,我们将从我之前的文章中提取contract.sol ,看看我们是否可以使用Go来部署和交互它。

为什么是Go?首先,Go很神奇;)而且最广泛使用的Ethereum客户端是用Go写的,这意味着有一个很好的生态系统,可以使用Go与Ethereum和智能合约进行交互,具有很好的功能,如代码生成和共享库的可重用帮助器。

在这个例子中,我们不会使用真正的区块链作为部署目标,而是使用go-ethereum 提供的SimulatedBackend ,这样我们就可以安全地进行测试和实验,而不需要花费任何金钱。

Smart Contract 本身是非常简单的--我不会多说它做什么或如何工作,因为这已经被涵盖了。我只想说,合同的部署有3个参数。

  • 项目的最低参赛费
  • 提交新项目的截止日期
  • 支持项目的最后期限

然后,在第一阶段,项目可以使用nameurl ,交易至少包括Minimum Entry Fee 。在第二阶段,项目可以通过发送乙醚到合同上的地址来支持。

然而,在这篇文章中,我们将重点讨论。

  • 部署合约
  • 从合约中读取数据
  • 与合同互动(交易)。
  • 通过地址实例化已部署的合约

我们将在Go中完成这一切,并且只用不到70行的代码。)

让我们开始吧!

代码示例

为了能够跟上进度,你需要一些东西。首先,最重要的是,你需要solcSolidity编译器。

然后,只需获取go-ethereum 并构建它。

go get github.com/ethereum/go-ethereum
cd $GOPATH/src/github.com/ethereum/go-ethereum/
make
make devtools

好了--有了solcgeth devtools ,我们可以开始生成Go-version的contract.sol 文件,其中有我们的智能合约。

abigen --sol=Contract.sol --pkg=main --out=contract.go

正如你所看到的,我们有部署和实例化合约的方法,以及所有公共合约方法对Go的映射。

下一步是将合约部署到模拟的后端。

要做到这一点,需要进行一些设置。如上所述,为了简单起见,我们将使用SimulatedBackend 作为我们的目标区块链,但在这篇文章的末尾,将有一个简短的章节介绍如何用testnet ,甚至是真正的以太坊区块链来做这件事。

使用go-ethereum's 的一些依赖性,我们可以开始设置了。

import(
    "fmt"
    "log"
    "math/big"
    "time"

    "github.com/ethereum/go-ethereum/accounts/abi/bind"
    "github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
    "github.com/ethereum/go-ethereum/core"
    "github.com/ethereum/go-ethereum/crypto"
)

func main() {
    key, _ := crypto.GenerateKey()
    auth := bind.NewKeyedTransactor(key)

    alloc := make(core.GenesisAlloc)
    alloc[auth.From] = core.GenesisAccount{Balance: big.NewInt(133700000)}
    sim := backends.NewSimulatedBackend(alloc)

我们只需创建一个密钥,用一堆以太坊创建一个Genesis账户,并启动模拟后端,返回一个bind.ContractBackend

现在我们可以使用生成的DeployWinnerTakesAll 方法部署合同。

addr, _, contract, err := DeployWinnerTakesAll(auth, sim, big.NewInt(10), big.NewInt(time.Now().Add(2*time.Minute).Unix()), big.NewInt(time.Now().Add(5*time.Minute).Unix()))
if err != nil {
    log.Fatalf("could not deploy contract: %v", err)
}

我们传入一个auth 对象,它代表我们的身份,后端sim ,以及Minimum Entry FeeProject DeadlineCampaign Deadline 的值,每个都用一个大Int。该方法返回合同将被部署到的地址,以及合同的句柄和一个错误。还有一个返回的交易对象,但我们不会在这里处理它。

现在,合同已经部署完毕,我们应该能够与它进行交互。例如,我们可以检查我们发送的最后期限是否在合同中正确设置。

deadlineCampaign, _ := contract.DeadlineCampaign(nil)
fmt.Printf("Pre-mining Campaign Deadline: %s\n", deadlineCampaign)

然而,如果我们执行这个,我们得到的反馈是:<nil> 的期限。也就是说,因为我们的合约还没有被开采。如果我们使用真实的网络作为后端,我们将不得不等待,直到发生这种情况,但用我们的模拟后端,我们可以简单地做这个。

fmt.Println("Mining...")
sim.Commit()

postDeadlineCampaign, _ := contract.DeadlineCampaign(nil)
fmt.Printf("Post-mining Campaign Deadline: %s\n", time.Unix(postDeadlineCampaign.Int64(), 0))

然后我们就能得到我们在部署时设定的日期。

Post-mining Campaign Deadline: 2017-07-23 20:37:22 +0200 CEST

很好。所以,我们可以读取合同中所暴露的数据。现在我们想和它进行交互。在这种情况下,最简单的事情是我们提出一个新的项目,发送一个带有nameurl 的项目的交易,其值至少为Minimum Entry Fee

numOfProjects, _ := contract.NumberOfProjects(nil)
fmt.Printf("Number of Projects before: %d\n", numOfProjects)

fmt.Println("Adding new project...")
contract.SubmitProject(&bind.TransactOpts{
    From:     auth.From,
    Signer:   auth.Signer,
    GasLimit: big.NewInt(2381623),
    Value:    big.NewInt(10),
}, "test project", "http://www.example.com")

当然,我们需要再次开采...

fmt.Println("Mining...")
sim.Commit()

numOfProjects, _ = contract.NumberOfProjects(nil)
fmt.Printf("Number of Projects after: %d\n", numOfProjects)
info, _ := contract.GetProjectInfo(nil, auth.From)
fmt.Printf("Project Info: %v\n", info)

...但是我们会得到以下输出。

Number of Projects before: 0
Adding new project...
Mining...
Number of Projects after: 1
Project Info: {test project http://www.example.com 0}

真棒--这意味着我们的项目被创建了。所以我们能够部署一个合同,对它进行读写。

但是,如果合同已经被部署了,而我们只是想与之互动呢?幸运的是,生成的代码包括一个NewWinnerTakesAll 方法,该方法仅使用已部署合同的地址,让我们将合同实例化。

instContract, err := NewWinnerTakesAll(addr, sim)
if err != nil {
    log.Fatalf("could not instantiate contract: %v", err)
}
numOfProjects, _ = instContract.NumberOfProjects(nil)
fmt.Printf("Number of Projects of instantiated Contract: %d\n", numOfProjects)

我们得到的返回值与我们部署的合同相同,并且可以以完全相同的方式与这个按地址实例化的版本进行交互。

好了,我们经历了与合同进行有意义的交互所需的所有步骤,但只是在模拟的后端。为了使用测试网或真正的Ethereum区块链,我们只需要调整一些东西。

const key = "your key json"
conn, err := rpc.NewIPCClient("/path/to/your/.ethereum/testnet/geth.ipc")
if err != nil {
    log.Fatalf("could not create ipc client: %v", err)
}
auth, err := bind.NewTransactor(strings.NewReader(key), "your password")
if err != nil {
    log.Fatalf("could not create auth: %v", err)
}

这就产生了我们上面自己创建的auth对象。当然,请不要在你的代码中使用明文的钥匙和/或密码,而是以安全的方式加载它们。)

如果合同已经部署,我们不需要创建NewIPCClient ,而可以直接拨号到一个节点。

conn, err := ethclient.Dial("/path/to/your/.ethereum/testnet/geth.ipc")
if err != nil {
    log.Fatalf("could not connect to remote node: %v", err)
}

这就是了!

结论

正如我在这篇文章的开头所说,在我看来,依靠Solidity智能合约进行严肃的应用还为时过早,但这一方法和其他几种基于区块链的智能合约的潜力是巨大的,所以了解它周围的技术当然是值得的。

Go很适合与基于Ethereum的智能合约互动的任务,因为有很多可重用的代码来自geth ,甚至还有一些关于如何做开始的文档。当然,这也可以用任何其他语言实现(例如:使用web3),但如果Go是你喜欢的,它似乎是一个坚实的选择。)