美文网首页
以太坊竟然升级了,好在Go语言如何调用智能合约变化不大

以太坊竟然升级了,好在Go语言如何调用智能合约变化不大

作者: 柏链教育 | 来源:发表于2019-12-26 17:51 被阅读0次

    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.1
    at 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部署可以看到如下效果:

    image

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

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

    image

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

    image

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

    image

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

    合约案例:每人一个去中心化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 远程调用
    image

    我们都知道以太坊是存储数据的,当然就很容易联想到数据库也是存储数据的,数据库对外提供了多种语言的访问机制,以太坊同样如此。对于以太坊智能合约以及相关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语言调用合约的步骤

    image

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

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

    yekaideMBP:~ yk$ cd $GOPATH/src/github.com/ethereum/go-ethereum/cmd/abigen/
    yekaideMBP:abigen yk$ ls
    main.go
    yekaideMBP:abigen yk$ pwd
    /Users/yk/src/gowork/src/github.com/ethereum/go-ethereum/cmd/abigen
    yekaideMBP:abigen yk$ go build -i
    yekaideMBP:abigen yk$ ls -l
    total 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编译器,在本地编译也可,只不过需要随时关注版本更新的情况,不如在线方便。具体拷贝位置,如下图所示:

    image

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

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

    这样就可以得到person.go的Go源码文件,<mark style="box-sizing: border-box;">注意此文件为自动生成,不要修改!</mark>

    接下来就是调用的问题了,该文件会给我们提供一个入口函数,叫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语言如何调用智能合约变化不大

          本文链接:https://www.haomeiwen.com/subject/hdsloctx.html