张家口以太坊智能合约开发实战pdf_以太坊竟然升级了,好在Go语言如何调用智能合约变化不大!...

2009年,比特币诞生,距离现在已经过去10年了。区块链技术也从一开始的不为人知,到如今的名扬天下。使用区块链技术,可以制造无须任何国家背书的金融产品,让区块链技术为世人所认知。作为继承和改造的产品以太坊,它推出的全球化编程技术又极大的推动了区块链技术的发展。

以太坊作为改进者,它增加了智能合约的功能,全球爱好者都可以编写自己的智能合约,在全球的以太坊网络中运行,它有一个美好的梦想就是让全球都实行契约化。那么到底什么是智能合约呢?

什么是智能合约?

具体什么是智能合约,我们还是先看一下以太坊智能合约的英文:smart contract,可以分为两部分,智能于合约。对于合约就很好理解了,合约就是合同,事先约定好的规则以及事情发生后双方要付出和接受的代价等。智能的含义就是这个合约(合同)是可以自动被触发和执行的,不用担心违约和赖账的问题,任何内容都写在合同中,一目了然。这样带来的好处就是双方即使没有彼此建立信任也可以完成交易。

智能合约该如何编写呢?

开发以太坊智能合约门槛并不高,目前互联网也有大量的学习资源,只不过自己筛选需要一些过程。那么如开发智能合约呢?从目前的区块链技术发展来看,可以用solidity,python,golang,c++,c#等语言开发智能合约,但从最初的智能合约产生来说,solidity还是更原汁原味一些,solidity其实就是一门语言,把它当成一门新的语言学习就好了。学习一门新的语言的过程无外乎是下面这样的步骤:

·环境安装,运行环境,IDE等

·基础语法

·小案例学习

·项目实战

对于solidity的学习也会包含这些内容,但毕竟区块链是特殊的技术,智能合约的学习也会比其他内容多一些特殊性,比如你要了解区块链的底层原理,你要了解智能合约的运行原理,还有由于智能合约离钱太近了,你还要学习如何编写安全的智能合约,最后就是要吃透一些智能合约讨论出来的标准,比如毁誉参半的ERC20,ERC721等等。

以太坊开发环境安装

智能合约编辑环境 -- remix

remix其实就是一个集成在网页中的在线编辑环境,不过对网络有一定的要求,相对来说更新的更及时一些,如果在线环境不能使用,可以使用本地环境安装,在后面的geth安装示例中有介绍,不过推荐使用在线环境,本地会经常有问题。remix在线环境:http://remix.ethereum.org

以太坊客户端安装 -- geth

在这里主要就是geth的安装,针对不同平台都可以安装,推荐用类unix系统。具体安装可以参考

也有一些人比较喜欢使用ganache作为以太坊客户端的学习工具,相对来说图形化比命令行好接受一些。

geth 私链搭建

geth是以太坊提供的客户端软件,用它你可以加入到以太坊主网中,也可以自己搭建私链,对于学习者来说,一般都是自己搭建私链。

·创世块配置- genesis.json

{  "config": {        "chainId": 18,        "homesteadBlock": 0,        "eip155Block": 0,        "eip158Block": 0    },  "alloc"      : {},  "coinbase"   : "0x0000000000000000000000000000000000000000",  "difficulty" : "0x2",  "extraData"  : "",  "gasLimit"   : "0xffffffff",  "nonce"      : "0x0000000000000042",  "mixhash"    : "0x0000000000000000000000000000000000000000000000000000000000000000",  "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",  "timestamp"  : "0x00"}

·创世块参数

  。chainId 私有网络ID

  。difficulty 挖矿难度,越低挖矿速度越快

  。coinbase 默认的挖矿账户,如果不设定,则使用默认第一个账户

  。gasLimit gas消耗限制,如果过小可能会影响合约发布和运行,建议设为:0xffffffff

·初始化

geth init genesis.json --datadir ./data

如果是首次启动,或者genesi.json文件发生变,那么必须初始化,data 是存放数据的目录,可以不存在,建议找一个独立目录。

·启动

geth --datadir ./data --networkid 18 --port 30303 --rpc  --rpcport 8545 --rpcapi 'db,net,eth,web3,personal' --rpccorsdomain '*' --gasprice 0  console 2> 1.log

参数介绍

  。--datadir 指定数据路径

  。--networkid 指定私链ID,与配置文件chainId一样

  。--port p2p端口,节点之间通信采用的端口

  。--rpc 所有rpc相关都是代表远程调用的参数设计,可以设定ip,端口(此端口为对外提供http服务端口),api等

  。--gasprice 设定gas的价格,如果gas为0则不消耗以太币

简易教程

可以下载本人github工程,直接用脚本启动即可,三步就够了

$git clone https://github.com/yekai1003/rungeth$cd rungeth$geth init genesis.json --datadir ./data $./rungeth.sh

geth启动后,我们会进入到一个管理台当中,在其中可以创建账户,解锁账户,挖矿,查询余额等等操作。

geth命令行客户端操作

geth启动后,进入管理台,会看到如下的信息:

ykdeMac-mini:eth yekai$ ./rungeth.sh localhost:rungeth yekai$ ./rungeth.sh Welcome to the Geth JavaScript console!instance: Geth/v1.9.6-stable/darwin-amd64/go1.13.1at block: 0 (Thu, 01 Jan 1970 08:00:00 CST) datadir: /Users/yekai/eth/rungeth/data modules: admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0> 

·创建账户

注意123是密码,返回的字符串才是以太坊的账户地址
> eth.accounts[]> personal.newAccount("123")"0xdc78ec60eba4eb3bf8a497d94223615d43352c7e"> eth.accounts["0xdc78ec60eba4eb3bf8a497d94223615d43352c7e"]

eth.accounts 是查看当前有哪些账户。

  • 余额查看

> acc0=eth.accounts[0]"0xdc78ec60eba4eb3bf8a497d94223615d43352c7e"> eth.getBalance(acc0)0
  • 挖矿

一开始账户肯定没有钱,需要挖一挖,才能有钱。

> miner.start(1)null> eth.getBalance(acc0)0> eth.getBalance(acc0)0> eth.getBalance(acc0)0> eth.getBalance(acc0)0> eth.getBalance(acc0)20000000000000000000> miner.stop()null

miner.start(1) 启动挖矿,其中的1可以不写,代表的是实际挖矿的线程数量。

  • 解锁账户

账户有钱后就可以挥霍了,但是在remix环境使用之时,必须先要解锁。

> personal.unlockAccount(acc0,"123")true

如果解锁命令失败,则需要在geth启动的时候添加参数,这也是以太坊新版客户端的一个更新。

--allow-insecure-unlock
智能合约开发

先来看一个简单的例子,编写一个最简单的智能合约:

pragma solidity^0.5.11;contract Person {    string public name ;    function setName(string memory _name) public {        name = _name;    }}

第一句是交代编译器版本,要使用大于等于0.5.11的编译器对本合约进行编译,编译器版本不能超过0.5.的范围。

  • contract Person是定义合约的名字

  • setName是一个公共函数,任何人可以调用,目的是修改内部成员name

  • name与_name是内部成员和形参的区别,在以太坊中却非常重要,内部成员name是要存储在全球计算机上,需要消耗gas,_name是临时存储,不会消耗gas,当然任何指令的执行都需要消耗gas。

在remix部署可以看到如下效果:

1e5411865219dafab4a3dcb7c6e3923c.png

我们看到一个黄颜色的setName函数和一个蓝颜色的name函数,黄颜色的每次调用都要收取gas,蓝颜色的则不需要提供gas。可能也有人奇怪name函数是怎么出来的,只要我们把变量声明为public,它就会自动生成。

我们可以先调用一下name看看效果,结果什么都没有。

174228d00c25e123c358c640c946953c.png

我们用setName来修改一下名字:

27a9e625b953af16c8c598ece8173eb7.png

之后我们再来点击name,就可以看到效果了。

0f9517dce7953c62aaa04dcd4ada13c3.png

以上就是一个最简单的智能合约运行,怎么样,感觉操作复杂吗?

合约案例:每人一个去中心化ID
pragma solidity^0.5.11;pragma experimental ABIEncoderV2; //支持结构体变量contract PersonDID {        address owner;    struct Person {        bytes32 DID;        string name;        string sex;        uint256 age;        bool isExists;    }        //内部一个自动增长的ID    uint256 autoID;        //地址==>人,每个地址一个人    mapping(address=>Person)  persons;        //构造函数,constructor是关键字    constructor() public {        autoID = 1;        owner = msg.sender;    }        //将个人与ID绑定    function bindID(string memory _name, string memory _sex, uint256 _age) public {        //验证该人员是否已经绑定过了        require(!persons[msg.sender].isExists,"caller must not exists");        //assert(!persons[msg.sender].isExists,"caller must not exists");        //利用hash函数计算一个DID的值        bytes32 DID = keccak256(abi.encode(now, autoID));        Person memory p =  Person(DID, _name, _sex, _age, true);        persons[msg.sender] = p;        autoID ++;    }    //验证该DID是否属于调用者    function verifyPeron(bytes32 _DID) public view returns (Person memory) {        Person storage p = persons[msg.sender];        require(_DID == p.DID );//调用者的DID与传入的DID相等        return p;    }        //获取DID    function getDID() public view returns (bytes32) {        return persons[msg.sender].DID;    }}

智能合约如何调用

首先要明确,智能合约是运行在以太坊上的智能合同,所以要调用智能合约必然要和以太坊打交道。至于调用的方式,可以采用两种:rpc和ipc。

  • ipc 本地套接字调用

  • rpc 远程调用

a1d09951ddd0f692390d33c5750aa8e3.png

我们都知道以太坊是存储数据的,当然就很容易联想到数据库也是存储数据的,数据库对外提供了多种语言的访问机制,以太坊同样如此。对于以太坊智能合约以及相关api的调用,可以使用java,js,python,golang等多种语言,只要相应研究对应的sdk即可。

接下来我们主要介绍Go语言如何调用智能合约。

首先需要下载go版本的以太坊源码

$ cd $GOPATH/src$ mkdir -p github.com/ethereum$ cd github.com/ethereum/$ git clone https://github.com/ethereum/go-ethereum.git

引用源码的rpc库

import "github.com/ethereum/go-ethereum/rpc"

创建一个账户

func NewAcct(pass string) {    //获得与geth的连接  cli, err := rpc.Dial("http://localhost:8545")  if err != nil {    log.Fatal("connet to geth error:", err)  }  //函数结束自动关闭连接  defer cli.Close()  var account string  //调用call方法,call可以调用personal.newAccount  err = cli.Call(&account, "personal_newAccount", pass)  if err != nil {    log.Fatal("call personal_newAccount error:", err)  }  fmt.Println("account=", account)}
Go语言调用合约的步骤

77d67716f233d23883985a9ae2c61059.png

将源码文件获得abi,之后用abi编译成对应的Go代码,然后其实也就没有然后了,Go代码都给你了,调用就可以了,不过有些细节我们还是要注意的。

如果使用命令行安装的方式abigen程序会伴随安装,如果没有则需要借助源码安装的方式,下面的步骤可以作为参考。

yekaideMBP:~ yk$ cd $GOPATH/src/github.com/ethereum/go-ethereum/cmd/abigen/yekaideMBP:abigen yk$ lsmain.goyekaideMBP:abigen yk$ pwd/Users/yk/src/gowork/src/github.com/ethereum/go-ethereum/cmd/abigenyekaideMBP:abigen yk$ go build -iyekaideMBP:abigen yk$ ls -ltotal 20904-rwxr-xr-x  1 yk  staff  10694032  7 29 00:02 abigen-rw-r--r--  1 yk  staff      5006  7 21 20:45 main.go

使用abigen将abi翻译成go(最好先将abigen拷贝到$PATH的某个路径下)

abigen --abi xx.abi --pkg pkgname --type apiname --out xx.go

abigen参数说明:

  • abi 文件在 remix 部署时可以得到

  • Pkg 指定的是编译成的 go 文件对应的 package 名称

  • type指定的是go文件的入口函数,可以认为是类名

  • out 指定输出go文件名称

接下来,我们我们之前编写的合约代码来转换为Go代码,然后加以调用。首先先要得到abi文件,这个可以在remix环境直接拷贝得到,其实如果安装了solc编译器,在本地编译也可,只不过需要随时关注版本更新的情况,不如在线方便。具体拷贝位置,如下图所示:

d842b179190f68ed9a41bf232e72bc20.png

将abi文件拷贝到我们的代码目录,然后保存为person.abi文件,执行下面的命令:

abigen -abi person.abi -out person.go -type person -pkg main

这样就可以得到person.go的Go源码文件,注意此文件为自动生成,不要修改!

接下来就是调用的问题了,该文件会给我们提供一个入口函数,叫NewPerson。

// NewPerson creates a new instance of Person, bound to a specific deployed contract.func NewPerson(address common.Address, backend bind.ContractBackend) (*Person, error) {  contract, err := bindPerson(address, backend, backend, backend)  if err != nil {    return nil, err  }  return &Person{PersonCaller: PersonCaller{contract: contract}, PersonTransactor: PersonTransactor{contract: contract}, PersonFilterer: PersonFilterer{contract: contract}}, nil}

这个函数的返回结果就是我们合约实例对象,通过合约实例对象,就可以调用合约内的方法了,关键是传递参数。

身份问题

因为合约函数在调用的时候需要确认身份,接下来,我们介绍如何签名,这个需要对钱包技术有一些了解,简单来说就是借助keystore+密码一起来构建身份。

//设置签名func MakeAuth(addr, pass string) (*bind.TransactOpts, error) {  keystorePath := "/Users/yekai/eth/rungeth/data/keystore"  fileName, err := GetFileName(string([]rune(addr)[2:]), keystorePath)  if err != nil {    fmt.Println("failed to GetFileName", err)    return nil, err  }  file, err := os.Open(keystorePath + "/" + fileName)  if err != nil {    fmt.Println("failed to open file ", err)    return nil, err  }  auth, err := bind.NewTransactor(file, pass)  if err != nil {    fmt.Println("failed to NewTransactor  ", err)    return nil, err  }  auth.GasLimit = 300000  return auth, err}

核心思路就是找到geth运行后产生的keystore文件,读取后用密码进行解析得到私钥,此后就可以创作成数字身份,进行签名交易。

签名搞定后,就可以考虑调用合约函数的问题了。步骤如下:

  1. 连接到geth节点

  2. 通过合约地址构造合约实例

  3. 设置数字身份

  4. 通过身份,合约实例调用合约具体函数,注意各个参数传递

代码如下:

//1. 连接到节点  cli, err := ethclient.Dial("http://localhost:8545")  if err != nil {    fmt.Println("Failed to Dial ", err)    return  }  //2. 构造合约实例,注意传入合约地址  ins, err := NewPerson(common.HexToAddress("0x5ff2D46A84c4ABB525beF98DFd71C1Dc1060Fb8c"), cli)  if err != nil {    fmt.Println("Failed to NewPerson", err)  }  //3. 用账户地址构建签名,传入密码  auth, err := MakeAuth("0xbd72018032e5f0b1a19943f5e7d6225e0ab99fbd", "123")  if err != nil {    fmt.Println("failed to MakeAuth")    return  }  //4. 调用合约函数  tx, err := ins.BindID(auth, "wangyuyan", "woman", big.NewInt(35))  if err != nil {    fmt.Println("Failed to BindID", err)    return  }  //打印交易hash结果,注意此交易需要矿工确认后才会生效,所以不会立即在网络中体现  fmt.Println(tx.Hash().Hex())

如果不需要消耗gas,但还需要账户的情况下,这个时候就需要构造CallOpts

type CallOpts struct {  Pending     bool              From        common.Address    BlockNumber *big.Int          Context     context.Context }

此时不需要密码和keystore文件了,这样构造即可。

opt := &bind.CallOpts{    false,    common.HexToAddress("0xbd72018032e5f0b1a19943f5e7d6225e0ab99fbd"),    nil,    context.Background(),  }

之后仍然是调用合约函数,示例如下:

//4. 调用合约函数  hash, err := ins.GetDID(opt)  if err != nil {    fmt.Println("Failed to GetDID", err)    return  }  data, err := ins.VerifyPeron(opt, hash)  fmt.Printf("%s , %s, %s, %d\n", common.ToHex(data.DID[:]), data.Name, data.Sex, data.Age)

总结一下,本文主要介绍了以太坊节点的搭建,命令行操作账户,包括创建,解锁,挖矿等等,之后又介绍了智能合约如何编写,如何部署,如何测试,以及如何通过Go语言调用智能合约,如何构造签名等知识点,希望对学习者有所帮助。

吐槽一下:以太坊升级对于开发者影响还是挺大的,比如这次升级就让编写本文的我有些无力吐槽,首先remix环境出现一些问题,调用智能合约会失败,当然Go语言调用时也出现了问题,好在经过摸索,发现变化并不大,还好还好!

结尾福利:想学习Go语言或区块链的小伙伴可以联系张老师开通免费课程!

   6bee289f3fdab133fa62fadd6b6f7edd.png

看回放可以关注公众号,并点击“公开课”,即可前往柏链官网观看回放!

f805037c9b6e6cd47501436079df418c.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值