用Go语言开发以太坊合约

2023-10-29

转发请注明出处:https://blog.csdn.net/ahy231/article/details/114112638

网上关于 go 语言开发 DApp 的教程较少,因此我只能通过官方文档来系统学习 go 语言的 DApp 开发。这篇文章是我对 https://geth.ethereum.org/docs/dapp/native-bindings 这篇文档的翻译,如有不符之处,欢迎斧正。

译文

Go 语言合约绑定

[请注意,event 尚未实现,因为它们需要一些仍在审查中的RPC订阅功能。]

以太坊平台最初的路线图和梦想是以各种语言提供一致协议的可靠、高性能的客户端实现,这将为 JavaScript dapp 提供一个RPC接口进行通信,朝着Mist浏览器的方向发展,用户可以通过它与区块链进行交互。

尽管这是一个主流采用的坚实计划,并涵盖了人们提出的大量用例(主要是人们手动与区块链交互的情况),但它避开了服务器端(后端、全自动、devops)用例,在这些用例中,由于 JavaScript 的动态性,JavaScript 通常不是首选语言。

本页介绍了服务器端本机 Dapps 的概念:任何以太坊契约的 Go 语言绑定都是编译时类型安全、高性能的,最重要的是,可以从契约 ABI 和可选的 EVM 字节码完全自动生成。

这个页面是以一种更为初学者友好的教程风格编写的,以使人们更容易从编写 Go-native-dapp 开始。所使用的概念将随着开发人员的需要或遇到而逐步引入。但是,我们假设读者对以太坊非常熟悉,对其稳定性有相当的了解,并且可以编写代码。

代币合同

为了避免陷入无用的学术示例的谬误,我们将以官方令牌契约作为引入 Go 本机绑定的基础。如果你对合同不熟悉,浏览一下链接页面就足够了,细节现在还不相关。简而言之,契约实现了一个可以部署在以太坊之上的定制代币代码。为了确保本教程在链接网站发生变化时不会过时,Token 契约的 Solidity 源代码在 token.sol

Go 绑定生成器

通过以太坊客户端公开的RPC接口,已经可以与来自 Go 的以太坊区块链上的合约(或事实上的任何其他语言)进行交互。然而,编写样例代码,将良好的 Go 语言结构转换为 RPC 调用并返回,这是非常耗时的,而且也是非常脆弱的:实现中的 bug 只能在运行时检测到,而且几乎不可能调用合约,因为即使是稳定性上的微小变化,移植到 Go 上也会非常痛苦。

为了避免所有这些混乱,go-ethereum 实现引入了一个源代码生成器,它可以将 ethereum-ABI 定义转换为易于使用、类型安全的 go 包。假设您设置了有效的 Go 开发环境,安装了 godep,并且正确签出了 go-ethereum 存储库,则可以使用以下方法构建生成器:

$ cd $GOPATH/src/github.com/ethereum/go-ethereum
$ godep go install ./cmd/abigen

生成绑定

生成到以太坊合约的 Go 绑定所需的一件重要事情是合约的 ABI 定义 JSON 文件。对于我们的代币合约教程,您可以通过自己(例如通过 @chriseth 的在线 Solidity compiler )编译稳固性代码来获得,也可以下载我们预先编译的 token.abi

要生成绑定,只需调用:

$ abigen --abi token.abi --pkg main --type Token --out token.go

这里标识是:

  • --abi: Mandatory path to the contract ABI to bind to
  • --pkg: Mandatory Go package name to place the Go code into
  • --type: Optional Go type name to assign to the binding struct
  • --out: Optional output path for the generated Go source file (not set = stdout)

这将为代币合约生成一个类型安全的 Go 绑定。生成的代码将类似于 token.go,但请自行生成,因为这将随发电机投入更多工作而改变。

访问以太坊合约

要与部署在区块链上的合约进行交互,您需要知道合约本身的地址,并需要指定访问以太坊的后端。绑定生成程序提供开箱即用的RPC后端,通过该后端,您可以通过 IPCHTTPWebSockets 连接到现有的以太坊节点。

我们将使用部署在测试链上的基金会的Unicorn token 合约来演示调用合约方法。其部署在地址 0X21E6FC92F93C8A1BB41E61EE64E64E1F88A54D3576

要运行以下代码段,请确保运行一个 Geth 实例并将其连接到部署上述合约的现代测试网络。此外,请将以下 IPC 套接字的路径更新为您自己的本地 Geth 节点报告的一。

package main

import (
	"fmt"
	"log"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/ethclient"
)

func main() {
	// Create an IPC based RPC connection to a remote node
	conn, err := ethclient.Dial("/home/karalabe/.ethereum/testnet/geth.ipc")
	if err != nil {
		log.Fatalf("Failed to connect to the Ethereum client: %v", err)
	}
	// Instantiate the contract and display its name
	token, err := NewToken(common.HexToAddress("0x21e6fc92f93c8a1bb41e2be64b4e1f88a54d3576"), conn)
	if err != nil {
		log.Fatalf("Failed to instantiate a Token contract: %v", err)
	}
	name, err := token.Name(nil)
	if err != nil {
		log.Fatalf("Failed to retrieve token name: %v", err)
	}
	fmt.Println("Token name:", name)
}

输出:

Token name: Testnet Unicorn

如果查看为读取代币名称而调用的方法 token.Name(nil),它需要传递一个参数,即使原始的稳固性合同不需要任何参数。这是一 *bind.CallOpts 类型,可用于微调呼叫。

  • Pending:是进入待定合同状态还是当前稳定状态
  • GasLimit:对呼叫可能消耗的计算资源设置限制

与以太坊合约进行交易

调用改变合同状态(即交易)的方法涉及更多,因为实时交易需要授权并广播到网络。与在我们所连接的节点中存储帐户和密匙的传统方式相反,Go 绑定要求在本地签署事务,而不将其委托给远程节点。这样做是为了促进以太坊社区的整体方向,在以太坊社区中,账户对 DAPP 是保密的,而不是在 DAPP 之间共享(默认情况下)。

因此,为了允许与合约进行交易,代码需要实现一个方法,该方法提供一个输入交易、签署该交易并返回一个授权输出交易。由于大多数用户的密匙采用 Web3 秘密存储格式,因此 bind 包包含一个小型实用方法( bind.NewTransactor(keyjson, passphrase) ),可以从一个密匙文件和相关密码创建一个授权的交易者,而无需用户自己实现密匙签名。

更改先前的代码段以将一个 unicorn 发送到零地址:

package main

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

	"github.com/ethereum/go-ethereum/accounts/abi/bind"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/ethclient"
)

const key = `paste the contents of your *testnet* key json here`

func main() {
	// Create an IPC based RPC connection to a remote node and instantiate a contract binding
	conn, err := ethclient.Dial("/home/karalabe/.ethereum/testnet/geth.ipc")
	if err != nil {
		log.Fatalf("Failed to connect to the Ethereum client: %v", err)
	}
	token, err := NewToken(common.HexToAddress("0x21e6fc92f93c8a1bb41e2be64b4e1f88a54d3576"), conn)
	if err != nil {
		log.Fatalf("Failed to instantiate a Token contract: %v", err)
	}
	// Create an authorized transactor and spend 1 unicorn
	auth, err := bind.NewTransactor(strings.NewReader(key), "my awesome super secret password")
	if err != nil {
		log.Fatalf("Failed to create authorized transactor: %v", err)
	}
	tx, err := token.Transfer(auth, common.HexToAddress("0x0000000000000000000000000000000000000000"), big.NewInt(1))
	if err != nil {
		log.Fatalf("Failed to request token transfer: %v", err)
	}
	fmt.Printf("Transfer pending: 0x%x\n", tx.Hash())
}

输出:

Transfer pending: 0x4f4aaeb29ed48e88dd653a81f0b05d4df64a86c99d4e83b5bfeb0f0006b0e55b

注意,很有可能您将没有任何可用的测试链 unicorn,因此上述程序将失败并出现错误。发送至少 2.014 测试网以太到 foundation testnet tipjar

与上一节中仅读取合约状态的方法调用类似,事务方法也需要一个强制的第一参数 *bind.TransactOpts 类型,其授权交易并可能对其进行微调:

  • From:用于调用方法的帐户的地址(强制)
  • Signer:在广播事务前在本地签署事务的方法(强制)
  • Nonce:用于交易排序的账户随机数(可选)
  • GasLimit:对呼叫可能消耗的计算资源设置限制(可选)
  • GasPrice:显式设置 Gas 价格以运行交易(可选)
  • Value:随方法调用转移的任何资金(可选)

如果使用 bind.NewTransactor 函数生成身份验证选项,两个强制的参数会被 bind 包自动填写。如果未设置,nonce和gas相关的域,它们将由绑定自动导出。未设定的 Value 假定为零。

预先配置的合约 Session

如前二节所述,读取和状态修改合约调用都需要一个强制的第一参数,该参数既可以授权也可以微调一些内部参数。然而,大多数情况下,我们希望使用相同的参数并使用相同的账户进行交易,因此始终构建调用、交易选项或将其与绑定一起传递可能变得不易。

为避免这些情况,generator 还创建可预先配置调优和授权参数的专用包装器,允许在不需要额外参数的情况下调用所有已定义的方法。

这些名称与原始合约类型名称类似,仅以 Sessions 作为其后缀:

// Wrap the Token contract instance into a session
session := &TokenSession{
	Contract: token,
	CallOpts: bind.CallOpts{
		Pending: true,
	},
	TransactOpts: bind.TransactOpts{
		From:     auth.From,
		Signer:   auth.Signer,
		GasLimit: big.NewInt(3141592),
	},
}
// Call the previous methods without the option parameters
session.Name()
session.Transfer("0x0000000000000000000000000000000000000000"), big.NewInt(1))

在以太坊上部署合约

与现有合约互动固然不错,但让我们更进一步,在以太坊区块链上部署一份全新合约!然而,要做到这一点,我们用于生成绑定的合约 ABI 是不够的。我们也需要已编译的字节码以允许部署它。

要获得该位元组码,请返回可用于生成该位元组码的在线编译程序,或下载我们的 token.bin 为了创建部署代码,还需要重新运行包含了字节码的 Go generator

$ abigen --abi token.abi --pkg main --type Token --out token.go --bin token.bin

这将产生类似于 token.go 。如果快速浏览此文件,将发现与先前的代码相比,新注入的额外 DeployToken 函数。除了由 Solidity 指定的所有参数外,它还需要常规授权选项来部署与之签订的合同,以及通过以太坊后端来部署合同。

综上所述,结果是:

package main

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

	"github.com/ethereum/go-ethereum/accounts/abi/bind"
	"github.com/ethereum/go-ethereum/ethclient"
)

const key = `paste the contents of your *testnet* key json here`

func main() {
	// Create an IPC based RPC connection to a remote node and an authorized transactor
	conn, err := rpc.NewIPCClient("/home/karalabe/.ethereum/testnet/geth.ipc")
	if err != nil {
		log.Fatalf("Failed to connect to the Ethereum client: %v", err)
	}
	auth, err := bind.NewTransactor(strings.NewReader(key), "my awesome super secret password")
	if err != nil {
		log.Fatalf("Failed to create authorized transactor: %v", err)
	}
	// Deploy a new awesome contract for the binding demo
	address, tx, token, err := DeployToken(auth, conn), new(big.Int), "Contracts in Go!!!", 0, "Go!")
	if err != nil {
		log.Fatalf("Failed to deploy new token contract: %v", err)
	}
	fmt.Printf("Contract pending deploy: 0x%x\n", address)
	fmt.Printf("Transaction waiting to be mined: 0x%x\n\n", tx.Hash())

	// Don't even wait, check its presence in the local pending state
	time.Sleep(250 * time.Millisecond) // Allow it to be processed by the local node :P

	name, err := token.Name(&bind.CallOpts{Pending: true})
	if err != nil {
		log.Fatalf("Failed to retrieve pending name: %v", err)
	}
	fmt.Println("Pending name:", name)
}

代码按预期执行:它要求在以太坊区块链上创建一个全新的代币合约,我们可以等待该合约被挖掘,或如上述代码中所述,在挂起状态下对其开始调用方法:)

Contract pending deploy: 0x46506d900559ad005feb4645dcbb2dbbf65e19cc
Transaction waiting to be mined: 0x6a81231874edd2461879b7280ddde1a857162a744e3658ca7ec276984802183b

Pending name: Contracts in Go!!!

直接绑定 Solidty

如果您一直遵循本教程,直至目前为止,您可能已经意识到每个合约修改都需要重新编译,所产生的 ABIbytecodes(特别是如果需要多个合约)将单独保存到文件中,然后为其执行绑定。这在第n次迭代后可能会变得相当烦人,因此 abigen 命令支持直接从 Solidity 源文件( --sol )进行绑定,后者第一次将源代码(通过 --solc ,默认为 solc )编译到其组成组件中并使用该组件进行绑定。

绑定 官方代币合约 token.sol 然后将需要运行:

$ abigen --sol token.sol --pkg main --out token.go

注:从稳固性( --sol )进行构建与单独设置绑定组件( --abi--bin--type )是互斥的,因为所有组件均从稳固性代码中提取并直接生成构建结果。

直接从 Solidity 构建一个合约有一个好的副作用,即包含在一个 Solidity 源文件中的所有合约都是构建和绑定的,因此,如果您的文件包含多个合约源,它们中的每一个都可以从 Go 代码中获得。示例 Token solidity 文件对应的结果是 token.go

项目集成(即go generate)

abigen 命令是以一种与现有 Go 工具链完美结合的方式发出的:我们可以利用 Go generate 来记下所有细节,而不必记下将以太坊合约绑定到 Go 项目中所需的确切命令。

//go:generate abigen --sol token.sol --pkg main --out token.go

之后,无论何时修改 Solidity 合约,我们都可以简单地在包上调用 go generate(甚至是通过 go generate ./...),而无需记忆并运行上述命令,它将为我们正确地生成新的绑定。

区块链模拟器

能够从本机 Go 代码中部署和访问已经部署的 Ethereum 契约是一个非常强大的特性,但是开发本机代码有一个方面甚至测试链都不适合:自动单元测试。使用 go-ethereum 内部构造可以创建测试链并验证它们,但是使用这种低级机制进行高级合约测试是不可行的。

为了解决最后一个使本机 DApps 难以运行(和测试)的问题,我们还实现了一个模拟区块链,可以将其设置为本机契约的后端,就像实时RPC后端一样:backends.NewSimulatedBackend(genesisAccounts)

package main

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

	"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() {
	// Generate a new random account and a funded simulator
	key, _ := crypto.GenerateKey()
	auth := bind.NewKeyedTransactor(key)

	sim := backends.NewSimulatedBackend(core.GenesisAccount{Address: auth.From, Balance: big.NewInt(10000000000)})

	// Deploy a token contract on the simulated blockchain
	_, _, token, err := DeployMyToken(auth, sim, new(big.Int), "Simulated blockchain tokens", 0, "SBT")
	if err != nil {
		log.Fatalf("Failed to deploy new token contract: %v", err)
	}
	// Print the current (non existent) and pending name of the contract
	name, _ := token.Name(nil)
	fmt.Println("Pre-mining name:", name)

	name, _ = token.Name(&bind.CallOpts{Pending: true})
	fmt.Println("Pre-mining pending name:", name)

	// Commit all pending transactions in the simulator and print the names again
	sim.Commit()

	name, _ = token.Name(nil)
	fmt.Println("Post-mining name:", name)

	name, _ = token.Name(&bind.CallOpts{Pending: true})
	fmt.Println("Post-mining pending name:", name)
}

输出:

Pre-mining name: 
Pre-mining pending name: Simulated blockchain tokens
Post-mining name: Simulated blockchain tokens
Post-mining pending name: Simulated blockchain tokens

注意,我们不必等待本地私有链矿工或测试链矿工来收集当前挂起的事务。当我们决定挖掘下一个块时,我们只需 Commit() 模拟器。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

用Go语言开发以太坊合约 的相关文章

随机推荐

  • react-hooks 在不编写 class 的情况下使用 state 以及其他的 React 特性

    文章目录 hooks 一 hook 1 useState 2 useEffect 3 useLayoutEffect 4 自定义Hook 5 useRef 6 useImperativeHandle 7 useContext 8 useRe
  • led灯条串联图_单片机入门教学-LED驱动电路

    欢迎关注 嵌入式干货铺子 每日更新干货教程 做单片机开发 看不懂电路图是万万不能的 分析电路原理图是一个合格的单片机工程师必须掌握的 后续干货铺子会做一个分析电路图的系列教程 从最基本的电路开始 手把手掌握单片机电路的设计 欢迎大家关注 接
  • Java 基本数据类型(四类八种)

    基本数据类型 四类八种 整数类 byte short int long 浮点类 float double 字符类 char 布尔型 boolean 除此之外即为引用类数据类型 一 整数类 不同类型表示不同长度 1 Byte 使用1个字节存储
  • 共识算法 —— PoA

    定义 PoA的全称是 Proof Of Authority 权威证明 网上有些文章全称写得是 Proof Of Activity 个人感觉明显不对 大家自行鉴别 最早提出人是Ethereum 以太坊前技术专家Gavin Wood 在2017
  • hive小文件过多问题解决方法

    小文件产生原因 hive 中的小文件肯定是向 hive 表中导入数据时产生 所以先看下向 hive 中导入数据的几种方式 直接向表中插入数据 insert into table A values 1 zhangsan 88 2 lisi 6
  • 如此优雅,4款 Python 自动数据分析神器真香啊

    我们做数据分析 在第一次拿到数据集的时候 一般会用统计学或可视化方法来了解原始数据 比如了解列数 行数 取值分布 缺失值 列之间的相关关系等等 这个过程我们叫做 EDA Exploratory Data Analysis 探索性数据分析 用
  • 一文看懂什么是区块链分叉

    一些链上资产采用的工作量证明机制 就是让矿工互相竞争求解一个数学题 谁先解出来了 他就大喊一声 我的工作量证明成功了 你们快来看 全体矿工就都过来把那一页目抄写一份 贴在自己账本的最后面 然后又开始新的记账过程 在这个过程中 经常会出现这样
  • 通俗易懂----C语言时间日期与时间戳互相转化

    前言 如果你也对时间转化的高效代码而苦恼 不妨看看以下内容 一定会给你带来良好的体验和启迪 1 时间戳含义 1 1时间戳是衡量时间的一种标准 用来特定电子数据提供一个绑定时间戳 从而有效地证明该电子数据的产生时间及未被修改 常用于保 防伪等
  • 前端小作业~基础知识点串接

    包子们 这次网页虽然不难 但是扣细节的地方多啊 都是最近学的零零散散的知识的拼接与整合 也是尽力而为了 搞了将近四个小时
  • R语言常用快捷键1

    快捷键 1赋值符号 lt alt 2管道符 gt Ctrl Shift M 3注释 Ctrl Shift C 4默认颜色 5折叠所有代码 alt o 6展开所有代码 shift alt o 7添加代码块 Ctrl alt i 1赋值符号 l
  • [从零学习汇编语言] - 转移指令进阶

    文章目录 前言 回顾 1 转移指令原理 2 已接触过的操作符 3 寄存器回顾 通用数据处理寄存器 指针寄存器 变址寄存器 段地址寄存器 其他寄存器 一 ret及retf 1 1 ret指令 1 2 retf指令 1 3 小练习 二 Call
  • Clannad【2018四川省赛】【AC自动机 + DP】

    题目链接 第十届四川省赛C题 挺好的一道题 就是要做一个last优化 每次的last要返回到之前的有值节点 也就是单词的尾的对应节点 然后就不会超时了 呜呜呜 之前一直超时 以为是初始化的memset 问题 以前被卡过memset 然后发现
  • 【cdk的使用】C语言 一个仅用200行代码实现的logger系统 使用

    Github地址 https github com wujin1989 cdk 经常有人说 一个好的代码 不需要记录太多的log 确实 我信了 这也导致cdk也是基于这点出发的 所以cdk的logger系统很简单 仅仅200行左右代码 但是
  • [转]新一代 Linux 文件系统 btrfs 简介

    刘 明 ovis poly sina com 软件工程师 上海交通大学电子与通信工程系 2009 年 8 月 20 日 Btrfs 被称为是下一代 Linux 文件系统 近年来 ext2 3 遇到越来越多的扩展性问题 在期待 ext4 的同
  • 网编(20):UDP传输数据经常遇到的问题

    相对于TCP 协议的程序设计 UDP 协议的程序虽然程序设计的环节要少一些 但是由于UDP 协议缺少流量控制等机制 容易出现一些难以解决的问题 UDP 的报文丢失 报文乱序 connect 函数 流量控制 外出网络接口的选择等是比较容易出现
  • 简单谈谈weex、nvue和vue

    1 区别 weex we except 自己的理解 支持原生和vue并写 原生渲染 但是组件库不完善 仅支持编写app nvue嵌入了weex引擎 又有uniapp丰富的组件库 相当于dcloud写的weex升级版 在uniapp中支持使用
  • 把xml转为成javaBean javaBean转为成xml

    1 先定义javaBean package com wutka jox test import com wutka jox import java util public class TestBean implements java io
  • 【满分】【华为OD机试真题2023 JS】计算网络信号

    华为OD机试真题 2023年度机试题库全覆盖 刷题指南点这里 计算网络信号 知识点广搜数组 时间限制 1s 空间限制 256MB 限定语言 不限 题目描述 网络信号经过传递会逐层衰减 且遇到阻隔物无法直接穿透 在此情况下需要计算某个位置的网
  • Selenium成长之路-09简单对象定位之link text方法

    有的时候 我们需要操作的元素是一个文字链接 那么我们可以通过 linktext 或 partiallinktext 进行元素 定位 今天我们先来介绍link text元素定位 百度首页上面的 新闻 hao123 地图 等就可以使用link
  • 用Go语言开发以太坊合约

    转发请注明出处 https blog csdn net ahy231 article details 114112638 序 网上关于 go 语言开发 DApp 的教程较少 因此我只能通过官方文档来系统学习 go 语言的 DApp 开发 这