在这篇文章中,我们将从我之前的文章中提取contract.sol ,看看我们是否可以使用Go来部署和交互它。
为什么是Go?首先,Go很神奇;)而且最广泛使用的Ethereum客户端是用Go写的,这意味着有一个很好的生态系统,可以使用Go与Ethereum和智能合约进行交互,具有很好的功能,如代码生成和共享库的可重用帮助器。
在这个例子中,我们不会使用真正的区块链作为部署目标,而是使用go-ethereum 提供的SimulatedBackend ,这样我们就可以安全地进行测试和实验,而不需要花费任何金钱。
Smart Contract 本身是非常简单的--我不会多说它做什么或如何工作,因为这已经被涵盖了。我只想说,合同的部署有3个参数。
- 项目的最低参赛费
- 提交新项目的截止日期
- 支持项目的最后期限
然后,在第一阶段,项目可以使用name 和url ,交易至少包括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
好了--有了solc 和geth 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 Fee 、Project Deadline 和Campaign 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
很好。所以,我们可以读取合同中所暴露的数据。现在我们想和它进行交互。在这种情况下,最简单的事情是我们提出一个新的项目,发送一个带有name 和url 的项目的交易,其值至少为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是你喜欢的,它似乎是一个坚实的选择。)