FISCO BCOS | 開發第一個區塊鏈應用

BSN研習社發表於2022-12-09

本章將會介紹一個基於FISCO BCOS區塊鏈的業務應用場景開發全過程,從業務場景分析,到合約的設計實現,然後介紹合約編譯以及如何部署到區塊鏈,最後介紹一個應用模組的實現,透過我們提供的Java SDK實現對區塊鏈上合約的呼叫訪問。

本教程要求使用者熟悉Linux操作環境,具備Java開發的基本技能,能夠使用Gradle工具,熟悉Solidity語法。

如果您還未搭建區塊鏈網路,或未下載控制檯,請先走完教程搭建第一個區塊鏈網路,再回到本教程。

1. 瞭解應用需求

區塊鏈天然具有防篡改,可追溯等特性,這些特性決定其更容易受金融領域的青睞。本示例中,將會提供一個簡易的資產管理的開發示例,並最終實現以下功能:

  • 能夠在區塊鏈上進行資產註冊
  • 能夠實現不同賬戶的轉賬
  • 可以查詢賬戶的資產金額

2. 設計與開發智慧合約

在區塊鏈上進行應用開發時,結合業務需求,首先需要設計對應的智慧合約,確定合約需要儲存的資料,在此基礎上確定智慧合約對外提供的介面,最後給出各個介面的具體實現。

第一步. 設計智慧合約

儲存設計

FISCO BCOS提供合約CRUD介面開發模式,可以透過合約建立表,並對建立的表進行增刪改查操作。針對本應用需要設計一個儲存資產管理的表t_asset,該表欄位如下:

  • account: 主鍵,資產賬戶(string型別)
  • asset_value: 資產金額(uint256型別)

其中account是主鍵,即操作t_asset表時需要傳入的欄位,區塊鏈根據該主鍵欄位查詢表中匹配的記錄。t_asset表示例如下:

account asset_value
Alice 10000
Bob 20000

介面設計

按照業務的設計目標,需要實現資產註冊,轉賬,查詢功能,對應功能的介面如下:

// 查詢資產金額
function select(string account) public constant returns(int256, uint256)
// 資產註冊
function register(string account, uint256 amount) public returns(int256)
// 資產轉移
function transfer(string from_asset_account, string to_asset_account, uint256 amount) public returns(int256)

第二步. 開發原始碼

根據我們第一步的儲存和介面設計,建立一個Asset的智慧合約,實現註冊、轉賬、查詢功能,並引入一個叫Table的系統合約,這個合約提供了CRUD介面。

# 進入console/contracts目錄
cd ~/fisco/console/contracts/solidity
# 建立Asset.sol合約檔案
vi Asset.sol
# 將Assert.sol合約內容寫入。
# 並鍵入wq儲存退出。

Asset.sol的內容如下:

pragma solidity ^0.4.24;
import "./Table.sol";
contract Asset {
    // event
    event RegisterEvent(int256 ret, string account, uint256 asset_value);
    event TransferEvent(int256 ret, string from_account, string to_account, uint256 amount);
    constructor() public {
        // 建構函式中建立t_asset表
        createTable();
    }
    function createTable() private {
        TableFactory tf = TableFactory(0x1001);
        // 資產管理表, key : account, field : asset_value
        // |  資產賬戶(主鍵)      |     資產金額       |
        // |-------------------- |-------------------|
        // |        account      |    asset_value    |
        // |---------------------|-------------------|
        //
        // 建立表
        tf.createTable("t_asset", "account", "asset_value");
    }
    function openTable() private returns(Table) {
        TableFactory tf = TableFactory(0x1001);
        Table table = tf.openTable("t_asset");
        return table;
    }
    /*
    描述 : 根據資產賬戶查詢資產金額
    引數 :
            account : 資產賬戶
    返回值:
            引數一: 成功返回0, 賬戶不存在返回-1
            引數二: 第一個引數為0時有效,資產金額
    */
    function select(string account) public constant returns(int256, uint256) {
        // 開啟表
        Table table = openTable();
        // 查詢
        Entries entries = table.select(account, table.newCondition());
        uint256 asset_value = 0;
        if (0 == uint256(entries.size())) {
            return (-1, asset_value);
        } else {
            Entry entry = entries.get(0);
            return (0, uint256(entry.getInt("asset_value")));
        }
    }
    /*
    描述 : 資產註冊
    引數 :
            account : 資產賬戶
            amount  : 資產金額
    返回值:
            0  資產註冊成功
            -1 資產賬戶已存在
            -2 其他錯誤
    */
    function register(string account, uint256 asset_value) public returns(int256){
        int256 ret_code = 0;
        int256 ret= 0;
        uint256 temp_asset_value = 0;
        // 查詢賬戶是否存在
        (ret, temp_asset_value) = select(account);
        if(ret != 0) {
            Table table = openTable();
            Entry entry = table.newEntry();
            entry.set("account", account);
            entry.set("asset_value", int256(asset_value));
            // 插入
            int count = table.insert(account, entry);
            if (count == 1) {
                // 成功
                ret_code = 0;
            } else {
                // 失敗? 無許可權或者其他錯誤
                ret_code = -2;
            }
        } else {
            // 賬戶已存在
            ret_code = -1;
        }
        emit RegisterEvent(ret_code, account, asset_value);
        return ret_code;
    }
    /*
    描述 : 資產轉移
    引數 :
            from_account : 轉移資產賬戶
            to_account : 接收資產賬戶
            amount : 轉移金額
    返回值:
            0  資產轉移成功
            -1 轉移資產賬戶不存在
            -2 接收資產賬戶不存在
            -3 金額不足
            -4 金額溢位
            -5 其他錯誤
    */
    function transfer(string from_account, string to_account, uint256 amount) public returns(int256) {
        // 查詢轉移資產賬戶資訊
        int ret_code = 0;
        int256 ret = 0;
        uint256 from_asset_value = 0;
        uint256 to_asset_value = 0;
        // 轉移賬戶是否存在?
        (ret, from_asset_value) = select(from_account);
        if(ret != 0) {
            ret_code = -1;
            // 轉移賬戶不存在
            emit TransferEvent(ret_code, from_account, to_account, amount);
            return ret_code;
        }
        // 接受賬戶是否存在?
        (ret, to_asset_value) = select(to_account);
        if(ret != 0) {
            ret_code = -2;
            // 接收資產的賬戶不存在
            emit TransferEvent(ret_code, from_account, to_account, amount);
            return ret_code;
        }
        if(from_asset_value < amount) {
            ret_code = -3;
            // 轉移資產的賬戶金額不足
            emit TransferEvent(ret_code, from_account, to_account, amount);
            return ret_code;
        }
        if (to_asset_value + amount < to_asset_value) {
            ret_code = -4;
            // 接收賬戶金額溢位
            emit TransferEvent(ret_code, from_account, to_account, amount);
            return ret_code;
        }
        Table table = openTable();
        Entry entry0 = table.newEntry();
        entry0.set("account", from_account);
        entry0.set("asset_value", int256(from_asset_value - amount));
        // 更新轉賬賬戶
        int count = table.update(from_account, entry0, table.newCondition());
        if(count != 1) {
            ret_code = -5;
            // 失敗? 無許可權或者其他錯誤?
            emit TransferEvent(ret_code, from_account, to_account, amount);
            return ret_code;
        }
        Entry entry1 = table.newEntry();
        entry1.set("account", to_account);
        entry1.set("asset_value", int256(to_asset_value + amount));
        // 更新接收賬戶
        table.update(to_account, entry1, table.newCondition());
        emit TransferEvent(ret_code, from_account, to_account, amount);
        return ret_code;
    }
}

Asset.sol所引用的Table.sol已在~/fisco/console/contracts/solidity目錄下。該系統合約檔案中的介面由FISCO BCOS底層實現。當業務合約需要操作CRUD介面時,均需要引入該介面合約檔案。Table.sol合約詳細介面參考這裡。

執行ls命令,確保Asset.sol和Table.sol在目錄~/fisco/console/contracts/solidity下。

undefined

3. 編譯智慧合約

.sol的智慧合約需要編譯成ABI和BIN檔案才能部署至區塊鏈網路上。有了這兩個檔案即可憑藉Java SDK進行合約部署和呼叫。但這種呼叫方式相對繁瑣,需要使用者根據合約ABI來傳參和解析結果。為此,控制檯提供的編譯工具不僅可以編譯出ABI和BIN檔案,還可以自動生成一個與編譯的智慧合約同名的合約Java類。這個Java類是根據ABI生成的,幫助使用者解析好了引數,提供同名的方法。當應用需要部署和呼叫合約時,可以呼叫該合約類的對應方法,傳入指定引數即可。使用這個合約Java類來開發應用,可以極大簡化使用者的程式碼。

# 建立工作目錄~/fisco
mkdir -p ~/fisco
# 下載控制檯
cd ~/fisco && curl -#LO 
# 切換到fisco/console/目錄
cd ~/fisco/console/
# 若控制檯版本大於等於2.8.0,編譯合約方法如下:(可透過bash sol2java.sh -h命令檢視該指令碼使用方法)
bash sol2java.sh -p org.fisco.bcos.asset.contract
# 若控制檯版本小於2.8.0,編譯合約(後面指定一個Java的包名引數,可以根據實際專案路徑指定包名)如下:
./sol2java.sh org.fisco.bcos.asset.contract

執行成功之後,將會在console/contracts/sdk目錄生成java、abi和bin目錄,如下所示。

# 其它無關檔案省略
|-- abi # 生成的abi目錄,存放solidity合約編譯生成的abi檔案
|   |-- Asset.abi
|   |-- Table.abi
|-- bin # 生成的bin目錄,存放solidity合約編譯生成的bin檔案
|   |-- Asset.bin
|   |-- Table.bin
|-- contracts # 存放solidity合約原始碼檔案,將需要編譯的合約複製到該目錄下
|   |-- Asset.sol # 複製進來的Asset.sol合約,依賴Table.sol
|   |-- Table.sol # 實現系統CRUD操作的合約介面檔案
|-- java  # 存放編譯的包路徑及Java合約檔案
|   |-- org
|        |--fisco
|             |--bcos
|                  |--asset
|                       |--contract
|                             |--Asset.java  # Asset.sol合約生成的Java檔案
|                             |--Table.java  # Table.sol合約生成的Java檔案
|-- sol2java.sh

java目錄下生成了org/fisco/bcos/asset/contract/包路徑目錄,該目錄下包含Asset.java和Table.java兩個檔案,其中Asset.java是Java應用呼叫Asset.sol合約需要的檔案。

Asset.java的主要介面:

package org.fisco.bcos.asset.contract;
public class Asset extends Contract {
    // Asset.sol合約 transfer介面生成
    public TransactionReceipt transfer(String from_account, String to_account, BigInteger amount);
    // Asset.sol合約 register介面生成
    public TransactionReceipt register(String account, BigInteger asset_value);
    // Asset.sol合約 select介面生成
    public Tuple2<BigInteger, BigInteger> select(String account) throws ContractException;
    // 載入Asset合約地址,生成Asset物件
    public static Asset load(String contractAddress, Client client, CryptoKeyPair credential);
    // 部署Assert.sol合約,生成Asset物件
    public static Asset deploy(Client client, CryptoKeyPair credential) throws ContractException;
}

其中load與deploy函式用於構造Asset物件,其他介面分別用來呼叫對應的solidity合約的介面。

4. 建立區塊鏈應用專案

第一步. 安裝環境

首先,我們需要安裝JDK以及整合開發環境

  • Java:JDK 14 (JDK1.8至JDK 14都支援)

首先,在官網上下載JDK14並安裝

然後,修改環境變數

# 確認您當前的java版本
$ java -version
# 確認您的java路徑
$ ls /Library/Java/JavaVirtualMachines
# 返回
# jdk-14.0.2.jdk
# 如果使用的是bash
$ vim .bash_profile 
# 在檔案中加入JAVA_HOME的路徑
# export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-14.0.2.jdk/Contents/Home 
$ source .bash_profile
# 如果使用的是zash
$ vim .zashrc
# 在檔案中加入JAVA_HOME的路徑
# export JAVA_HOME = Library/Java/JavaVirtualMachines/jdk-14.0.2.jdk/Contents/Home 
$ source .zashrc
# 確認您的java版本
$ java -version
# 返回
# java version "14.0.2" 2020-07-14
# Java(TM) SE Runtime Environment (build 14.0.2+12-46)
# Java HotSpot(TM) 64-Bit Server VM (build 14.0.2+12-46, mixed mode, sharing)
  • IDE:IntelliJ IDE.

進入IntelliJ IDE官網,下載並安裝社群版IntelliJ IDE

第二步. 建立一個Java工程

在IntelliJ IDE中建立一個gradle專案,勾選Gradle和Java,並輸入工程名asset-app。

注意:該專案的原始碼可以用以下方法獲得並參考。(此步驟為非必須步驟)

$ cd ~/fisco
$ curl -#LO 
# 解壓得到Java工程專案asset-app
$ tar -zxf asset-app.tar.gz

undefined

第三步. 引入FISCO BCOS Java SDK

在build.gradle檔案中的dependencies下加入對FISCO BCOS Java SDK的引用。

repositories {
    mavenCentral()
    maven {
        allowInsecureProtocol = true
        url "
    }
    maven {
        allowInsecureProtocol = true
        url "
    }
}

引入Java SDK jar包

testImplementation group: 'junit', name: 'junit', version: '4.12'
implementation ('org.fisco-bcos.java-sdk:fisco-bcos-java-sdk:2.9.1')

undefined

第四步. 配置SDK證照

修改build.gradle檔案,引入Spring框架。

undefined

def spring_version = "4.3.27.RELEASE"
List spring = [
        "org.springframework:spring-core:$spring_version",
        "org.springframework:spring-beans:$spring_version",
        "org.springframework:spring-context:$spring_version",
        "org.springframework:spring-tx:$spring_version",
]
dependencies {
    testImplementation group: 'junit', name: 'junit', version: '4.12'
    implementation ("org.fisco-bcos.java-sdk:fisco-bcos-java-sdk:2.9.1")
    implementation spring
}

在asset-app/test/resources目錄下建立配置檔案applicationContext.xml,寫入配置內容。各配置項的內容可參考Java SDK 配置說明,該配置說明以toml配置檔案為例,本例中的配置項與該配置項相對應。

undefined

applicationContext.xml的內容如下:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="
       xmlns:xsi="
       xsi:schemaLocation="
    /spring-beans-4.0.xsd">
    <bean id="defaultConfigProperty" class="org.fisco.bcos.sdk.config.model.ConfigProperty">
        <property name="cryptoMaterial">
            <map>
                <entry key="certPath" value="conf" />
            </map>
        </property>
        <property name="network">
            <map>
                <entry key="peers">
                    <list>
                        <value>127.0.0.1:20200</value>
                        <value>127.0.0.1:20201</value>
                    </list>
                </entry>
            </map>
        </property>
        <property name="account">
            <map>
                <entry key="keyStoreDir" value="account" />
                <entry key="accountAddress" value="" />
                <entry key="accountFileFormat" value="pem" />
                <entry key="password" value="" />
                <entry key="accountFilePath" value="" />
            </map>
        </property>
        <property name="threadPool">
            <map>
                <entry key="channelProcessorThreadSize" value="16" />
                <entry key="receiptProcessorThreadSize" value="16" />
                <entry key="maxBlockingQueueSize" value="102400" />
            </map>
        </property>
    </bean>
    <bean id="defaultConfigOption" class="org.fisco.bcos.sdk.config.ConfigOption">
        <constructor-arg name="configProperty">
            <ref bean="defaultConfigProperty"/>
        </constructor-arg>
    </bean>
    <bean id="bcosSDK" class="org.fisco.bcos.sdk.BcosSDK">
        <constructor-arg name="configOption">
            <ref bean="defaultConfigOption"/>
        </constructor-arg>
    </bean>
</beans>

注意:如果搭鏈時設定的jsonrpc_listen_ip為127.0.0.1或者0.0.0.0,channel_port為20200, 則applicationContext.xml配置不用修改。若區塊鏈節點配置有改動,需要同樣修改配置applicationContext.xml的network屬性下的peers配置選項,配置所連線節點的IP:channel_listen_port。

在以上配置檔案中,我們指定了證照存放的位certPath的值為conf。接下來我們需要把SDK用於連線節點的證照放到指定的conf目錄下。

# 假設我們將asset-app放在~/fisco目錄下 進入~/fisco目錄
$ cd ~/fisco
# 建立放置證照的資料夾
$ mkdir -p asset-app/src/test/resources/conf
# 複製節點證照到專案的資源目錄
$ cp -r nodes/127.0.0.1/sdk/* asset-app/src/test/resources/conf
# 若在IDE直接執行,複製證照到resources路徑
$ mkdir -p asset-app/src/main/resources/conf
$ cp -r nodes/127.0.0.1/sdk/* asset-app/src/main/resources/conf

undefined

5. 業務邏輯開發

我們已經介紹瞭如何在自己的專案中引入以及配置Java SDK,本節介紹如何透過Java程式呼叫合約,同樣以示例的資產管理說明。

第一步.將3編譯好的Java合約引入專案中

cd ~/fisco  
# 將編譯好的合約Java類引入專案中。
cp console/contracts/sdk/java/org/fisco/bcos/asset/contract/Asset.java asset-app/src/main/java/org/fisco/bcos/asset/contract/Asset.java

undefined

第二步.開發業務邏輯

在路徑/src/main/java/org/fisco/bcos/asset/client目錄下,建立AssetClient.java類,透過呼叫Asset.java實現對合約的部署與呼叫

undefined

AssetClient.java 程式碼如下:

package org.fisco.bcos.asset.client;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.List;
import java.util.Properties;
import org.fisco.bcos.asset.contract.Asset;
import org.fisco.bcos.sdk.BcosSDK;
import org.fisco.bcos.sdk.abi.datatypes.generated.tuples.generated.Tuple2;
import org.fisco.bcos.sdk.client.Client;
import org.fisco.bcos.sdk.crypto.keypair.CryptoKeyPair;
import org.fisco.bcos.sdk.model.TransactionReceipt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
public class AssetClient {
    static Logger logger = LoggerFactory.getLogger(AssetClient.class);
    private BcosSDK bcosSDK;
    private Client client;
    private CryptoKeyPair cryptoKeyPair;
    public void initialize() throws Exception {
        @SuppressWarnings("resource")
        ApplicationContext context =
                new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        bcosSDK = context.getBean(BcosSDK.class);
        client = bcosSDK.getClient(1);
        cryptoKeyPair = client.getCryptoSuite().createKeyPair();
        client.getCryptoSuite().setCryptoKeyPair(cryptoKeyPair);
        logger.debug("create client for group1, account address is " + cryptoKeyPair.getAddress());
    }
    public void deployAssetAndRecordAddr() {
        try {
            Asset asset = Asset.deploy(client, cryptoKeyPair);
            System.out.println(
                    " deploy Asset success, contract address is " + asset.getContractAddress());
            recordAssetAddr(asset.getContractAddress());
        } catch (Exception e) {
            // TODO Auto-generated catch block
            // e.printStackTrace();
            System.out.println(" deploy Asset contract failed, error message is  " + e.getMessage());
        }
    }
    public void recordAssetAddr(String address) throws FileNotFoundException, IOException {
        Properties prop = new Properties();
        prop.setProperty("address", address);
        final Resource contractResource = new ClassPathResource("contract.properties");
        FileOutputStream fileOutputStream = new FileOutputStream(contractResource.getFile());
        prop.store(fileOutputStream, "contract address");
    }
    public String loadAssetAddr() throws Exception {
        // load Asset contact address from contract.properties
        Properties prop = new Properties();
        final Resource contractResource = new ClassPathResource("contract.properties");
        prop.load(contractResource.getInputStream());
        String contractAddress = prop.getProperty("address");
        if (contractAddress == null || contractAddress.trim().equals("")) {
            throw new Exception(" load Asset contract address failed, please deploy it first. ");
        }
        logger.info(" load Asset address from contract.properties, address is {}", contractAddress);
        return contractAddress;
    }
    public void queryAssetAmount(String assetAccount) {
        try {
            String contractAddress = loadAssetAddr();
            Asset asset = Asset.load(contractAddress, client, cryptoKeyPair);
            Tuple2<BigInteger, BigInteger> result = asset.select(assetAccount);
            if (result.getValue1().compareTo(new BigInteger("0")) == 0) {
                System.out.printf(" asset account %s, value %s \n", assetAccount, result.getValue2());
            } else {
                System.out.printf(" %s asset account is not exist \n", assetAccount);
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            // e.printStackTrace();
            logger.error(" queryAssetAmount exception, error message is {}", e.getMessage());
            System.out.printf(" query asset account failed, error message is %s\n", e.getMessage());
        }
    }
    public void registerAssetAccount(String assetAccount, BigInteger amount) {
        try {
            String contractAddress = loadAssetAddr();
            Asset asset = Asset.load(contractAddress, client, cryptoKeyPair);
            TransactionReceipt receipt = asset.register(assetAccount, amount);
            List<Asset.RegisterEventEventResponse> response = asset.getRegisterEventEvents(receipt);
            if (!response.isEmpty()) {
                if (response.get(0).ret.compareTo(new BigInteger("0")) == 0) {
                    System.out.printf(
                            " register asset account success => asset: %s, value: %s \n", assetAccount, amount);
                } else {
                    System.out.printf(
                            " register asset account failed, ret code is %s \n", response.get(0).ret.toString());
                }
            } else {
                System.out.println(" event log not found, maybe transaction not exec. ");
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            // e.printStackTrace();
            logger.error(" registerAssetAccount exception, error message is {}", e.getMessage());
            System.out.printf(" register asset account failed, error message is %s\n", e.getMessage());
        }
    }
    public void transferAsset(String fromAssetAccount, String toAssetAccount, BigInteger amount) {
        try {
            String contractAddress = loadAssetAddr();
            Asset asset = Asset.load(contractAddress, client, cryptoKeyPair);
            TransactionReceipt receipt = asset.transfer(fromAssetAccount, toAssetAccount, amount);
            List<Asset.TransferEventEventResponse> response = asset.getTransferEventEvents(receipt);
            if (!response.isEmpty()) {
                if (response.get(0).ret.compareTo(new BigInteger("0")) == 0) {
                    System.out.printf(
                            " transfer success => from_asset: %s, to_asset: %s, amount: %s \n",
                            fromAssetAccount, toAssetAccount, amount);
                } else {
                    System.out.printf(
                            " transfer asset account failed, ret code is %s \n", response.get(0).ret.toString());
                }
            } else {
                System.out.println(" event log not found, maybe transaction not exec. ");
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            // e.printStackTrace();
            logger.error(" registerAssetAccount exception, error message is {}", e.getMessage());
            System.out.printf(" register asset account failed, error message is %s\n", e.getMessage());
        }
    }
    public static void Usage() {
        System.out.println(" Usage:");
        System.out.println(
                "\t java -cp conf/:lib/*:apps/* org.fisco.bcos.asset.client.AssetClient deploy");
        System.out.println(
                "\t java -cp conf/:lib/*:apps/* org.fisco.bcos.asset.client.AssetClient query account");
        System.out.println(
                "\t java -cp conf/:lib/*:apps/* org.fisco.bcos.asset.client.AssetClient register account value");
        System.out.println(
                "\t java -cp conf/:lib/*:apps/* org.fisco.bcos.asset.client.AssetClient transfer from_account to_account amount");
        System.exit(0);
    }
    public static void main(String[] args) throws Exception {
        if (args.length < 1) {
            Usage();
        }
        AssetClient client = new AssetClient();
        client.initialize();
        switch (args[0]) {
            case "deploy":
                client.deployAssetAndRecordAddr();
                break;
            case "query":
                if (args.length < 2) {
                    Usage();
                }
                client.queryAssetAmount(args[1]);
                break;
            case "register":
                if (args.length < 3) {
                    Usage();
                }
                client.registerAssetAccount(args[1], new BigInteger(args[2]));
                break;
            case "transfer":
                if (args.length < 4) {
                    Usage();
                }
                client.transferAsset(args[1], args[2], new BigInteger(args[3]));
                break;
            default:
            {
                Usage();
            }
        }
        System.exit(0);
    }
}

讓我們透過AssetClient這個例子,來了解FISCO BCOS Java SDK的呼叫:

  • 初始化

初始化程式碼的主要功能為構造Client與CryptoKeyPair物件,這兩個物件在建立對應的合約類物件(呼叫合約類的deploy或者load函式)時需要使用。

// 函式initialize中進行初始化 
// 初始化BcosSDK
@SuppressWarnings("resource")
ApplicationContext context =
        new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
bcosSDK = context.getBean(BcosSDK.class);
// 初始化可向群組1發交易的Client
client = bcosSDK.getClient(1);
// 隨機生成傳送交易的公私鑰對
cryptoKeyPair = client.getCryptoSuite().createKeyPair();
client.getCryptoSuite().setCryptoKeyPair(cryptoKeyPair);
logger.debug("create client for group1, account address is " + cryptoKeyPair.getAddress());
  • 構造合約類物件

可以使用deploy或者load函式初始化合約物件,兩者使用場景不同,前者適用於初次部署合約,後者在合約已經部署並且已知合約地址時使用。

// 部署合約
Asset asset = Asset.deploy(client, cryptoKeyPair);
// 載入合約地址
Asset asset = Asset.load(contractAddress, client, cryptoKeyPair);
  • 介面呼叫

使用合約物件呼叫對應的介面,處理返回結果。

// select介面呼叫
 Tuple2<BigInteger, BigInteger> result = asset.select(assetAccount);
// register介面呼叫
TransactionReceipt receipt = asset.register(assetAccount, amount);
// transfer介面
TransactionReceipt receipt = asset.transfer(fromAssetAccount, toAssetAccount, amount);

在asset-app/tool目錄下新增一個呼叫AssetClient的指令碼asset_run.sh。

undefined

#!/bin/bash 
function usage() 
{
    echo " Usage : "
    echo "   bash asset_run.sh deploy"
    echo "   bash asset_run.sh query    asset_account "
    echo "   bash asset_run.sh register asset_account asset_amount "
    echo "   bash asset_run.sh transfer from_asset_account to_asset_account amount "
    echo " "
    echo " "
    echo "examples : "
    echo "   bash asset_run.sh deploy "
    echo "   bash asset_run.sh register  Asset0  10000000 "
    echo "   bash asset_run.sh register  Asset1  10000000 "
    echo "   bash asset_run.sh transfer  Asset0  Asset1 11111 "
    echo "   bash asset_run.sh query Asset0"
    echo "   bash asset_run.sh query Asset1"
    exit 0
}
    case $1 in
    deploy)
            [ $# -lt 1 ] && { usage; }
            ;;
    register)
            [ $# -lt 3 ] && { usage; }
            ;;
    transfer)
            [ $# -lt 4 ] && { usage; }
            ;;
    query)
            [ $# -lt 2 ] && { usage; }
            ;;
    *)
        usage
            ;;
    esac
    java -Djdk.tls.namedGroups="secp256k1" -cp 'apps/*:conf/:lib/*' org.fisco.bcos.asset.client.AssetClient $@

接著,配置好log。在asset-app/src/test/resources目錄下建立log4j.properties

undefined

### set log levels ###
log4j.rootLogger=DEBUG, file
### output the log information to the file ###
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.DatePattern='_'yyyyMMddHH'.log'
log4j.appender.file.File=./log/sdk.log
log4j.appender.file.Append=true
log4j.appender.file.filter.traceFilter=org.apache.log4j.varia.LevelRangeFilter
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p] [%-d{yyyy-MM-dd HH:mm:ss}] %C{1}.%M(%L) | %m%n
###output the log information to the console ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%p] [%-d{yyyy-MM-dd HH:mm:ss}] %C{1}.%M(%L) | %m%n

接著,透過配置gradle中的Jar命令,指定複製和編譯任務。並引入日誌庫,在asset-app/src/test/resources目錄下,建立一個空的contract.properties檔案,用於應用在執行時存放合約地址。

undefined

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
    compile ("org.fisco-bcos.java-sdk:fisco-bcos-java-sdk:2.9.1")
    compile spring
    compile ('org.slf4j:slf4j-log4j12:1.7.25')
    runtime ('org.slf4j:slf4j-log4j12:1.7.25')
}
jar {
    destinationDir file('dist/apps')
    archiveName project.name + '.jar'
    exclude '**/*.xml'
    exclude '**/*.properties'
    exclude '**/*.crt'
    exclude '**/*.key'
    doLast {
        copy {
            from configurations.runtime
            into 'dist/lib'
        }
        copy {
            from file('src/test/resources/')
            into 'dist/conf'
        }
        copy {
            from file('tool/')
            into 'dist/'
        }
        copy {
            from file('src/test/resources/contract')
            into 'dist/contract'
        }
    }
}

至此,我們已經完成了這個應用的開發。最後,我們得到的assert-app的目錄結構如下:

|-- build.gradle // gradle配置檔案
|-- gradle
|   |-- wrapper
|       |-- gradle-wrapper.jar // 用於下載Gradle的相關程式碼實現
|       |-- gradle-wrapper.properties // wrapper所使用的配置資訊,比如gradle的版本等資訊
|-- gradlew // Linux或者Unix下用於執行wrapper命令的Shell指令碼
|-- gradlew.bat // Windows下用於執行wrapper命令的批處理指令碼
|-- src
|   |-- main
|   |   |-- java
|   |   |     |-- org
|   |   |          |-- fisco
|   |   |                |-- bcos
|   |   |                      |-- asset
|   |   |                            |-- client // 放置客戶端呼叫類
|   |   |                                   |-- AssetClient.java
|   |   |                            |-- contract // 放置Java合約類
|   |   |                                   |-- Asset.java
|   |   |-- resources
|   |        |-- conf
|   |               |-- ca.crt
|   |               |-- node.crt
|   |               |-- node.key
|   |               |-- sdk.crt
|   |               |-- sdk.key
|   |               |-- sdk.publickey
|   |        |-- applicationContext.xml // 專案配置檔案
|   |        |-- contract.properties // 儲存部署合約地址的檔案
|   |        |-- log4j.properties // 日誌配置檔案
|   |        |-- contract //存放solidity約檔案
|   |                |-- Asset.sol
|   |                |-- Table.sol
|   |-- test
|       |-- resources // 存放程式碼資原始檔
|           |-- conf
|                  |-- ca.crt
|                  |-- node.crt
|                  |-- node.key
|                  |-- sdk.crt
|                  |-- sdk.key
|                  |-- sdk.publickey
|           |-- applicationContext.xml // 專案配置檔案
|           |-- contract.properties // 儲存部署合約地址的檔案
|           |-- log4j.properties // 日誌配置檔案
|           |-- contract //存放solidity約檔案
|                   |-- Asset.sol
|                   |-- Table.sol
|
|-- tool
    |-- asset_run.sh // 專案執行指令碼

6. 執行應用

至此我們已經介紹使用區塊鏈開發資產管理應用的所有流程並實現了功能,接下來可以執行專案,測試功能是否正常。

undefined

  • 編譯
# 切換到專案目錄
$ cd ~/fisco/asset-app
# 編譯專案
$ ./gradlew build

編譯成功之後,將在專案根目錄下生成dist目錄。dist目錄下有一個asset_run.sh指令碼,簡化專案執行。現在開始一一驗證本文開始定下的需求。

  • 部署Asset.sol合約
# 進入dist目錄
$ cd dist
$ bash asset_run.sh deploy
Deploy Asset successfully, contract address is 0xd09ad04220e40bb8666e885730c8c460091a4775
  • 註冊資產
$ bash asset_run.sh register Alice 100000
Register account successfully => account: Alice, value: 100000
$ bash asset_run.sh register Bob 100000
Register account successfully => account: Bob, value: 100000
  • 查詢資產
$ bash asset_run.sh query Alice
account Alice, value 100000
$ bash asset_run.sh query Bob
account Bob, value 100000
  • 資產轉移
$ bash asset_run.sh transfer Alice Bob  50000
Transfer successfully => from_account: Alice, to_account: Bob, amount: 50000
$ bash asset_run.sh query Alice
account Alice, value 50000
$ bash asset_run.sh query Bob
account Bob, value 150000

總結: 至此,我們透過合約開發,合約編譯,SDK配置與業務開發構建了一個基於FISCO BCOS聯盟區塊鏈的應用。

文章來源:FISCO BCOS

文章原標題:《開發第一個區塊鏈應用》

如有侵權請與我們聯絡刪除。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70012206/viewspace-2927495/,如需轉載,請註明出處,否則將追究法律責任。

相關文章