智能合约调用是实现一个 DApp 的关键,一个完整的 DApp 包括前端、后端、智能合约及区块 链系统,智能合约的调用是连接区块链与前后端的关键。
创新互联公司专注为客户提供全方位的互联网综合服务,包含不限于做网站、网站设计、双鸭山网络推广、重庆小程序开发、双鸭山网络营销、双鸭山企业策划、双鸭山品牌公关、搜索引擎seo、人物专访、企业宣传片、企业代运营等,从售前售中售后,我们都将竭诚为您服务,您的肯定,是我们最大的嘉奖;创新互联公司为所有大学生创业者提供双鸭山建站搭建服务,24小时服务热线:028-86922220,官方网址:www.cdcxhl.com
我们先来了解一下智能合约调用的基础原理。智能合约运行在以太坊节点的 EVM 中。因此要 想调用合约必须要访问某个节点。
以后端程序为例,后端服务若想连接节点有两种可能,一种是双 方在同一主机,此时后端连接节点可以采用 本地 IPC(Inter-Process Communication,进 程间通信)机制,也可以采用 RPC(Remote Procedure Call,远程过程调用)机制;另 一种情况是双方不在同一台主机,此时只能采用 RPC 机制进行通信。
提到 RPC, 读者应该对 Geth 启动参数有点印象,Geth 启动时可以选择开启 RPC 服务,对应的 默认服务端口是 8545。。
接着,我们来了解一下智能合约运行的过程。
智能合约的运行过程是后端服务连接某节点,将 智能合约的调用(交易)发送给节点,节点在验证了交易的合法性后进行全网广播,被矿工打包到 区块中代表此交易得到确认,至此交易才算完成。
就像数据库一样,每个区块链平台都会提供主流 开发语言的 SDK(Software Development Kit,软件开发工具包),由于 Geth 本身就是用 Go 语言 编写的,因此若想使用 Go 语言连接节点、发交易,直接在工程内导入 go-ethereum(Geth 源码) 包就可以了,剩下的问题就是流程和 API 的事情了。
总结一下,智能合约被调用的两个关键点是节点和 SDK。
由于 IPC 要求后端与节点必须在同一主机,所以很多时候开发者都会采用 RPC 模式。除了 RPC,以太坊也为开发者提供了 json- rpc 接口,本文就不展开讨论了。
接下来介绍如何使用 Go 语言,借助 go-ethereum 源码库来实现智能合约的调用。这是有固定 步骤的,我们先来说一下总体步骤,以下面的合约为例。
步骤 01:编译合约,获取合约 ABI(Application Binary Interface,应用二进制接口)。 单击【ABI】按钮拷贝合约 ABI 信息,将其粘贴到文件 calldemo.abi 中(可使用 Go 语言IDE 创建该文件,文件名可自定义,后缀最好使用 abi)。
最好能将 calldemo.abi 单独保存在一个目录下,输入“ls”命令只能看到 calldemo.abi 文件,参 考效果如下:
步骤 02:获得合约地址。注意要将合约部署到 Geth 节点。因此 Environment 选择为 Web3 Provider。
在【Environment】选项框中选择“Web3 Provider”,然后单击【Deploy】按钮。
部署后,获得合约地址为:0xa09209c28AEf59a4653b905792a9a910E78E7407。
步骤 03:利用 abigen 工具(Geth 工具包内的可执行程序)编译智能合约为 Go 代码。abigen 工具的作用是将 abi 文件转换为 Go 代码,命令如下:
其中各参数的含义如下。 (1)abi:是指定传入的 abi 文件。 (2)type:是指定输出文件中的基本结构类型。 (3)pkg:指定输出文件 package 名称。 (4)out:指定输出文件名。 执行后,将在代码目录下看到 funcdemo.go 文件,读者可以打开该文件欣赏一下,注意不要修改它。
步骤 04:创建 main.go,填入如下代码。 注意代码中 HexToAddress 函数内要传入该合约部署后的地址,此地址在步骤 01 中获得。
步骤 04:设置 go mod,以便工程自动识别。
前面有所提及,若要使用 Go 语言调用智能合约,需要下载 go-ethereum 工程,可以使用下面 的指令:
该指令会自动将 go-ethereum 下载到“$GOPATH/src/github点抗 /ethereum/go-ethereum”,这样还算 不错。不过,Go 语言自 1.11 版本后,增加了 module 管理工程的模式。只要设置好了 go mod,下载 依赖工程的事情就不必关心了。
接下来设置 module 生效和 GOPROXY,命令如下:
在项目工程内,执行初始化,calldemo 可以自定义名称。
步骤 05:运行代码。执行代码,将看到下面的效果,以及最终输出的 2020。
上述输出信息中,可以看到 Go 语言会自动下载依赖文件,这就是 go mod 的神奇之处。看到 2020,相信读者也知道运行结果是正确的了。
RLP(Recursive Length Prefix),中文翻译过来叫递归长度前缀编码,它是以太坊序列化所采用的编码方式。RLP主要用于以太坊中数据的网络传输和持久化存储。
对象序列化方法有很多种,常见的像JSON编码,但是JSON有个明显的缺点:编码结果比较大。例如有如下的结构:
变量s序列化的结果是{"name":"icattlecoder","sex":"male"},字符串长度35,实际有效数据是icattlecoder 和male,共计16个字节,我们可以看到JSON的序列化时引入了太多的冗余信息。假设以太坊采用JSON来序列化,那么本来50GB的区块链可能现在就要100GB,当然实际没这么简单。
所以,以太坊需要设计一种结果更小的编码方法。
RLP编码的定义只处理两类数据:一类是字符串(例如字节数组),一类是列表。字符串指的是一串二进制数据,列表是一个嵌套递归的结构,里面可以包含字符串和列表,例如["cat",["puppy","cow"],"horse",[[]],"pig",[""],"sheep"]就是一个复杂的列表。其他类型的数据需要转成以上的两类,转换的规则不是RLP编码定义的,可以根据自己的规则转换,例如struct可以转成列表,int可以转成二进制(属于字符串一类),以太坊中整数都以大端形式存储。
从RLP编码的名字可以看出它的特点:一个是递归,被编码的数据是递归的结构,编码算法也是递归进行处理的;二是长度前缀,也就是RLP编码都带有一个前缀,这个前缀是跟被编码数据的长度相关的,从下面的编码规则中可以看出这一点。
对于值在[0, 127]之间的单个字节,其编码是其本身。
例1:a的编码是97。
如果byte数组长度l = 55,编码的结果是数组本身,再加上128+l作为前缀。
例2:空字符串编码是128,即128 = 128 + 0。
例3:abc编码结果是131 97 98 99,其中131=128+len("abc"),97 98 99依次是a b c。
如果数组长度大于55, 编码结果第一个是183加数组长度的编码的长度,然后是数组长度的本身的编码,最后是byte数组的编码。
请把上面的规则多读几篇,特别是数组长度的编码的长度。
例4:编码下面这段字符串:
The length of this sentence is more than 55 bytes, I know it because I pre-designed it
这段字符串共86个字节,而86的编码只需要一个字节,那就是它自己,因此,编码的结果如下:
184 86 84 104 101 32 108 101 110 103 116 104 32 111 102 32 116 104 105 115 32 115 101 110 116 101 110 99 101 32 105 115 32 109 111 114 101 32 116 104 97 110 32 53 53 32 98 121 116 101 115 44 32 73 32 107 110 111 119 32 105 116 32 98 101 99 97 117 115 101 32 73 32 112 114 101 45 100 101 115 105 103 110 101 100 32 105 116
其中前三个字节的计算方式如下:
184 = 183 + 1,因为数组长度86编码后仅占用一个字节。
86即数组长度86
84是T的编码
例5:编码一个重复1024次"a"的字符串,其结果为:185 4 0 97 97 97 97 97 97 ...。
1024按 big endian编码为0 0 4 0,省略掉前面的零,长度为2,因此185 = 183 + 2。
规则1~3定义了byte数组的编码方案,下面介绍列表的编码规则。在此之前,我们先定义列表长度是指子列表编码后的长度之和。
如果列表长度小于55,编码结果第一位是192加列表长度的编码的长度,然后依次连接各子列表的编码。
注意规则4本身是递归定义的。
例6:["abc", "def"]的编码结果是200 131 97 98 99 131 100 101 102。
其中abc的编码为131 97 98 99,def的编码为131 100 101 102。两个子字符串的编码后总长度是8,因此编码结果第一位计算得出:192 + 8 = 200。
如果列表长度超过55,编码结果第一位是247加列表长度的编码长度,然后是列表长度本身的编码,最后依次连接各子列表的编码。
规则5本身也是递归定义的,和规则3相似。
例7:
["The length of this sentence is more than 55 bytes, ", "I know it because I pre-designed it"]
的编码结果是:
248 88 179 84 104 101 32 108 101 110 103 116 104 32 111 102 32 116 104 105 115 32 115 101 110 116 101 110 99 101 32 105 115 32 109 111 114 101 32 116 104 97 110 32 53 53 32 98 121 116 101 115 44 32 163 73 32 107 110 111 119 32 105 116 32 98 101 99 97 117 115 101 32 73 32 112 114 101 45 100 101 115 105 103 110 101 100 32 105 116
其中前两个字节的计算方式如下:
248 = 247 +1
88 = 86 + 2,在规则3的示例中,长度为86,而在此例中,由于有两个子字符串,每个子字符串本身的长度的编码各占1字节,因此总共占2字节。
第3个字节179依据规则2得出179 = 128 + 51
第55个字节163同样依据规则2得出163 = 128 + 35
例8:最后我们再来看个稍复杂点的例子以加深理解递归长度前缀,
["abc",["The length of this sentence is more than 55 bytes, ", "I know it because I pre-designed it"]]
编码结果是:
248 94 131 97 98 99 248 88 179 84 104 101 32 108 101 110 103 116 104 32 111 102 32 116 104 105 115 32 115 101 110 116 101 110 99 101 32 105 115 32 109 111 114 101 32 116 104 97 110 32 53 53 32 98 121 116 101 115 44 32 163 73 32 107 110 111 119 32 105 116 32 98 101 99 97 117 115 101 32 73 32 112 114 101 45 100 101 115 105 103 110 101 100 32 105 116
列表第一项字符串abc根据规则2,编码结果为131 97 98 99,长度为4。
列表第二项也是一个列表项:
["The length of this sentence is more than 55 bytes, ", "I know it because I pre-designed it"]
根据规则5,结果为
248 88 179 84 104 101 32 108 101 110 103 116 104 32 111 102 32 116 104 105 115 32 115 101 110 116 101 110 99 101 32 105 115 32 109 111 114 101 32 116 104 97 110 32 53 53 32 98 121 116 101 115 44 32 163 73 32 107 110 111 119 32 105 116 32 98 101 99 97 117 115 101 32 73 32 112 114 101 45 100 101 115 105 103 110 101 100 32 105 116
长度为90,因此,整个列表的编码结果第二位是90 + 4 = 94, 占用1个字节,第一位247 + 1 = 248
以上5条就是RPL的全部编码规则。
各语言在具体实现RLP编码时,首先需要将对像映射成byte数组或列表两种形式。以go语言编码struct为例,会将其映射为列表,例如Student这个对象处理成列表["icattlecoder","male"]
如果编码map类型,可以采用以下列表形式:
[["",""],["",""],["",""]]
解码时,首先根据编码结果第一个字节f的大小,执行以下的规则判断:
1. 如果f∈ [0,128), 那么它是一个字节本身。
2. 如果f∈[128,184),那么它是一个长度不超过55的byte数组,数组的长度为 l=f-128
3. 如果f∈[184,192),那么它是一个长度超过55的数组,长度本身的编码长度ll=f-183,然后从第二个字节开始读取长度为ll的bytes,按照BigEndian编码成整数l,l即为数组的长度。
4. 如果f∈(192,247],那么它是一个编码后总长度不超过55的列表,列表长度为l=f-192。递归使用规则1~4进行解码。
5. 如果f∈(247,256],那么它是编码后长度大于55的列表,其长度本身的编码长度ll=f-247,然后从第二个字节读取长度为ll的bytes,按BigEndian编码成整数l,l即为子列表长度。然后递归根据解码规则进行解码。
以上解释了什么叫递归长度前缀编码,这个名字本身很好的解释了编码规则。
(1) 以太坊源码学习—RLP编码( )
(2)简单分析RLP编码原理
( )
在这个章节中我们会介绍如何用Go来编译,部署,写入和读取智能合约。
与智能合约交互,我们要先生成相应智能合约的应用二进制接口ABI(application binary interface),并把ABI编译成我们可以在Go应用中调用的格式。
第一步是安装 Solidity编译器 ( solc ).
Solc 在Ubuntu上有snapcraft包。
Solc在macOS上有Homebrew的包。
其他的平台或者从源码编译的教程请查阅官方solidity文档 install guide .
我们还得安装一个叫 abigen 的工具,来从solidity智能合约生成ABI。
假设您已经在计算机上设置了Go,只需运行以下命令即可安装 abigen 工具。
我们将创建一个简单的智能合约来测试。 学习更复杂的智能合约,或者智能合约的开发的内容则超出了本书的范围。 我强烈建议您查看 truffle framework 来学习开发和测试智能合约。
这里只是一个简单的合约,就是一个键/值存储,只有一个外部方法来设置任何人的键/值对。 我们还在设置值后添加了要发出的事件。
虽然这个智能合约很简单,但它将适用于这个例子。
现在我们可以从一个solidity文件生成ABI。
它会将其写入名为“Store_sol_Store.abi”的文件中
现在让我们用 abigen 将ABI转换为我们可以导入的Go文件。 这个新文件将包含我们可以用来与Go应用程序中的智能合约进行交互的所有可用方法。
为了从Go部署智能合约,我们还需要将solidity智能合约编译为EVM字节码。 EVM字节码将在事务的数据字段中发送。 在Go文件上生成部署方法需要bin文件。
现在我们编译Go合约文件,其中包括deploy方法,因为我们包含了bin文件。
在接下来的课程中,我们将学习如何部署智能合约,然后与之交互。
Commands
Store.sol
solc version used for these examples
如果你还没看之前的章节,请先学习 编译智能合约的章节 因为这节内容,需要先了解如何将智能合约编译为Go文件。
假设你已经导入从 abigen 生成的新创建的Go包文件,并设置ethclient,加载您的私钥,下一步是创建一个有配置密匙的交易发送器(tansactor)。 首先从go-ethereum导入 accounts/abi/bind 包,然后调用传入私钥的 NewKeyedTransactor 。 然后设置通常的属性,如nonce,燃气价格,燃气上线限制和ETH值。
如果你还记得上个章节的内容, 我们创建了一个非常简单的“Store”合约,用于设置和存储键/值对。 生成的Go合约文件提供了部署方法。 部署方法名称始终以单词 Deploy 开头,后跟合约名称,在本例中为 Store 。
deploy函数接受有密匙的事务处理器,ethclient,以及智能合约构造函数可能接受的任何输入参数。我们测试的智能合约接受一个版本号的字符串参数。 此函数将返回新部署的合约地址,事务对象,我们可以交互的合约实例,还有错误(如果有)。
就这么简单:)你可以用事务哈希来在Etherscan上查询合约的部署状态:
Commands
Store.sol
contract_deploy.go
solc version used for these examples
这写章节需要了解如何将智能合约的ABI编译成Go的合约文件。如果你还没看, 前先读 上一个章节 。
一旦使用 abigen 工具将智能合约的ABI编译为Go包,下一步就是调用“New”方法,其格式为“Newcontractname style="box-sizing: border-box; font-size: 16px; -ms-text-size-adjust: auto; -webkit-tap-highlight-color: transparent;"”,所以在我们的例子中如果你 回想一下它将是 NewStore 。 此初始化方法接收智能合约的地址,并返回可以开始与之交互的合约实例。/contractname
Commands
Store.sol
contract_load.go
solc version used for these examples
这写章节需要了解如何将智能合约的ABI编译成Go的合约文件。如果你还没看, 前先读 上一个章节 。
在上个章节我们学习了如何在Go应用程序中初始化合约实例。 现在我们将使用新合约实例提供的方法来阅读智能合约。 如果你还记得我们在部署过程中设置的合约中有一个名为 version 的全局变量。 因为它是公开的,这意味着它们将成为我们自动创建的getter函数。 常量和view函数也接受 bind.CallOpts 作为第一个参数。了解可用的具体选项要看相应类的 文档 一般情况下我们可以用 nil 。
Commands
Store.sol
contract_read.go
solc version used for these examples
这写章节需要了解如何将智能合约的ABI编译成Go的合约文件。如果你还没看, 前先读 上一个章节 。
写入智能合约需要我们用私钥来对交易事务进行签名。
我们还需要先查到nonce和燃气价格。
接下来,我们创建一个新的keyed transactor,它接收私钥。
然后我们需要设置keyed transactor的标准交易选项。
现在我们加载一个智能合约的实例。如果你还记得 上个章节 我们创建一个名为 Store 的合约,并使用 abigen 工具生成一个Go文件。 要初始化它,我们只需调用合约包的 New 方法,并提供智能合约地址和ethclient,它返回我们可以使用的合约实例。
我们创建的智能合约有一个名为 SetItem 的外部方法,它接受solidity“bytes32”格式的两个参数(key,value)。 这意味着Go合约包要求我们传递一个长度为32个字节的字节数组。 调用 SetItem 方法需要我们传递我们之前创建的 auth 对象(keyed transactor)。 在幕后,此方法将使用它的参数对此函数调用进行编码,将其设置为事务的 data 属性,并使用私钥对其进行签名。 结果将是一个已签名的事务对象。
现在我就可以看到交易已经成功被发送到了以太坊网络了:
要验证键/值是否已设置,我们可以读取智能合约中的值。
搞定!
Commands
Store.sol
contract_write.go
solc version used for these examples
有时您需要读取已部署的智能合约的字节码。 由于所有智能合约字节码都存在于区块链中,因此我们可以轻松获取它。
首先设置客户端和要读取的字节码的智能合约地址。
现在你需要调用客户端的 codeAt 方法。 codeAt 方法接受智能合约地址和可选的块编号,并以字节格式返回字节码。
你也可以在etherscan上查询16进制格式的字节码
contract_bytecode.go
首先创建一个ERC20智能合约interface。 这只是与您可以调用的函数的函数定义的契约。
然后将interface智能合约编译为JSON ABI,并使用 abigen 从ABI创建Go包。
假设我们已经像往常一样设置了以太坊客户端,我们现在可以将新的 token 包导入我们的应用程序并实例化它。这个例子里我们用 Golem 代币的地址.
我们现在可以调用任何ERC20的方法。 例如,我们可以查询用户的代币余额。
我们还可以读ERC20智能合约的公共变量。
我们可以做一些简单的数学运算将余额转换为可读的十进制格式。
同样的信息也可以在etherscan上查询:
Commands
erc20.sol
contract_read_erc20.go
solc version used for these examples
Fabric区块链开发详解,本课程面向初学者,内容即包含Hyperledger Fabric的身份证书与MSP服务、权限策略、通道配置与启动、链码通信接口等核心概念,也包含Fabric网络设计、链码与应用开发的操作实践,是学习Fabric区块链开发的最佳选择。
给你个免费区块链博客看看。