区块链供应链金融实战2

Posted 最老程序员闫涛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了区块链供应链金融实战2相关的知识,希望对你有一定的参考价值。

在本篇博文中,我们将讲解在金链盟下,编写一个最简单的用户间转账的智能合约,并通过Java应用程序调用这个智能合约,为我们实现复杂的区块链供应链金融应用打下基础。
我们在这里利用Fisco Bcos提供的CRUD接口,来开发智能合约,供Web服务器来调用,从而实现各种复杂的业务逻辑。之所以选择CRUD接口,是因为该接口屏蔽了区块链的细节,使我们像做普通数据库应用开发一样来做智能合约开发。
我们首先创建资产管理表(t_cbdc):

字段名中文名称类型备注
account账户名称string主键
cbdc_value余额uint256以分乘10000为单位

在FISCO BCOS中,智能合约通常使用solidity语言来编写,下面我们来实现上述业务逻辑。
我们要实现如下接口:

// 查询资产金额
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)

接下来我们实现cbdc.sol智能合约,这个智能合约与官网教程中的几乎一模一样,但是修改了名字:

pragma solidity ^0.4.24;

import "./Table.sol";

contract Cbdc 
    // event
    event RegisterEvent(int256 ret, string account, uint256 cbdc_value);
    event TransferEvent(int256 ret, string from_account, string to_account, uint256 amount);
    
    constructor() public 
        // 构造函数中创建t_cbdc表
        createTable();
    

    function createTable() private 
        TableFactory tf = TableFactory(0x1001); 
        // 资产管理表, key : account, field : cbdc_value
        // |  资产账户(主键)      |     资产金额       |
        // |-------------------- |-------------------|
        // |        account      |    cbdc_value    |     
        // |---------------------|-------------------|
        //
        // 创建表
        tf.createTable("t_cbdc", "account", "cbdc_value");
    

    function openTable() private returns(Table) 
        TableFactory tf = TableFactory(0x1001);
        Table table = tf.openTable("t_cbdc");
        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 cbdc_value = 0;
        if (0 == uint256(entries.size())) 
            return (-1, cbdc_value);
         else 
            Entry entry = entries.get(0);
            return (0, uint256(entry.getInt("cbdc_value")));
        
    

    /*
    描述 : 资产注册
    参数 : 
            account : 资产账户
            amount  : 资产金额
    返回值:
            0  资产注册成功
            -1 资产账户已存在
            -2 其他错误
    */
    function register(string account, uint256 cbdc_value) public returns(int256)
        int256 ret_code = 0;
        int256 ret= 0;
        uint256 temp_cbdc_value = 0;
        // 查询账户是否存在
        (ret, temp_cbdc_value) = select(account);
        if(ret != 0) 
            Table table = openTable();
            
            Entry entry = table.newEntry();
            entry.set("account", account);
            entry.set("cbdc_value", int256(cbdc_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, cbdc_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_cbdc_value = 0;
        uint256 to_cbdc_value = 0;
        
        // 转移账户是否存在?
        (ret, from_cbdc_value) = select(from_account);
        if(ret != 0) 
            ret_code = -1;
            // 转移账户不存在
            emit TransferEvent(ret_code, from_account, to_account, amount);
            return ret_code;

        

        // 接受账户是否存在?
        (ret, to_cbdc_value) = select(to_account);
        if(ret != 0) 
            ret_code = -2;
            // 接收资产的账户不存在
            emit TransferEvent(ret_code, from_account, to_account, amount);
            return ret_code;
        

        if(from_cbdc_value < amount) 
            ret_code = -3;
            // 转移资产的账户金额不足
            emit TransferEvent(ret_code, from_account, to_account, amount);
            return ret_code;
         

        if (to_cbdc_value + amount < to_cbdc_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("cbdc_value", int256(from_cbdc_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("cbdc_value", int256(to_cbdc_value + amount));
        // 更新接收账户
        table.update(to_account, entry1, table.newCondition());

        emit TransferEvent(ret_code, from_account, to_account, amount);

        return ret_code;
    

将Cbdc.sol放到fbc/console/contracts/solidity目录下,在fbc/console目录下,运行如下命令进行编译:

./sol2java com.arxandt.scf.fbc.sol.contract

如果编写的文件没有语法错误的话,编译好的文件将放在console/contracts/sdk目录下。
下载官网中的java工程asset-app.tar.gz,我们设scf/fbc为根目录,在该目录下解压asset-app.tar.gz。
将生成的java文件拷贝到对应的目录下:

cp console/contracts/sdk/java/com/arxandt/scf/fbc/sol/contract/Cbdc.java asset-app/src/main/java/com/arxandt/scf/fbc/sol/contract/.

将合约源码拷贝到/src/main/resources/contract目录下

cp console/contracts/solidity/*.sol asset-app/src/main/resources/contract/.

进入asset-app目录下,拷贝证书文件:

cp ../nodes/0.0.0.0/sdk/* src/main/resources/.

这是一个gradle的java工程,我们首先编译该工程,看看有没有问题:

./gradlew build

编译之后,会在dist/apps目录下生成asset-app.jar文件。确保节点处于启动状态,进入到dist目录,运行该程序:

bash asset_run.sh deploy

运行结果为:

创建两个测试账户:

bash asset_run.sh register alice007 100
bash asset_run.sh register bob007 200


账户间转账:

bash asset_run.sh transfer bob007 alice007 80


查询账户余额:

bash asset_run.sh query alice007


如果大家可以成功复制上述结果,我们就可以开始下一步开发工作了。官方给的示例是一个独立的程序,而我们实际的应用场景中,我们通常需要在Web服务器中调用智能合约,来完成相应的业务逻辑。因此我们下一步的任务就是将这个工程改造为SpringBoot工程,将deploy、register、transfer操作变为相应的RESTful请求。
为了使用智能合约,我们需要定义一个可以调用Cbdc智能合约的工具类,如下所示:

package com.arxandt.scf.fbc.sol.client;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.List;
import java.util.Properties;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.fisco.bcos.channel.client.Service;
import org.fisco.bcos.web3j.crypto.Credentials;
import org.fisco.bcos.web3j.crypto.Keys;
import org.fisco.bcos.web3j.protocol.Web3j;
import org.fisco.bcos.web3j.protocol.channel.ChannelEthereumService;
import org.fisco.bcos.web3j.protocol.core.methods.response.TransactionReceipt;
import org.fisco.bcos.web3j.tuples.generated.Tuple2;
import org.fisco.bcos.web3j.tx.gas.StaticGasProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import com.arxandt.scf.fbc.sol.contract.Cbdc;
import com.arxandt.scf.fbc.sol.contract.Cbdc.RegisterEventEventResponse;
import com.arxandt.scf.fbc.sol.contract.Cbdc.TransferEventEventResponse;

public class CbdcClient 

	static Logger logger = LoggerFactory.getLogger(CbdcClient.class);

	private Web3j web3j;

	private Credentials credentials;

	public Web3j getWeb3j() 
		return web3j;
	

	public void setWeb3j(Web3j web3j) 
		this.web3j = web3j;
	

	public Credentials getCredentials() 
		return credentials;
	

	public void setCredentials(Credentials credentials) 
		this.credentials = credentials;
	

	public final static String CONTRACT_FILE = "/home/yantao/scf/contract.properties";
	/**
	 * for CBDC contract
	 * 
	 * */
	public void recordCbdcAddr(String address) throws FileNotFoundException, IOException 
		Properties prop = new Properties();
		prop.setProperty("address", address);
		FileOutputStream fileOutputStream = new FileOutputStream(CbdcClient.CONTRACT_FILE, false);
		prop.store(fileOutputStream, "contract address");
		System.out.println("CbdcClient.recordCbdcAddr 4");
	

	/**
	 * For CBDC contract
	 */
	public String loadCbdcAddr() throws Exception 
		// load Cbdc contact address from contract.properties
		Properties prop = new Properties();
		prop.load(new FileInputStream(CbdcClient.CONTRACT_FILE));
		String contractAddress = prop.getProperty("address");
		if (contractAddress == null || contractAddress.trim().equals("")) 
			throw new Exception(" load Cbdc contract address failed, please deploy it first. ");
		
		
		logger.info(" load Cbdc address from contract.properties, address is ", contractAddress);
		return contractAddress;
	

	public void initialize() throws Exception 
		// init the Service
		@SuppressWarnings("resource")
		ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
		Service service = context.getBean(Service.class);
		service.run();
		ChannelEthereumService channelEthereumService = new ChannelEthereumService();
		channelEthereumService.setChannelService(service);
		Web3j web3j = Web3j.build(channelEthereumService, 1);
		// init Credentials
		Credentials credentials = Credentials.create(Keys.createEcKeyPair());
		setCredentials(credentials);
		setWeb3j(web3j);
		logger.debug(" web3j is " + web3j + " ,credentials is " + credentials);
	

	private static BigInteger gasPrice = new BigInteger("30000000");
	private static BigInteger gasLimit = new BigInteger("30000000");

	public void deployCbdcAndRecordAddr() 
		try 
			Cbdc cbdc = Cbdc.deploy(web3j, credentials, new StaticGasProvider(gasPrice, gasLimit)).send();
			System.out.println(" deploy CBDC success, contract address is " + cbdc.getContractAddress());

			recordCbdcAddr(cbdc.getContractAddress());
		 catch (Exception e) 
			System.out.println(" deploy CBDC contract failed, error message is  " + e.getMessage());
		
	


	public BigInteger queryCbdcAmount(String cbdcAccount) 
		BigInteger balance = new BigInteger("0");
		try 
			String contractAddress = loadCbdcAddr();

			Cbdc cbdc = Cbdc.load(contractAddress, web3j, credentials, new StaticGasProvider(gasPrice, gasLimit));
			Tuple2<BigInteger, BigInteger> result = cbdc.select(cbdcAccount).send();
			balance = result.getValue2();
			if (result.getValue1().compareTo(new BigInteger("0")) == 0) 
				System.out.printf(" cbdc account %s, value %s \\n", cbdcAccount, result.getValue2());
			 else 
				System.out.printf(" %s cbdc account is not exist \\n", cbdcAccount);
			
		 catch (Exception e) 
			logger.error(" queryCbdcAmount exception, error message is ", e.getMessage());
			System.out.printf(" query cbdc account failed, error message is %s\\n", e.getMessage());
		
		return balance;
	

	public void registerCbdcAccount(String cbdcAccount, BigInteger amount) 
		try 
			String contractAddress = loadCbdcAddr();
			Cbdc cbdc = Cbdc.load(contractAddress, web3j, credentials, new StaticGasProvider(gasPrice, gasLimit));
			TransactionReceipt receipt = cbdc.register(cbdcAccount, amount).send();
			List<RegisterEventEventResponse> response = cbdc.getRegisterEventEvents(receipt);
			if (!response.isEmpty()) 
				if (response.get(0).ret.compareTo(new BigInteger("0")) == 0) 
					System.out.printf(" register cbdc account success => cbdc: %s, value: %s \\n", cbdcAccount,
							amount);
				 else 
					System.out.printf(" register cbdc 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) 以上是关于区块链供应链金融实战2的主要内容,如果未能解决你的问题,请参考以下文章

区块链供应链金融实战3

Hyperledger Fabric区块链供应链金融实战1

区块链技术在金融领域应用发展情况

区块链作为金融基础设施发展

区块链使能的供应链金融——区块链信息治理

基于区块链技术的供应链金融平台