基于以太坊的区块链溯源案例开发

2021年1月7日11:36:10以太坊123,156阅读模式
摘要

以太坊溯源方案demo,便于熟悉区块链上链及溯源流程,包括以太坊网络搭建,合约开发和部署及后台网关开发整套代码。

 

基于以太坊的区块链溯源案例开发

 

一种基于以太坊的溯源方案,可以运行demo感受以太坊特性。如图所示是本项目Demo的整体架构设计,分为三层:应用层(APP),服务层(Nodejs-service),区块链层(以太坊),,该设计思路与方案的可见下面内容。应用层通过请求服务层,将操作区块链的操作交给服务层去做,避免了直接操作区块链,在服务层又可以作很多事情,如权限控制检查用户操作、消息队列避免拥塞等,最核心的功能是将来自APP的请求根据业务的不同来分离,进一步操作智能合约上不同的方法。区块链网络层运行以太坊节点,在其上部署智能合约,并处理来自服务层的数据。

基于以太坊的区块链溯源案例开发

图0-1溯源项目架构设计

 

基于以上设计架构,本文分为三部分进行阐述:

第一部分,以太坊网络的搭建,介绍以太坊私链的搭建过程,不论后面合约逻辑如何,本章是搭建以太坊私链所通用的技术。

第二部分,合约开发和部署,介绍合约的设计逻辑和方案,并附上源码。

第三部分,后台开发,即是基于nodejs的服务层开发,作为用户App和区块链网络之间的媒介,介绍如何响应用户请求并随之操作区块链。随后介绍用户如何利用该层的API服务与区块链交互。

第一部分 以太坊网络搭建

1.下载geth客户端

(1) 下载

进入网站https://geth.ethereum.org/downloads/,进行geth客户端下载,可根据不同的操作系统自行下载匹配的geth。

(2) 安装

直接点击二进制包进行安装。

2.使用geth生成账户

(1) 使用geth命令:geth account new

(2) 输入密码,geth会根据用户输入自动生成生成keystore文件,(默认路径为C:\Users\Administrator.xxx\AppData\Roaming\Ethereum)

(3) 在系统任意位置创建数据目录data0,作为同步以太坊数据的目录。

(4) 将上述keystore文件复制到data0文件夹下

(5) 按照上述方式预先生成n个账户,并将每个账户对应的密码文件(以回车换行)放入data0同级目录,存为pwd。

提示:挖矿者的账户的keystore一定要存在于 "extraData",如:

"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000e578252579e5f43fe124fe1d8236f0e5250c11970000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"

其中红色部分为miner的地址。

3.配置创世区块

在data0同级目录下创建创始区块配置文件,并将上述生成的账户填入配置文件alloc字段中,以自定义分配初始以太币:

4.初始化

进入到以太坊数据目录,然后使用geth命令:

geth --datadir data0 init Genesis.json

5.启动geth命令

 

(各参数详细解释,请使用geth --help查询)

6.其他

以太坊节点启动正常后,使用内置API(web3)可进行如下操作:

开始挖矿:miner.start()

停止挖矿:miner.stop()

生成新的账户:personal.newAccount()

查看挖矿奖励者:eth.coinbase

账户之间转账交易:

amount = web3.toWei(5,'ether')
personal.unlockAccount(eth.accounts[0])
eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:amount})

第二部分 合约开发和部署

1. 业务需求

我们以药品溯源为例,整个业务场景中,业务节点可分为三类,药品生产商、代理商以及零售商;数据可分为两部分,药品信息及流转信息。药品信息是由药品生产商生产药品时所添加,药品的流转信息由流转渠道上的代理商及零售商等所添加,如图2-1所示。向区块链添加数据时,使用该商品的ID+本业务节点信息上传即可。

基于以太坊的区块链溯源案例开发

图2-1 药品溯源业务

2.合约数据结构

合约使用solidity开发,基于以上业务需求,使用solidity内置的struct及mapping数据类型,并进行嵌套。得到药品溯源可用的数据结构:

溯源信息的存储结构如图2-2所示,由mapping+struct嵌套结构,左边是一个mapping集合,每个mapping元素为一个键值对,”键”为ID值,通过该ID可以获得对应的Struct, 该Struct中包含“药品信息”和“溯源信息”两部分。当查询时,通过ID号码(如二维码编码的NWRuxXJ75fBWUjBZi)查出Mapping中“键”为“NWRuxXJ75fBWUjBZi”的Struct,该Strcut数据为{ dragInfo:999感冒药,哈药六厂, traceInfo:代理商A=>代理商B=>零售商X}

基于以太坊的区块链溯源案例开发

图2-2 合约存储结构

3.开发准备

(1) Solidity

Solidity是一种类似JavaScript的智能合约高级语言,运行在Ethereum虚拟机(EVM)之上,它被设计成以编译的方式生成以太坊虚拟机代码,是开发以太坊智能合约官方推荐语言。

(2) Truffle

Truffle是一个世界级的开发环境,测试框架,以太坊的资源管理通道,致力于让以太坊上的开发变得简单,Truffle有以下:

内置的智能合约编译,链接,部署和二进制文件的管理。

快速开发下的自动合约测试。

脚本化的,可扩展的部署与发布框架。

部署到不管多少的公网或私网的网络环境管理功能

使用EthPM&NPM提供的包管理,使用ERC190标准。

与合约直接通信的直接交互控制台(写完合约就可以命令行里验证了)。

可配的构建流程,支持紧密集成。

在Truffle环境里支持执行外部的脚本。

4.合约代码

说明:由于solidity0.4版本不支持嵌套结构mapping的遍历,因此以字符拼接的方式添加溯源信息,方便直接展示。

pragma solidity  ^0.4.24;  

import "./strings.sol";  
contract testTraceability_addTime {  
	using strings for *;  
	string constant optStr = "==>";  
	string constant leftOpt = "(";  
	string constant rightOpt = ")";  
	event SavedProducer(uint256 dragId,string dragInfo,string[] traceInfo);  
	struct Drag {  
		string dragInfo;  
		string traceInfo;  
	}  
	mapping(string => Drag) allDragsInfo;  
	constructor () public{  
	}  
	function getDrugInformationWithID(string id) returns(string memory,string memory){  
		return(allDragsInfo[id].dragInfo,allDragsInfo[id].traceInfo);  
	}  
	function addDragInfo_init(string ID,string memory dragInfomation,string memory company,string memory time) public {  
		allDragsInfo[ID].dragInfo = dragInfomation;  
		string memory info1 = time.toSlice().concat(rightOpt.toSlice());  
		string memory info2 = leftOpt.toSlice().concat(info1.toSlice());  
		string memory info3 = company.toSlice().concat(info2.toSlice());  
		string memory concatStr = info3.toSlice().concat(optStr.toSlice());  
		allDragsInfo[ID].traceInfo = concatStr;  
	}  
	function addDragInfo_A(string ID,string memory company,string memory time) public {  
		string memory info1 = time.toSlice().concat(rightOpt.toSlice());  
		string memory info2 = leftOpt.toSlice().concat(info1.toSlice());  
		string memory info3 = company.toSlice().concat(info2.toSlice());  
		string memory concatStr = info3.toSlice().concat(optStr.toSlice());  
		allDragsInfo[ID].traceInfo = allDragsInfo[ID].traceInfo.toSlice().concat(concatStr.toSlice());  
	}  
	function addDragInfo_B(string ID,string memory company,string memory time) public {  
		string memory info1 = time.toSlice().concat(rightOpt.toSlice());  
		string memory info2 = leftOpt.toSlice().concat(info1.toSlice());  
		string memory info3 = company.toSlice().concat(info2.toSlice());  
		string memory concatStr = info3.toSlice().concat(optStr.toSlice());  
		allDragsInfo[ID].traceInfo = allDragsInfo[ID].traceInfo.toSlice().concat(concatStr.toSlice());  
	}  
	function addDragInfo_C(string ID,string memory company,string memory time) public {  
		string memory info1 = time.toSlice().concat(rightOpt.toSlice());  
		string memory info2 = leftOpt.toSlice().concat(info1.toSlice());  
		string memory info3 = company.toSlice().concat(info2.toSlice());  
		string memory concatStr = info3.toSlice().concat(optStr.toSlice());  
		allDragsInfo[ID].traceInfo = allDragsInfo[ID].traceInfo.toSlice().concat(concatStr.toSlice());  
	}  
}  

5.合约说明

对于溯源的数据结构操作,有addDragInfo_init,addDragInfo_A,addDragInfo_B,addDragInfo_C以及getDrugInformationWithID C等操作方法,addDragInfo_init为药品生产商添加信息方法,addDragInfo_A,addDragInfo_B,addDragInfo_C三个方法为流转渠道上节点操作,最后getDrugInformationWithID方法为用户使用产品的ID去获取全部溯源信息的操作。

6.使用truffle进行合约部署

(1) 安装truffle

全局安装truffle的npm包:

npm install truffle -g

(2) truffle初始化

进入工程目录,并运行以下命令:

truffle init

(3) 修改相关配置文件

修改如图2-3所示三个文件:

基于以太坊的区块链溯源案例开发

图2-3 truffle运行相关配置文件

介绍下这三个文件:

1_initial_migration.js(默认迁移合约)

var Migrations = artifacts.require("./Migrations.sol");  
module.exports = function(deployer) {  
   deployer.deploy(Migrations);  
}; 

2_deploy_contracts.js(配置需要部署的合约)

//var testTraceability = artifacts.require("./testTraceability.sol");  
var testTraceability_addTime = artifacts.require("./testTraceability_addTime.sol");  
var strings = artifacts.require("./strings.sol");  

module.exports = function(deployer) {  
	deployer.deploy(strings,  {from:"0xe578252579e5f43fe124fe1d8236f0e5250c1197"});// 部署合约,使用旷工地址(保证gas费充足)  
	deployer.link(strings, testTraceability);   // 库链接  
	deployer.deploy(testTraceability,  {from:"0xe578252579e5f43fe124fe1d8236f0e5250c1197"});// 部署合约  
	deployer.deploy(testTraceability_addTime,  {from:"0xe578252579e5f43fe124fe1d8236f0e5250c1197"});// 部署合约  
};  

3_truffle.js(配置目标以太坊相关等信息)

/*  
  *  NB:  since  truffle-hdwallet-provider  0.0.5  you  must  wrap  HDWallet  providers  in  a    
  *  function  when  declaring  them.  Failure  to  do  so  will  cause  commands  to  hang.  ex:  
  *  ```  
  *  mainnet:  {  
  *          provider:  function()  {    
  *              return  new  HDWalletProvider(mnemonic,  'https://mainnet.infura.io/<infura-key>')    
  *          },  
  *          network_id:  '1',  
  *          gas:  4500000,  
  *          gasPrice:  10000000000,  
  *      },  
  */    
//  Allows  us  to  use  ES6  in  our  migrations  and  tests.   

require('babel-register')({  
	ignore: /node_modules\/(?!zeppelin-solidity)/  
});  

require('babel-polyfill');  
module.exports = {  
   //See <http://truffleframework.com/docs/advanced/configuration>  
   //to customize your Truffle configuration!  
    networks: {  
        development: {  
            host: "127.0.0.1",//合约部署的以太坊节点ip  
            port: 7545,       //合约部署的以太坊节点端口  
            network_id: "*",  // 以太坊网络id  
            gas: 8000000      // 合约部署gas上限  
         }  
     },  
     mocha: {  
         useColors: true  
     },  
     solc: {  
         optimizer: {  
             enabled: true,  
             runs: 200  
         }  
     }  
 };  

(4) 编译合约

在工程目录下运行:

truffle compile

truffle compile --all (全部重新编译)

(5) 迁移合约

在工程目录下运行

truffle migrate

truffle migrate --reset (全部重新执行迁移脚本)

第三部分 后台开发

1.后台开发说明

本区块链网络是基于以太坊私链,因此为了保证数据安全和可靠,用户不能直接操作区块链,必须通过中间件服务。因此开发node中间件,可以管控上链数据,并且可以做一些其他必要业务,如权限管理,消息队列,区块数据暂存等等。

2.需求分析

由第二部分已知,三类业务节点(生产商、销售商、零售商)需要分别向区块链中上数据,并且必须基于中间服务,而非直接操作区块链。所以,采用图3-1所示的后台架构,即B端以及C端所有的用户均基于中间件node服务去间接操作或访问区块链。因此,根据各个用户职能的不同开放不同API服务,各个使用者在区块链上进行不同的事务。如图中所示,简单而言,就是药品生产商、代理商A、代理商B、零售商向区块链写数据,用户从区块链读数据。

基于以太坊的区块链溯源案例开发

图3-1 node后台架构图

3.开发准备

(1) Nodejs

在整个开源社区JavaScript应用得最为广泛,目前JavaScript语言所对应的大部分项目都是基于Node.js平台的,所以Nodejs是当之无愧的、最流行的开发平台之一。Nodejs是一个搭建中Chrome V8上的JavaScript即时运行平台,采用的是时间驱动和非阻塞I/O模型,既轻量又高效,非常适合数据密集型、实时的应用,如股票、基金、加密货币等。尤其是Nodejs的包管理工具npm,已经成为全球最活跃的社区,有非常多的第三方包可供开发人员选用,区块链相关的绝大多数组件更是以npm包的形式被开发和应用,因此Nodejs是区块链开发的强大工具。

使用:Nodejs环境安装

(2) Express

Express 是一个简洁而灵活的 node.js Web应用框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。使用 Express 可以快速地搭建一个完整功能的网站,并且支持各种 HTTP 实用工具和中间件,快速方便地创建强大的 API。

使用:引入npm包。

(3) web3 API

以太坊网络是由节点组成的,每一个节点都包含了区块链的一份拷贝。当我们需要调用一份智能合约中的方法时,需要从其中一个节点中查找并告诉它,而以太坊节点只能识别一种叫做 JSON-RPC 的语言,这种语言可读性并不好。Web3.js 可以把这些晦涩难懂的查询语句都隐藏起来了,只需要与方便易懂的 JavaScript 界面进行交互即可。

web3.js是以太坊提供的一个Javascript库,它封装了以太坊的JSON RPC API,提供了一系列与区块链交互的Javascript对象和函数,包括查看网络状态,查看本地账户、查看交易和区块、发送交易、编译/部署智能合约、调用智能合约等,其中最重要的就是与智能合约交互的API。因此,要开发基于以太坊的项目必须熟悉web3js中的常用API。

使用:引入npm包。

4.node代码

通过nodejs+express+web3js开发简单的node server,向外界暴露API接口。外部通过Http请求调用API服务,操作区块链。

const web3 = require('web3');  
const ethRPC = "ws://localhost:8546";  
const serverPort = 10999;  
const provider = new web3.providers.WebsocketProvider(ethRPC);  
const web3Client = new web3(provider);  
const coinbase = '0xe578252579e5f43fe124fe1d8236f0e5250c1197';  
const contract = require("truffle-contract");  
const express = require('express');  
const app = express();  
const cors = require('cors');  

app.use(cors());  
var Resp = (function(yj) {  
    yj = function() {  
       this.dragId = '';  
       this.dragName = '';  
       this.trace = '';  
    };  

    yj.prototype = {  
        toJson: function() {  
           return JSON.stringify(this);  
        }  
   };  
        return yj;  

 }(Resp || function(){}));  


function eraseErrorOfContract(arr) {  

    for (let contrc of arr) {  
           if (typeof contrc.currentProvider.sendAsync !== "function") {  
           contrc.currentProvider.sendAsync = function () {  
                return contrc.currentProvider.send.apply(  
                    contrc.currentProvider, arguments  
               );  
           }  
       }  
   }  
}  

const traceabilityJSON = require('../build/contracts/testTraceability_addTime');  
const traceability = contract(traceabilityJSON);  

traceability.setProvider(provider);  
var traceabilityInst;  
eraseErrorOfContract([traceability]);  

traceability.deployed({  
    from: coinbase,  
    gas: 600000  
}).then(instance => {  
    traceabilityInst = instance;  
    // traceabilityInst.addDragInfo_init('NWRuxXJ75fBWUjBZiDIyR16PdFLIOG/odIos/Z8ggxNqrvQyPMcnXjAVB0JAmHYu4LGxyOaA4FIlOJgzGrPvcIndf0EbYZ80l',"999pai","hayao",{from:coinbase,gas:600000}).then(result => {  

    //     console.log(result);  
    //     return traceabilityInst.addDragInfo_A('NWRuxXJ75fBWUjBZiDIyR16PdFLIOG/odIos/Z8ggxNqrvQyPMcnXjAVB0JAmHYu4LGxyOaA4FIlOJgzGrPvcIndf0EbYZ80l',"经销商1",{from:coinbase,gas:600000})  

    // }).then(result2 => {  
    //     console.log(result2);  
    //     return traceabilityInst.getDrugInformationWithID.call('NWRuxXJ75fBWUjBZiDIyR16PdFLIOG/odIos/Z8ggxNqrvQyPMcnXjAVB0JAmHYu4LGxyOaA4FIlOJgzGrPvcIndf0EbYZ80l',{from:coinbase,gas:600000});  

    // }).then(result3 =>{  
    //     console.log(result3);  
    // }).catch(err => {  
    //     console.log(err)  
    // });  
});  
   
app.get('/addDragInfo_produce',function (req,res) {  // app => router  
    let currentTime = new Date().toLocaleString();  
    traceabilityInst.addDragInfo_init(req.query.id,req.query.dragInfo,req.query.company,currentTime,{from:coinbase,gas:600000}).then(result => {  
        console.log(result);  
        res.end("您(生产商)已将药品信息上链!交易hash:"+result.tx);  
    }).catch(err => {  
        console.log(err);  
        res.end(JSON.stringify(err))  
    });  
});  

app.get('/addDragInfo_A',function (req,res) {  // app => router  
    let currentTime = new Date().toLocaleString();  
    traceabilityInst.addDragInfo_A(req.query.id,req.query.company,currentTime,{from:coinbase,gas:600000}).then(result => {  
        res.end("您(销售商A)已将信息上链!交易hash:"+result.tx)  
    }).catch(err => {  
        res.end(JSON.stringify(err))  
    });  
});  

app.get('/addDragInfo_B',function (req,res) {  // app => router  
    let currentTime = new Date().toLocaleString();  
    traceabilityInst.addDragInfo_B(req.query.id,req.query.company,currentTime,{from:coinbase,gas:600000}).then(result => {  
        res.end("您(销售商B)已将信息上链!交易hash:"+result.tx)  
    }).catch(err => {  
        res.end(JSON.stringify(err))  
    });  
});  

app.get('/addDragInfo_C',function (req,res) {  // app => router  
    let currentTime = new Date().toLocaleString();  
    traceabilityInst.addDragInfo_C(req.query.id,req.query.company,currentTime,{from:coinbase,gas:600000}).then(result => {  
        res.end("您(销售商C)已将信息上链!交易hash:"+result.tx)  
    }).catch(err => {  
        res.end(JSON.stringify(err))  
    });  
});  

app.get('/getDrugInformationWithID',function (req,res) {  // app => router  

    let ret = new Resp();  
    traceabilityInst.getDrugInformationWithID.call(req.query.id,{from:coinbase,gas:600000}).then(result => {  

        ret.dragId = req.query.id;  
        ret.dragName = result[0] ;  
        ret.trace = result[1];  
        res.setHeader('Content-Type', 'text/plain;charset=utf-8');  
        res.end("药品ID:"+ret.dragId+'\n'+"药品名称:"+ret.dragName+'\n'+"溯源信息:"+ret.trace);  
    }).catch(err => {  
        res.end(JSON.stringify(err))  
    });  
});  

var server = app.listen(serverPort, function () {  
    var host = server.address().address;  
    var port = server.address().port;  
    console.log( 'Express started on http://'+ host +':' + port  );  
}); 

5.node服务说明

有需求分析可知,药品生产商、代理商、零售商向区块链写数据,用户从区块链读数据。因此,通过不同的用户通过http访问不同的API,达到各自的业务目的,如下所示:

生产商数据上链操作:http://localhost:8546/addDragInfo_produce

代理商A数据上链操作:http://localhost:8546/addDragInfo_A

代理商B数据上链操作:http://localhost:8546/addDragInfo_B

用户查询溯源信息操作:http://localhost:8546/getDrugInformationWithID