一、智能合約介紹
智能合約是指把合同/協議條款以代碼的形式電子化地放到區塊鏈網絡上。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: 交易管理
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","預約審核人":"李波 ","操作人員":"李波 ","儀器工作狀態":"正常 ","預約形式":"必須預約 ","預約類型":"項目預約","儀器大類":"室內分析測試設備","儀器中類":"波譜儀器 ","儀器小類":"核磁共振波譜儀器"}