基於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的使用


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM