• 基于Fisco-Bcos的区块链智能合约-简单案例实践


    一、智能合约介绍

    智能合约是指把合同/协议条款以代码的形式电子化地放到区块链网络上。FISCO BCOS平台支持两种智能合约类型:Solidity智能合约与预编译智能合约

    Solidity与Java类似。代码写好后,都需要通过编译器将代码转换成二进制,在Java中,编译器是Javac,而对于Solidity,是solc。生成后的二进制代码,会放到虚拟机里执行。Java代码在Java虚拟机(JVM)中执行,在Solidity中,是一个区块链上的虚拟机EVM。目的,是给区块链提供一套统一的逻辑,让相同的代码跑在区块链的每个节点上,借助共识算法,让区块链的数据以统一的方式进行改变,达到全局一致的结果

     

    设计目的:

    为区块链提供一套统一的逻辑,让相同的代码跑在区块链的每个节点上,借助共识算法,让区块链的数据以统一的方式进行改变,达到全局一致的结果

    Solidity 局限与改进

    - Solidity不够灵活
    受自身堆栈深度的限制,函数传参和局部参数的个数总和不能超过16个,Solidity是一种强类型的语言,但其类型转换较为麻烦
    - 性能差

    底层存储单位是32字节(256 bits),对硬盘的读写要求较高,浪费了大量的存储资源

    针对上述两点,FISCO BCOS提供了一种用C++写合约方式:预编译合约。开发者可以用C++编写智能合约逻辑,并将其内置在节点中,

    预编译合约突破了Solidity语言的限制,借助强大的C++语言,可以灵活的实现各种逻辑,灵活性大大提高。同时,C++的性能优势也得到了很好的利用,通过预编译合约编写的逻辑,相比于Solidity语言来说,性能得到提升

    合约编写

    开发工具:remix-ide的使用,开发编译过程选择在线remix

    Remix是功能强大的开源工具,可帮助您直接从浏览器编写Solidity合同。Remix用JavaScript编写,支持在浏览器和本地使用。

    Remix还支持智能合约的测试,调试和部署等等。

    优点:

    1. 动态编译、可调控编译版本
    2. 即时错误提醒
    3. 代码自动补全
    4. 发布阶段,代码问题提醒
    5. 对设计方法的简单调用

     

     

     

     

    认识合约

    例:

    pragma solidity ^ 0.4.26;
    
    constant Sample{
       //变量  address表示账户地址
        address private _admin;
        uint private _state;
    
        //修饰符 ,为函数提供一些额外的功能,例如检查、清理等工作
        // 检测函数的调用者是否为函数部署时设定的那个管理员(即合约的部署人)
        modifier onlyAdmin(){
            require(msg.sender==_admin,"You are not admin");
            _;
    
        }
        //事件
        // 记录事件定义的参数,存储到区块链交易的日志中,提供廉价的存储。
        //  提供一种回调机制,在事件执行成功后,由节点向注册监听的SDK发送回调通知,触发回调函数被执行。
        // 提供一个过滤器,支持参数的检索和过滤。
        event SetState(unit valule);
    
        //构造方法  构造函数用于初始化合约
        constructor() public {
            _admin=msg.sender;
        }
    
       //函数 方法
       function setSate(unit value) public  onlyAdmin(){
           _state=value;
           emit SetState(value);
       }
       function getValue() public view return (uint){
         return _state;  
       }
    }

    二、案列合约设计

    逻辑如下:

    定义:

    - 定义事件方法AddEqu(string equnum, string data)
    - 构造函数中创建t_equipment表
    - 查询方法:select(string equnum),根据设备编号查询设备备案信息,或使用记录。( 成功返回0, 设备不存在返回-1)
    - addEqu(string equnum, string data),添加数据前校验数据唯一性,已存在不在插入

    Eqump合约类图

    Contract:Java与智能合约进行交互的实体合约类型抽象

    ManagedTransaction: 交易管理

    Eqump合约核心代码

    Eqump.sol

    pragma solidity  ^ 0.4.25;
    
    import "./Table.sol";
    contract Eqump{
        // event
        event AddEqu(string equnum, string data);
        //
        constructor()  public {
            // 构造函数中创建t_equipment表
            createTable();
        }
    
        function createTable() private {
            TableFactory tf = TableFactory(0x1001); // 创建表
            tf.createTable("t_equipment", "equnum", "data");
        }
    
        function openTable() private view  returns(Table) {
            TableFactory tf = TableFactory(0x1001);
            Table table = tf.openTable("t_equipment");
            return table;
        }
    
        /*
        描述 : 根据设备管理信息查询设备信息
        参数 : 
                equ_num : 设备编号
        返回值:
                参数一: 成功返回0, 设备不存在返回-1     
        */
        function select(string equnum) public view returns(int256, string) {
            // 打开表
            Table table = openTable();
            // 查询
            Entries entries = table.select(equnum, table.newCondition());
           if (0 == uint256(entries.size())) {
                return (-1, "");
            } else {
                  Entry entry = entries.get(0);
                return (0, entry.getString("data"));
            }
        }
        /*
        描述 : 添加信息
        参数 : 
                equnum : 案信息主键
                data  : 信息
        返回值:
                 0 备案成功
                -1 备案信息已存在
                -2 其他错误
        */
        function addEqu(string equnum, string data) public returns(int256){
            int256 ret_code = 0;
            Table table = openTable();
            Entries entries = table.select(equnum, table.newCondition());
            if(0 == uint256(entries.size())) {
                Entry entry = table.newEntry();
                entry.set("equnum", equnum);
                entry.set("data", data);
                // 插入
                int count = table.insert(equnum, entry);
                if (count == 1) {
                    // 成功
                    ret_code = 0;
                } else {
                    // 失败? 无权限或者其他错误
                    ret_code = -2;
                }
            } else {
                // 备案信息
                ret_code = -1;
            }
    
            emit AddEqu(equnum, data);
            return ret_code;
        }
    
    }
    pragma solidity ^0.4.24;
    
    contract TableFactory {
        function openTable(string) public constant returns (Table);  // 打开表
        function createTable(string,string,string) public returns(int);  // 创建表
    }
    
    // 查询条件
    contract Condition {
        //等于
        function EQ(string, int) public;
        function EQ(string, string) public;
        
        //不等于
        function NE(string, int) public;
        function NE(string, string)  public;
        
        //大于
        function GT(string, int) public;
        //大于或等于
        function GE(string, int) public;
        
        //小于
        function LT(string, int) public;
        //小于或等于
        function LE(string, int) public;
        
        //限制返回记录条数
        function limit(int) public;
        function limit(int, int) public;
    }
    
    // 单条数据记录
    contract Entry {
        function getInt(string) public constant returns(int);
        function getAddress(string) public constant returns(address);
        function getBytes64(string) public constant returns(byte[64]);
        function getBytes32(string) public constant returns(bytes32);
        function getString(string) public constant returns(string);
        
        function set(string, int) public;
        function set(string, string) public;
        function set(string, address) public;
    }
    
    // 数据记录集
    contract Entries {
        function get(int) public constant returns(Entry);
        function size() public constant returns(int);
    }
    
    // Table主类
    contract Table {
        // 查询接口
        function select(string, Condition) public constant returns(Entries);
        // 插入接口
        function insert(string, Entry) public returns(int);
        // 更新接口
        function update(string, Entry, Condition) public returns(int);
        // 删除接口
        function remove(string, Condition) public returns(int);
        
        function newEntry() public constant returns(Entry);
        function newCondition() public constant returns(Condition);
    }

    编译发布

    WeBASE简介:

    WeBASE(WeBank Blockchain Application Software Extension) 是在区块链应用和FISCO-BCOS节点之间搭建的一套通用组件。围绕交易、合约、密钥管理,数据,可视化管理来设计各个模块,开发者可以根据业务所需,选择子系统进行部署。WeBASE屏蔽了区块链底层的复杂度,降低开发者的门槛,大幅提高区块链应用的开发效率,包含节点前置、节点管理、交易链路,数据导出,Web管理平台等子系统。

    过程

    • - 编译发布

    • 测试验证    发交易-->addEqu
    {
      equnum : y1
      data:y1
    }

     发交易-->select

    {
     equnum:y1
    }

    基于web3sdk 调试Eqump

    1. 在IDE⾥编写智能合约。
    2. 合约编写完成后,拿到fisco ckient 命令⾏⼯具内进⾏编译和⽣成java SDK的操作。 在/home/FISCO-BCOS/generator/console⽬录下执⾏⼀下命令,将合约解析成java SDK⽂件。

    sh sol2java.sh com.wg.service

     

    特点

    • - 轻量化配置,即可连接区块链节点
    • - 根据.sol 合约文件,一键生成.abi 和 .bin文件
    • - 一键生成java 合约文件

    基于springboot-demo项目

    applycation.yml

    encrypt-type: # 0:普通, 1:国密
      encrypt-type: 0
    
    group-channel-connections-config:
      caCert: ca.crt
      sslCert: sdk.crt
      sslKey: sdk.key
      all-channel-connections:
        - group-id: 1 #group ID
          connections-str:
            # 若节点小于v2.3.0版本,查看配置项listen_ip:channel_listen_port
            - 127.0.0.1:20200 # node channel_listen_ip:channel_listen_port
            - 127.0.0.1:20201
        - group-id: 2
          connections-str:
            # 若节点小于v2.3.0版本,查看配置项listen_ip:channel_listen_port
            - 127.0.0.1:20202 # node channel_listen_ip:channel_listen_port
            - 127.0.0.1:20203
    channel-service:
      group-id: 1 # sdk实际连接的群组
      agency-name: fisco # 机构名称

    SSL连接配置

    国密区块链和非国密区块链环境下,节点与SDK之间均可以建立SSL的连接,将节点所在目录nodes/${ip}/sdk/目录下的ca.crt、sdk.crt和sdk.key文件拷贝到项目的资源目录。(低于2.1版本的FISCO BCOS节点目录下只有node.crt和node.key,需将其重命名为sdk.crt和sdk.key以兼容最新的SDK)

    启动

    无异常,看到区块链的版本、java环境地址、端口为正常启动。

    2020-07-17 09:13:21,417 [nioEventLoopGroup-2-1] INFO  [org.fisco.bcos.channel.handler.ConnectionCallback] ConnectionCallback.java:267 -  support channel handshake node: Version [buildTime=20200602 03:35:56, buildType=Linux/clang/Release, chainID=1, version=2.4.1, gitBranch=HEAD, gitCommit=f6f2b4f12d5441e24c81a7c862691636c9cb3263, supportedVersion=2.4.1], content: {"id":0,"jsonrpc":"2.0","result":{"Build Time":"20200602 03:35:56","Build Type":"Linux/clang/Release","Chain Id":"1","FISCO-BCOS Version":"2.4.1","Git Branch":"HEAD","Git Commit Hash":"f6f2b4f12d5441e24c81a7c862691636c9cb3263","Supported Version":"2.4.1"}}
    
    2020-07-17 09:13:21,422 [nioEventLoopGroup-2-1] INFO  [org.fisco.bcos.channel.handler.ConnectionCallback] ConnectionCallback.java:167 -  channel protocol handshake success, set socket channel protocol, host: 10.2.23.16:20200, channel protocol: ChannelProtocol [protocol=3, nodeVersion=2.4.1, EnumProtocol=VERSION_3]
    2020-07-17 09:13:21,424 [restartedMain] INFO  [org.fisco.bcos.channel.client.Service] Service.java:373 -  Connect to  nodes: [10.2.23.16:20200] ,groupId: 1 ,caCert: class path resource [ca.crt] ,sslKey: class path resource [sdk.key] ,sslCert: class path resource [sdk.crt] ,java version: 1.8.0_151 ,java vendor: Oracle Corporation
    2020-07-17 09:13:21,432 [nioEventLoopGroup-2-1] INFO  [org.fisco.bcos.channel.handler.ConnectionCallback] ConnectionCallback.java:338 -  send update topic message request, seq: 89300763da2a4279bcb49b4b8187e477, content: ["_block_notify_1"]
    2020-07-17 09:13:21,434 [nioEventLoopGroup-2-1] INFO  [org.fisco.bcos.channel.handler.ConnectionCallback] ConnectionCallback.java:370 -  query block number host: 10.2.23.16:20200, seq: 0db7f13819ec425c8d9494cb68cd98cd, content: {"jsonrpc":"2.0","method":"getBlockNumber","params":[1],"id":1}
    2020-07-17 09:13:21,440 [nioEventLoopGroup-2-1] INFO  [org.fisco.bcos.channel.handler.ConnectionCallback] ConnectionCallback.java:395 -  query blocknumer, host:10.2.23.16:20200, blockNumber: 336 

    验证

    编写单元测试

    核心代码

    package org.fisco.bcos;
    
    import org.fisco.bcos.constants.GasConstants;
    import org.fisco.bcos.solidity.Eqump;
    import org.fisco.bcos.web3j.crypto.Credentials;
    import org.fisco.bcos.web3j.protocol.Web3j;
    import org.fisco.bcos.web3j.tuples.generated.Tuple2;
    import org.fisco.bcos.web3j.tx.gas.StaticGasProvider;
    import org.junit.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    
    import java.math.BigInteger;
    
    import static org.junit.Assert.assertTrue;
    
    /**
     * 财政部大型设备合约单元测试
     */
    public class EqumpTest extends BaseTest {
    
        @Autowired
        private Web3j web3j;
        @Autowired
        private Credentials credentials;
    
        /**
         * 部署调用合约
         * @throws Exception
         */
        @Test
        public void deployAndCall() throws Exception {
            // deploy contract
            Eqump eqump =
                    Eqump.deploy(web3j, credentials, new StaticGasProvider(GasConstants.GAS_PRICE, GasConstants.GAS_LIMIT)).send();
            if (eqump != null) {
                System.out.println("Eqump address is: " + eqump.getContractAddress());
                // call set function
                eqump.addEqu("1A2B","12312").send();
                // call get function
                Tuple2<BigInteger, String> send = eqump.select("1A2B").send();
                System.out.println(send.getValue1());
                System.out.println(send.getValue2());
                assertTrue("Eqump!".equals(send));
            }
        }
    
        /**
         * 查询
         * @throws Exception
         */
        @Test
        public void queryAndCall() throws Exception {
            // deploy contract
            Eqump eqump =
                    Eqump.deploy(web3j, credentials, new StaticGasProvider(GasConstants.GAS_PRICE, GasConstants.GAS_LIMIT)).send();
            if (eqump != null) {
                System.out.println("Eqump address is: " + eqump.getContractAddress());
                // call set function
                // call get function
                Tuple2<BigInteger, String> send = eqump.select("y6").send();
                System.out.println(send.getValue1());
                System.out.println(send.getValue2());
            }
        }
    }

    核心业务代码

    /**
         * 添加设备信息
         *
         * @param dataArray
         * @throws InterruptedException
         */
        private void addIpassItem(JSONArray dataArray) {
            System.out.println("===========================addIpassItem 添加设备信息业务开始================================");
            try {
                Eqump eqump =
                        Eqump.deploy(web3j, credentials, new StaticGasProvider(GasConstants.GAS_PRICE, GasConstants.GAS_LIMIT)).send();
                for (int i = 0; i < dataArray.size(); i++) {
                    List list = (List) dataArray.getJSONObject(i).get("equipmentInfor");
                    long startime = System.currentTimeMillis();
                    for (int j = 0; j < list.size(); j++) {
                        JSONObject jobj = (JSONObject) list.get(j);
                        String sbbh = StringUtil.validator(jobj.get("设备编号"));
                        String jsonStr = StringUtil.validator(jobj);
                        if (eqump != null) {
                            System.out.println("Eqump address is: " + eqump.getContractAddress());
                            eqump.addEqu(sbbh,jsonStr).send();
                        }
                    }
                    System.out.println("耗时:" + (System.currentTimeMillis() - startime) + " 毫秒");
                }
            } catch (NumberFormatException  e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("===========================addIpassItem 添加设备信息业务结束================================");
        }
    
    
        /**
         * 添加设备使用信息
         *
         * @param dataArray
         * @throws InterruptedException
         */
        private void addIpassUse(JSONArray dataArray) {
            System.out.println("===========================addIpassUse 添加设备信息业务开始================================");
            try {
                for (int i = 0; i < dataArray.size(); i++) {
                    List list = (List) dataArray.getJSONObject(i).get("equipmentUsageRec");
                    long startime = System.currentTimeMillis();
                    for (int j = 0; j < list.size(); j++) {
                        JSONObject jobj = (JSONObject) list.get(j);
                        String sbbh = StringUtil.validator(jobj.get("设备编号"));
                        String jsonStr = StringUtil.validator(jobj);
                        Eqump eqump =
                                Eqump.deploy(web3j, credentials, new StaticGasProvider(GasConstants.GAS_PRICE, GasConstants.GAS_LIMIT)).send();
                        if (eqump != null) {
                            System.out.println("Eqump address is: " + eqump.getContractAddress());
                            eqump.addEqu(sbbh,jsonStr).send();
                        }
                    }
                    System.out.println("耗时:" + (System.currentTimeMillis() - startime) + " 毫秒");
                }
            } catch (NumberFormatException  e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("===========================addIpassUse 添加设备信息业务结束================================");
        }

    日志信息

    ===========================addIpassItem 添加设备信息业务开始================================
    2020-07-23 17:20:57,261 [http-nio-8080-exec-1] INFO  [org.fisco.bcos.web3j.utils.Async] Async.java:19 -  default set setExeutor , pool size is 50
    2020-07-23 17:20:57,262 [http-nio-8080-exec-1] INFO  [org.fisco.bcos.web3j.utils.Async] Async.java:81 -  set setExeutor because executor null, executor is java.util.concurrent.ThreadPoolExecutor@3ac27c97[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
    2020-07-23 17:20:57,458 [nioEventLoopGroup-2-1] INFO  [org.fisco.bcos.channel.client.Service] Service.java:1388 - Receive block notify: {"blockNumber":353,"groupID":1}
    
    Eqump address is: 0x0066699656ac8bc09ec364858680f2357f899ae0
    2020-07-23 17:21:01,012 [nioEventLoopGroup-2-1] INFO  [org.fisco.bcos.channel.client.Service] Service.java:1388 - Receive block notify: {"blockNumber":354,"groupID":1}
    
    Eqump address is: 0x0066699656ac8bc09ec364858680f2357f899ae0
    2020-07-23 17:21:02,807 [nioEventLoopGroup-2-1] INFO  [org.fisco.bcos.channel.client.Service] Service.java:1388 - Receive block notify: {"blockNumber":355,"groupID":1}
    
    Eqump address is: 0x0066699656ac8bc09ec364858680f2357f899ae0
    2020-07-23 17:21:04,341 [nioEventLoopGroup-2-1] INFO  [org.fisco.bcos.channel.client.Service] Service.java:1388 - Receive block notify: {"blockNumber":356,"groupID":1}
    
    耗时:6593 毫秒
    ===========================addIpassItem 添加设备信息业务结束================================

    webase验证

    {"序号":"3","仪器名称":"600MHz超导核磁共振仪","仪器型号":"Avance III 600","设备编号":"Avance III 600","所属单位":"昆明植物研究所 ","所属区域中心":"昆明生物多样性大型仪器区域中心","制造商名称":"瑞士布鲁克公司 ","国别":"瑞士","购置时间":"20100109","放置地点":"分析测试中心101","预约审核人":"李波 ","操作人员":"李波 ","仪器工作状态":"正常 ","预约形式":"必须预约 ","预约类型":"项目预约","仪器大类":"室内分析测试设备","仪器中类":"波谱仪器 ","仪器小类":"核磁共振波谱仪器"}

    资料及参考

    spring-boot-starter

    Solidity官方文档

    FISCO BCOS学习资料索引

    在线remix

    remix-ide的使用

  • 相关阅读:
    Django实战(4):scaffold生成物分析
    Django实战(3):Django也可以有scaffold
    创建第一个模型类
    1. 实战系列的开发目标
    Django第一步
    URLconf+MTV:Django眼中的MVC
    mp4文件格式解析
    傅里叶分析之掐死教程(完整版)更新于2014.06.06
    关于Spinlock机制的一点思考
    spinlock变量没有初始化
  • 原文地址:https://www.cnblogs.com/aGboke/p/14119341.html
Copyright © 2020-2023  润新知