基於Fisco-Bcos的區塊鏈智慧合約-簡單案例實踐

Vip灬cnblog發表於2020-12-11

一、智慧合約介紹

智慧合約是指把合同/協議條款以程式碼的形式電子化地放到區塊鏈網路上。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的使用

相關文章