├── .gitignore ├── README.md ├── images ├── 创建应用.png ├── 合约部署.png ├── 应用详情.png └── 编译部署流程.png ├── pom.xml ├── src └── main │ ├── java │ └── cn │ │ └── hyperchain │ │ └── application │ │ ├── Application.java │ │ ├── QsnarkTest.java │ │ ├── ServletInitializer.java │ │ ├── common │ │ ├── constant │ │ │ └── Code.java │ │ ├── listener │ │ │ └── StartupListener.java │ │ ├── response │ │ │ ├── BaseResult.java │ │ │ └── BaseResultFactory.java │ │ └── utils │ │ │ ├── ContractUtils.java │ │ │ ├── DateUtils.java │ │ │ ├── Logger.java │ │ │ └── PropertiesUtils.java │ │ ├── config │ │ └── Swagger2.java │ │ ├── contract │ │ ├── business │ │ │ └── Actor.java │ │ └── invoke │ │ │ └── ContractInvoke.java │ │ └── controller │ │ └── ContractController.java │ └── resources │ ├── contractSourceCode │ └── supplychain.sol │ ├── hyperchain.properties │ ├── lib │ └── qsnarksdk-1.0-SNAPSHOT-jar-with-dependencies.jar │ └── 参考合约 │ ├── SafeMath.sol │ ├── supplychainNoSafeMath.sol │ ├── supplychainWithSafeMath.sol │ ├── testOverflow.sol │ └── useSafeMath.sol └── web └── WEB-INF └── web.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *.iml 3 | .idea 4 | bin 5 | .project 6 | .classpath 7 | .factorypath 8 | log 9 | .settings 10 | .vscode -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # minata 2 | 基于开发者平台的供应链溯源案例(网易云课程) 3 | ## 环境准备 4 | ### 注册与授权 5 | 1. 使用手机号进行注册后 6 | 2. 进入控制台 7 | 3. 创建应用 8 | ![创建应用](images/创建应用.png) 9 | 4. 获取AppKey和AppSecret 10 | ![应用信息](images/应用详情.png) 11 | 1. 获取token 12 | 使用我们的注册的账户和密码包括刚刚的appkey和appsecret来获取token 13 | 在这里我们使用的是开发者平台提供的sdk 14 | ```java 15 | GetTokenReturn getTokenReturn = api.getAccess_Token( 16 | "AppKey", 17 | "AppSecret", 18 | "phone", 19 | "password"); 20 | logger.info(getTokenReturn.getCode(), 21 | getTokenReturn.getAccess_token(), 22 | getTokenReturn.getRefresh_token()); 23 | ``` 24 | 25 | ### 合约的编写 26 | 27 | #### 业务分析 28 | 当前的业务是溯源案例,即供应链溯源。所谓的供应链溯源实则是将商品从原料到买家手中的一些列的行为给记录到区块链上,利用区块链的不可篡改,去中心化这样的特点来保证整个一溯源流程的安全可信。 29 | #### 场景描述 30 | 供应链溯源合约有以下几类参与方: 31 | 32 | - 商品厂商:保存于mapping(address => User) producerMap 33 | - 各级经销商:保存于mapping(address => User) retailerMap 34 | - 消费者:保存于mapping(address => User) customerMap 35 | 各类参与方均通过newUser方法进行**上链**登记。通过传递不同的Actor值来指定不同参与方。 36 | 37 | 厂商首先通过newProduct方法将出厂商品登记到区块链,随后商品分销到下一级经销商,接下来的环节,每一次商品的分销均在接收商品的经销商处调用retailerInnerTransfer方法将商品进行上链登记,最终商品在零售商处由消费者购买,调用fromRetailerToCustomer方法进行最终登记,完成整个出厂-多级分销-零售的流程。商品一旦由厂商登记上链,便可通过getCommodity查询到商品当前的分销信息,只有处于该分销路径上的参与方才允许查询。此外,通过addWhiteList可以为指定参与方添加顶级查询权限,被添加到WhiteList的参与方,即使不参与到某商品的分销路径中,也可查询到该商品的分销信息。 38 | #### 合约代码 39 | ```js 40 | contract SupplyChain { 41 | enum Actor{ Others, Producer, Retailer, Customer} 42 | enum Commoditytype{ Wine, Jewelry, Shrimp, Others} 43 | enum Phase{ Supplier, Producer, Dealer, Retailer, Customer} 44 | 45 | struct User{ 46 | bytes32 ID; 47 | bytes32 name; 48 | Actor actor; 49 | } 50 | 51 | struct Commodity{ 52 | bytes32 commodityID; 53 | bytes32 commodityName; 54 | uint produceTime; 55 | bytes32 producerName; 56 | uint[] timestamps; 57 | bytes32[] retailerNames; 58 | uint sellTime; 59 | bytes32 customerName; 60 | bool isBinding; 61 | address owner; 62 | } 63 | 64 | mapping(address => User) producerMap; 65 | mapping(address => User) retailerMap; 66 | mapping(address => User) customerMap; 67 | mapping(bytes32 => Commodity) commodityMap; 68 | mapping(address => bool) whiteList; 69 | address owner; 70 | 71 | function SupplyChain(){ 72 | owner = msg.sender; 73 | whiteList[owner] = true; 74 | } 75 | 76 | function addWhiteList(address addr){ 77 | whiteList[addr] = true; 78 | } 79 | 80 | function newUser(bytes32 ID, bytes32 name,Actor actor) returns(bool, string){ 81 | User user; 82 | 83 | if(actor == Actor.Producer){ 84 | user = producerMap[msg.sender]; 85 | }else if(actor == Actor.Retailer){ 86 | user = retailerMap[msg.sender]; 87 | }else if(actor == Actor.Customer){ 88 | user = customerMap[msg.sender]; 89 | }else{ 90 | return (false,"the actor is not belong"); 91 | } 92 | 93 | if(user.ID != 0x0){ 94 | return (false, "this ID has been occupied!"); 95 | } 96 | user.ID = ID; 97 | user.name = name; 98 | user.actor = actor; 99 | return (true, "Success"); 100 | } 101 | 102 | // this interface just for producer 103 | function newProduct(bytes32 commodityID, bytes32 commodityName, 104 | uint timestamp) returns(bool,bytes32){ 105 | Commodity commodity = commodityMap[commodityID]; 106 | if(commodity.commodityID != 0x0) { 107 | return (false,"The commodityID already exist!"); 108 | } 109 | User user = producerMap[msg.sender]; 110 | if(user.ID == 0x0) { 111 | return (false,"The producer don't exist!"); 112 | } 113 | commodity.commodityID = commodityID; 114 | commodity.commodityName = commodityName; 115 | commodity.produceTime = timestamp; 116 | commodity.producerName = user.name; 117 | return (true,"Success,produce a new product"); 118 | } 119 | 120 | 121 | // this interface just for retailer 122 | function retailerInnerTransfer(bytes32 commodityID,uint timestamp) returns(bool, string){ 123 | Commodity commodity = commodityMap[commodityID]; 124 | if(commodity.commodityID == 0x0) { 125 | return (false,"The commodityID don't exist!"); 126 | } 127 | User user = retailerMap[msg.sender]; 128 | if(user.ID == 0x0) { 129 | return (false,"The retailer don't exist!"); 130 | } 131 | commodity.timestamps.push(timestamp); 132 | commodity.retailerNames.push( user.name ); 133 | return (true,"Success"); 134 | } 135 | 136 | function fromRetailerToCustomer(bytes32 commodityID,uint timestamp) returns(bool, string){ 137 | Commodity commodity = commodityMap[commodityID]; 138 | if(commodity.commodityID == 0x0) { 139 | return (false,"The commodityID don't exist!"); 140 | } 141 | commodity.sellTime = timestamp; 142 | return (true,"Success,Has been sold"); 143 | } 144 | 145 | // just for Supervision organization 146 | function getCommodityRecordsByWhiteList(bytes32 commodityID) returns(bool,string, 147 | bytes32 producerName,uint produceTime, bytes32[] retailerNames,uint[] retailerTimes 148 | , bytes32 customerName,uint sellTime){ 149 | if(!whiteList[msg.sender]){ 150 | return (false,"you has no access",producerName, produceTime, retailerNames, retailerTimes, customerName,commodity.sellTime); 151 | } 152 | Commodity commodity = commodityMap[commodityID]; 153 | if(commodity.commodityID == 0x0){ 154 | return (false,"The commodityID is not exist",producerName, produceTime, retailerNames, retailerTimes,customerName,commodity.sellTime); 155 | } 156 | return (true,"Success",commodity.producerName, commodity.produceTime, commodity.retailerNames, commodity.timestamps, commodity.customerName,commodity.sellTime); 157 | } 158 | 159 | 160 | function getCommodity(bytes32 commodityID,Actor actor) returns(bool,string, 161 | bytes32 producerName,uint produceTime,bytes32[] retailerNames,uint[] retailerTimes 162 | , bytes32 customerName,uint sellTime){ 163 | Commodity commodity = commodityMap[commodityID]; 164 | if(commodity.commodityID == 0x0){ 165 | return (false,"The commodityID is not exist",producerName,produceTime, 166 | retailerNames,retailerTimes,customerName,sellTime); 167 | } 168 | User user; 169 | if(actor == Actor.Producer){ 170 | user = producerMap[msg.sender]; 171 | }else if(actor == Actor.Retailer){ 172 | user = retailerMap[msg.sender]; 173 | }else if(actor == Actor.Customer){ 174 | user = customerMap[msg.sender]; 175 | }else{ 176 | return (false,"the actor is not belong",producerName,produceTime, 177 | retailerNames,retailerTimes,customerName,sellTime); 178 | } 179 | if(commodity.isBinding){ 180 | if(commodity.owner != msg.sender){ 181 | return (false,"warning,this commodity has been bound",producerName,produceTime, 182 | retailerNames,retailerTimes,customerName,sellTime); 183 | }else{ 184 | (false,"has already bind",commodity.producerName,commodity.retailerNames,commodity.customerName); 185 | } 186 | } 187 | if(commodity.sellTime > 0 ) { 188 | commodity.isBinding = true; 189 | commodity.owner = msg.sender; 190 | commodity.customerName = user.name; 191 | } 192 | return (true,"Success",commodity.producerName,commodity.produceTime,commodity.retailerNames,commodity.timestamps,commodity.customerName,commodity.sellTime); 193 | } 194 | } 195 | ``` 196 | ### 在使用sdk进行编译和部署 197 | ![编译部署](images/编译部署流程.png) 198 | #### 对应的hpyerchain.properties的配置 199 | 由于在sdk上进行编译和部署则需要持久化编译产生的abi和bin已经部署后获得的合约地址 200 | 于是我们在配置文件中配置相关的文件去保存这些内容 201 | **文件内容** 202 | 203 | ```shell 204 | access_token=WVDSRC22PTGS3H4GMFQHGA 205 | # refresh_token=8ZEPJTSRV2CRCGMZHG4MPA 206 | hyperchain_user_address=0xc18b43373fc5ac8effbb5871937056e4bb3aa0ab 207 | # 是否重新编译和部署合约 208 | redeploy=true 209 | # 合约地址文件 210 | contract_address_path=contractAddress 211 | # Abi文件 212 | bin_path=BIN 213 | # ABI文件 214 | abi_path=ABI 215 | ``` 216 | 217 | - 编译 218 | 使用sdk在编译时需要携带token,token存在7200秒的有效时间,超出了时间需要再次获取 219 | ```java 220 | //合约内容,一般会从合约文件中读取 221 | String contractContent = " 222 | contract Accumulator { 223 | uint32 sum = 0; 224 | function increment() { 225 | sum = sum + 1; 226 | } 227 | function getSum() returns(uint32) { 228 | return sum; 229 | } 230 | function add(uint32 num1,uint32 num2) { 231 | sum = sum+num1+num2; 232 | } 233 | } 234 | "; 235 | ``` 236 | 237 | ABI 238 | ```json 239 | [{ 240 | "constant": false, 241 | "inputs": [{ 242 | "name": "num1", 243 | "type": "uint32" 244 | }, { 245 | "name": "num2", 246 | "type": "uint32" 247 | }], 248 | "name": "add", 249 | "outputs": [], 250 | "payable": false, 251 | "type": "function" 252 | }, { 253 | "constant": false, 254 | "inputs": [], 255 | "name": "getSum", 256 | "outputs": [{ 257 | "name": "", 258 | "type": "uint32" 259 | }], 260 | "payable": false, 261 | "type": "function" 262 | }, { 263 | "constant": false, 264 | "inputs": [], 265 | "name": "increment", 266 | "outputs": [], 267 | "payable": false, 268 | "type": "function" 269 | }] 270 | //传入有效的token 进行调用 271 | CompileReturn compileReturn = api.compileContract(ACCESS_TOKEN, contractConten); 272 | ``` 273 | - 部署 274 | ```java 275 | //部署合约并输出合约地址 需要传入 hyperchain账户地址 276 | DeployConReturn deployConReturn = api.deployContract(ACCESS_TOKEN, 277 | BIN, 278 | ADDRESS, 279 | (DevCallback) address -> System.out.println(address)); 280 | ``` 281 | 282 | ### 在开发者平台网页上进行部署合约 283 | ![合约部署](images/合约部署.png) 284 | #### 对应的hyperchain配置 285 | 由于已经部署好了合约则只需闯入abi和合约地址 286 | ```shell 287 | access_token=WVDSRC22PTGS3H4GMFQHGA 288 | # refresh_token=8ZEPJTSRV2CRCGMZHG4MPA 289 | hyperchain_user_address=0xc18b43373fc5ac8effbb5871937056e4bb3aa0ab 290 | # 是否重新编译和部署合约 291 | redeploy=false 292 | ####已有部署好的合约 293 | #合约地址 294 | contract_address=0x07c3c7c4cc7820a169964b1fa5a951b94da618d7 295 | # abi(如果通过代码部署则不填写) 296 | abi=[{"constant":false,"inputs":[{"name":"num1","type":"uint32"},{"name":"num2","type":"uint32"}],"name":"add","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"getSum","outputs":[{"name":"","type":"uint32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"increment","outputs":[],"payable":false,"type":"function"}] 297 | ``` 298 | ### 调用合约函数 299 | ```java 300 | FuncParamReal funcParamReal1 = new FuncParamReal("uint32", 456); 301 | FuncParamReal funcParamReal2 = new FuncParamReal("uint32", 0); 302 | api.invokeContract( 303 | ACCESS_TOKEN, 304 | false, 305 | ADDRESS, 306 | CONTRACT_ADDRESS, 307 | ABI, 308 | list -> { 309 | System.out.println(list); 310 | }, 311 | "add", 312 | funcParamReal1, 313 | funcParamReal2 314 | ); 315 | ``` 316 | -------------------------------------------------------------------------------- /images/创建应用.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qulian-netease/minata/b03a554d3e5b299f7d7d8140d380cec195d8f5f0/images/创建应用.png -------------------------------------------------------------------------------- /images/合约部署.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qulian-netease/minata/b03a554d3e5b299f7d7d8140d380cec195d8f5f0/images/合约部署.png -------------------------------------------------------------------------------- /images/应用详情.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qulian-netease/minata/b03a554d3e5b299f7d7d8140d380cec195d8f5f0/images/应用详情.png -------------------------------------------------------------------------------- /images/编译部署流程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qulian-netease/minata/b03a554d3e5b299f7d7d8140d380cec195d8f5f0/images/编译部署流程.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | cn.hyperchain.application 8 | minata 9 | 1.0-SNAPSHOT 10 | 11 | 12 | org.springframework.boot 13 | spring-boot-starter-parent 14 | 2.0.2.RELEASE 15 | 16 | 17 | 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-aop 22 | 23 | 24 | junit 25 | junit 26 | 4.12 27 | compile 28 | 29 | 30 | 31 | 32 | 33 | redis.clients 34 | jedis 35 | 2.9.0 36 | 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-data-redis 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-web 45 | 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-test 50 | test 51 | 52 | 53 | 54 | 55 | io.springfox 56 | springfox-swagger2 57 | 2.7.0 58 | 59 | 60 | 61 | io.springfox 62 | springfox-swagger-ui 63 | 2.7.0 64 | 65 | 66 | 67 | com.alibaba 68 | fastjson 69 | 1.2.39 70 | 71 | 72 | 73 | io.jsonwebtoken 74 | jjwt 75 | 0.9.0 76 | 77 | 78 | org.springframework 79 | spring-test 80 | 5.0.6.RELEASE 81 | compile 82 | 83 | 84 | org.springframework.boot 85 | spring-boot-test 86 | 87 | 88 | 89 | cn.hyperchain 90 | qsnarksdk 91 | 1.0 92 | system 93 | ${project.basedir}/src/main/resources/lib/qsnarksdk-1.0-SNAPSHOT-jar-with-dependencies.jar 94 | 95 | 96 | 97 | 98 | 99 | 100 | org.springframework.boot 101 | spring-boot-maven-plugin 102 | 103 | true 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/main/java/cn/hyperchain/application/Application.java: -------------------------------------------------------------------------------- 1 | package cn.hyperchain.application; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * @author sunligang 8 | * @date 2018/07/05 9 | */ 10 | 11 | @SpringBootApplication 12 | public class Application { 13 | public static void main(String[] args) { 14 | SpringApplication.run(Application.class, args); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/cn/hyperchain/application/QsnarkTest.java: -------------------------------------------------------------------------------- 1 | package cn.hyperchain.application; 2 | 3 | import cn.hyperchain.application.common.utils.Logger; 4 | import cn.qsnark.sdk.exception.TxException; 5 | import cn.qsnark.sdk.rpc.QsnarkAPI; 6 | import cn.qsnark.sdk.rpc.function.FuncParamReal; 7 | import cn.qsnark.sdk.rpc.function.FunctionDecode; 8 | import cn.qsnark.sdk.rpc.returns.*; 9 | import org.bouncycastle.pqc.math.linearalgebra.ByteUtils; 10 | import org.junit.Test; 11 | 12 | import java.io.IOException; 13 | import java.io.UnsupportedEncodingException; 14 | 15 | 16 | /** 17 | * @author sunligang 18 | * @date 2018/07/05 19 | */ 20 | public class QsnarkTest { 21 | private static QsnarkAPI api = new QsnarkAPI(); 22 | 23 | private static Logger logger = Logger.Builder.getLogger(QsnarkTest.class); 24 | 25 | /** 26 | * 从开发者平台获取token,当然也可以通过sdk api获取 27 | */ 28 | private final static String ACCESS_TOKEN = "RHMW6K5MNZGYFFL13MKJVQ"; 29 | /** 30 | * hyperchain 账户地址 31 | */ 32 | private final static String ADDRESS = "0xa06781871b39a22e0b0576a0eda9ceffda0560e3"; 33 | private final static String BIN = "0x60606040526000805463ffffffff19169055341561001957fe5b5b610101806100296000396000f300606060405263ffffffff60e060020a6000350416633ad14af381146034578063569c5f6d146052578063d09de08a146078575bfe5b3415603b57fe5b605063ffffffff600435811690602435166087565b005b3415605957fe5b605f60a9565b6040805163ffffffff9092168252519081900360200190f35b3415607f57fe5b605060b6565b005b6000805463ffffffff808216850184011663ffffffff199091161790555b5050565b60005463ffffffff165b90565b6000805463ffffffff8082166001011663ffffffff199091161790555b5600a165627a7a723058205196f5c898c244d3ada034d11893c7a5d67acac307f8e5db125810804cf7bb690029"; 34 | private final static String ABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"num1\",\"type\":\"uint32\"},{\"name\":\"num2\",\"type\":\"uint32\"}],\"name\":\"add\",\"outputs\":[],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"getSum\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"payable\":false,\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"increment\",\"outputs\":[],\"payable\":false,\"type\":\"function\"}]"; 35 | private final static String CONTRACT_ADDRESS = "0xc878e596ecf53e9d761ff0664ea126e035644f66"; 36 | 37 | /** 38 | * 创建账户 39 | */ 40 | @Test 41 | public void createAccount() throws Exception { 42 | CreteAccountReturn creteAccountReturn = 43 | api.createAccount(ACCESS_TOKEN); 44 | logger.info(creteAccountReturn.getCode(), 45 | creteAccountReturn.getAddress(), 46 | creteAccountReturn.getId(), 47 | creteAccountReturn.getStatus(), 48 | creteAccountReturn.getTime()); 49 | } 50 | 51 | 52 | @Test 53 | public void compile() throws IOException { 54 | String s = "contract Accumulator{ uint32 sum = 0; function increment(){ sum = sum + 1; } function getSum() returns(uint32){ return sum; } function add(uint32 num1,uint32 num2) { sum = sum+num1+num2; } }"; 55 | CompileReturn compileReturn = api.compileContract(ACCESS_TOKEN, s); 56 | } 57 | 58 | /** 59 | * 合约部署 60 | */ 61 | @Test 62 | public void deploy() throws IOException, InterruptedException { 63 | GetTxReciptReturn getTxReciptReturn = api.deploysyncContract( 64 | ACCESS_TOKEN, 65 | BIN, 66 | ADDRESS 67 | ); 68 | logger.info(getTxReciptReturn); 69 | } 70 | 71 | 72 | /** 73 | * 合约维护 74 | */ 75 | @Test 76 | public void maintainContract() throws IOException { 77 | //升级合约 78 | MainTainReturn upgradeMainTainReturn = api.maintainContract(ACCESS_TOKEN, ADDRESS, 1, BIN, CONTRACT_ADDRESS); 79 | //冻结合约 80 | MainTainReturn freezeMainTainReturn = api.maintainContract(ACCESS_TOKEN, ADDRESS, 2, BIN, CONTRACT_ADDRESS); 81 | //解冻合约 82 | MainTainReturn unFreezeMainTainReturn = api.maintainContract(ACCESS_TOKEN, ADDRESS, 3, BIN, CONTRACT_ADDRESS); 83 | } 84 | 85 | 86 | /** 87 | * 调用 88 | */ 89 | @Test 90 | public void invoke() throws TxException, InterruptedException, IOException { 91 | FuncParamReal funcParamReal1 = new FuncParamReal("uint32", 456); 92 | FuncParamReal funcParamReal2 = new FuncParamReal("uint32", 0); 93 | api.invokeContract( 94 | ACCESS_TOKEN, 95 | false, 96 | ADDRESS, 97 | CONTRACT_ADDRESS, 98 | ABI, 99 | list -> { 100 | System.out.println(list); 101 | }, 102 | "add", 103 | funcParamReal1, 104 | funcParamReal2 105 | ); 106 | } 107 | 108 | @Test 109 | public void invoke2() throws TxException, InterruptedException, IOException { 110 | GetTxReciptReturn getTxReciptReturn = api.invokesyncContract( 111 | ACCESS_TOKEN, 112 | false, 113 | ADDRESS, 114 | CONTRACT_ADDRESS, 115 | ABI, 116 | "getSum" 117 | ); 118 | logger.info(getTxReciptReturn.getRet()); 119 | System.out.println(FunctionDecode.resultDecode("getSum", ABI, "0x00000000000000000000000000000000000000000000000000000000000001c8")); 120 | } 121 | 122 | 123 | @Test 124 | public void hexToString() throws UnsupportedEncodingException { 125 | String strDecoded = new String(ByteUtils.fromHexString("1a7468697320494420686173206265656e206f6363757069656421"), "UTF-8"); 126 | logger.info(strDecoded); 127 | } 128 | 129 | 130 | 131 | 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/cn/hyperchain/application/ServletInitializer.java: -------------------------------------------------------------------------------- 1 | package cn.hyperchain.application; 2 | 3 | import org.springframework.boot.builder.SpringApplicationBuilder; 4 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 5 | 6 | /** 7 | * Created by superlee on 2017/11/3. 8 | * 启动类,继承 SpringBootServletInitializer 并重写 configure 方法 9 | * 部署到外部tomcat容器中 10 | */ 11 | public class ServletInitializer extends SpringBootServletInitializer { 12 | 13 | @Override 14 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 15 | return application.sources(Application.class); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/cn/hyperchain/application/common/constant/Code.java: -------------------------------------------------------------------------------- 1 | package cn.hyperchain.application.common.constant; 2 | 3 | 4 | /** 5 | * @author sunligang 6 | */ 7 | 8 | public enum Code { 9 | SUCCESS(0, "成功"), 10 | INVOKE_FAIL(1, "失败"); 11 | 12 | 13 | 14 | 15 | private int code; 16 | private String msg; 17 | 18 | 19 | Code(int code, String msg) { 20 | this.code = code; 21 | this.msg = msg; 22 | } 23 | 24 | public String getMsg() { 25 | return msg; 26 | } 27 | 28 | public void setMsg(String msg) { 29 | this.msg = msg; 30 | } 31 | 32 | public int getCode() { 33 | return code; 34 | } 35 | 36 | public void setCode(int code) { 37 | this.code = code; 38 | } 39 | 40 | public static String getMsgByCodeInt(int codeInt) { 41 | for (Code e : Code.values()) { 42 | if (e.getCode() == codeInt) { 43 | return e.msg; 44 | } 45 | } 46 | throw new IllegalArgumentException("未定义的code码:" + codeInt); 47 | } 48 | 49 | public static Code getCodeByCodeInt(int codeInt) { 50 | for (Code code : Code.values()) { 51 | if (code.getCode() == codeInt) { 52 | return code; 53 | } 54 | } 55 | throw new IllegalArgumentException("未定义的code码:" + codeInt); 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/cn/hyperchain/application/common/listener/StartupListener.java: -------------------------------------------------------------------------------- 1 | package cn.hyperchain.application.common.listener; 2 | 3 | import cn.hyperchain.application.common.utils.ContractUtils; 4 | import cn.hyperchain.application.common.utils.Logger; 5 | import cn.hyperchain.application.common.utils.PropertiesUtils; 6 | import org.springframework.context.ApplicationListener; 7 | import org.springframework.context.event.ContextRefreshedEvent; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.Properties; 11 | 12 | 13 | /** 14 | * 在启动时根据配置是否需要重新部署合约 15 | * @author sunligang 16 | */ 17 | @Component 18 | public class StartupListener implements ApplicationListener { 19 | 20 | private static Logger logger = Logger.Builder.getLogger(StartupListener.class); 21 | @Override 22 | public void onApplicationEvent(ContextRefreshedEvent event){ 23 | //读取参数 24 | Properties properties = PropertiesUtils.getInstance("hyperchain.properties"); 25 | boolean isReDploy= "true".equals(properties.getProperty("redeploy", "true")); 26 | if(isReDploy){ 27 | logger.info("重新部署合约"); 28 | ContractUtils.compileAndDeploy(properties.getProperty("access_token")); 29 | }else { 30 | logger.info("不重新部署合约"); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/cn/hyperchain/application/common/response/BaseResult.java: -------------------------------------------------------------------------------- 1 | package cn.hyperchain.application.common.response; 2 | 3 | 4 | import cn.hyperchain.application.common.constant.Code; 5 | import cn.hyperchain.application.common.utils.DateUtils; 6 | 7 | /** 8 | * Created by superlee on 2017/11/6. 9 | */ 10 | @SuppressWarnings("unchecked") 11 | public final class BaseResult { 12 | private static final String EMPTY_DATA_INFO = "no data!"; 13 | private int code; 14 | private String message; 15 | private T data = (T) EMPTY_DATA_INFO; 16 | private String date = DateUtils.now(); 17 | 18 | public BaseResult() { 19 | 20 | this.data = (T) EMPTY_DATA_INFO; 21 | } 22 | 23 | public BaseResult(String msg) { 24 | this(); 25 | this.code = 200; 26 | this.message = msg; 27 | } 28 | 29 | public BaseResult(int code, String message) { 30 | this.code = code; 31 | this.message = message + ":" + date; 32 | } 33 | 34 | public BaseResult(int code, String message, T data) { 35 | this.code = code; 36 | this.message = message + ":" + date; 37 | this.data = data; 38 | } 39 | 40 | // 与Code码交互 41 | public BaseResult(Code code) { 42 | this(); 43 | this.code = code.getCode(); 44 | this.message = code.getMsg() + ":" + date; 45 | } 46 | 47 | /** 48 | * 返回结果代码code和消息msg,不需要返回值 49 | * 50 | * @param code 结果类型 51 | */ 52 | public final void returnWithoutValue(Code code) { 53 | this.code = code.getCode(); 54 | this.message = code.getMsg(); 55 | } 56 | 57 | /** 58 | * 返回结果代码code和消息msg,并返回值 59 | * 60 | * @param code 结果类型 61 | * @param object 返回值 62 | */ 63 | public final void returnWithValue(Code code, T object) { 64 | returnWithoutValue(code); 65 | this.data = object; 66 | } 67 | 68 | public int getCode() { 69 | return code; 70 | } 71 | 72 | public void setCode(int code) { 73 | this.code = code; 74 | } 75 | 76 | public String getMessage() { 77 | return message; 78 | } 79 | 80 | public void setMessage(String message) { 81 | this.message = message; 82 | } 83 | 84 | public T getData() { 85 | return data; 86 | } 87 | 88 | public void setData(T data) { 89 | this.data = data; 90 | } 91 | 92 | @Override 93 | public String toString() { 94 | return "BaseResult{" + 95 | "code=" + code + 96 | ", message='" + message + '\'' + 97 | ", data=" + data + 98 | '}'; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/cn/hyperchain/application/common/response/BaseResultFactory.java: -------------------------------------------------------------------------------- 1 | package cn.hyperchain.application.common.response; 2 | 3 | 4 | import cn.hyperchain.application.common.constant.Code; 5 | 6 | /** 7 | * Created by superlee on 2017/11/7. 8 | * baseResult 工程方法 9 | */ 10 | @SuppressWarnings("unchecked") 11 | public final class BaseResultFactory { 12 | 13 | 14 | public static BaseResult produceEmptyResult(Code code) { 15 | return new BaseResult(code); 16 | } 17 | 18 | public static BaseResult produceEmptyResult(int codeInt, String msg) { 19 | return new BaseResult(codeInt, msg); 20 | } 21 | 22 | public static BaseResult produceResult(int codeInt, String msg, Object data) { 23 | return new BaseResult(codeInt, msg, data); 24 | } 25 | 26 | public static BaseResult produceResult(Code code, Object data) { 27 | return new BaseResult(code.getCode(), code.getMsg(), data); 28 | } 29 | 30 | /** 31 | * 构建成功返回的数据(应该比较常用) 32 | * @param data 33 | * @return 34 | */ 35 | public static BaseResult creatSuccessReult(Object data){ 36 | BaseResult result = new BaseResult(Code.SUCCESS); 37 | result.setData(data); 38 | return result; 39 | } 40 | 41 | public static BaseResult creatSuccessReult(){ 42 | return new BaseResult(Code.SUCCESS); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/cn/hyperchain/application/common/utils/ContractUtils.java: -------------------------------------------------------------------------------- 1 | package cn.hyperchain.application.common.utils; 2 | 3 | import cn.qsnark.sdk.rpc.QsnarkAPI; 4 | import cn.qsnark.sdk.rpc.returns.CompileReturn; 5 | 6 | import java.io.*; 7 | import java.nio.ByteBuffer; 8 | import java.nio.channels.FileChannel; 9 | import java.util.Properties; 10 | 11 | /** 12 | * solidity合约工具 13 | * 14 | * @author sunligang 15 | * @date 2018/06/14 16 | */ 17 | public class ContractUtils { 18 | 19 | private static Logger logger = Logger.Builder.getLogger(ContractUtils.class); 20 | 21 | private static QsnarkAPI api = new QsnarkAPI(); 22 | private static Properties properties = PropertiesUtils.getInstance("hyperchain.properties"); 23 | private static String accessToken; 24 | private static String address; 25 | private static String abi; 26 | private static String bin; 27 | private static String contractAddress; 28 | 29 | static { 30 | //从配置文件中读取token 31 | accessToken = properties.getProperty("access_token"); 32 | // abi 33 | abi = properties.getProperty("abi"); 34 | //合约地址 35 | contractAddress = properties.getProperty("contract_address"); 36 | // 账户地址 37 | address = properties.getProperty("address"); 38 | } 39 | 40 | 41 | /** 42 | * 单个sol合约文件的编译和部署,并且合约文件需要位于/resource/contractSourceCode下 43 | * 44 | * @return 编译结果json 45 | */ 46 | public static boolean compileAndDeploy(String token) { 47 | //合约文件夹 48 | String contractFolder = ContractUtils.class.getResource("/contractSourceCode").getPath(); 49 | File folder = new File(contractFolder); 50 | String contractFile = ""; 51 | File[] files = null; 52 | if (folder.isDirectory()) { 53 | files = folder.listFiles(); 54 | assert files != null; 55 | if (files.length > 0) { 56 | contractFile = files[0].getPath(); 57 | } 58 | } 59 | 60 | String contractContent = readFromFile(contractFile); 61 | //编译合约 62 | CompileReturn compile; 63 | try { 64 | compile = api.compileContract(token, contractContent); 65 | } catch (IOException e) { 66 | e.printStackTrace(); 67 | return false; 68 | } 69 | 70 | bin = compile.getCts_bin(); 71 | 72 | abi = compile.getCts_bin(); 73 | 74 | 75 | try { 76 | contractAddress = api.deploysyncContract(accessToken, 77 | bin, 78 | address 79 | ).getContract_address(); 80 | } catch (InterruptedException e) { 81 | e.printStackTrace(); 82 | } catch (IOException e) { 83 | e.printStackTrace(); 84 | return false; 85 | } 86 | //将bin和abi和合约地址保存到配置的文件中 87 | writeIntoFile(bin, properties.getProperty("bin_path")); 88 | writeIntoFile(abi, properties.getProperty("abi_path")); 89 | writeIntoFile(contractAddress, properties.getProperty("contract_address_path")); 90 | return true; 91 | } 92 | 93 | /** 94 | * 根据路径读取文件 95 | * 96 | * @param path 文件路径 97 | * @return 98 | */ 99 | private static String readFromFile(String path) { 100 | File file = new File(path); 101 | FileReader fileReader = null; 102 | try { 103 | fileReader = new FileReader(file); 104 | } catch (FileNotFoundException e) { 105 | e.printStackTrace(); 106 | } 107 | StringBuilder stringBuilder = new StringBuilder(); 108 | char[] buffer = new char[128]; 109 | int len; 110 | try { 111 | while ((len = fileReader.read(buffer)) != -1) { 112 | stringBuilder.append(buffer, 0, len); 113 | } 114 | } catch (IOException e) { 115 | e.printStackTrace(); 116 | } 117 | return stringBuilder.toString(); 118 | } 119 | 120 | 121 | /** 122 | * 将string写入到目的文件 123 | * 124 | * @param strSource 125 | * @param destPath 126 | */ 127 | private synchronized static void writeIntoFile(String strSource, String destPath) { 128 | // 如果不存在则创建 129 | File file = new File(destPath); 130 | if (!file.exists()) { 131 | try { 132 | file.createNewFile(); 133 | } catch (IOException e) { 134 | e.printStackTrace(); 135 | } 136 | } 137 | FileChannel fileChannel = null; 138 | try { 139 | fileChannel = new FileOutputStream(destPath).getChannel(); 140 | } catch (FileNotFoundException e) { 141 | e.printStackTrace(); 142 | } 143 | ByteBuffer byteBuffer = ByteBuffer.allocate(128); 144 | byte[] info = strSource.getBytes(); 145 | int index = 0; 146 | while (index < info.length) { 147 | byteBuffer.put(info, index, Math.min(info.length - index, 128)); 148 | index += 128; 149 | byteBuffer.flip(); 150 | try { 151 | fileChannel.write(byteBuffer); 152 | } catch (IOException e) { 153 | e.printStackTrace(); 154 | } 155 | byteBuffer.clear(); 156 | } 157 | } 158 | 159 | public static String getAbi() { 160 | return abi; 161 | } 162 | 163 | 164 | public static String getContractAddress() { 165 | return contractAddress; 166 | } 167 | 168 | /** 169 | * 获取账户地址 170 | * 171 | * @return 172 | */ 173 | public static String getAddress() { 174 | return address; 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /src/main/java/cn/hyperchain/application/common/utils/DateUtils.java: -------------------------------------------------------------------------------- 1 | package cn.hyperchain.application.common.utils; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | 6 | /** 7 | * 日期工具类 8 | * 9 | * @author faxiang 10 | * @version v1.0 11 | * @since 2017/12/20 12 | */ 13 | public class DateUtils { 14 | 15 | private static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss"; 16 | 17 | /** 18 | * 获取当前时间(格式 yyyy-MM-dd HH:mm:ss) 19 | * 20 | * @return 时间字符串 21 | */ 22 | public static String now() { 23 | return convertDate2String(DATE_PATTERN); 24 | } 25 | 26 | /** 27 | * 获取昨天的时间(格式 yyyy-MM-dd HH:mm:ss) 28 | * 29 | * @return 时间字符串 30 | 31 | 32 | /** 33 | * 转换当前日期为指定的格式 34 | * 35 | * @param pattern 格式 36 | * @return 日期字符串 37 | */ 38 | public static String convertDate2String(String pattern) { 39 | return convertDate2String(new Date(), pattern); 40 | } 41 | 42 | 43 | /** 44 | * 转换日期为指定的格式 45 | * 46 | * @param date 待转换的日期对象 47 | * @param pattern 格式 48 | * @return 日期字符串 49 | */ 50 | public static String convertDate2String(Date date, String pattern) { 51 | SimpleDateFormat df; 52 | String returnValue = ""; 53 | 54 | if (date != null) { 55 | df = new SimpleDateFormat(pattern); 56 | returnValue = df.format(date); 57 | } 58 | 59 | return returnValue; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/cn/hyperchain/application/common/utils/Logger.java: -------------------------------------------------------------------------------- 1 | package cn.hyperchain.application.common.utils; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Arrays; 5 | import java.util.Calendar; 6 | 7 | /** 8 | * @author sunligang 9 | */ 10 | public class Logger { 11 | 12 | public static class Builder { 13 | public static Logger getLogger(Class clazz) { 14 | return new Logger(clazz); 15 | } 16 | } 17 | 18 | private static final int COMPLETE_DISPLAY_PACKAGE_NAME = 2; 19 | private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); 20 | private StringBuilder simpleClazzName = new StringBuilder(); 21 | 22 | private Calendar calendar = Calendar.getInstance(); 23 | 24 | Logger(Class clazz) { 25 | String[] packageNameArray = clazz.getName().split("\\."); 26 | for (int i = 0; i < packageNameArray.length; i++) { 27 | if (i < packageNameArray.length - COMPLETE_DISPLAY_PACKAGE_NAME) { 28 | simpleClazzName.append(packageNameArray[i], 0, 1); 29 | } else { 30 | simpleClazzName.append(packageNameArray[i]); 31 | } 32 | if (i < packageNameArray.length - 1) { 33 | simpleClazzName.append("."); 34 | } 35 | } 36 | } 37 | 38 | public void info(Object info) { 39 | System.out.println(simpleDateFormat.format(calendar.getTime()) + "[" + simpleClazzName + "] info :" + info); 40 | } 41 | 42 | public void info(Object... info) { 43 | System.out.print(simpleDateFormat.format(calendar.getTime()) + "[" + simpleClazzName + "] info :"); 44 | Arrays.stream(info).forEach(e -> { 45 | System.out.print(" " + e); 46 | }); 47 | System.out.println(); 48 | } 49 | 50 | 51 | public void blackLine() { 52 | System.out.println(); 53 | } 54 | 55 | public void err(Object info) { 56 | System.err.println(simpleDateFormat.format(calendar.getTime()) + "[" + simpleClazzName + "] err :" + info); 57 | } 58 | 59 | public void warn(Object info) { 60 | System.err.println(simpleDateFormat.format(calendar.getTime()) + "[" + simpleClazzName + "] warm :" + info); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/cn/hyperchain/application/common/utils/PropertiesUtils.java: -------------------------------------------------------------------------------- 1 | package cn.hyperchain.application.common.utils; 2 | 3 | 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.util.Properties; 7 | 8 | /** 9 | * @author sunligang 10 | * @date 2018/07/05 11 | */ 12 | public class PropertiesUtils { 13 | public static Properties getInstance(String path) { 14 | Properties properties = new Properties(); 15 | // 使用ClassLoader加载properties配置文件生成对应的输入流 16 | InputStream in = PropertiesUtils.class.getClassLoader().getResourceAsStream(path); 17 | // 使用properties对象加载输入流 18 | try { 19 | properties.load(in); 20 | } catch (IOException e) { 21 | e.printStackTrace(); 22 | } 23 | return properties; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/cn/hyperchain/application/config/Swagger2.java: -------------------------------------------------------------------------------- 1 | package cn.hyperchain.application.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import springfox.documentation.builders.ApiInfoBuilder; 6 | import springfox.documentation.builders.ParameterBuilder; 7 | import springfox.documentation.builders.PathSelectors; 8 | import springfox.documentation.builders.RequestHandlerSelectors; 9 | import springfox.documentation.schema.ModelRef; 10 | import springfox.documentation.service.ApiInfo; 11 | import springfox.documentation.service.Parameter; 12 | import springfox.documentation.spi.DocumentationType; 13 | import springfox.documentation.spring.web.plugins.Docket; 14 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | /** 20 | * Created by superlee on 2017/11/3. 21 | * swagger配置 22 | */ 23 | @Configuration 24 | @EnableSwagger2 25 | public class Swagger2 { 26 | @Bean 27 | public Docket createRestApi() { 28 | return new Docket(DocumentationType.SWAGGER_2) 29 | .select() 30 | .apis(RequestHandlerSelectors.basePackage("cn.hyperchain.application.controller")) 31 | .paths(PathSelectors.any()) 32 | .build() 33 | .apiInfo(apiInfo()); 34 | } 35 | 36 | private ApiInfo apiInfo() { 37 | return new ApiInfoBuilder() 38 | .title("minata") 39 | .description("防伪溯源案例") 40 | .termsOfServiceUrl("暂无") 41 | .version("1.0") 42 | .build(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/cn/hyperchain/application/contract/business/Actor.java: -------------------------------------------------------------------------------- 1 | package cn.hyperchain.application.contract.business; 2 | 3 | /** 4 | * 参与方类型 5 | * 6 | * @author sunligang 7 | * @date 2018/07/09 8 | */ 9 | public enum Actor { 10 | //生产商 11 | Producer, 12 | //各级经销商 13 | Retailer, 14 | //消费者 15 | Customer, 16 | //其他 17 | Others 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/cn/hyperchain/application/contract/invoke/ContractInvoke.java: -------------------------------------------------------------------------------- 1 | package cn.hyperchain.application.contract.invoke; 2 | 3 | import cn.hyperchain.application.common.constant.Code; 4 | import cn.hyperchain.application.common.response.BaseResult; 5 | import cn.hyperchain.application.common.response.BaseResultFactory; 6 | import cn.hyperchain.application.common.utils.ContractUtils; 7 | import cn.hyperchain.application.contract.business.Actor; 8 | import cn.qsnark.sdk.exception.TxException; 9 | import cn.qsnark.sdk.rpc.QsnarkAPI; 10 | import cn.qsnark.sdk.rpc.function.FuncParamReal; 11 | import cn.qsnark.sdk.rpc.function.FunctionDecode; 12 | import cn.qsnark.sdk.rpc.returns.GetTxReciptReturn; 13 | import org.springframework.stereotype.Component; 14 | 15 | import java.io.IOException; 16 | import java.io.UnsupportedEncodingException; 17 | import java.util.List; 18 | 19 | 20 | /** 21 | * @author sunligang 22 | * @date 2018/07/05 23 | */ 24 | @Component 25 | public class ContractInvoke { 26 | 27 | private static QsnarkAPI api = new QsnarkAPI(); 28 | 29 | private final static String FLAG_OK = "ok"; 30 | 31 | /** 32 | * 对应于合约的newUser方法 33 | * 34 | * @return 35 | */ 36 | public BaseResult newUser(String token, String invokeAddress, String ID, String name, Actor actor) { 37 | //构造参数 38 | FuncParamReal[] arrFunParamReal = new FuncParamReal[3]; 39 | arrFunParamReal[0] = new FuncParamReal("bytes32", ID); 40 | arrFunParamReal[1] = new FuncParamReal("bytes32", name); 41 | //enum对应于string 42 | switch (actor) { 43 | case Producer: 44 | arrFunParamReal[2] = new FuncParamReal("uint8", 1); 45 | break; 46 | case Retailer: 47 | arrFunParamReal[2] = new FuncParamReal("uint8", 2); 48 | break; 49 | case Customer: 50 | arrFunParamReal[2] = new FuncParamReal("uint8", 3); 51 | break; 52 | case Others: 53 | arrFunParamReal[2] = new FuncParamReal("uint8", 0); 54 | break; 55 | default: 56 | break; 57 | } 58 | GetTxReciptReturn getTxReciptReturn = null; 59 | try { 60 | getTxReciptReturn = api.invokesyncContract( 61 | token, 62 | false, 63 | invokeAddress, 64 | ContractUtils.getContractAddress(), 65 | ContractUtils.getAbi(), 66 | "newUser", 67 | arrFunParamReal 68 | ); 69 | } catch (IOException | TxException | InterruptedException e) { 70 | e.printStackTrace(); 71 | } 72 | 73 | BaseResult baseResult = null; 74 | 75 | if (FLAG_OK.equals(getTxReciptReturn.getStatus())) { 76 | try { 77 | baseResult = BaseResultFactory.produceResult( 78 | Code.SUCCESS, 79 | FunctionDecode.resultDecode("newUser", ContractUtils.getAbi(), getTxReciptReturn.getRet())); 80 | } catch (UnsupportedEncodingException e) { 81 | e.printStackTrace(); 82 | } 83 | } else { 84 | try { 85 | baseResult = BaseResultFactory.produceResult( 86 | Code.INVOKE_FAIL, 87 | FunctionDecode.resultDecode("newUser", ContractUtils.getAbi(), getTxReciptReturn.getRet())); 88 | } catch (UnsupportedEncodingException e) { 89 | e.printStackTrace(); 90 | } 91 | } 92 | return baseResult; 93 | } 94 | 95 | 96 | /** 97 | * 添加白名单 98 | * 99 | * @return 100 | */ 101 | public BaseResult addWhiteList(String token, String invokeAddress, String address) { 102 | //构造参数 103 | FuncParamReal[] arrFunParamReal = new FuncParamReal[1]; 104 | arrFunParamReal[0] = new FuncParamReal("address", address); 105 | GetTxReciptReturn getTxReciptReturn = null; 106 | try { 107 | getTxReciptReturn = api.invokesyncContract( 108 | token, 109 | false, 110 | invokeAddress, 111 | ContractUtils.getContractAddress(), 112 | ContractUtils.getAbi(), 113 | "addWhiteList", 114 | arrFunParamReal 115 | ); 116 | } catch (IOException | TxException | InterruptedException e) { 117 | e.printStackTrace(); 118 | } 119 | 120 | BaseResult baseResult = null; 121 | 122 | if (FLAG_OK.equals(getTxReciptReturn.getStatus())) { 123 | try { 124 | baseResult = BaseResultFactory.produceResult( 125 | Code.SUCCESS, 126 | FunctionDecode.resultDecode("addWhiteList", ContractUtils.getAbi(), getTxReciptReturn.getRet())); 127 | } catch (UnsupportedEncodingException e) { 128 | e.printStackTrace(); 129 | } 130 | } else { 131 | try { 132 | baseResult = BaseResultFactory.produceResult( 133 | Code.INVOKE_FAIL, 134 | FunctionDecode.resultDecode("addWhiteList", ContractUtils.getAbi(), getTxReciptReturn.getRet())); 135 | } catch (UnsupportedEncodingException e) { 136 | e.printStackTrace(); 137 | } 138 | } 139 | return baseResult; 140 | } 141 | 142 | 143 | /** 144 | * 对应于合约的newProduct方法 145 | * 146 | * @return 147 | */ 148 | public BaseResult newProduct(String token, String invokeAddress, String commodityID, String commodityName, int price ) { 149 | //构造参数 150 | FuncParamReal[] arrFunParamReal = new FuncParamReal[4]; 151 | arrFunParamReal[0] = new FuncParamReal("bytes32", commodityID); 152 | arrFunParamReal[1] = new FuncParamReal("bytes32", commodityName); 153 | arrFunParamReal[2] = new FuncParamReal("uint256", System.currentTimeMillis()); 154 | arrFunParamReal[3] = new FuncParamReal("uint256", price); 155 | GetTxReciptReturn getTxReciptReturn = null; 156 | try { 157 | getTxReciptReturn = api.invokesyncContract( 158 | token, 159 | false, 160 | invokeAddress, 161 | ContractUtils.getContractAddress(), 162 | ContractUtils.getAbi(), 163 | "newProduct", 164 | arrFunParamReal 165 | ); 166 | } catch (IOException | TxException | InterruptedException e) { 167 | e.printStackTrace(); 168 | } 169 | 170 | BaseResult baseResult = null; 171 | 172 | if (FLAG_OK.equals(getTxReciptReturn.getStatus())) { 173 | try { 174 | baseResult = BaseResultFactory.produceResult( 175 | Code.SUCCESS, 176 | FunctionDecode.resultDecode("newProduct", ContractUtils.getAbi(), getTxReciptReturn.getRet())); 177 | } catch (UnsupportedEncodingException e) { 178 | e.printStackTrace(); 179 | } 180 | } else { 181 | try { 182 | baseResult = BaseResultFactory.produceResult( 183 | Code.INVOKE_FAIL, 184 | FunctionDecode.resultDecode("newProduct", ContractUtils.getAbi(), getTxReciptReturn.getRet())); 185 | } catch (UnsupportedEncodingException e) { 186 | e.printStackTrace(); 187 | } 188 | } 189 | return baseResult; 190 | } 191 | 192 | 193 | /** 194 | * 对应于合约的retailerInnerTransfer方法 195 | * 196 | * @return 197 | */ 198 | public BaseResult retailerInnerTransfer(String token, String invokeAddress, String commodityID) { 199 | //构造参数 200 | FuncParamReal[] arrFunParamReal = new FuncParamReal[2]; 201 | arrFunParamReal[0] = new FuncParamReal("bytes32", commodityID); 202 | arrFunParamReal[1] = new FuncParamReal("uint256", System.currentTimeMillis()); 203 | GetTxReciptReturn getTxReciptReturn = null; 204 | try { 205 | getTxReciptReturn = api.invokesyncContract( 206 | token, 207 | false, 208 | invokeAddress, 209 | ContractUtils.getContractAddress(), 210 | ContractUtils.getAbi(), 211 | "retailerInnerTransfer", 212 | arrFunParamReal 213 | ); 214 | } catch (IOException | TxException | InterruptedException e) { 215 | e.printStackTrace(); 216 | } 217 | 218 | BaseResult baseResult = null; 219 | 220 | if (FLAG_OK.equals(getTxReciptReturn.getStatus())) { 221 | try { 222 | baseResult = BaseResultFactory.produceResult( 223 | Code.SUCCESS, 224 | FunctionDecode.resultDecode("retailerInnerTransfer", ContractUtils.getAbi(), getTxReciptReturn.getRet())); 225 | } catch (UnsupportedEncodingException e) { 226 | e.printStackTrace(); 227 | } 228 | } else { 229 | try { 230 | baseResult = BaseResultFactory.produceResult( 231 | Code.INVOKE_FAIL, 232 | FunctionDecode.resultDecode("retailerInnerTransfer", ContractUtils.getAbi(), getTxReciptReturn.getRet())); 233 | } catch (UnsupportedEncodingException e) { 234 | e.printStackTrace(); 235 | } 236 | } 237 | return baseResult; 238 | } 239 | 240 | 241 | /** 242 | * 对应于合约的fromRetailerToCustomer方法 243 | * 244 | * @return 245 | */ 246 | public BaseResult fromRetailerToCustomer(String token, String invokeAddress, String commodityID) { 247 | //构造参数 248 | FuncParamReal[] arrFunParamReal = new FuncParamReal[2]; 249 | arrFunParamReal[0] = new FuncParamReal("bytes32", commodityID); 250 | arrFunParamReal[1] = new FuncParamReal("uint256", System.currentTimeMillis()); 251 | GetTxReciptReturn getTxReciptReturn = null; 252 | try { 253 | getTxReciptReturn = api.invokesyncContract( 254 | token, 255 | false, 256 | invokeAddress, 257 | ContractUtils.getContractAddress(), 258 | ContractUtils.getAbi(), 259 | "fromRetailerToCustomer", 260 | arrFunParamReal 261 | ); 262 | } catch (IOException | TxException | InterruptedException e) { 263 | e.printStackTrace(); 264 | } 265 | 266 | BaseResult baseResult = null; 267 | 268 | if (FLAG_OK.equals(getTxReciptReturn.getStatus())) { 269 | try { 270 | baseResult = BaseResultFactory.produceResult( 271 | Code.SUCCESS, 272 | FunctionDecode.resultDecode("fromRetailerToCustomer", ContractUtils.getAbi(), getTxReciptReturn.getRet())); 273 | } catch (UnsupportedEncodingException e) { 274 | e.printStackTrace(); 275 | } 276 | } else { 277 | try { 278 | baseResult = BaseResultFactory.produceResult( 279 | Code.INVOKE_FAIL, 280 | FunctionDecode.resultDecode("fromRetailerToCustomer", ContractUtils.getAbi(), getTxReciptReturn.getRet())); 281 | } catch (UnsupportedEncodingException e) { 282 | e.printStackTrace(); 283 | } 284 | } 285 | return baseResult; 286 | } 287 | 288 | /** 289 | * 对应于合约的getCommodityRecordsByWhiteList方法 290 | * 291 | * @return 292 | */ 293 | public BaseResult getCommodityRecordsByWhiteList(String token, String invokeAddress, String commodityID) { 294 | //构造参数 295 | FuncParamReal[] arrFunParamReal = new FuncParamReal[1]; 296 | arrFunParamReal[0] = new FuncParamReal("bytes32", commodityID); 297 | GetTxReciptReturn getTxReciptReturn = null; 298 | try { 299 | getTxReciptReturn = api.invokesyncContract( 300 | token, 301 | false, 302 | invokeAddress, 303 | ContractUtils.getContractAddress(), 304 | ContractUtils.getAbi(), 305 | "getCommodityRecordsByWhiteList", 306 | arrFunParamReal 307 | ); 308 | } catch (IOException | TxException | InterruptedException e) { 309 | e.printStackTrace(); 310 | } 311 | 312 | BaseResult baseResult = null; 313 | 314 | if (FLAG_OK.equals(getTxReciptReturn.getStatus())) { 315 | try { 316 | baseResult = BaseResultFactory.produceResult( 317 | Code.SUCCESS, 318 | FunctionDecode.resultDecode("getCommodityRecordsByWhiteList", ContractUtils.getAbi(), getTxReciptReturn.getRet())); 319 | } catch (UnsupportedEncodingException e) { 320 | e.printStackTrace(); 321 | } 322 | } else { 323 | try { 324 | baseResult = BaseResultFactory.produceResult( 325 | Code.INVOKE_FAIL, 326 | FunctionDecode.resultDecode("getCommodityRecordsByWhiteList", ContractUtils.getAbi(), getTxReciptReturn.getRet())); 327 | } catch (UnsupportedEncodingException e) { 328 | e.printStackTrace(); 329 | } 330 | } 331 | return baseResult; 332 | } 333 | 334 | /** 335 | * 对应于合约的getCommodity方法 336 | * 337 | * @return 338 | */ 339 | public BaseResult getCommodity(String token, String invokeAddress, String commodityID, Actor actor) { 340 | //构造参数 341 | FuncParamReal[] arrFunParamReal = new FuncParamReal[2]; 342 | arrFunParamReal[0] = new FuncParamReal("bytes32", commodityID); 343 | //enum对应于string 344 | switch (actor) { 345 | case Producer: 346 | arrFunParamReal[1] = new FuncParamReal("uint8", 1); 347 | break; 348 | case Retailer: 349 | arrFunParamReal[1] = new FuncParamReal("uint8", 2); 350 | break; 351 | case Customer: 352 | arrFunParamReal[1] = new FuncParamReal("uint8", 3); 353 | break; 354 | case Others: 355 | arrFunParamReal[1] = new FuncParamReal("uint8", 0); 356 | break; 357 | default: 358 | break; 359 | } 360 | GetTxReciptReturn getTxReciptReturn = null; 361 | try { 362 | getTxReciptReturn = api.invokesyncContract( 363 | token, 364 | false, 365 | invokeAddress, 366 | ContractUtils.getContractAddress(), 367 | ContractUtils.getAbi(), 368 | "getCommodity", 369 | arrFunParamReal 370 | ); 371 | } catch (IOException | TxException | InterruptedException e) { 372 | e.printStackTrace(); 373 | } 374 | 375 | BaseResult baseResult = null; 376 | 377 | if (FLAG_OK.equals(getTxReciptReturn.getStatus())) { 378 | try { 379 | baseResult = BaseResultFactory.produceResult( 380 | Code.SUCCESS, 381 | FunctionDecode.resultDecode("getCommodity", ContractUtils.getAbi(), getTxReciptReturn.getRet())); 382 | } catch (UnsupportedEncodingException e) { 383 | e.printStackTrace(); 384 | } 385 | } else { 386 | try { 387 | baseResult = BaseResultFactory.produceResult( 388 | Code.INVOKE_FAIL, 389 | FunctionDecode.resultDecode("getCommodity", ContractUtils.getAbi(), getTxReciptReturn.getRet())); 390 | } catch (UnsupportedEncodingException e) { 391 | e.printStackTrace(); 392 | } 393 | } 394 | return baseResult; 395 | } 396 | 397 | /** 398 | * 监管部门(白名单)查询一批商品的价格 399 | * @param commodityIDs 商品id们 400 | * @return 401 | */ 402 | public BaseResult getTotalPrice(String token, String invokeAddress, List commodityIDs){ 403 | //构造参数 404 | FuncParamReal[] arrFunParamReal = new FuncParamReal[1]; 405 | arrFunParamReal[0] = new FuncParamReal("bytes32[]", commodityIDs); 406 | GetTxReciptReturn getTxReciptReturn = null; 407 | try { 408 | getTxReciptReturn = api.invokesyncContract( 409 | token, 410 | false, 411 | invokeAddress, 412 | ContractUtils.getContractAddress(), 413 | ContractUtils.getAbi(), 414 | "getTotalPrice", 415 | arrFunParamReal 416 | ); 417 | } catch (IOException | TxException | InterruptedException e) { 418 | e.printStackTrace(); 419 | } 420 | 421 | BaseResult baseResult = null; 422 | 423 | if (FLAG_OK.equals(getTxReciptReturn.getStatus())) { 424 | try { 425 | baseResult = BaseResultFactory.produceResult( 426 | Code.SUCCESS, 427 | FunctionDecode.resultDecode("getTotalPrice", ContractUtils.getAbi(), getTxReciptReturn.getRet())); 428 | } catch (UnsupportedEncodingException e) { 429 | e.printStackTrace(); 430 | } 431 | } else { 432 | try { 433 | baseResult = BaseResultFactory.produceResult( 434 | Code.INVOKE_FAIL, 435 | FunctionDecode.resultDecode("getTotalPrice", ContractUtils.getAbi(), getTxReciptReturn.getRet())); 436 | } catch (UnsupportedEncodingException e) { 437 | e.printStackTrace(); 438 | } 439 | } 440 | return baseResult; 441 | } 442 | } 443 | -------------------------------------------------------------------------------- /src/main/java/cn/hyperchain/application/controller/ContractController.java: -------------------------------------------------------------------------------- 1 | package cn.hyperchain.application.controller; 2 | 3 | import cn.hyperchain.application.common.response.BaseResult; 4 | import cn.hyperchain.application.contract.business.Actor; 5 | import cn.hyperchain.application.contract.invoke.ContractInvoke; 6 | import io.swagger.annotations.ApiOperation; 7 | import io.swagger.annotations.ApiParam; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import java.util.List; 12 | 13 | 14 | /** 15 | * 合约操作 16 | * 17 | * @author sunligang 18 | * @date 2018/07/09 19 | */ 20 | @RestController 21 | public class ContractController { 22 | 23 | @Autowired 24 | private ContractInvoke contractInvoke; 25 | 26 | 27 | /** 28 | * 添加白名单 29 | * 30 | * @param address 账户地址 31 | */ 32 | @ApiOperation(value = "消费者发起的转移", notes = "防伪溯源案例") 33 | @RequestMapping(value = "/v1/whiteList", method = RequestMethod.POST) 34 | public BaseResult addWhiteList( 35 | @ApiParam(value = "accessToken") @RequestParam String token, 36 | @ApiParam(value = "调用者账户地址") @RequestParam String invokeAddress, 37 | @ApiParam(value = "账户地址") @RequestParam String address) { 38 | return contractInvoke.addWhiteList(token, invokeAddress, address); 39 | } 40 | 41 | /** 42 | * 创建一个参与方 43 | * 44 | * @param ID 参与方ID 注意需要是hyperchain账户 45 | * @param name 参与方姓名 46 | * @param actor 参与方类型 47 | * @return 创建用户是否成功 48 | */ 49 | @ApiOperation(value = "增加一个参与方", notes = "防伪溯源案例") 50 | @RequestMapping(value = "/v1/user", method = RequestMethod.POST) 51 | public BaseResult newUser(@ApiParam(value = "accessToken") @RequestParam String token, 52 | @ApiParam(value = "调用者账户地址") @RequestParam String invokeAddress, 53 | @ApiParam(value = "参与方ID") @RequestParam String ID, 54 | @ApiParam(value = "参与方name") @RequestParam String name, 55 | @ApiParam(value = "参与方类型") @RequestParam Actor actor) { 56 | return contractInvoke.newUser(token, invokeAddress, ID, name, actor); 57 | } 58 | 59 | /** 60 | * 生产一件商品 61 | * 62 | * @param commodityID 商品ID 63 | * @param commodityName 商品name 64 | * @return 1.是否已经有了该商品 2.调用者是否是一个生产者 3.成功生产 65 | */ 66 | @ApiOperation(value = "生产一件产品", notes = "防伪溯源案例") 67 | @RequestMapping(value = "/v1/product", method = RequestMethod.POST) 68 | public BaseResult newProduct(@ApiParam(value = "accessToken") @RequestParam String token, 69 | @ApiParam(value = "调用者账户地址") @RequestParam String invokeAddress, 70 | @ApiParam(value = "商品ID") @RequestParam String commodityID, 71 | @ApiParam(value = "商品name") @RequestParam String commodityName, 72 | @ApiParam(value = "商品价格") @RequestParam Integer price) { 73 | return contractInvoke.newProduct(token, invokeAddress, commodityID, commodityName, price); 74 | } 75 | 76 | 77 | /** 78 | * 经销商发起的商品转移 79 | * 80 | * @param commodityID 商品ID 81 | * @return 1.是否已经有了该商品 2.调用者是否是一个经销商 3.成功生产 82 | */ 83 | @ApiOperation(value = "经销商发起的转移", notes = "防伪溯源案例") 84 | @RequestMapping(value = "/v1/retailerInnerTransfer", method = RequestMethod.POST) 85 | public BaseResult retailerInnerTransfer( 86 | @ApiParam(value = "accessToken") @RequestParam String token, 87 | @ApiParam(value = "调用者账户地址") @RequestParam String invokeAddress, 88 | @ApiParam(value = "商品ID") @RequestParam String commodityID) { 89 | return contractInvoke.retailerInnerTransfer(token, invokeAddress, commodityID); 90 | } 91 | 92 | 93 | /** 94 | * 消费者发起的商品转移 95 | * 96 | * @param commodityID 商品ID 97 | * @return 1.是否已经有了该商品 2.调用者是否是一个消费者 3.成功生产 98 | */ 99 | @ApiOperation(value = "消费者发起的转移", notes = "防伪溯源案例") 100 | @RequestMapping(value = "/v1/fromRetailerToCustomer", method = RequestMethod.POST) 101 | public BaseResult fromRetailerToCustomer(@ApiParam(value = "accessToken") @RequestParam String token, 102 | @ApiParam(value = "调用者账户地址") @RequestParam String invokeAddress, 103 | @ApiParam(value = "商品ID") @RequestParam String commodityID) { 104 | return contractInvoke.fromRetailerToCustomer(token, invokeAddress, commodityID); 105 | } 106 | 107 | /** 108 | * 通过白名单获取商品记录 109 | * 110 | * @param commodityID 商品ID 111 | * @return 1.是否已经有了该商品 2.调用者是否是一个消费者 3.成功生产 112 | */ 113 | @ApiOperation(value = "消费者发起的转移", notes = "防伪溯源案例") 114 | @RequestMapping(value = "/v1/getCommodityRecordsByWhiteList", method = RequestMethod.POST) 115 | public BaseResult getCommodityRecordsByWhiteList( 116 | @ApiParam(value = "accessToken") @RequestParam String token, 117 | @ApiParam(value = "调用者账户地址") @RequestParam String invokeAddress, 118 | @ApiParam(value = "商品ID") @RequestParam String commodityID) { 119 | return contractInvoke.getCommodityRecordsByWhiteList(token, invokeAddress, commodityID); 120 | } 121 | 122 | 123 | /** 124 | * 只获取当前账户的商品信息 125 | * 126 | * @param commodityID 商品ID 127 | * @param actor 账户类型 128 | * @return 129 | */ 130 | @ApiOperation(value = "通过调用者账户去获取账户信息", notes = "防伪溯源案例") 131 | @RequestMapping(value = "/v1/getCommodity", method = RequestMethod.POST) 132 | public BaseResult getCommodity( 133 | @ApiParam(value = "accessToken") @RequestParam String token, 134 | @ApiParam(value = "调用者账户地址") @RequestParam String invokeAddress, 135 | @ApiParam(value = "商品ID") @RequestParam String commodityID, 136 | @ApiParam(value = "参与方类型") @RequestParam Actor actor) { 137 | return contractInvoke.getCommodity(token, invokeAddress, commodityID, actor); 138 | } 139 | 140 | /** 141 | * 获取商品的总价格 142 | * 143 | * @return 144 | */ 145 | @ApiOperation(value = "通过商品ID列表获取总价格", notes = "防伪溯源案例") 146 | @RequestMapping(value = "/v1/totalPrice", method = RequestMethod.POST) 147 | public BaseResult getTotalPrice( 148 | @ApiParam(value = "accessToken") @RequestParam String token, 149 | @ApiParam(value = "调用者账户地址") @RequestParam String invokeAddress, 150 | @ApiParam(value = "商品ID列表") @RequestParam List commodityIDs) { 151 | return contractInvoke.getTotalPrice(token, invokeAddress, commodityIDs); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/resources/contractSourceCode/supplychain.sol: -------------------------------------------------------------------------------- 1 | 2 | library SafeMath{ 3 | 4 | function mul(uint8 a , uint8 b ) internal returns( uint8 ){ 5 | if(a == 0 ){ 6 | return 0; 7 | } 8 | uint8 c = a * b ; 9 | assert(c /a == b ); 10 | return c; 11 | } 12 | function div(uint256 a , uint256 b ) internal returns( uint256 ){ 13 | uint256 c = a / b ; 14 | return c; 15 | } 16 | function sub(uint256 a , uint256 b ) internal returns( uint256 ){ 17 | assert(b<=a); 18 | return a - b; 19 | } 20 | function add(uint256 a , uint256 b ) internal returns( uint256 ){ 21 | uint256 c = a + b; 22 | assert(c >= a); 23 | return c; 24 | } 25 | } 26 | 27 | contract SupplyChain { 28 | using SafeMath for uint256; 29 | enum Actor{Others, Producer, Retailer, Customer} 30 | struct User { 31 | bytes32 ID; 32 | bytes32 name; 33 | Actor actor; 34 | } 35 | 36 | struct Commodity { 37 | uint256 price; 38 | bytes32 commodityID; 39 | bytes32 commodityName; 40 | uint produceTime; 41 | bytes32 producerName; 42 | uint[] timestamps; 43 | bytes32[] retailerNames; 44 | uint sellTime; 45 | bytes32 customerName; 46 | bool isBinding; 47 | address owner; 48 | } 49 | 50 | mapping(address => User) producerMap; 51 | mapping(address => User) retailerMap; 52 | mapping(address => User) customerMap; 53 | mapping(bytes32 => Commodity) commodityMap; 54 | mapping(address => bool) whiteList; 55 | address owner; 56 | 57 | modifier onlyOwner { 58 | if (msg.sender != owner) 59 | assert(false); 60 | _; 61 | } 62 | function SupplyChain(){ 63 | owner = msg.sender; 64 | whiteList[owner] = true; 65 | } 66 | 67 | function addWhiteList(address addr){ 68 | whiteList[addr] = true; 69 | } 70 | 71 | function newUser(bytes32 ID, bytes32 name, Actor actor) returns (bool, string){ 72 | User user; 73 | 74 | if (actor == Actor.Producer) { 75 | user = producerMap[msg.sender]; 76 | } else if (actor == Actor.Retailer) { 77 | user = retailerMap[msg.sender]; 78 | } else if (actor == Actor.Customer) { 79 | user = customerMap[msg.sender]; 80 | } else { 81 | return (false, "the actor is not belong"); 82 | } 83 | 84 | if (user.ID != 0x0) { 85 | return (false, "this ID has been occupied!"); 86 | } 87 | user.ID = ID; 88 | user.name = name; 89 | user.actor = actor; 90 | return (true, "Success"); 91 | } 92 | 93 | function newProduct(bytes32 commodityID, bytes32 commodityName, 94 | uint timestamp, uint _price) returns (bool, bytes32){ 95 | Commodity commodity = commodityMap[commodityID]; 96 | if (commodity.commodityID != 0x0) { 97 | return (false, "The commodityID already exist!"); 98 | } 99 | User user = producerMap[msg.sender]; 100 | if (user.ID == 0x0) { 101 | return (false, "The producer don't exist!"); 102 | } 103 | commodity.commodityID = commodityID; 104 | commodity.commodityName = commodityName; 105 | commodity.produceTime = timestamp; 106 | commodity.producerName = user.name; 107 | commodity.price = _price; 108 | return (true, "Success,produce a new product"); 109 | } 110 | 111 | 112 | function retailerInnerTransfer(bytes32 commodityID, uint timestamp) returns (bool, string){ 113 | Commodity commodity = commodityMap[commodityID]; 114 | if (commodity.commodityID == 0x0) { 115 | return (false, "The commodityID don't exist!"); 116 | } 117 | User user = retailerMap[msg.sender]; 118 | if (user.ID == 0x0) { 119 | return (false, "The retailer don't exist!"); 120 | } 121 | commodity.timestamps.push(timestamp); 122 | commodity.retailerNames.push(user.name); 123 | return (true, "Success"); 124 | } 125 | 126 | function fromRetailerToCustomer(bytes32 commodityID, uint timestamp) returns (bool, string){ 127 | Commodity commodity = commodityMap[commodityID]; 128 | if (commodity.commodityID == 0x0) { 129 | return (false, "The commodityID don't exist!"); 130 | } 131 | commodity.sellTime = timestamp; 132 | return (true, "Success,Has been sold"); 133 | } 134 | 135 | function getCommodityRecordsByWhiteList(bytes32 commodityID) returns (bool, string, 136 | bytes32 producerName, uint produceTime, bytes32[] retailerNames, uint[] retailerTimes 137 | , bytes32 customerName, uint sellTime, uint price){ 138 | if (!whiteList[msg.sender]) { 139 | return (false, "you has no access", producerName, produceTime, retailerNames, retailerTimes, customerName, commodity.sellTime, commodity.price); 140 | } 141 | Commodity commodity = commodityMap[commodityID]; 142 | if (commodity.commodityID == 0x0) { 143 | return (false, "The commodityID is not exist", producerName, produceTime, retailerNames, retailerTimes, customerName, commodity.sellTime, commodity.price); 144 | } 145 | return (true, "Success", commodity.producerName, commodity.produceTime, commodity.retailerNames, commodity.timestamps, commodity.customerName, commodity.sellTime, commodity.price); 146 | } 147 | 148 | 149 | function getCommodity(bytes32 commodityID, Actor actor) returns (bool, string, 150 | bytes32 producerName, uint produceTime, bytes32[] retailerNames, uint[] retailerTimes 151 | , bytes32 customerName, uint sellTime, uint price){ 152 | Commodity commodity = commodityMap[commodityID]; 153 | if (commodity.commodityID == 0x0) { 154 | return (false, "The commodityID is not exist", producerName, produceTime, 155 | retailerNames, retailerTimes, customerName, sellTime, price); 156 | } 157 | User user; 158 | if (actor == Actor.Producer) { 159 | user = producerMap[msg.sender]; 160 | } else if (actor == Actor.Retailer) { 161 | user = retailerMap[msg.sender]; 162 | } else if (actor == Actor.Customer) { 163 | user = customerMap[msg.sender]; 164 | } else { 165 | return (false, "the actor is not belong", producerName, produceTime, 166 | retailerNames, retailerTimes, customerName, sellTime, price); 167 | } 168 | if (commodity.isBinding) { 169 | if (commodity.owner != msg.sender) { 170 | return (false, "warning,this commodity has been bound", producerName, produceTime, 171 | retailerNames, retailerTimes, customerName, sellTime, price); 172 | } 173 | } 174 | if (commodity.sellTime > 0) { 175 | commodity.isBinding = true; 176 | commodity.owner = msg.sender; 177 | commodity.customerName = user.name; 178 | } 179 | return (true, "Success", commodity.producerName, commodity.produceTime, commodity.retailerNames, commodity.timestamps, commodity.customerName, commodity.sellTime, commodity.price); 180 | } 181 | 182 | function getTotalPrice(bytes32[] commodityIDs) returns (bool, string, uint256 totalPrice ){ 183 | if (!whiteList[msg.sender]) { 184 | return (false, "you has no access", totalPrice); 185 | } 186 | 187 | for(uint8 i = 0; i < commodityIDs.length ; i++ ){ 188 | Commodity commodity = commodityMap[commodityIDs[i]]; 189 | if (commodity.commodityID == 0x0) { 190 | return (false, "The commodityID is not exist", totalPrice); 191 | }else{ 192 | totalPrice.add(commodity.price); 193 | } 194 | } 195 | return (true, "get the total price ", totalPrice); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/main/resources/hyperchain.properties: -------------------------------------------------------------------------------- 1 | # 是否重新编译和部署合约 2 | redeploy=false 3 | 4 | # 合约地址 5 | contract_address=0xdc58ca03fd1cd4c1515bff3f693e543e37848f02 6 | # abi 7 | abi=[{"constant":false,"inputs":[{"name":"ID","type":"bytes32"},{"name":"name","type":"bytes32"},{"name":"actor","type":"uint8"}],"name":"newUser","outputs":[{"name":"","type":"bool"},{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"commodityIDs","type":"bytes32[]"}],"name":"getTotalPrice","outputs":[{"name":"","type":"bool"},{"name":"","type":"string"},{"name":"totalPrice","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"commodityID","type":"bytes32"},{"name":"timestamp","type":"uint256"}],"name":"retailerInnerTransfer","outputs":[{"name":"","type":"bool"},{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"commodityID","type":"bytes32"},{"name":"commodityName","type":"bytes32"},{"name":"timestamp","type":"uint256"},{"name":"_price","type":"uint256"}],"name":"newProduct","outputs":[{"name":"","type":"bool"},{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"commodityID","type":"bytes32"},{"name":"timestamp","type":"uint256"}],"name":"fromRetailerToCustomer","outputs":[{"name":"","type":"bool"},{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"addWhiteList","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"commodityID","type":"bytes32"}],"name":"getCommodityRecordsByWhiteList","outputs":[{"name":"","type":"bool"},{"name":"","type":"string"},{"name":"producerName","type":"bytes32"},{"name":"produceTime","type":"uint256"},{"name":"retailerNames","type":"bytes32[]"},{"name":"retailerTimes","type":"uint256[]"},{"name":"customerName","type":"bytes32"},{"name":"sellTime","type":"uint256"},{"name":"price","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"commodityID","type":"bytes32"},{"name":"actor","type":"uint8"}],"name":"getCommodity","outputs":[{"name":"","type":"bool"},{"name":"","type":"string"},{"name":"producerName","type":"bytes32"},{"name":"produceTime","type":"uint256"},{"name":"retailerNames","type":"bytes32[]"},{"name":"retailerTimes","type":"uint256[]"},{"name":"customerName","type":"bytes32"},{"name":"sellTime","type":"uint256"},{"name":"price","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[],"payable":false,"type":"constructor"}] 8 | -------------------------------------------------------------------------------- /src/main/resources/lib/qsnarksdk-1.0-SNAPSHOT-jar-with-dependencies.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qulian-netease/minata/b03a554d3e5b299f7d7d8140d380cec195d8f5f0/src/main/resources/lib/qsnarksdk-1.0-SNAPSHOT-jar-with-dependencies.jar -------------------------------------------------------------------------------- /src/main/resources/参考合约/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.10; 2 | 3 | library SafeMath{ 4 | function mul(uint256 a , uint256 b ) internal returns( uint256 ){ 5 | if(a == 0 ){ 6 | return 0; 7 | } 8 | uint256 c = a * b ; 9 | assert(c /a == b ); 10 | return c; 11 | } 12 | function div(uint256 a , uint256 b ) internal returns( uint256 ){ 13 | uint256 c = a / b ; 14 | return c; 15 | } 16 | function sub(uint256 a , uint256 b ) internal returns( uint256 ){ 17 | assert(b<=a); 18 | return a - b; 19 | } 20 | function add(uint256 a , uint256 b ) internal returns( uint256 ){ 21 | uint256 c = a + b; 22 | assert(c >= a); 23 | return c; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/参考合约/supplychainNoSafeMath.sol: -------------------------------------------------------------------------------- 1 | contract SupplyChain { 2 | enum Actor{Others, Producer, Retailer, Customer} 3 | struct User { 4 | bytes32 ID; 5 | bytes32 name; 6 | Actor actor; 7 | } 8 | 9 | struct Commodity { 10 | bytes32 commodityID; 11 | bytes32 commodityName; 12 | uint produceTime; 13 | bytes32 producerName; 14 | uint[] timestamps; 15 | bytes32[] retailerNames; 16 | uint sellTime; 17 | bytes32 customerName; 18 | bool isBinding; 19 | address owner; 20 | } 21 | 22 | mapping(address => User) producerMap; 23 | mapping(address => User) retailerMap; 24 | mapping(address => User) customerMap; 25 | mapping(bytes32 => Commodity) commodityMap; 26 | mapping(address => bool) whiteList; 27 | address owner; 28 | 29 | modifier onlyOwner { 30 | if (msg.sender != owner) 31 | assert(false); 32 | _; 33 | } 34 | function SupplyChain(){ 35 | owner = msg.sender; 36 | whiteList[owner] = true; 37 | } 38 | 39 | function addWhiteList(address addr){ 40 | whiteList[addr] = true; 41 | } 42 | 43 | function newUser(bytes32 ID, bytes32 name, Actor actor) returns (bool, string){ 44 | User user; 45 | 46 | if (actor == Actor.Producer) { 47 | user = producerMap[msg.sender]; 48 | } else if (actor == Actor.Retailer) { 49 | user = retailerMap[msg.sender]; 50 | } else if (actor == Actor.Customer) { 51 | user = customerMap[msg.sender]; 52 | } else { 53 | return (false, "the actor is not belong"); 54 | } 55 | 56 | if (user.ID != 0x0) { 57 | return (false, "this ID has been occupied!"); 58 | } 59 | user.ID = ID; 60 | user.name = name; 61 | user.actor = actor; 62 | return (true, "Success"); 63 | } 64 | 65 | // this interface just for producer 66 | function newProduct(bytes32 commodityID, bytes32 commodityName, 67 | uint timestamp) returns (bool, bytes32){ 68 | Commodity commodity = commodityMap[commodityID]; 69 | if (commodity.commodityID != 0x0) { 70 | return (false, "The commodityID already exist!"); 71 | } 72 | User user = producerMap[msg.sender]; 73 | if (user.ID == 0x0) { 74 | return (false, "The producer don't exist!"); 75 | } 76 | commodity.commodityID = commodityID; 77 | commodity.commodityName = commodityName; 78 | commodity.produceTime = timestamp; 79 | commodity.producerName = user.name; 80 | return (true, "Success,produce a new product"); 81 | } 82 | 83 | 84 | // this interface just for retailer 85 | function retailerInnerTransfer(bytes32 commodityID, uint timestamp) returns (bool, string){ 86 | Commodity commodity = commodityMap[commodityID]; 87 | if (commodity.commodityID == 0x0) { 88 | return (false, "The commodityID don't exist!"); 89 | } 90 | User user = retailerMap[msg.sender]; 91 | if (user.ID == 0x0) { 92 | return (false, "The retailer don't exist!"); 93 | } 94 | commodity.timestamps.push(timestamp); 95 | commodity.retailerNames.push(user.name); 96 | return (true, "Success"); 97 | } 98 | 99 | function fromRetailerToCustomer(bytes32 commodityID, uint timestamp) returns (bool, string){ 100 | Commodity commodity = commodityMap[commodityID]; 101 | if (commodity.commodityID == 0x0) { 102 | return (false, "The commodityID don't exist!"); 103 | } 104 | commodity.sellTime = timestamp; 105 | return (true, "Success,Has been sold"); 106 | } 107 | 108 | // just for Supervision organization 109 | function getCommodityRecordsByWhiteList(bytes32 commodityID) returns (bool, string, 110 | bytes32 producerName, uint produceTime, bytes32[] retailerNames, uint[] retailerTimes 111 | , bytes32 customerName, uint sellTime){ 112 | if (!whiteList[msg.sender]) { 113 | return (false, "you has no access", producerName, produceTime, retailerNames, retailerTimes, customerName, commodity.sellTime); 114 | } 115 | Commodity commodity = commodityMap[commodityID]; 116 | if (commodity.commodityID == 0x0) { 117 | return (false, "The commodityID is not exist", producerName, produceTime, retailerNames, retailerTimes, customerName, commodity.sellTime); 118 | } 119 | return (true, "Success", commodity.producerName, commodity.produceTime, commodity.retailerNames, commodity.timestamps, commodity.customerName, commodity.sellTime); 120 | } 121 | 122 | 123 | function getCommodity(bytes32 commodityID, Actor actor) returns (bool, string, 124 | bytes32 producerName, uint produceTime, bytes32[] retailerNames, uint[] retailerTimes 125 | , bytes32 customerName, uint sellTime){ 126 | Commodity commodity = commodityMap[commodityID]; 127 | if (commodity.commodityID == 0x0) { 128 | return (false, "The commodityID is not exist", producerName, produceTime, 129 | retailerNames, retailerTimes, customerName, sellTime); 130 | } 131 | User user; 132 | if (actor == Actor.Producer) { 133 | user = producerMap[msg.sender]; 134 | } else if (actor == Actor.Retailer) { 135 | user = retailerMap[msg.sender]; 136 | } else if (actor == Actor.Customer) { 137 | user = customerMap[msg.sender]; 138 | } else { 139 | return (false, "the actor is not belong", producerName, produceTime, 140 | retailerNames, retailerTimes, customerName, sellTime); 141 | } 142 | if (commodity.isBinding) { 143 | if (commodity.owner != msg.sender) { 144 | return (false, "warning,this commodity has been bound", producerName, produceTime, 145 | retailerNames, retailerTimes, customerName, sellTime); 146 | } 147 | } 148 | if (commodity.sellTime > 0) { 149 | commodity.isBinding = true; 150 | commodity.owner = msg.sender; 151 | commodity.customerName = user.name; 152 | } 153 | return (true, "Success", commodity.producerName, commodity.produceTime, commodity.retailerNames, commodity.timestamps, commodity.customerName, commodity.sellTime); 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /src/main/resources/参考合约/supplychainWithSafeMath.sol: -------------------------------------------------------------------------------- 1 | 2 | library SafeMath{ 3 | 4 | function mul(uint8 a , uint8 b ) internal returns( uint8 ){ 5 | if(a == 0 ){ 6 | return 0; 7 | } 8 | uint8 c = a * b ; 9 | assert(c /a == b ); 10 | return c; 11 | } 12 | function div(uint256 a , uint256 b ) internal returns( uint256 ){ 13 | uint256 c = a / b ; 14 | return c; 15 | } 16 | function sub(uint256 a , uint256 b ) internal returns( uint256 ){ 17 | assert(b<=a); 18 | return a - b; 19 | } 20 | function add(uint256 a , uint256 b ) internal returns( uint256 ){ 21 | uint256 c = a + b; 22 | assert(c >= a); 23 | return c; 24 | } 25 | } 26 | 27 | contract SupplyChain { 28 | using SafeMath for uint256; 29 | enum Actor{Others, Producer, Retailer, Customer} 30 | struct User { 31 | bytes32 ID; 32 | bytes32 name; 33 | Actor actor; 34 | } 35 | 36 | struct Commodity { 37 | uint256 price; 38 | bytes32 commodityID; 39 | bytes32 commodityName; 40 | uint produceTime; 41 | bytes32 producerName; 42 | uint[] timestamps; 43 | bytes32[] retailerNames; 44 | uint sellTime; 45 | bytes32 customerName; 46 | bool isBinding; 47 | address owner; 48 | } 49 | 50 | mapping(address => User) producerMap; 51 | mapping(address => User) retailerMap; 52 | mapping(address => User) customerMap; 53 | mapping(bytes32 => Commodity) commodityMap; 54 | mapping(address => bool) whiteList; 55 | address owner; 56 | 57 | modifier onlyOwner { 58 | if (msg.sender != owner) 59 | assert(false); 60 | _; 61 | } 62 | function SupplyChain(){ 63 | owner = msg.sender; 64 | whiteList[owner] = true; 65 | } 66 | 67 | function addWhiteList(address addr){ 68 | whiteList[addr] = true; 69 | } 70 | 71 | function newUser(bytes32 ID, bytes32 name, Actor actor) returns (bool, string){ 72 | User user; 73 | 74 | if (actor == Actor.Producer) { 75 | user = producerMap[msg.sender]; 76 | } else if (actor == Actor.Retailer) { 77 | user = retailerMap[msg.sender]; 78 | } else if (actor == Actor.Customer) { 79 | user = customerMap[msg.sender]; 80 | } else { 81 | return (false, "the actor is not belong"); 82 | } 83 | 84 | if (user.ID != 0x0) { 85 | return (false, "this ID has been occupied!"); 86 | } 87 | user.ID = ID; 88 | user.name = name; 89 | user.actor = actor; 90 | return (true, "Success"); 91 | } 92 | 93 | function newProduct(bytes32 commodityID, bytes32 commodityName, 94 | uint timestamp, uint _price) returns (bool, bytes32){ 95 | Commodity commodity = commodityMap[commodityID]; 96 | if (commodity.commodityID != 0x0) { 97 | return (false, "The commodityID already exist!"); 98 | } 99 | User user = producerMap[msg.sender]; 100 | if (user.ID == 0x0) { 101 | return (false, "The producer don't exist!"); 102 | } 103 | commodity.commodityID = commodityID; 104 | commodity.commodityName = commodityName; 105 | commodity.produceTime = timestamp; 106 | commodity.producerName = user.name; 107 | commodity.price = _price; 108 | return (true, "Success,produce a new product"); 109 | } 110 | 111 | 112 | function retailerInnerTransfer(bytes32 commodityID, uint timestamp) returns (bool, string){ 113 | Commodity commodity = commodityMap[commodityID]; 114 | if (commodity.commodityID == 0x0) { 115 | return (false, "The commodityID don't exist!"); 116 | } 117 | User user = retailerMap[msg.sender]; 118 | if (user.ID == 0x0) { 119 | return (false, "The retailer don't exist!"); 120 | } 121 | commodity.timestamps.push(timestamp); 122 | commodity.retailerNames.push(user.name); 123 | return (true, "Success"); 124 | } 125 | 126 | function fromRetailerToCustomer(bytes32 commodityID, uint timestamp) returns (bool, string){ 127 | Commodity commodity = commodityMap[commodityID]; 128 | if (commodity.commodityID == 0x0) { 129 | return (false, "The commodityID don't exist!"); 130 | } 131 | commodity.sellTime = timestamp; 132 | return (true, "Success,Has been sold"); 133 | } 134 | 135 | function getCommodityRecordsByWhiteList(bytes32 commodityID) returns (bool, string, 136 | bytes32 producerName, uint produceTime, bytes32[] retailerNames, uint[] retailerTimes 137 | , bytes32 customerName, uint sellTime, uint price){ 138 | if (!whiteList[msg.sender]) { 139 | return (false, "you has no access", producerName, produceTime, retailerNames, retailerTimes, customerName, commodity.sellTime, commodity.price); 140 | } 141 | Commodity commodity = commodityMap[commodityID]; 142 | if (commodity.commodityID == 0x0) { 143 | return (false, "The commodityID is not exist", producerName, produceTime, retailerNames, retailerTimes, customerName, commodity.sellTime, commodity.price); 144 | } 145 | return (true, "Success", commodity.producerName, commodity.produceTime, commodity.retailerNames, commodity.timestamps, commodity.customerName, commodity.sellTime, commodity.price); 146 | } 147 | 148 | 149 | function getCommodity(bytes32 commodityID, Actor actor) returns (bool, string, 150 | bytes32 producerName, uint produceTime, bytes32[] retailerNames, uint[] retailerTimes 151 | , bytes32 customerName, uint sellTime, uint price){ 152 | Commodity commodity = commodityMap[commodityID]; 153 | if (commodity.commodityID == 0x0) { 154 | return (false, "The commodityID is not exist", producerName, produceTime, 155 | retailerNames, retailerTimes, customerName, sellTime, price); 156 | } 157 | User user; 158 | if (actor == Actor.Producer) { 159 | user = producerMap[msg.sender]; 160 | } else if (actor == Actor.Retailer) { 161 | user = retailerMap[msg.sender]; 162 | } else if (actor == Actor.Customer) { 163 | user = customerMap[msg.sender]; 164 | } else { 165 | return (false, "the actor is not belong", producerName, produceTime, 166 | retailerNames, retailerTimes, customerName, sellTime, price); 167 | } 168 | if (commodity.isBinding) { 169 | if (commodity.owner != msg.sender) { 170 | return (false, "warning,this commodity has been bound", producerName, produceTime, 171 | retailerNames, retailerTimes, customerName, sellTime, price); 172 | } 173 | } 174 | if (commodity.sellTime > 0) { 175 | commodity.isBinding = true; 176 | commodity.owner = msg.sender; 177 | commodity.customerName = user.name; 178 | } 179 | return (true, "Success", commodity.producerName, commodity.produceTime, commodity.retailerNames, commodity.timestamps, commodity.customerName, commodity.sellTime, commodity.price); 180 | } 181 | 182 | function getTotalPrice(bytes32[] commodityIDs) returns (bool, string, uint256 totalPrice ){ 183 | if (!whiteList[msg.sender]) { 184 | return (false, "you has no access", totalPrice); 185 | } 186 | 187 | for(uint8 i = 0; i < commodityIDs.length ; i++ ){ 188 | Commodity commodity = commodityMap[commodityIDs[i]]; 189 | if (commodity.commodityID == 0x0) { 190 | return (false, "The commodityID is not exist", totalPrice); 191 | }else{ 192 | totalPrice.add(commodity.price); 193 | } 194 | } 195 | return (true, "get the total price ", totalPrice); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/main/resources/参考合约/testOverflow.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.10; 2 | contract Test { 3 | function testOverflow(uint8 _value1, uint8 _value2 ) returns(uint8) { 4 | uint8 ret ; 5 | ret = _value1 * _value2; 6 | return ret; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/resources/参考合约/useSafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.10; 2 | 3 | library SafeMath{ 4 | function mul(uint8 a , uint8 b ) internal returns( uint8 ){ 5 | if(a == 0 ){ 6 | return 0; 7 | } 8 | uint8 c = a * b ; 9 | assert(c /a == b ); 10 | return c; 11 | } 12 | function div(uint256 a , uint256 b ) internal returns( uint256 ){ 13 | uint256 c = a / b ; 14 | return c; 15 | } 16 | function sub(uint256 a , uint256 b ) internal returns( uint256 ){ 17 | assert(b<=a); 18 | return a - b; 19 | } 20 | function add(uint256 a , uint256 b ) internal returns( uint256 ){ 21 | uint256 c = a + b; 22 | assert(c >= a); 23 | return c; 24 | } 25 | } 26 | 27 | contract Test { 28 | using SafeMath for uint8; 29 | function testOverflow(uint8 _value1, uint8 _value2 ) returns(uint8) { 30 | uint8 ret ; 31 | ret = _value1.mul(_value2); 32 | return ret; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /web/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | --------------------------------------------------------------------------------