美文网首页
2 Solidity概述与环境搭建

2 Solidity概述与环境搭建

作者: 智能合约大师兄 | 来源:发表于2019-11-18 14:05 被阅读0次

    2.1技术架构概述

    尽管本书主要关注的是在以太坊平台上用solidity进行智能合约的开发和部署,但如果不能对整个区块链技术驱动的项目有个全面的了解和把控,不说会导致项目失败但起码也是不能有效合理的利用区块链技术带来的各种好处。对整个基于区块链技术驱动的项目从大的技术架构上进行考虑,再到开发阶段的技术路线选择以及最终的产品表现形式的把控是一个综合的考虑,也是团队和项目经理综合能力的最好体现。虽非技术细节但处处体现各阶段技术选择的权衡以及项目整体的宏观把握。因此本章先概述下区块链技术驱动的项目技术架构以及最终产品表现形式,孰优孰劣需要项目经理根据实际情况进行权衡选择。

    区块链技术具有很大的技术优势和潜力,如果能将业务场景和流程进行区块链化改造,特别是涉及多部门和地区协作的金融类项目,能大大简化工作流程提高业务处理效率。但整个区块链相关技术栈还是比较新的领域,其配套生态和工具链还有待进一步完善和加强。因此如何根据具体业务目标和场景选择合适的技术体系架构是任何一个项目启动首先要考虑的问题。

    区块链技术驱动的项目从技术体系上大致分为混合技术架构和纯区块链技术驱动架构。前者重复利用团队现有的技术积累如重复利用各种开发框架和关系数据库,并将关键模块进行区块链化改造。好处就是能快速进行项目落地相关的技术坑要少得多,需要仔细衡量的是那些功能和模块用现有技术实现,那些功能模块需要进行区块链化改造。但缺点也显而易见需要一个传统的中心化后台服务进行业务支撑,一旦后台服务出问题整个业务流程会被中断,没有完全体现出区块链技术带来的好处和优势,其产品技术架构如下:


    image.png

    这种技术架构能够较好的利用现有的技术投资和积累,同时叠加区块链技术带来的好处,在进行业务逐步升级和改造中得到了越来越多的青睐,是将传统业务进行区块链化改造的性价比比较高的技术方案。现在常见的一些手机钱包软件以及各大主流虚拟币交易所等大多采用了这种混合技术架构,传统业务也是多采用这种技术架构。

    与此相对另外一种基于区块链技术的产品技术架构就比较激进了,其去掉了传统的后台支撑服务并完全由区块链技术进行驱动,多见于一些新型而小巧的项目并且项目目标相对比较单一和明确。这种技术架构的好处就是整个系统架构简单可靠零宕机可能,后台完成智能合约相关功能的开发和部署后,前端只需要进行界面配合而不需要借助传统的后台支撑服务。目前越来越多的小而美的应用采用这种方式,同时配合浏览器钱包插件metamask前端人员能很快的过渡到区块链领域的项目开发,加速区块链生态的成熟和发展。国外的一些保险、博彩以及小游戏等包括国内的一些区块链游戏基本采用这种模式,后台完成智能合约开发和部署后前端采用web页面进行交互,需要和以太坊网络进行交互时调用metamask功能和和接口完成付款和合约交互功能。

    另外一种比较常见的纯区块链驱动方式其后台也是纯智能合约,然后通过以太坊兼容的手机app如PandaX和trust钱包之类进行界面交互,底层和区块链网络的交互利用web3j(web3库的java实现)或类似的底层库进行付款转账和合约交互,在移动化已经成为主流的当下也是有不少吸引力的。因为原生app开发能充分利用硬件性能提高用户体验而且能针对业务场景进行深度定制,也能和后台区块链网络进行交互,在游戏、社会调查等区块链应用领域会大有可为。

    还有一种相对少见的方式是利用reactive 等混合开发模式打包为app如imtoken之类,界面还是通过web方式进行展示和交互,底层利用web3.js库和以太坊网络进行交互,特殊的功能模块js不能覆盖的采用原生开发进行补充,好处是能快速出东西缺点是受限于reactive的功能封装以及js的性能,做些简单的页面和交互问题不大,深度定制比较考验团队技术实力。

    另外一些不常见的使用方式如搭建nodejs环境通过web3.js和区块链进行原生交互多见于早起开发和测试阶段,面向终端用户因为环境搭建麻烦以及账户管理混乱基本被废弃了,现在开发也多用第三方框架和工具比较少直接使用该库,读者可以暂时忽略。

    在PC上除了利用metamask和区块链进行交互外(多见于面向最终客户的终端web产品),还有两种常见的应用模式,一种是采用跨平台的混合开发方案如electron之类然后打包应用为exe,利用web3.js或直接用json rpc与后台节点进行通信;另外一种就是直接fork某个官方代码版本,然后改造内部功能模块进行消息捕获和以太网络通信,这个多见于开发阶段也就是所谓的挂钩子,是很理想而高效的消息处理方案。

    纯区块链技术驱动的产品架构是笔者比较喜欢的技术架构,能简化系统体系架构大大节约开发成本和时间,加速产品落地和推广同时充分体会区块链之美。其技术架构一般如下:


    image.png

    目前来说要和以太坊网络进行交互大致有3个途径:1 封装web3.js或web3j功能库;2 调用metamask暴露的功能和接口;3 利用原始的JSON RPC请求,然后自己处理编码和签名以及账户管理等。由于方式3通过原始JSON RPC和以太坊网络进行交互多见于早期开发阶段,极易出错特别是对参数编码和返回值解码处理所以不再推荐。关于和链上合约交互的各种方式后续会有专门的章节进行深入的讨论,现在我们先关注智能合约的基础部分。

    只要和以太网络进行交互,无论是转账、部署或调用智能合约都离不开后台实际的以太坊节点。但由于国内严肃的网络环境节点维护和网络同步都是不可忽视的拦路虎,虽然在国外根本不是问题。由于区块链应用的特殊性如果区块没有完全同步完那么所有的应用和状态变化都无法生效。在开发阶段本地搭建私有链环境是最方便和便宜的,但在部署阶段就不得不考虑后台实际的以太节点。如果技术方案采用metamask并加入某个公共测试网络或主网,那么可以不用管节点维护和同步问题,其底层已经由服务提供团队进行了处理并屏蔽细节了,但metamask插件本身的安装还是需要科学上网才能完成。但如果采用一个成熟可用的以太云节点是不错的选择,这里笔者推荐https://infura.io注册后会生成自己的访问key,并且其支持主网和各种公共测试网络,在本地完成测试后可以通过这个云节点进行部署和进一步测试,和全功能节点相比少了一些辅助功能,但支持所有的标准json rpc接口请求列表。

    至于采用哪种客户端形式进行应用展示需要结合团队的技术储备、具体的业务场景以及工期和预算等进行综合考虑。但只要是区块链技术驱动的项目或者更确切的说为智能合约驱动的应用项目,无论是混合架构模式还是纯区块链模式,后台都离不开智能合约的开发、调试和部署,这个也是本书关注的重点。

    2.2 智能合约版Hello World

    智能合约本质上也是一段特殊的程序代码,只是其运行在分布式的区块链平台上,由网络中所有的参与节点进行分布式的运行和处理。以太坊平台官方推荐的智能合约编写语言是Solidity,虽然语法类似JavaScript但其本质还是静态语言,需要进行编译部署然后才能运行。现在网络上出现的一些在线学习工具等其后台大多用的是js vm,把编译和部署这个环节进行了自动化。

    任何程序设计类书籍总是从hello world这样的经典范例程序开头的,笔者也遵循前辈惯例。如果读者暂时看不懂没关系后面会详细讲解代码含义。和其他程序设计语言一样,solidity编写的智能合约也是由一些数据(区块链里面一般称为状态或状态变量),以及对这些状态数据进行读写的操作组成。为了有个整体的认识,请参考如下代码范例:

    1  pragma solidity ^0.5.0;
    2
    3  /**
    4 * @title HelloWorld
    5 * @dev demo HelloWorld for solidity by dayangxi(185098638@qq.com)
    6 */
    7  contract HelloWorld {
    8  string public info;
    9
    10 function HelloWorld() public {
    11 info = "hello solid1 world!";
    12 }
    13
    14 /**
    15  * @dev set the inner info
    16  * @param iinfo the new info
    17 */
    18 function setInfo(string memory iinfo) public {
    19 info = iinfo;
    20 }
    21
    22 /**
    23  * @dev get the inner info
    24  * @return string give back the inner iinfo
    25 */
    26 function getInfo() public view returns(string memory) {
    27 return info;
    28 }
    29 }
    

    需要提醒读者注意的是由于以太坊相关的配套生态和各种工具都处于不断迭代升级和发展中,solidity相关的语法和用法每个版本都有一定的变化,尽管本书在写作都尽量采用相关工具和库的最新版本,但可预见的是当读者阅读此书时,相关的实践和操作等可能已经落伍,如果这种情况不幸发生记得参考solidity以及相关工具和库最新在线英文版本文档进行查询和验证,同时如果能反馈给笔者进行修改就更好了。

    每个solidity合约都是以contract关键字后跟具体的合约名字开头,通过大括号{}包围合约实现体而形成的。虽然一个合约文件可以包括多个合约并分别部署,但当业务功能比较复杂涉及多个合约、库以及外部依赖时,项目管理工作量会急剧上升。为了避免后续不必要的麻烦建议一个合约文件一个合约,并且合约名字和文件名字保持一致类似java类文件命名,如此处合约名字为HelloWorld,则其合约文件名字则为HelloWorld.sol。后缀.sol代表是solidity文件类型,目前也有专门的编辑插件支持,这样通过文件名字可以清晰的看出是功能含义。

    由于各个版本solidity语法变化比较大,因此现在的合约编写的时候都会指定一个编译版本进行兼容。代码第1行指示此合约需要用版本不小于0.5.0的solidity编译器进行编译(由于当前最新版本的open-zepplin库2.2.3采用的语法是0.5.0,所以本书讲解solidity以该版本为基础方便集成和扩展该库)。

    第3到6行为注释代码,solidity支持常见的//开头的单行注释和//包裹的多行注释,以及类似java的标准化文档注释/*/。虽非强制要求但笔者还是强烈建议合约本身以及关键函数和状态变量还是采用这种标准化文档注释,这样方便一些代码工具做形式化验证以及文档自动生成。

    第8行定义了一个合约内部的状态变量,变量类型为string(一种UTF8编码的变长字节数组为内置类型,关于变量类型后续有专门章节介绍),public关键字表明该状态变量从而可以被继承此合约的子孙合约访问,另外一个隐含的功能是针对编译器会自动为public类型状态变量,默认生成一个对应的getter访问器函数,类似 XXX.info()用以简化外部或其他合约的访问。

    第10到12行为合约的构造函数,以constructor关键字开头没有入口参数。当合约被部署时构造函数会唯一的执行一次,并初始化内部状态变量的值为” hello solidity world !”,部署完毕后构造函数不能再次执行。

    需要注意的是如果其他合约通过new显示构造此合约的实例时也会执行一次构造函数,但如果通过地址转换的模式进行构建实例则不会执行构造函数,因为此时合约已经部署了只是通过地址进行引用而已,示意代码如下:

    contract TestHello {
     function doTestNew() public {
     HelloWorld helloInst = new HelloWorld();
     }
    
     function doTestAt(address target) public view returns(string) {
     require(target != address(0x00) && target != address(this));
     HelloWorld helloInst = HelloWorld(target);
     return helloInst.getInfo();
     }
    }
    

    doTestNew函数会引起合约构造函数的执行,但doTestAt不会引发构造函数的运行。为了查看函数内部的执行情况,可以通过合约事件机制进行记录,关于事件后续会有专门讲解。

    第14行到20行为合约状态变量修器,修改内部状态变量为新值。以关键字function开头后面跟函数名称和参数列表,其中参数列表可以为空;再给定函数对外的可见性权限public,意思是不管在合约内部、继承该合约的子合约中或其它外部合约中,都可以通过内部函数调用或者外部函数调用的方式访问该函数,同时对外部的账户可见。需要注意的是string作为入口参数时需要强制指定存储位置为memory,代表临时存储空间而非永久存储空间(关于储存空间memory和storage后续会有专门章节讲解)。

    从这里开始就是和传统程序设计不一样的地方了,尽管从语法上讲修改内部状态变量的函数也可以定义返回值,但该返回值只有在合约之间互相调用时才有意义,对外部客户端提供访问时该返回值是无意义的也不能获取。由于区块链本身的分布式异步执行特点,交易被提交并请求执行时,其不知道被执行的确切时间(等同于不知道具体打包此交易的区块号和是否会被成功打包),所以提交交易时返回的不是此交易的执行结果而是交易特定的哈希,后面外部客户端可以通过此交易哈希查询交易是否成功执行或者错误原因。关于交易状态查询目前推荐: etherscan.io,以太官方钱包自带的交易状态查询也是用的此网站,其支持主网和各种类型的测试网络。所以需要对外部提供访问的函数可以不定义返回值,作为父类合约被继承或者库函数提供功能时不在此范围,可以提供返回值指示代码执行是否正确或者返回需要的值。为了加速开发测试有些模拟环境可以使得交易即时生效,也就是修改状态变量后客户马上看到结果。

    第22行到28行就相对简单和好理解了,返回当前内部状态变量的值可以类比其他传统的程序设计语言。也是以function关键字开头后跟参数列表,其中参数列表可以为空,然后给定函数对外的可见性控制public,意味着内部外部包括继承合约都可以自由使用。和修改函数不一样的是多了个view限定符,表示此函数只是读取状态变量值不会修改值,利于编译器做检查和实施优化。returns指示后面的参数列表为返回值类型列表,再次体现了solidity作为一种特殊的静态编程语言的严谨性。具体返回值通过return返回,需要特别指出的是返回值如同python,可以返回多个类型多个参数的元组,如下示:

    function getInfo() public view returns(string, uint8) {
     return (info, 123);
    }
    

    在提取返回值时,定义对应的变量并通过元组的方式获取,如下示:

    string memory a;
    uint8 b;
    (a, b) = helloInst.getInfo();
    

    此范例合约没多少实际意义和功能,主要提供一个对智能合约的整体感性认识。得益于以太坊网络提供的基础设施,通过简单的编程就完成了一个链上可用的智能合约,可以允许任何人修改这个内部状态变量的值,同时其他人也可以轻易覆盖掉先前的值,也允许任何人读取该内部状态的值。但是你的状态变量的值会永久存在于以太坊区块链的历史中哪怕天崩地裂。后面我们会知道怎么样施加限制使得只有特定的人员如创建者或管理员才能修改值。

    由于最新版本solidity编译器的进化,很多工作已经自动化完成了,使得人们可以专注于业务逻辑的实现同时利用以太坊平台带来的好处,如下为完成同样功能的代码实例:

    contract HelloWorld {
     string public info = “ hello solidity world!”;
     function setInfo(string memory nnew) public {
     info = nnew;
     }
    }
    

    在变量声明的同时赋初始值,并指定访问符为public这样编译器可以自动生成getter访问器,我们只需要实现状态变量的修改就可以了。

    先暂停休息一下介绍到此处一个简单而完整的智能合约就介绍完了。如果是第一次接触solidity编写智能合约,虽然实例合约本身比较简单但其背后的知识点比较多也比较新,有些特点是区块链本身所规定的需要好好消化和整理下。接下来我会交给大家怎么样把这个合约在实际环境中跑起来。

    相关文章

      网友评论

          本文标题:2 Solidity概述与环境搭建

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