diff --git a/build.sbt b/build.sbt index ad6e95a3..74ee7983 100644 --- a/build.sbt +++ b/build.sbt @@ -60,6 +60,9 @@ libraryDependencies ++= Seq( "com.typesafe.akka" %% "akka-http" % akkaHttpVersion, "com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion, "de.heikoseeberger" % "akka-http-json4s_2.11" % "1.16.1", + "org.json4s" %% "json4s-native" % "3.5.4", + "org.json4s" %% "json4s-jackson" % "3.5.4", + "ch.megard" %% "akka-http-cors" % "0.2.2" ) diff --git a/conf/system.conf b/conf/system.conf index e2e27bf7..d2df08bc 100644 --- a/conf/system.conf +++ b/conf/system.conf @@ -77,11 +77,11 @@ system { statistic_enable = 0 // 0,unable;able httpServicePort = 8081//http服务的端口号,默认为8081 - + checkCertValidate = 0//设置是否检查证书的有效性,默认为0 0=不校验,1=校验 contractOperationMode = 0//设置合约的运行方式,0=debug方式,1=deploy,默认为debug方式,如果发布部署,必须使用deploy方式。 - + block { //块内交易的最大数量 trans_num_limit = 50 diff --git a/jks/mykeystore_5.jks b/jks/mykeystore_5.jks new file mode 100644 index 00000000..48a17d29 Binary files /dev/null and b/jks/mykeystore_5.jks differ diff --git a/scripts/api_req/assets5.xml b/scripts/api_req/assets5.xml new file mode 100644 index 00000000..3ed67b0c --- /dev/null +++ b/scripts/api_req/assets5.xml @@ -0,0 +1,203 @@ + + + + + 1 + + + + + + 0 + string + + + val sp0 = ctx.api.getVal(sid) + val sp = read[IPTSignShare](ctx.api.getVal(sid).asInstanceOf[String]) + splitShare(data.amount, sp.account_remain, sp.tpl_param) + case TPL.Fixed => + val sp = read[IPTSignFixed](ctx.api.getVal(sid).asInstanceOf[String]) + splitFixedRatio(data.amount, sp.account_remain, sp.ratio) + } + //返回分账计算结果 + addToAccount(ctx, mr) + mr + } + + /** + * 将分账结果增加到账户并持久化 + */ + def addToAccount(ctx: ContractContext, mr:Map[String,Int]){ + for ((k, v) <- mr) { + val sk = ctx.api.getVal(k) + var dk = if(sk==null) 0 else sk.toString.toInt + ctx.api.setVal(k, dk+v) + } + } + /** + * 合约方法入口 + */ + def onAction(ctx: ContractContext,action:String, sdata:String ):Object={ + val json = parse(sdata) + + action match { + case ACTION.SignShare => + signShare(ctx,json.extract[IPTSignShare]) + case ACTION.SignFixed => + signFixed(ctx,json.extract[IPTSignFixed]) + case ACTION.Split => + split(ctx, json.extract[IPTSplit]) + case ACTION.ConfirmSign => + confirmSign(ctx,json.extract[IPTConfirm]) + case ACTION.CancelSign => + cancelSign(ctx, json.extract[IPTConfirm]) + } + } + //TODO case Transaction.Type.CHAINCODE_DESC 增加对合约描述的处理 + def descAction(ctx: ContractContext,action:String, sdata:String ):String={ + val json = parse(sdata) + null + } + +/** + * 内部函数, 获得分阶段的分成 + */ + def getShare(sr: Int, ar: Array[ShareRatio]) : Int={ + var rv = 0 + for(el <- ar) { + //击中金额范围 + if(sr > el.from && sr <= el.to) { + //固定金额 + if(el.fixed > 0) + rv = el.fixed + else //按比例分成 + rv = (sr * el.ratio) .toInt + } + } + rv + } +/** + * 合约中内置多种分账模版,签约时可选择模版,如果出现新的分账模版,则部署一版新的合约 + * 分成模型, 除了销售方之外, 其他各方要求一个最低金额,分成按照金额阶段有所不同。 + */ + def splitShare(sr: Int, account_remain:String, rule: ShareMap): Map[String,Int] ={ + //分账结果 + val rm : Map[String, Int] = Map() + //分账余额 + var remain = sr + for ((k, v) <- rule) { + val rv = getShare(sr, v) + rm += (k -> rv) + remain -= rv + } + rm += (account_remain -> remain) + } + + +/** + * 各方固定比例分成,此模版仅仅为了合约对多模版的支持,可能无实际用途 + */ + def splitFixedRatio(sr: Int, account_remain: String, mr:FixedMap): Map[String,Int] ={ + val rm : Map[String, Int] = Map() + var remain = sr + //根据固定分成 + for ((k, v) <- mr) { + val rv = (sr* v ).toInt + rm += (k -> rv) + remain -= rv + } + //剩余的分给指定的余额账户 + rm += (account_remain -> remain) + } +} + +]]> + + 1 + \ No newline at end of file diff --git a/scripts/example_deploy.js b/scripts/example_deploy.js index 64f2464f..8d331581 100644 --- a/scripts/example_deploy.js +++ b/scripts/example_deploy.js @@ -1,67 +1,67 @@ -/* - * Copyright 2018 Blockchain Technology and Application Joint Lab, Fintech Research Center of ISCAS. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BA SIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -function loadCert(cert){ - shim.loadCert(cert); - print("cert:"+cert); -} -function write(pn,pv){ - shim.setVal(pn,pv); - // print("setState:"+pn+":"+pv) -} -function set(pm){ - for(x in pm){ - write(x,pm[x]); - } -} -function read(pn){ - return shim.getVal(pn); -} -function transfer(afrom,ato,amount){ - // print('tx_account:'+tx_account); - if(afrom != tx_account) - throw "["+tx_account+"]无权从["+afrom+"]转出资产" - var rfrom = read(afrom); - // print(rfrom + ":" + amount) - if(rfrom - println(s"transfer oook") - transfer(ctx,json.extract[Transfer]) - case "set" => - println(s"set") - set(ctx, json.extract[Map[String,Int]]) - case "put_proof" => - println(s"put_proof") - put_proof(ctx, json.extract[Proof]) - case "signup" => - println(s"signup") - null - case "destroyCert" => - println(s"destroyCert") - null - case "replaceCert" => - println(s"replaceCert") - null - } - } - -} + +import org.json4s._ +import org.json4s.jackson.JsonMethods._ +import rep.sc.contract._ +import rep.storage.FakeStorage.Key + +/** + * 资产管理合约 + */ + +class NewContract extends IContract{ + case class Transfer(from:String, to:String, amount:Int) + case class Proof(key:String, content:String) + case class ReplaceCert(cert:String, addr:String) + case class Cert(cert:String, info:String) + + implicit val formats = DefaultFormats + + def init(ctx: ContractContext){ + println(s"tid: $ctx.t.txid") + } + + def set(ctx: ContractContext, data:Map[String,Int]):Object={ + println(s"set data:$data") + for((k,v)<-data){ + ctx.api.setVal(k, v) + } + null + } + + + def read(ctx: ContractContext, key: String):Any={ + ctx.api.getVal(key) + } + + def loadCert(ctx: ContractContext){ + + } + + def write(ctx: ContractContext){ + + } + +// def put_proof(ctx: ContractContext, data:Proof):Object={ +// //先检查该hash是否已经存在,如果已存在,抛异常 +// val pn = data.key +// val pv = data.content +// var pv0 = ctx.api.getVal(pn) +// if(pv0 != null) +// throw new Exception("["+pn+"]已存在,当前值["+pv0+"]"); +// ctx.api.setVal(pn,pv); +// print("putProof:"+pn+":"+pv); +// "put_proof ok" +// } + + def put_proof(ctx: ContractContext, data:Map[String,Any]):Object={ + //先检查该hash是否已经存在,如果已存在,抛异常 + for((k,v)<-data){ + var pv0 = ctx.api.getVal(k) + if(pv0 != null) + throw new Exception("["+k+"]已存在,当前值["+pv0+"]"); + ctx.api.setVal(k,v); + print("putProof:"+k+":"+v); + } + + "put_proof ok" + } + + + + def signup(ctx: ContractContext, data: Cert):Object = { + val cert = data.cert + val info = data.info + ctx.api.check(ctx.t.cert.toStringUtf8,ctx.t) + ctx.api.signup(cert,info) + } + + def destroyCert(ctx: ContractContext, certAddr: String): Object = { + ctx.api.check(ctx.t.cert.toStringUtf8,ctx.t) //ctx中自带交易内容 + ctx.api.destroyCert(certAddr); + "destory scuccess" + } + + def replaceCert(ctx: ContractContext, data:ReplaceCert): Object = { + val cert = data.cert + val addr = data.addr + ctx.api.check(ctx.t.cert.toStringUtf8,ctx.t) + ctx.api.replaceCert(cert,addr); // 返回短地址 + } + + def transfer(ctx: ContractContext, data:Transfer):Object={ + val sfrom = ctx.api.getVal(data.from) + var dfrom =sfrom.toString.toInt + if(dfrom < data.amount) + throw new Exception("余额不足") + var dto = ctx.api.getVal(data.to).toString.toInt + //if(dto==null) dto = 0; + + ctx.api.setVal(data.from,dfrom - data.amount) + ctx.api.setVal(data.to,dto + data.amount) + "transfer ok" + } + + /** + * 根据action,找到对应的method,并将传入的json字符串parse为method需要的传入参数 + */ + def onAction(ctx: ContractContext,action:String, sdata:String ):Object={ + //println(s"onAction---") + //return "transfer ok" + val json = parse(sdata) + + action match { + case "transfer" => + println(s"transfer oook") + transfer(ctx,json.extract[Transfer]) + case "set" => + println(s"set") + set(ctx, json.extract[Map[String,Int]]) + case "put_proof" => + println(s"put_proof") + put_proof(ctx, json.extract[Proof]) + case "signup" => + println(s"signup") + null + case "destroyCert" => + println(s"destroyCert") + null + case "replaceCert" => + println(s"replaceCert") + null + } + } + +} diff --git a/src/main/scala/NewContract.scala b/src/main/scala/NewContract.scala index 7e4bf247..309d5251 100644 --- a/src/main/scala/NewContract.scala +++ b/src/main/scala/NewContract.scala @@ -42,10 +42,9 @@ class NewContract extends IContract{ } null } - - - def read(ctx: ContractContext, key: String):Any={ - ctx.api.getVal(key) + + def read(ctx: ContractContext, key: String):Object={ + ctx.api.getVal(key).toString } def loadCert(ctx: ContractContext, cert: String): Unit = { @@ -137,6 +136,9 @@ class NewContract extends IContract{ case "replaceCert" => println(s"replaceCert") replaceCert(ctx, json.extract[ReplaceCert]) + case "read"=> + println(s"read") + read(ctx, json.extract[String]) } } diff --git a/src/main/scala/rep/api/SwaggerDocService.scala b/src/main/scala/rep/api/SwaggerDocService.scala index 65d85a1a..10571359 100644 --- a/src/main/scala/rep/api/SwaggerDocService.scala +++ b/src/main/scala/rep/api/SwaggerDocService.scala @@ -1,44 +1,44 @@ -/* - * Copyright 2018 Blockchain Technology and Application Joint Lab, Fintech Research Center of ISCAS. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BA SIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package rep.api - -import scala.reflect.runtime.{universe=>ru} -import akka.actor.ActorSystem -import akka.stream.ActorMaterializer -import com.github.swagger.akka._ -import com.github.swagger.akka.model.`package`.Info -import rep.api.rest._ -import io.swagger.models.ExternalDocs -import io.swagger.models.auth.BasicAuthDefinition -import rep.app.conf.SystemProfile - -/**集成Swagger到AKKA HTTP - * @author c4w - * @constructor 创建提供Swagger文档服务的实例 - * @param system 传入的AKKA系统实例 - * - */ -class SwaggerDocService(system: ActorSystem) extends SwaggerHttpService with HasActorSystem { - override implicit val actorSystem: ActorSystem = system - override implicit val materializer: ActorMaterializer = ActorMaterializer() - override val apiTypes = Seq( - ru.typeOf[ChainService], - ru.typeOf[BlockService],ru.typeOf[TransactionService], - ru.typeOf[CertService], ru.typeOf[HashVerifyService]) - override val info = Info(version = "0.7") - override val externalDocs = Some(new ExternalDocs("Developers Guide", "https://repchaindoc.readthedocs.io/zh/latest/index.html")) - override val securitySchemeDefinitions = Map("basicAuth" -> new BasicAuthDefinition()) +/* + * Copyright 2018 Blockchain Technology and Application Joint Lab, Fintech Research Center of ISCAS. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BA SIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rep.api + +import scala.reflect.runtime.{universe=>ru} +import akka.actor.ActorSystem +import akka.stream.ActorMaterializer +import com.github.swagger.akka._ +import com.github.swagger.akka.model.`package`.Info +import rep.api.rest._ +import io.swagger.models.ExternalDocs +import io.swagger.models.auth.BasicAuthDefinition +import rep.app.conf.SystemProfile + +/**集成Swagger到AKKA HTTP + * @author c4w + * @constructor 创建提供Swagger文档服务的实例 + * @param system 传入的AKKA系统实例 + * + */ +class SwaggerDocService(system: ActorSystem) extends SwaggerHttpService with HasActorSystem { + override implicit val actorSystem: ActorSystem = system + override implicit val materializer: ActorMaterializer = ActorMaterializer() + override val apiTypes = Seq( + ru.typeOf[ChainService], + ru.typeOf[BlockService],ru.typeOf[TransactionService], + ru.typeOf[CertService], ru.typeOf[HashVerifyService]) + override val info = Info(version = "0.7") + override val externalDocs = Some(new ExternalDocs("Developers Guide", "https://repchaindoc.readthedocs.io/zh/latest/index.html")) + override val securitySchemeDefinitions = Map("basicAuth" -> new BasicAuthDefinition()) } \ No newline at end of file diff --git a/src/main/scala/rep/api/rest/RestActor.scala b/src/main/scala/rep/api/rest/RestActor.scala index 4d89502f..67e3303f 100644 --- a/src/main/scala/rep/api/rest/RestActor.scala +++ b/src/main/scala/rep/api/rest/RestActor.scala @@ -178,10 +178,8 @@ class RestActor extends Actor with ModuleHelper with RepLogging { val sig = txr.signature.toByteArray val tOutSig1 = txr.withSignature(ByteString.EMPTY) val tOutSig = tOutSig1.withMetadata(ByteString.EMPTY) - val cid = ChaincodeID.fromAscii(txr.chaincodeID.toStringUtf8).name val certKey = WorldStateKeyPreFix + cid + "_" + PRE_CERT + txr.cert.toStringUtf8 - try{ var cert = ECDSASign.getCertWithCheck(txr.cert.toStringUtf8,certKey,pe.getSysTag) if(cert != None){ @@ -231,9 +229,9 @@ class RestActor extends Actor with ModuleHelper with RepLogging { val future = sandbox ? PreTransaction(t) val result = Await.result(future, timeout.duration).asInstanceOf[DoTransactionResult] val rv = result - + ImpDataPreloadMgr.Free(pe.getDBTag,t.txid) - + rv.err match { case None => //预执行正常,提交并广播交易 diff --git a/src/main/scala/rep/api/rest/RestService.scala b/src/main/scala/rep/api/rest/RestService.scala index 7223427e..b27c4615 100644 --- a/src/main/scala/rep/api/rest/RestService.scala +++ b/src/main/scala/rep/api/rest/RestService.scala @@ -1,296 +1,296 @@ -/* - * Copyright 2018 Blockchain Technology and Application Joint Lab, Fintech Research Center of ISCAS. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BA SIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package rep.api.rest - -import scala.concurrent.{ ExecutionContext, Future } -import akka.actor.{ ActorRef, ActorSelection } -import akka.util.Timeout -import akka.http.scaladsl.model.Uri.Path.Segment -import akka.http.scaladsl.server.Directives -import io.swagger.annotations._ -import javax.ws.rs.Path -import akka.http.scaladsl.model._ -import akka.http.scaladsl.server._ -import StatusCodes._ -import Directives._ -import rep.sc.Sandbox.SandboxException -import rep.sc.Sandbox._ -import rep.sc.Shim._ - -import de.heikoseeberger.akkahttpjson4s.Json4sSupport -import rep.protos.peer._ -import rep.api.rest.RestActor._ -import spray.json.DefaultJsonProtocol._ -import org.json4s.{ DefaultFormats, Formats, jackson } - -import akka.http.scaladsl.server.Directives -import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport -import spray.json._ - - -import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport -import akka.http.scaladsl.model.{ContentTypes, HttpCharsets, MediaTypes} -import akka.http.scaladsl.unmarshalling.{FromEntityUnmarshaller, Unmarshaller} - -import scala.xml.NodeSeq - - -/** 获得区块链的概要信息 - * @author c4w - */ - -@Api(value = "/chaininfo", description = "获得当前区块链信息", produces = "application/json") -@Path("chaininfo") -class ChainService(ra: ActorRef)(implicit executionContext: ExecutionContext) - extends Directives { - - import akka.pattern.ask - import scala.concurrent.duration._ - - import Json4sSupport._ - implicit val serialization = jackson.Serialization // or native.Serialization - implicit val formats = DefaultFormats - implicit val timeout = Timeout(20.seconds) - - val route = getBlockChainInfo - - @ApiOperation(value = "返回块链信息", notes = "", nickname = "getChainInfo", httpMethod = "GET") - @ApiResponses(Array( - new ApiResponse(code = 200, message = "返回块链信息", response = classOf[QueryResult]))) - def getBlockChainInfo = - path("chaininfo") { - get { - complete { (ra ? ChainInfo).mapTo[QueryResult] } - } - } -} - -/** 获得指定区块的详细信息 - * @author c4w - */ - -@Api(value = "/block", description = "获得区块数据", produces = "application/json") -@Path("block") -class BlockService(ra: ActorRef)(implicit executionContext: ExecutionContext) - extends Directives { - import akka.pattern.ask - import scala.concurrent.duration._ - - implicit val timeout = Timeout(20.seconds) - import Json4sSupport._ - implicit val serialization = jackson.Serialization // or native.Serialization - implicit val formats = DefaultFormats - - val route = getBlockById ~ getBlockByHeight - - @Path("/hash/{blockId}") - @ApiOperation(value = "返回指定id的区块", notes = "", nickname = "getBlockById", httpMethod = "GET") - @ApiImplicitParams(Array( - new ApiImplicitParam(name = "blockId", value = "区块id", required = true, dataType = "string", paramType = "path"))) - @ApiResponses(Array( - new ApiResponse(code = 200, message = "返回区块json内容", response = classOf[QueryResult]))) - def getBlockById = - path("block" / "hash" / Segment) { blockId => - get { - complete { (ra ? BlockId(blockId)).mapTo[QueryResult] } - } - } - - @Path("/{blockHeight}") - @ApiOperation(value = "返回指定高度的区块", notes = "", nickname = "getBlockByHeight", httpMethod = "GET") - @ApiImplicitParams(Array( - new ApiImplicitParam(name = "blockHeight", value = "区块高度", required = true, dataType = "int", paramType = "path"))) - @ApiResponses(Array( - new ApiResponse(code = 200, message = "返回区块json内容", response = classOf[QueryResult]))) - def getBlockByHeight = - path("block" / Segment) { blockHeight => - get { - complete { (ra ? BlockHeight(blockHeight.toInt)).mapTo[QueryResult] } - } - } - -} - -/** 获得指定交易的详细信息,提交签名交易 - * @author c4w - */ - -@Api(value = "/transaction", description = "获得交易数据", consumes = "application/json,application/xml", produces = "application/json,application/xml") -@Path("transaction") -class TransactionService(ra: ActorRef)(implicit executionContext: ExecutionContext) - extends Directives { - - import akka.pattern.ask - import scala.concurrent.duration._ - - implicit val timeout = Timeout(20.seconds) - import Json4sSupport._ - import ScalaXmlSupport._ - - implicit val serialization = jackson.Serialization // or native.Serialization - implicit val formats = DefaultFormats - - implicit val specFormat = jsonFormat9(CSpec) - implicit val specUnmarshaller: FromEntityUnmarshaller[CSpec] = Unmarshaller.firstOf( - //只能处理application/xml - nodeSeqUnmarshaller(MediaTypes.`application/xml` withCharset HttpCharsets.`UTF-8`) map { - case NodeSeq.Empty => - throw Unmarshaller.NoContentException - case x => - CSpec( - (x \ "stype").text.toInt, - (x \ "idPath").text, - Some((x \ "idName").text), - (x \ "iptFunc").text, - Seq((x \ "iptArgs").text), - (x \ "timeout").text.toInt, - (x \ "secureContext").text, - (x \ "code").text, - (x \ "ctype").text.toInt - ) - }, - //只能处理application/json - unmarshaller[CSpec].forContentTypes(MediaTypes.`application/json`) - ) - - val route = getTransaction ~ postSignTransaction ~ postTransaction - - @Path("/{transactionId}") - @ApiOperation(value = "返回指定id的交易", notes = "", nickname = "getTransaction", httpMethod = "GET") - @ApiImplicitParams(Array( - new ApiImplicitParam(name = "transactionId", value = "交易id", required = false, dataType = "string", paramType = "path"))) - @ApiResponses(Array( - new ApiResponse(code = 200, message = "返回交易json内容", response = classOf[QueryResult]))) - def getTransaction = - path("transaction" / Segment) { transactionId => - get { - complete { (ra ? TransactionId(transactionId)).mapTo[QueryResult] } - } - } -//以十六进制字符串提交签名交易 - @Path("/postTranByString") - @ApiOperation(value = "提交带签名的交易", notes = "", nickname = "postSignTransaction", httpMethod = "POST") - @ApiImplicitParams(Array( - new ApiImplicitParam(name = "body", value = "交易内容", required = true, dataType = "string", paramType = "body"))) - @ApiResponses(Array( - new ApiResponse(code = 200, message = "返回交易id以及执行结果", response = classOf[PostResult]), - new ApiResponse(code = 202, message = "处理存在异常", response = classOf[PostResult]))) - def postSignTransaction = - path("transaction" / "postTranByString") { - post { - entity(as[String]) { trans => - //str=> - //complete(OK, trans) - complete { (ra ? tranSign(trans)).mapTo[PostResult] } - } - } - } - - @Path("/postTran") - @ApiOperation(value = "提交交易", notes = "", nickname = "postTransaction", httpMethod = "POST") - @ApiImplicitParams(Array( - new ApiImplicitParam(name = "body", value = "交易内容", required = true, - dataTypeClass = classOf[CSpec], paramType = "body"))) - @ApiResponses(Array( - new ApiResponse(code = 200, message = "返回交易id以及执行结果", response = classOf[PostResult]), - new ApiResponse(code = 202, message = "处理存在异常", response = classOf[PostResult]))) - def postTransaction = - path("transaction" / "postTran") { - post { - import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._ - entity(as[CSpec]) { request => - complete { (ra ? request).mapTo[PostResult] } - } - } - } - } - -@Api(value = "/certAddr", description = "获得证书短地址", produces = "application/json") -@Path("certAddr") -class CertService(ra: ActorRef)(implicit executionContext: ExecutionContext) - extends Directives { - - import akka.pattern.ask - import scala.concurrent.duration._ - - import Json4sSupport._ - implicit val serialization = jackson.Serialization // or native.Serialization - implicit val formats = DefaultFormats - implicit val timeout = Timeout(20.seconds) - - val route = getAddrByCert ~ getCertByAddr - - @Path("/getAddrByCert") - @ApiOperation(value = "返回证书短地址", notes = "", nickname = "getAddrByCert", httpMethod = "POST") - @ApiImplicitParams(Array( - new ApiImplicitParam(name = "body", value = "证书", required = true, dataTypeClass = classOf[PostCert], paramType = "body"))) - @ApiResponses(Array( - new ApiResponse(code = 200, message = "查询证书短地址", response = classOf[QueryAddr]))) - def getAddrByCert = - path("certAddr" / "getAddrByCert") { - post { - entity(as[PostCert]) { PostCert => - complete { (ra ? PostCert).mapTo[QueryAddr] } - } - } - } - - @Path("/getCertByAddr") - @ApiOperation(value = "返回证书字符串", notes = "", nickname = "getCertByAddr", httpMethod = "POST") - @ApiImplicitParams(Array( - new ApiImplicitParam(name = "body", value = "短地址", required = true, dataTypeClass = classOf[PostAddr], paramType = "body"))) - @ApiResponses(Array( - new ApiResponse(code = 200, message = "查询证书字符串", response = classOf[QueryCert]))) - def getCertByAddr = - path("certAddr" / "getCertByAddr") { - post { - entity(as[PostAddr]) { PostAddr => - complete { (ra ? PostAddr).mapTo[QueryCert] } - } - } - } - } - - -@Api(value = "/hash", description = "验证hash是否存在", produces = "application/json") -@Path("hash") -class HashVerifyService(ra: ActorRef)(implicit executionContext: ExecutionContext) - extends Directives { - - import akka.pattern.ask - import scala.concurrent.duration._ - - import Json4sSupport._ - implicit val serialization = jackson.Serialization // or native.Serialization - implicit val formats = DefaultFormats - implicit val timeout = Timeout(20.seconds) - - val route = verifyImageHash - - @Path("/verifyHash") - @ApiOperation(value = "返回hash是否存在", notes = "", nickname = "verifyHash", httpMethod = "POST") - @ApiImplicitParams(Array( - new ApiImplicitParam(name = "body", value = "hash值与cid", required = true, dataTypeClass = classOf[PostHash], paramType = "body"))) - @ApiResponses(Array(new ApiResponse(code = 200, message = "验证hash值", response = classOf[QueryHash]))) - def verifyImageHash = - path("hash" / "verifyHash") { - post { - entity(as[PostHash]) { PostHash => - complete { (ra ? PostHash).mapTo[QueryHash] } - } - } - } +/* + * Copyright 2018 Blockchain Technology and Application Joint Lab, Fintech Research Center of ISCAS. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BA SIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rep.api.rest + +import scala.concurrent.{ ExecutionContext, Future } +import akka.actor.{ ActorRef, ActorSelection } +import akka.util.Timeout +import akka.http.scaladsl.model.Uri.Path.Segment +import akka.http.scaladsl.server.Directives +import io.swagger.annotations._ +import javax.ws.rs.Path +import akka.http.scaladsl.model._ +import akka.http.scaladsl.server._ +import StatusCodes._ +import Directives._ +import rep.sc.Sandbox.SandboxException +import rep.sc.Sandbox._ +import rep.sc.Shim._ + +import de.heikoseeberger.akkahttpjson4s.Json4sSupport +import rep.protos.peer._ +import rep.api.rest.RestActor._ +import spray.json.DefaultJsonProtocol._ +import org.json4s.{ DefaultFormats, Formats, jackson } + +import akka.http.scaladsl.server.Directives +import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport +import spray.json._ + + +import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport +import akka.http.scaladsl.model.{ContentTypes, HttpCharsets, MediaTypes} +import akka.http.scaladsl.unmarshalling.{FromEntityUnmarshaller, Unmarshaller} + +import scala.xml.NodeSeq + + +/** 获得区块链的概要信息 + * @author c4w + */ + +@Api(value = "/chaininfo", description = "获得当前区块链信息", produces = "application/json") +@Path("chaininfo") +class ChainService(ra: ActorRef)(implicit executionContext: ExecutionContext) + extends Directives { + + import akka.pattern.ask + import scala.concurrent.duration._ + + import Json4sSupport._ + implicit val serialization = jackson.Serialization // or native.Serialization + implicit val formats = DefaultFormats + implicit val timeout = Timeout(20.seconds) + + val route = getBlockChainInfo + + @ApiOperation(value = "返回块链信息", notes = "", nickname = "getChainInfo", httpMethod = "GET") + @ApiResponses(Array( + new ApiResponse(code = 200, message = "返回块链信息", response = classOf[QueryResult]))) + def getBlockChainInfo = + path("chaininfo") { + get { + complete { (ra ? ChainInfo).mapTo[QueryResult] } + } + } +} + +/** 获得指定区块的详细信息 + * @author c4w + */ + +@Api(value = "/block", description = "获得区块数据", produces = "application/json") +@Path("block") +class BlockService(ra: ActorRef)(implicit executionContext: ExecutionContext) + extends Directives { + import akka.pattern.ask + import scala.concurrent.duration._ + + implicit val timeout = Timeout(20.seconds) + import Json4sSupport._ + implicit val serialization = jackson.Serialization // or native.Serialization + implicit val formats = DefaultFormats + + val route = getBlockById ~ getBlockByHeight + + @Path("/hash/{blockId}") + @ApiOperation(value = "返回指定id的区块", notes = "", nickname = "getBlockById", httpMethod = "GET") + @ApiImplicitParams(Array( + new ApiImplicitParam(name = "blockId", value = "区块id", required = true, dataType = "string", paramType = "path"))) + @ApiResponses(Array( + new ApiResponse(code = 200, message = "返回区块json内容", response = classOf[QueryResult]))) + def getBlockById = + path("block" / "hash" / Segment) { blockId => + get { + complete { (ra ? BlockId(blockId)).mapTo[QueryResult] } + } + } + + @Path("/{blockHeight}") + @ApiOperation(value = "返回指定高度的区块", notes = "", nickname = "getBlockByHeight", httpMethod = "GET") + @ApiImplicitParams(Array( + new ApiImplicitParam(name = "blockHeight", value = "区块高度", required = true, dataType = "int", paramType = "path"))) + @ApiResponses(Array( + new ApiResponse(code = 200, message = "返回区块json内容", response = classOf[QueryResult]))) + def getBlockByHeight = + path("block" / Segment) { blockHeight => + get { + complete { (ra ? BlockHeight(blockHeight.toInt)).mapTo[QueryResult] } + } + } + +} + +/** 获得指定交易的详细信息,提交签名交易 + * @author c4w + */ + +@Api(value = "/transaction", description = "获得交易数据", consumes = "application/json,application/xml", produces = "application/json,application/xml") +@Path("transaction") +class TransactionService(ra: ActorRef)(implicit executionContext: ExecutionContext) + extends Directives { + + import akka.pattern.ask + import scala.concurrent.duration._ + + implicit val timeout = Timeout(20.seconds) + import Json4sSupport._ + import ScalaXmlSupport._ + + implicit val serialization = jackson.Serialization // or native.Serialization + implicit val formats = DefaultFormats + + implicit val specFormat = jsonFormat9(CSpec) + implicit val specUnmarshaller: FromEntityUnmarshaller[CSpec] = Unmarshaller.firstOf( + //只能处理application/xml + nodeSeqUnmarshaller(MediaTypes.`application/xml` withCharset HttpCharsets.`UTF-8`) map { + case NodeSeq.Empty => + throw Unmarshaller.NoContentException + case x => + CSpec( + (x \ "stype").text.toInt, + (x \ "idPath").text, + Some((x \ "idName").text), + (x \ "iptFunc").text, + Seq((x \ "iptArgs").text), + (x \ "timeout").text.toInt, + (x \ "secureContext").text, + (x \ "code").text, + (x \ "ctype").text.toInt + ) + }, + //只能处理application/json + unmarshaller[CSpec].forContentTypes(MediaTypes.`application/json`) + ) + + val route = getTransaction ~ postSignTransaction ~ postTransaction + + @Path("/{transactionId}") + @ApiOperation(value = "返回指定id的交易", notes = "", nickname = "getTransaction", httpMethod = "GET") + @ApiImplicitParams(Array( + new ApiImplicitParam(name = "transactionId", value = "交易id", required = false, dataType = "string", paramType = "path"))) + @ApiResponses(Array( + new ApiResponse(code = 200, message = "返回交易json内容", response = classOf[QueryResult]))) + def getTransaction = + path("transaction" / Segment) { transactionId => + get { + complete { (ra ? TransactionId(transactionId)).mapTo[QueryResult] } + } + } +//以十六进制字符串提交签名交易 + @Path("/postTranByString") + @ApiOperation(value = "提交带签名的交易", notes = "", nickname = "postSignTransaction", httpMethod = "POST") + @ApiImplicitParams(Array( + new ApiImplicitParam(name = "body", value = "交易内容", required = true, dataType = "string", paramType = "body"))) + @ApiResponses(Array( + new ApiResponse(code = 200, message = "返回交易id以及执行结果", response = classOf[PostResult]), + new ApiResponse(code = 202, message = "处理存在异常", response = classOf[PostResult]))) + def postSignTransaction = + path("transaction" / "postTranByString") { + post { + entity(as[String]) { trans => + //str=> + //complete(OK, trans) + complete { (ra ? tranSign(trans)).mapTo[PostResult] } + } + } + } + + @Path("/postTran") + @ApiOperation(value = "提交交易", notes = "", nickname = "postTransaction", httpMethod = "POST") + @ApiImplicitParams(Array( + new ApiImplicitParam(name = "body", value = "交易内容", required = true, + dataTypeClass = classOf[CSpec], paramType = "body"))) + @ApiResponses(Array( + new ApiResponse(code = 200, message = "返回交易id以及执行结果", response = classOf[PostResult]), + new ApiResponse(code = 202, message = "处理存在异常", response = classOf[PostResult]))) + def postTransaction = + path("transaction" / "postTran") { + post { + import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._ + entity(as[CSpec]) { request => + complete { (ra ? request).mapTo[PostResult] } + } + } + } + } + +@Api(value = "/certAddr", description = "获得证书短地址", produces = "application/json") +@Path("certAddr") +class CertService(ra: ActorRef)(implicit executionContext: ExecutionContext) + extends Directives { + + import akka.pattern.ask + import scala.concurrent.duration._ + + import Json4sSupport._ + implicit val serialization = jackson.Serialization // or native.Serialization + implicit val formats = DefaultFormats + implicit val timeout = Timeout(20.seconds) + + val route = getAddrByCert ~ getCertByAddr + + @Path("/getAddrByCert") + @ApiOperation(value = "返回证书短地址", notes = "", nickname = "getAddrByCert", httpMethod = "POST") + @ApiImplicitParams(Array( + new ApiImplicitParam(name = "body", value = "证书", required = true, dataTypeClass = classOf[PostCert], paramType = "body"))) + @ApiResponses(Array( + new ApiResponse(code = 200, message = "查询证书短地址", response = classOf[QueryAddr]))) + def getAddrByCert = + path("certAddr" / "getAddrByCert") { + post { + entity(as[PostCert]) { PostCert => + complete { (ra ? PostCert).mapTo[QueryAddr] } + } + } + } + + @Path("/getCertByAddr") + @ApiOperation(value = "返回证书字符串", notes = "", nickname = "getCertByAddr", httpMethod = "POST") + @ApiImplicitParams(Array( + new ApiImplicitParam(name = "body", value = "短地址", required = true, dataTypeClass = classOf[PostAddr], paramType = "body"))) + @ApiResponses(Array( + new ApiResponse(code = 200, message = "查询证书字符串", response = classOf[QueryCert]))) + def getCertByAddr = + path("certAddr" / "getCertByAddr") { + post { + entity(as[PostAddr]) { PostAddr => + complete { (ra ? PostAddr).mapTo[QueryCert] } + } + } + } + } + + +@Api(value = "/hash", description = "验证hash是否存在", produces = "application/json") +@Path("hash") +class HashVerifyService(ra: ActorRef)(implicit executionContext: ExecutionContext) + extends Directives { + + import akka.pattern.ask + import scala.concurrent.duration._ + + import Json4sSupport._ + implicit val serialization = jackson.Serialization // or native.Serialization + implicit val formats = DefaultFormats + implicit val timeout = Timeout(20.seconds) + + val route = verifyImageHash + + @Path("/verifyHash") + @ApiOperation(value = "返回hash是否存在", notes = "", nickname = "verifyHash", httpMethod = "POST") + @ApiImplicitParams(Array( + new ApiImplicitParam(name = "body", value = "hash值与cid", required = true, dataTypeClass = classOf[PostHash], paramType = "body"))) + @ApiResponses(Array(new ApiResponse(code = 200, message = "验证hash值", response = classOf[QueryHash]))) + def verifyImageHash = + path("hash" / "verifyHash") { + post { + entity(as[PostHash]) { PostHash => + complete { (ra ? PostHash).mapTo[QueryHash] } + } + } + } } \ No newline at end of file diff --git a/src/main/scala/rep/app/conf/SystemProfile.scala b/src/main/scala/rep/app/conf/SystemProfile.scala index d6d634d6..c023670e 100644 --- a/src/main/scala/rep/app/conf/SystemProfile.scala +++ b/src/main/scala/rep/app/conf/SystemProfile.scala @@ -56,7 +56,7 @@ object SystemProfile { private def SERVERPORT_=(value: Int): Unit = { _SERVERPORT = value } - + private def CHECKCERTVALIDATE_=(value: Int): Unit = { _CHECKCERTVALIDATE = value } diff --git a/src/main/scala/rep/crypto/ECDSASign.scala b/src/main/scala/rep/crypto/ECDSASign.scala index 704a55a7..b2420faf 100644 --- a/src/main/scala/rep/crypto/ECDSASign.scala +++ b/src/main/scala/rep/crypto/ECDSASign.scala @@ -1,422 +1,422 @@ -/* - * Copyright 2018 Blockchain Technology and Application Joint Lab, Fintech Research Center of ISCAS. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BA SIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package rep.crypto - -import java.security._ -import java.io._ -import java.security.cert.{ Certificate, CertificateFactory } -import rep.app.conf.SystemProfile -import com.google.protobuf.ByteString -import fastparse.utils.Base64 -import rep.utils.SerializeUtils -import rep.storage._ -import scala.collection.mutable -import com.fasterxml.jackson.core.Base64Variants -import java.security.cert.X509Certificate - -/** - * 系统密钥相关伴生对象 - * @author shidianyue - * @version 0.7 - */ -object ECDSASign extends ECDSASign { - //TODO kami (现阶段alias和SYSName相同,将来不一定,所以目前在接口层将其分开,但是调用时用的是一个) - - //store itsself key and certification - var keyStorePath = mutable.HashMap[String, String]() - var password = mutable.HashMap[String, String]() - //store the trust list of other nodes' certification - var trustKeyStorePath = "" - var passwordTrust = "" - - var keyStore = mutable.HashMap[String, KeyStore]() - var trustKeyStore = KeyStore.getInstance(KeyStore.getDefaultType) - var trustkeysPubAddrMap = mutable.HashMap[String, Certificate]() - - def apply(alias: String, jksPath: String, password: String, jksTrustPath: String, passwordTrust: String) = { - keyStorePath(alias) = jksPath - this.password(alias) = password - //TODO kami 如果与之前路径不同,如何处理? - if (trustKeyStorePath == "") { - trustKeyStorePath = jksTrustPath - this.passwordTrust = passwordTrust - } - } - - /** - * 通过参数获取相关的密钥对、证书(动态加载) - * - * @param jks_file - * @param password - * @param alias - * @return - */ - def getKeyPairFromJKS(jks_file: File, password: String, alias: String): (PrivateKey, PublicKey) = { - val store = KeyStore.getInstance(KeyStore.getDefaultType) - val fis = new FileInputStream(jks_file) - val pwd = password.toCharArray() - store.load(fis, pwd) - val sk = store.getKey(alias, pwd) - val cert = store.getCertificate(alias) - (sk.asInstanceOf[PrivateKey], cert.getPublicKey()) - } - - /** - * 在信任列表中获取证书(通过alias) - * - * @param cert - * @return - */ - def getAliasByCert(cert: Certificate): Option[String] = { - val alias = trustKeyStore.getCertificateAlias(cert) - if (alias == null) Option.empty else Option(alias) - } - - /** - * 获取证书的Base58地址 - * @param cert - * @return - */ - def getAddrByCert(cert: Certificate): String = { - Base58.encode(Sha256.hash(cert.getPublicKey.getEncoded)) - } - - /** - * 获取证书的短地址(Bitcoin方法) - * @param cert 对象 - * @return - */ - def getBitcoinAddrByCert(cert: Certificate): String = { - BitcoinUtils.calculateBitcoinAddress(cert.getPublicKey.getEncoded) - } - - /** - * 获取证书的短地址 - * @param certByte 字节 - * @return - */ - def getBitcoinAddrByCert(certByte: Array[Byte]): String = { - val cert = SerializeUtils.deserialise(certByte).asInstanceOf[Certificate] - BitcoinUtils.calculateBitcoinAddress(cert.getPublicKey.getEncoded) - } - - /** - * 获取指定alias的证书地址 - * @param alias - * @return - */ - def getAddr(alias: String): String = { - getAddrByCert(keyStore(alias).getCertificate(alias)) - } - - /** - * 根据短地址获取证书 - * @param addr - * @return - */ - def getCertByBitcoinAddr(addr: String): Option[Certificate] = { - var tmpcert = trustkeysPubAddrMap.get(addr) - if(checkCertificate(new java.util.Date(), tmpcert.get)){ - tmpcert - }else{ - throw new RuntimeException("证书已经过期") - } - - - } - - /** - * 通过配置信息获取证书(动态加载) - * - * @param jks_file - * @param password - * @param alias - * @return - */ - def getCertFromJKS(jks_file: File, password: String, alias: String): Certificate = { - val store = KeyStore.getInstance(KeyStore.getDefaultType) - val fis = new FileInputStream(jks_file) - val pwd = password.toCharArray() - store.load(fis, pwd) - val sk = store.getKey(alias, pwd) - val cert = store.getCertificate(alias) - cert - } - - /** - * 将pem格式证书字符串转换为certificate - * @param pem - * @return - */ - def getCertByPem(pemcert: String): Certificate = { - val cf = CertificateFactory.getInstance("X.509") - val cert = cf.generateCertificate( - new ByteArrayInputStream( - Base64.Decoder(pemcert.replaceAll("\r\n", "").stripPrefix("-----BEGIN CERTIFICATE-----").stripSuffix("-----END CERTIFICATE-----")).toByteArray)) - cert - } - - - /** - * 获取alias的密钥对和证书(系统初始化) - * - * @param alias - * @return - */ - def getKeyPair(alias: String): (PrivateKey, PublicKey, Array[Byte]) = { - val sk = keyStore(alias).getKey(alias, password(alias).toCharArray) - val cert = keyStore(alias).getCertificate(alias) - if(checkCertificate(new java.util.Date(), cert)){ - (sk.asInstanceOf[PrivateKey], cert.getPublicKey(), SerializeUtils.serialise(cert)) - }else{ - throw new RuntimeException("证书已经过期") - } - } - - /** - * 获取alias的证书(系统初始化) - * - * @param alias - * @return - */ - def getCert(alias: String): Certificate = { - keyStore(alias).getCertificate(alias) - } - - /** - * 在信任列表中获取alias的证书(系统初始化) - * - * @param alias - * @return - */ - def getKeyPairTrust(alias: String): PublicKey = { - val sk = trustKeyStore.getKey(alias, passwordTrust.toCharArray) - val cert = trustKeyStore.getCertificate(alias) - cert.getPublicKey() - } - - /** - * 判断两个证书是否相同 - * - * @param alias - * @param cert - * @return - */ - def isCertTrust(alias: String, cert: Array[Byte]): Boolean = { - val sk = trustKeyStore.getKey(alias, passwordTrust.toCharArray) - val certT = trustKeyStore.getCertificate(alias) - //寻找方法能够恢复cert? - certT.getEncoded.equals(cert) - } - - /** - * 预加载系统密钥对和信任证书 - * - * @param alias - */ - def preLoadKey(alias: String): Unit = { - val fis = new FileInputStream(new File(keyStorePath(alias))) - val pwd = password(alias).toCharArray() - if (keyStore.contains(alias)) keyStore(alias).load(fis, pwd) - else { - val keyS = KeyStore.getInstance(KeyStore.getDefaultType) - keyS.load(fis, pwd) - keyStore(alias) = keyS - } - - val fisT = new FileInputStream(new File(trustKeyStorePath)) - val pwdT = passwordTrust.toCharArray() - trustKeyStore.load(fisT, pwdT) - loadTrustkeysPubAddrMap() - } - - /** - * 初始化信任证书中对短地址和证书的映射 - */ - def loadTrustkeysPubAddrMap(): Unit = { - val enums = trustKeyStore.aliases() - while (enums.hasMoreElements) { - val alias = enums.nextElement() - val cert = trustKeyStore.getCertificate(alias) - trustkeysPubAddrMap.put(getBitcoinAddrByCert(cert), cert) - } - } - - /** - * 获取本地证书,得到证书类和其序列化的字节序列 - * - * @param certPath - * @return 字节序列(通过base58进行转化) - */ - def loadCertByPath(certPath: String): (Certificate, Array[Byte], String) = { - val certF = CertificateFactory.getInstance("X.509") - val fileInputStream = new FileInputStream(certPath) - val x509Cert = certF.generateCertificate(fileInputStream) - val arrayCert = SerializeUtils.serialise(x509Cert) - (x509Cert, arrayCert, Base64.Encoder(arrayCert).toBase64) - } - - /** - * 添加证书到信任列表 - * - * @param cert 字节数组 - * @param alias - * @return - */ - def loadTrustedCert(cert: Array[Byte], alias: String): Boolean = { - val certTx = SerializeUtils.deserialise(cert).asInstanceOf[Certificate] - getAliasByCert(certTx).getOrElse(None) match { - case None => - trustKeyStore.setCertificateEntry(alias, certTx) - trustkeysPubAddrMap.put(getBitcoinAddrByCert(certTx), certTx) - val fileOutputStream = new FileOutputStream(trustKeyStorePath) - trustKeyStore.store(fileOutputStream, passwordTrust.toCharArray) - true - case _ => - false - } - } - - /** - * 添加证书到信任列表 - * - * @param cert base64字符串 - * @param alias - * @return - */ - def loadTrustedCertBase64(cert: String, alias: String): Boolean = { - val certTx = SerializeUtils.deserialise(Base64.Decoder(cert).toByteArray).asInstanceOf[Certificate] - getAliasByCert(certTx).getOrElse(None) match { - case None => - trustKeyStore.setCertificateEntry(alias, certTx) - trustkeysPubAddrMap.put(getBitcoinAddrByCert(certTx), certTx) - val fileOutputStream = new FileOutputStream(trustKeyStorePath) - trustKeyStore.store(fileOutputStream, passwordTrust.toCharArray) - true - case _ => - false - } - } - def main(args: Array[String]): Unit = { - println(ByteString.copyFromUtf8(ECDSASign.getBitcoinAddrByCert(ECDSASign.getCertFromJKS(new File("jks/mykeystore_1.jks"), "123", "1"))).toStringUtf8) - println(ECDSASign.getBitcoinAddrByCert(ECDSASign.getCertFromJKS(new File("jks/mykeystore_2.jks"), "123", "2"))) - println(ECDSASign.getBitcoinAddrByCert(ECDSASign.getCertFromJKS(new File("jks/mykeystore_3.jks"), "123", "3"))) - println(ECDSASign.getBitcoinAddrByCert(ECDSASign.getCertFromJKS(new File("jks/mykeystore_4.jks"), "123", "4"))) - println(ECDSASign.getBitcoinAddrByCert(ECDSASign.getCertFromJKS(new File("jks/mykeystore_5.jks"), "123", "5"))) - } - -} - -/** - * 系统密钥相关类 - * @author shidianyue - * @version 0.7 - * @since 1.0 - */ -class ECDSASign extends SignFunc { - /** - * 签名 - * - * @param privateKey - * @param message - * @return - */ - def sign(privateKey: PrivateKey, message: Array[Byte]): Array[Byte] = { - val s1 = Signature.getInstance("SHA1withECDSA"); - s1.initSign(privateKey) - s1.update(message) - s1.sign() - } - - /** - * 验证 - * - * @param signature - * @param message - * @param publicKey - * @return - */ - def verify(signature: Array[Byte], message: Array[Byte], publicKey: PublicKey): Boolean = { - val s2 = Signature.getInstance("SHA1withECDSA"); - s2.initVerify(publicKey) - s2.update(message) - s2.verify(signature) - } - - def getCertWithCheck(certAddr:String,certKey:String,sysTag:String):Option[java.security.cert.Certificate]={ - val cert = ECDSASign.getCertByBitcoinAddr(certAddr) - if(cert != None) { - if(checkCertificate(new java.util.Date(), cert.get)){ - cert - }else{ - throw new RuntimeException("证书已经过期") - } - }else{ - if(certKey == null || sysTag == null){ - throw new RuntimeException("没有证书") - }else{ - try{ - val sr: ImpDataAccess = ImpDataAccess.GetDataAccess(sysTag) - val cert = Option(sr.Get(certKey)) - if (cert != None){ - if (new String(cert.get) == "null") { - throw new RuntimeException("用户证书已经注销") - }else{ - val kvcert = SerializeUtils.deserialise(cert.get).asInstanceOf[Certificate] - if(kvcert != null){ - if(checkCertificate(new java.util.Date(), kvcert)){ - Some(kvcert) - }else{ - throw new RuntimeException("证书已经过期") - } - }else{ - throw new RuntimeException("证书内容错误") - } - } - }else{ - throw new RuntimeException("没有证书") - } - }catch{ - case e : Exception =>throw new RuntimeException("证书获取过程中发生错误",e) - } - } - } - } - - def checkCertificate(date:java.util.Date, cert:Certificate):Boolean={ - var isValid :Boolean = false - var start = System.currentTimeMillis() - try { - if(SystemProfile.getCheckCertValidate == 0){ - isValid = true - }else if(SystemProfile.getCheckCertValidate == 1){ - if(cert.isInstanceOf[X509Certificate]){ - var x509cert :X509Certificate = cert.asInstanceOf[X509Certificate] - x509cert.checkValidity(date) - isValid = true - } - }else{ - isValid = true - } - } catch{ - case e : Exception => isValid = false - } - var end = System.currentTimeMillis() - //println("check cert validate,spent time="+(end-start)) - isValid; - } - +/* + * Copyright 2018 Blockchain Technology and Application Joint Lab, Fintech Research Center of ISCAS. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BA SIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rep.crypto + +import java.security._ +import java.io._ +import java.security.cert.{ Certificate, CertificateFactory } +import rep.app.conf.SystemProfile +import com.google.protobuf.ByteString +import fastparse.utils.Base64 +import rep.utils.SerializeUtils +import rep.storage._ +import scala.collection.mutable +import com.fasterxml.jackson.core.Base64Variants +import java.security.cert.X509Certificate + +/** + * 系统密钥相关伴生对象 + * @author shidianyue + * @version 0.7 + */ +object ECDSASign extends ECDSASign { + //TODO kami (现阶段alias和SYSName相同,将来不一定,所以目前在接口层将其分开,但是调用时用的是一个) + + //store itsself key and certification + var keyStorePath = mutable.HashMap[String, String]() + var password = mutable.HashMap[String, String]() + //store the trust list of other nodes' certification + var trustKeyStorePath = "" + var passwordTrust = "" + + var keyStore = mutable.HashMap[String, KeyStore]() + var trustKeyStore = KeyStore.getInstance(KeyStore.getDefaultType) + var trustkeysPubAddrMap = mutable.HashMap[String, Certificate]() + + def apply(alias: String, jksPath: String, password: String, jksTrustPath: String, passwordTrust: String) = { + keyStorePath(alias) = jksPath + this.password(alias) = password + //TODO kami 如果与之前路径不同,如何处理? + if (trustKeyStorePath == "") { + trustKeyStorePath = jksTrustPath + this.passwordTrust = passwordTrust + } + } + + /** + * 通过参数获取相关的密钥对、证书(动态加载) + * + * @param jks_file + * @param password + * @param alias + * @return + */ + def getKeyPairFromJKS(jks_file: File, password: String, alias: String): (PrivateKey, PublicKey) = { + val store = KeyStore.getInstance(KeyStore.getDefaultType) + val fis = new FileInputStream(jks_file) + val pwd = password.toCharArray() + store.load(fis, pwd) + val sk = store.getKey(alias, pwd) + val cert = store.getCertificate(alias) + (sk.asInstanceOf[PrivateKey], cert.getPublicKey()) + } + + /** + * 在信任列表中获取证书(通过alias) + * + * @param cert + * @return + */ + def getAliasByCert(cert: Certificate): Option[String] = { + val alias = trustKeyStore.getCertificateAlias(cert) + if (alias == null) Option.empty else Option(alias) + } + + /** + * 获取证书的Base58地址 + * @param cert + * @return + */ + def getAddrByCert(cert: Certificate): String = { + Base58.encode(Sha256.hash(cert.getPublicKey.getEncoded)) + } + + /** + * 获取证书的短地址(Bitcoin方法) + * @param cert 对象 + * @return + */ + def getBitcoinAddrByCert(cert: Certificate): String = { + BitcoinUtils.calculateBitcoinAddress(cert.getPublicKey.getEncoded) + } + + /** + * 获取证书的短地址 + * @param certByte 字节 + * @return + */ + def getBitcoinAddrByCert(certByte: Array[Byte]): String = { + val cert = SerializeUtils.deserialise(certByte).asInstanceOf[Certificate] + BitcoinUtils.calculateBitcoinAddress(cert.getPublicKey.getEncoded) + } + + /** + * 获取指定alias的证书地址 + * @param alias + * @return + */ + def getAddr(alias: String): String = { + getAddrByCert(keyStore(alias).getCertificate(alias)) + } + + /** + * 根据短地址获取证书 + * @param addr + * @return + */ + def getCertByBitcoinAddr(addr: String): Option[Certificate] = { + var tmpcert = trustkeysPubAddrMap.get(addr) + if(checkCertificate(new java.util.Date(), tmpcert.get)){ + tmpcert + }else{ + throw new RuntimeException("证书已经过期") + } + + + } + + /** + * 通过配置信息获取证书(动态加载) + * + * @param jks_file + * @param password + * @param alias + * @return + */ + def getCertFromJKS(jks_file: File, password: String, alias: String): Certificate = { + val store = KeyStore.getInstance(KeyStore.getDefaultType) + val fis = new FileInputStream(jks_file) + val pwd = password.toCharArray() + store.load(fis, pwd) + val sk = store.getKey(alias, pwd) + val cert = store.getCertificate(alias) + cert + } + + /** + * 将pem格式证书字符串转换为certificate + * @param pem + * @return + */ + def getCertByPem(pemcert: String): Certificate = { + val cf = CertificateFactory.getInstance("X.509") + val cert = cf.generateCertificate( + new ByteArrayInputStream( + Base64.Decoder(pemcert.replaceAll("\r\n", "").stripPrefix("-----BEGIN CERTIFICATE-----").stripSuffix("-----END CERTIFICATE-----")).toByteArray)) + cert + } + + + /** + * 获取alias的密钥对和证书(系统初始化) + * + * @param alias + * @return + */ + def getKeyPair(alias: String): (PrivateKey, PublicKey, Array[Byte]) = { + val sk = keyStore(alias).getKey(alias, password(alias).toCharArray) + val cert = keyStore(alias).getCertificate(alias) + if(checkCertificate(new java.util.Date(), cert)){ + (sk.asInstanceOf[PrivateKey], cert.getPublicKey(), SerializeUtils.serialise(cert)) + }else{ + throw new RuntimeException("证书已经过期") + } + } + + /** + * 获取alias的证书(系统初始化) + * + * @param alias + * @return + */ + def getCert(alias: String): Certificate = { + keyStore(alias).getCertificate(alias) + } + + /** + * 在信任列表中获取alias的证书(系统初始化) + * + * @param alias + * @return + */ + def getKeyPairTrust(alias: String): PublicKey = { + val sk = trustKeyStore.getKey(alias, passwordTrust.toCharArray) + val cert = trustKeyStore.getCertificate(alias) + cert.getPublicKey() + } + + /** + * 判断两个证书是否相同 + * + * @param alias + * @param cert + * @return + */ + def isCertTrust(alias: String, cert: Array[Byte]): Boolean = { + val sk = trustKeyStore.getKey(alias, passwordTrust.toCharArray) + val certT = trustKeyStore.getCertificate(alias) + //寻找方法能够恢复cert? + certT.getEncoded.equals(cert) + } + + /** + * 预加载系统密钥对和信任证书 + * + * @param alias + */ + def preLoadKey(alias: String): Unit = { + val fis = new FileInputStream(new File(keyStorePath(alias))) + val pwd = password(alias).toCharArray() + if (keyStore.contains(alias)) keyStore(alias).load(fis, pwd) + else { + val keyS = KeyStore.getInstance(KeyStore.getDefaultType) + keyS.load(fis, pwd) + keyStore(alias) = keyS + } + + val fisT = new FileInputStream(new File(trustKeyStorePath)) + val pwdT = passwordTrust.toCharArray() + trustKeyStore.load(fisT, pwdT) + loadTrustkeysPubAddrMap() + } + + /** + * 初始化信任证书中对短地址和证书的映射 + */ + def loadTrustkeysPubAddrMap(): Unit = { + val enums = trustKeyStore.aliases() + while (enums.hasMoreElements) { + val alias = enums.nextElement() + val cert = trustKeyStore.getCertificate(alias) + trustkeysPubAddrMap.put(getBitcoinAddrByCert(cert), cert) + } + } + + /** + * 获取本地证书,得到证书类和其序列化的字节序列 + * + * @param certPath + * @return 字节序列(通过base58进行转化) + */ + def loadCertByPath(certPath: String): (Certificate, Array[Byte], String) = { + val certF = CertificateFactory.getInstance("X.509") + val fileInputStream = new FileInputStream(certPath) + val x509Cert = certF.generateCertificate(fileInputStream) + val arrayCert = SerializeUtils.serialise(x509Cert) + (x509Cert, arrayCert, Base64.Encoder(arrayCert).toBase64) + } + + /** + * 添加证书到信任列表 + * + * @param cert 字节数组 + * @param alias + * @return + */ + def loadTrustedCert(cert: Array[Byte], alias: String): Boolean = { + val certTx = SerializeUtils.deserialise(cert).asInstanceOf[Certificate] + getAliasByCert(certTx).getOrElse(None) match { + case None => + trustKeyStore.setCertificateEntry(alias, certTx) + trustkeysPubAddrMap.put(getBitcoinAddrByCert(certTx), certTx) + val fileOutputStream = new FileOutputStream(trustKeyStorePath) + trustKeyStore.store(fileOutputStream, passwordTrust.toCharArray) + true + case _ => + false + } + } + + /** + * 添加证书到信任列表 + * + * @param cert base64字符串 + * @param alias + * @return + */ + def loadTrustedCertBase64(cert: String, alias: String): Boolean = { + val certTx = SerializeUtils.deserialise(Base64.Decoder(cert).toByteArray).asInstanceOf[Certificate] + getAliasByCert(certTx).getOrElse(None) match { + case None => + trustKeyStore.setCertificateEntry(alias, certTx) + trustkeysPubAddrMap.put(getBitcoinAddrByCert(certTx), certTx) + val fileOutputStream = new FileOutputStream(trustKeyStorePath) + trustKeyStore.store(fileOutputStream, passwordTrust.toCharArray) + true + case _ => + false + } + } + def main(args: Array[String]): Unit = { + println(ByteString.copyFromUtf8(ECDSASign.getBitcoinAddrByCert(ECDSASign.getCertFromJKS(new File("jks/mykeystore_1.jks"), "123", "1"))).toStringUtf8) + println(ECDSASign.getBitcoinAddrByCert(ECDSASign.getCertFromJKS(new File("jks/mykeystore_2.jks"), "123", "2"))) + println(ECDSASign.getBitcoinAddrByCert(ECDSASign.getCertFromJKS(new File("jks/mykeystore_3.jks"), "123", "3"))) + println(ECDSASign.getBitcoinAddrByCert(ECDSASign.getCertFromJKS(new File("jks/mykeystore_4.jks"), "123", "4"))) + println(ECDSASign.getBitcoinAddrByCert(ECDSASign.getCertFromJKS(new File("jks/mykeystore_5.jks"), "123", "5"))) + } + +} + +/** + * 系统密钥相关类 + * @author shidianyue + * @version 0.7 + * @since 1.0 + */ +class ECDSASign extends SignFunc { + /** + * 签名 + * + * @param privateKey + * @param message + * @return + */ + def sign(privateKey: PrivateKey, message: Array[Byte]): Array[Byte] = { + val s1 = Signature.getInstance("SHA1withECDSA"); + s1.initSign(privateKey) + s1.update(message) + s1.sign() + } + + /** + * 验证 + * + * @param signature + * @param message + * @param publicKey + * @return + */ + def verify(signature: Array[Byte], message: Array[Byte], publicKey: PublicKey): Boolean = { + val s2 = Signature.getInstance("SHA1withECDSA"); + s2.initVerify(publicKey) + s2.update(message) + s2.verify(signature) + } + + def getCertWithCheck(certAddr:String,certKey:String,sysTag:String):Option[java.security.cert.Certificate]={ + val cert = ECDSASign.getCertByBitcoinAddr(certAddr) + if(cert != None) { + if(checkCertificate(new java.util.Date(), cert.get)){ + cert + }else{ + throw new RuntimeException("证书已经过期") + } + }else{ + if(certKey == null || sysTag == null){ + throw new RuntimeException("没有证书") + }else{ + try{ + val sr: ImpDataAccess = ImpDataAccess.GetDataAccess(sysTag) + val cert = Option(sr.Get(certKey)) + if (cert != None){ + if (new String(cert.get) == "null") { + throw new RuntimeException("用户证书已经注销") + }else{ + val kvcert = SerializeUtils.deserialise(cert.get).asInstanceOf[Certificate] + if(kvcert != null){ + if(checkCertificate(new java.util.Date(), kvcert)){ + Some(kvcert) + }else{ + throw new RuntimeException("证书已经过期") + } + }else{ + throw new RuntimeException("证书内容错误") + } + } + }else{ + throw new RuntimeException("没有证书") + } + }catch{ + case e : Exception =>throw new RuntimeException("证书获取过程中发生错误",e) + } + } + } + } + + def checkCertificate(date:java.util.Date, cert:Certificate):Boolean={ + var isValid :Boolean = false + var start = System.currentTimeMillis() + try { + if(SystemProfile.getCheckCertValidate == 0){ + isValid = true + }else if(SystemProfile.getCheckCertValidate == 1){ + if(cert.isInstanceOf[X509Certificate]){ + var x509cert :X509Certificate = cert.asInstanceOf[X509Certificate] + x509cert.checkValidity(date) + isValid = true + } + }else{ + isValid = true + } + } catch{ + case e : Exception => isValid = false + } + var end = System.currentTimeMillis() + //println("check cert validate,spent time="+(end-start)) + isValid; + } + } \ No newline at end of file diff --git a/src/main/scala/rep/crypto/Sha256.scala b/src/main/scala/rep/crypto/Sha256.scala index 32fbfad4..dcd66cd7 100644 --- a/src/main/scala/rep/crypto/Sha256.scala +++ b/src/main/scala/rep/crypto/Sha256.scala @@ -24,15 +24,16 @@ import com.google.protobuf.ByteString object Sha256 extends CryptographicHash{ override val DigestSize: Int = 32 def hash(input: Array[Byte]): Array[Byte] = MessageDigest.getInstance("SHA-256").digest(input) + def hashstr(input: Array[Byte]):String ={ BytesHex.bytes2hex(hash(input)) } - + def hashstr(input: String):String ={ val iptb = ByteString.copyFromUtf8(input) BytesHex.bytes2hex(hash(iptb.toByteArray())) } - + def hashToBytes(input: String):Array[Byte] ={ val iptb = ByteString.copyFromUtf8(input) hash(iptb.toByteArray()) diff --git a/src/main/scala/rep/log/EventActor.scala b/src/main/scala/rep/log/EventActor.scala index 8b450652..74db0ea2 100644 --- a/src/main/scala/rep/log/EventActor.scala +++ b/src/main/scala/rep/log/EventActor.scala @@ -1,120 +1,120 @@ -/* - * Copyright 2018 Blockchain Technology and Application Joint Lab, Fintech Research Center of ISCAS. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BA SIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package rep.log - -import rep.network.Topic -import rep.protos.peer._ -import akka.stream.actor._ -import akka.actor.{Props,Address,PoisonPill} -import akka.cluster.pubsub.DistributedPubSub -import akka.cluster.pubsub.DistributedPubSubMediator.{Publish, Subscribe} -import akka.cluster.Cluster -import akka.cluster.ClusterEvent._ -import akka.cluster.MemberStatus -import rep.ui.web.EventServer -import rep.network.tools.PeerExtension -import rep.storage._ -import akka.stream.Graph - -/** 负责处理websocket事件订阅的actor伴生对象 - * @author c4w - */ -object EventActor { - def props: Props = Props[EventActor] -} - -/** 负责处理websocket事件订阅的Actor,一方面作为消费者在后台订阅Event,另一方面作为WebSocket的source - * 向浏览器push事件流, 事件流采用protobuf流 - * @author c4w - * @constructor - */ -class EventActor extends ActorPublisher[Event] { - import scala.concurrent.duration._ - - val cluster = Cluster(context.system) - var nodes = Set.empty[Address] - var buffer = Vector.empty[Event] - - /** 启动,订阅集群入网、离网事件,订阅Topic事件 - * - */ - override def preStart(): Unit ={ - cluster.subscribe(self, classOf[MemberEvent]) - val mediator = DistributedPubSub(context.system).mediator - //发送订阅Event - mediator ! Subscribe(Topic.Event, self) - //发送当前出块人 - val pe = PeerExtension(context.system) - self ! new Event( pe.getBlocker.toString, "", Event.Action.CANDIDATOR) - } - - /** 停止处理,取消订阅 - * - */ - override def postStop(): Unit = - cluster unsubscribe self - - /** 接收Event处理,支持所谓“背压”方式,即根据web端消费能力push - * - */ - override def receive: Receive = { - //Topic事件 - case evt:Event=> - //当浏览器关掉,自杀 - if(this.isCanceled){ - self ! PoisonPill - }else{ - if (buffer.isEmpty && totalDemand > 0) { - onNext(evt) - } - else { - buffer :+= evt - if (totalDemand > 0) { - val (use,keep) = buffer.splitAt(totalDemand.toInt) - buffer = keep - use foreach onNext - } - } - } - //集群事件 - case state: CurrentClusterState => - val iter = state.members.iterator; - iter.foreach { m => - if (m.status == MemberStatus.Up){ - self ! new Event( m.address.toString, "", Event.Action.MEMBER_UP) - } - } - //节点入网 - case MemberUp(member) => - nodes += member.address - self !new Event( member.address.toString, "", Event.Action.MEMBER_UP) - case UnreachableMember(member) => - //节点离网 - case MemberRemoved(member, _) => - val maddr = member.address.toString - val saddr = Cluster(context.system).selfAddress.toString - //println(s"-------$maddr-----$saddr") - if(maddr == saddr){ - context.system.terminate() - }else{ - nodes -= member.address - self ! new Event( member.address.toString,"",Event.Action.MEMBER_DOWN) - } - case _: MemberEvent => // ignore - - } -} - +/* + * Copyright 2018 Blockchain Technology and Application Joint Lab, Fintech Research Center of ISCAS. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BA SIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rep.log + +import rep.network.Topic +import rep.protos.peer._ +import akka.stream.actor._ +import akka.actor.{Props,Address,PoisonPill} +import akka.cluster.pubsub.DistributedPubSub +import akka.cluster.pubsub.DistributedPubSubMediator.{Publish, Subscribe} +import akka.cluster.Cluster +import akka.cluster.ClusterEvent._ +import akka.cluster.MemberStatus +import rep.ui.web.EventServer +import rep.network.tools.PeerExtension +import rep.storage._ +import akka.stream.Graph + +/** 负责处理websocket事件订阅的actor伴生对象 + * @author c4w + */ +object EventActor { + def props: Props = Props[EventActor] +} + +/** 负责处理websocket事件订阅的Actor,一方面作为消费者在后台订阅Event,另一方面作为WebSocket的source + * 向浏览器push事件流, 事件流采用protobuf流 + * @author c4w + * @constructor + */ +class EventActor extends ActorPublisher[Event] { + import scala.concurrent.duration._ + + val cluster = Cluster(context.system) + var nodes = Set.empty[Address] + var buffer = Vector.empty[Event] + + /** 启动,订阅集群入网、离网事件,订阅Topic事件 + * + */ + override def preStart(): Unit ={ + cluster.subscribe(self, classOf[MemberEvent]) + val mediator = DistributedPubSub(context.system).mediator + //发送订阅Event + mediator ! Subscribe(Topic.Event, self) + //发送当前出块人 + val pe = PeerExtension(context.system) + self ! new Event( pe.getBlocker.toString, "", Event.Action.CANDIDATOR) + } + + /** 停止处理,取消订阅 + * + */ + override def postStop(): Unit = + cluster unsubscribe self + + /** 接收Event处理,支持所谓“背压”方式,即根据web端消费能力push + * + */ + override def receive: Receive = { + //Topic事件 + case evt:Event=> + //当浏览器关掉,自杀 + if(this.isCanceled){ + self ! PoisonPill + }else{ + if (buffer.isEmpty && totalDemand > 0) { + onNext(evt) + } + else { + buffer :+= evt + if (totalDemand > 0) { + val (use,keep) = buffer.splitAt(totalDemand.toInt) + buffer = keep + use foreach onNext + } + } + } + //集群事件 + case state: CurrentClusterState => + val iter = state.members.iterator; + iter.foreach { m => + if (m.status == MemberStatus.Up){ + self ! new Event( m.address.toString, "", Event.Action.MEMBER_UP) + } + } + //节点入网 + case MemberUp(member) => + nodes += member.address + self !new Event( member.address.toString, "", Event.Action.MEMBER_UP) + case UnreachableMember(member) => + //节点离网 + case MemberRemoved(member, _) => + val maddr = member.address.toString + val saddr = Cluster(context.system).selfAddress.toString + //println(s"-------$maddr-----$saddr") + if(maddr == saddr){ + context.system.terminate() + }else{ + nodes -= member.address + self ! new Event( member.address.toString,"",Event.Action.MEMBER_DOWN) + } + case _: MemberEvent => // ignore + + } +} + diff --git a/src/main/scala/rep/network/PeerHelper.scala b/src/main/scala/rep/network/PeerHelper.scala index 4780c356..89c205f8 100644 --- a/src/main/scala/rep/network/PeerHelper.scala +++ b/src/main/scala/rep/network/PeerHelper.scala @@ -132,7 +132,6 @@ object PeerHelper { }catch{ case e:RuntimeException => throw e } - t } diff --git a/src/main/scala/rep/network/cache/TransactionPool.scala b/src/main/scala/rep/network/cache/TransactionPool.scala index bd523bd7..afaf9f98 100644 --- a/src/main/scala/rep/network/cache/TransactionPool.scala +++ b/src/main/scala/rep/network/cache/TransactionPool.scala @@ -93,7 +93,7 @@ class TransactionPool(moduleName: String) extends ModuleBase(moduleName) { }catch{ case e : RuntimeException => throw e } - + CheckedTransactionResult(result, resultMsg) } diff --git a/src/main/scala/rep/network/consensus/block/BlockHelper.scala b/src/main/scala/rep/network/consensus/block/BlockHelper.scala index 416c5dc5..57753b7a 100644 --- a/src/main/scala/rep/network/consensus/block/BlockHelper.scala +++ b/src/main/scala/rep/network/consensus/block/BlockHelper.scala @@ -53,7 +53,6 @@ object BlockHelper { }catch{ case e:RuntimeException => throw e } - } /** @@ -77,7 +76,7 @@ object BlockHelper { }catch{ case e : RuntimeException => false } - + } //用于对交易对签名验证 @@ -88,7 +87,6 @@ object BlockHelper { val tOutSig1 = t.withSignature(ByteString.EMPTY) val tOutSig = tOutSig1.withMetadata(ByteString.EMPTY) - try{ val cid = ChaincodeID.fromAscii(t.chaincodeID.toStringUtf8).name val certKey = IdxPrefix.WorldStateKeyPreFix + cid + "_" + "CERT_" + t.cert.toStringUtf8 // 普通用户证书的key @@ -101,7 +99,6 @@ object BlockHelper { }catch{ case e : RuntimeException => resultMsg = s"The transaction(${t.txid}) is not trusted${e.getMessage}" } - result } @@ -234,5 +231,5 @@ object BlockHelper { println("sorted") } } - + } diff --git a/src/main/scala/rep/network/consensus/endorse/EndorsementModule.scala b/src/main/scala/rep/network/consensus/endorse/EndorsementModule.scala index 303e4d8b..f37c202d 100644 --- a/src/main/scala/rep/network/consensus/endorse/EndorsementModule.scala +++ b/src/main/scala/rep/network/consensus/endorse/EndorsementModule.scala @@ -73,7 +73,7 @@ class EndorsementModule(moduleName: String) extends ModuleBase(moduleName) { ) r } - + private def hasRepeatOfTrans(trans:Seq[Transaction]):Boolean={ var isRepeat : Boolean = false val aliaslist = trans.distinct @@ -95,7 +95,7 @@ class EndorsementModule(moduleName: String) extends ModuleBase(moduleName) { } - + private def endorseForWork(blk:Block, actRef: ActorRef,blkidentifier:String)={ val dbinstancename = "endorse_"+blk.transactions.head.txid val preload: ImpDataPreload = ImpDataPreloadMgr.GetImpDataPreload(pe.getSysTag,dbinstancename) diff --git a/src/main/scala/rep/network/consensus/transaction/PreloadTransactionModule.scala b/src/main/scala/rep/network/consensus/transaction/PreloadTransactionModule.scala index f5df2a29..80229c13 100644 --- a/src/main/scala/rep/network/consensus/transaction/PreloadTransactionModule.scala +++ b/src/main/scala/rep/network/consensus/transaction/PreloadTransactionModule.scala @@ -127,15 +127,25 @@ class PreloadTransactionModule(moduleName: String, transProcessor:ActorRef) exte logTime(s"Trans Preload End, Trans size ${transResult.size - errorCount}", CRFD_STEP._6_PRELOAD_END, getActorRef(ActorType.STATISTIC_COLLECTION)) isPreload = false - ImpDataPreloadMgr.Free(pe.getDBTag,blk.transactions.head.txid) - clearCache() //是否是多余的,确保一定执行了 + freeSource schedulerLink = clearSched() } + def freeSource={ + if(blk != null){ + if(blk.transactions.size > 0){ + ImpDataPreloadMgr.Free(pe.getDBTag,blk.transactions.head.txid) + } + } + clearCache() //是否是多余的,确保一定执行了 + } + override def receive = { case PreTransBlock(blc, from,blkIdentifier) => val preBlk = dataaccess.getBlockByHash(blk.previousBlockHash.toStringUtf8) + freeSource + if((preBlk!=null && dataaccess.getBlockChainInfo().currentWorldStateHash == getBlkFromByte(preBlk).stateHash.toStringUtf8) || blk.previousBlockHash == ByteString.EMPTY){ blkIdentifier_src = blkIdentifier @@ -214,10 +224,12 @@ class PreloadTransactionModule(moduleName: String, transProcessor:ActorRef) exte preLoadFeedBackInfo(false, blk, preloadFrom, pe.getMerk) blkIdentifier_src = "" isPreload = false - ImpDataPreloadMgr.Free(pe.getDBTag,blk.transactions.head.txid) - clearCache() //是否是多余的,确保一定执行了 + freeSource schedulerLink = clearSched() - case false => logMsg(LOG_TYPE.INFO, "Preload trans timeout checked, successfully") + case false => { + freeSource + logMsg(LOG_TYPE.INFO, "Preload trans timeout checked, successfully") + } } diff --git a/src/main/scala/rep/network/consensus/vote/CRFDVoter.scala b/src/main/scala/rep/network/consensus/vote/CRFDVoter.scala index c1d72839..b867f05b 100644 --- a/src/main/scala/rep/network/consensus/vote/CRFDVoter.scala +++ b/src/main/scala/rep/network/consensus/vote/CRFDVoter.scala @@ -97,5 +97,5 @@ trait CRFDVoter extends VoterBase { } } - + } diff --git a/src/main/scala/rep/network/tools/PeerExtension.scala b/src/main/scala/rep/network/tools/PeerExtension.scala index df3a0b8f..5cd05e74 100644 --- a/src/main/scala/rep/network/tools/PeerExtension.scala +++ b/src/main/scala/rep/network/tools/PeerExtension.scala @@ -259,7 +259,7 @@ class PeerExtensionImpl extends Extension { candidatorLock.unlock() } } - + def resetCandidator(nds: Array[ String ]): Unit = { candidatorLock.lock() try{ diff --git a/src/main/scala/rep/sc/Shim.scala b/src/main/scala/rep/sc/Shim.scala index b45455d4..3a8f78ce 100644 --- a/src/main/scala/rep/sc/Shim.scala +++ b/src/main/scala/rep/sc/Shim.scala @@ -213,7 +213,7 @@ class Shim(system: ActorSystem, cid: String) { val sig = tx.signature.toByteArray val tOutSig1 = tx.withSignature(ByteString.EMPTY) val tOutSig = tOutSig1.withMetadata(ByteString.EMPTY) - + try{ val peercer = ECDSASign.getCertByBitcoinAddr(bitcoinaddr) ECDSASign.verify(sig, PeerHelper.getTxHash(tOutSig), peercer.get.getPublicKey) match { diff --git a/src/main/scala/rep/sc/contract/Compiler.scala b/src/main/scala/rep/sc/contract/Compiler.scala index 5757b736..952591f3 100644 --- a/src/main/scala/rep/sc/contract/Compiler.scala +++ b/src/main/scala/rep/sc/contract/Compiler.scala @@ -27,6 +27,7 @@ import java.io._ import org.json4s._ import org.json4s.jackson.JsonMethods._ import rep.crypto.Sha256 + import rep.app.conf.SystemProfile import scala.reflect.runtime.currentMirror import scala.tools.reflect.ToolBox @@ -120,10 +121,20 @@ class Compiler(targetDir: Option[File], bDebug:Boolean) { * @return 编译生成的类定义 */ def compilef(pcode: String, cid: String): Class[_]= { - val p1 = pcode.indexOf("extends IContract{") + //去掉package声明,将class放在default路径下 + var p0 = pcode.indexOf("import") + //第一个定位点应加强容错能力,允许空白字符 + val pattern = "extends\\s+IContract\\s*\\{".r + val p1str = pattern.findFirstIn(pcode).get + val p1 = pcode.indexOf(p1str) val p2 = pcode.lastIndexOf("}") val p3 = pcode.lastIndexOf("class ",p1) + //可能不存在import指令 + if(p0.equals(-1)) + p0 = p3 + if(p1.equals(-1) || p1.equals(-1) || p1.equals(-1)) + throw new RuntimeException("合约语法错误") val className = if(cid!=null) PRE_CLS_NAME+cid else classNameForCode(pcode) try{ val cl = Class.forName(className) @@ -132,7 +143,7 @@ class Compiler(targetDir: Option[File], bDebug:Boolean) { case e:Throwable => findClass(className).getOrElse { //替换类名为 hash256 - val ncode = pcode.substring(0,p3) + "class "+className+ " "+pcode.substring(p1,p2+1) + val ncode = pcode.substring(p0,p3) + "class "+className+ " "+pcode.substring(p1,p2+1) //+"\nscala.reflect.classTag[ContractAssets2].runtimeClass" if(path_source!=null) saveCode(className,ncode) diff --git a/src/main/scala/rep/sc/scalax/SandboxScala.scala b/src/main/scala/rep/sc/scalax/SandboxScala.scala index b51d76b3..76ae17a9 100644 --- a/src/main/scala/rep/sc/scalax/SandboxScala.scala +++ b/src/main/scala/rep/sc/scalax/SandboxScala.scala @@ -72,6 +72,7 @@ class SandboxScala(cid:String) extends Sandbox(cid){ shim.ol.append(new Oper(key, null, txid)) encodeJson(cid) //新建class实例并执行合约,传参为json数据 + //TODO case Transaction.Type.CHAINCODE_DESC 增加对合约描述的处理 case Transaction.Type.CHAINCODE_INVOKE => //获得合约action val action = cs.ctorMsg.get.function diff --git a/src/main/scala/rep/sc/tpl/SupplyTPL.scala b/src/main/scala/rep/sc/tpl/SupplyTPL.scala new file mode 100644 index 00000000..9b51c47a --- /dev/null +++ b/src/main/scala/rep/sc/tpl/SupplyTPL.scala @@ -0,0 +1,171 @@ + + +package rep.sc.tpl + +import rep.sc.contract._ +import org.json4s._ +import org.json4s.jackson.JsonMethods._ +import scala.reflect.ManifestFactory.classType +import scala.collection.mutable.Map +import org.json4s.native.Serialization +import org.json4s.native.Serialization.{read, write} +import org.json4s.{DefaultFormats, Formats, jackson} + +/** + * 供应链分账合约 + */ +class SupplyTPL extends IContract { + import rep.sc.tpl.SupplyType._ + + val SPLIT_CHAR = "_"; + val TPL_MODE = "_PM"; + implicit val formats = DefaultFormats + + + def init(ctx: ContractContext){ + println(s"tid: $ctx.t.txid") + } + + /** + * 追加确认签名 TODO 逻辑实现 + */ + def confirmSign(ctx: ContractContext, data:IPTConfirm ):Object={ + null + } + /** + * 取消追加确认签名 TODO 逻辑实现 + */ + def cancelSign(ctx: ContractContext, data:IPTConfirm ):Object={ + null + } + /** + * 设计方、原料方、生产方、销售方 签订对销售额的分成合约, 对于销售方账号+产品型号决定唯一的分账合约 + */ + def signShare(ctx: ContractContext, data:IPTSignShare ):Object={ + val sid = data.account_sale +SPLIT_CHAR + data.product_id + val pid = sid+TPL_MODE + //签约输入持久化,默认的类型转换无法胜任,以json字符串形式持久化 + ctx.api.setVal(sid, write(data)) + ctx.api.setVal(pid, TPL.Share) + sid + } + + def signFixed(ctx: ContractContext, data:IPTSignFixed ):Object={ + val sid = data.account_sale +SPLIT_CHAR + data.product_id + val pid = sid+TPL_MODE + //签约输入持久化 + ctx.api.setVal(sid, write(data)) + ctx.api.setVal(pid, TPL.Fixed) + sid + } + + /** + * 分账的调度方法,负责根据调用相应的分账模版, 传入模版定制参数和销售数据,进行分账 + */ + def split(ctx: ContractContext, data:IPTSplit ):Object={ + //根据销售方账号和产品Id获得分账脚本 + val sid = data.account_sale +SPLIT_CHAR + data.product_id + val pid = sid + TPL_MODE + val tm = ctx.api.getVal(pid).asInstanceOf[String] + + //根据签约时选择的分账方式模版,验证定制参数 + val mr = tm match { + case TPL.Share => + val sp0 = ctx.api.getVal(sid) + val sp = read[IPTSignShare](ctx.api.getVal(sid).asInstanceOf[String]) + splitShare(data.amount, sp.account_remain, sp.tpl_param) + case TPL.Fixed => + val sp = read[IPTSignFixed](ctx.api.getVal(sid).asInstanceOf[String]) + splitFixedRatio(data.amount, sp.account_remain, sp.ratio) + } + //返回分账计算结果 + addToAccount(ctx, mr) + mr + } + + /** + * 将分账结果增加到账户并持久化 + */ + def addToAccount(ctx: ContractContext, mr:Map[String,Int]){ + for ((k, v) <- mr) { + val sk = ctx.api.getVal(k) + var dk = if(sk==null) 0 else sk.toString.toInt + ctx.api.setVal(k, dk+v) + } + } + /** + * 合约方法入口 + */ + def onAction(ctx: ContractContext,action:String, sdata:String ):Object={ + val json = parse(sdata) + + action match { + case ACTION.SignShare => + signShare(ctx,json.extract[IPTSignShare]) + case ACTION.SignFixed => + signFixed(ctx,json.extract[IPTSignFixed]) + case ACTION.Split => + split(ctx, json.extract[IPTSplit]) + case ACTION.ConfirmSign => + confirmSign(ctx,json.extract[IPTConfirm]) + case ACTION.CancelSign => + cancelSign(ctx, json.extract[IPTConfirm]) + } + } + //TODO case Transaction.Type.CHAINCODE_DESC 增加对合约描述的处理 + def descAction(ctx: ContractContext,action:String, sdata:String ):String={ + val json = parse(sdata) + null + } + +/** + * 内部函数, 获得分阶段的分成 + */ + def getShare(sr: Int, ar: Array[ShareRatio]) : Int={ + var rv = 0 + for(el <- ar) { + //击中金额范围 + if(sr > el.from && sr <= el.to) { + //固定金额 + if(el.fixed > 0) + rv = el.fixed + else //按比例分成 + rv = (sr * el.ratio) .toInt + } + } + rv + } +/** + * 合约中内置多种分账模版,签约时可选择模版,如果出现新的分账模版,则部署一版新的合约 + * 分成模型, 除了销售方之外, 其他各方要求一个最低金额,分成按照金额阶段有所不同。 + */ + def splitShare(sr: Int, account_remain:String, rule: ShareMap): Map[String,Int] ={ + //分账结果 + val rm : Map[String, Int] = Map() + //分账余额 + var remain = sr + for ((k, v) <- rule) { + val rv = getShare(sr, v) + rm += (k -> rv) + remain -= rv + } + rm += (account_remain -> remain) + } + + +/** + * 各方固定比例分成,此模版仅仅为了合约对多模版的支持,可能无实际用途 + */ + def splitFixedRatio(sr: Int, account_remain: String, mr:FixedMap): Map[String,Int] ={ + val rm : Map[String, Int] = Map() + var remain = sr + //根据固定分成 + for ((k, v) <- mr) { + val rv = (sr* v ).toInt + rm += (k -> rv) + remain -= rv + } + //剩余的分给指定的余额账户 + rm += (account_remain -> remain) + } +} \ No newline at end of file diff --git a/src/main/scala/rep/sc/tpl/SupplyType.scala b/src/main/scala/rep/sc/tpl/SupplyType.scala new file mode 100644 index 00000000..d1a8068b --- /dev/null +++ b/src/main/scala/rep/sc/tpl/SupplyType.scala @@ -0,0 +1,53 @@ +package rep.sc.tpl + +import scala.collection.mutable.Map + +/** + * 分账接口 + */ +trait ISupplySplit { + /** + * @param sr 销售收入 + * @param accounts 参与分账的账户 + * @return 分账结果 + */ + def split(sr: Int, accounts: Array[String]): Array[Int] +} + + +package object SupplyType { + object ACTION { + val SignShare = "SignShare" + val SignFixed = "SignFixed" + val ConfirmSign = "ConfirmSign" + val CancelSign = "CancelSign" + val Split = "Split" + + } + + object TPL { + val Share = "Share" + val Fixed = "Fixed" + } + /** + * 按销售收入分段分成/固定值 的设置项 + */ + case class ShareRatio(from: Int, to: Int, ratio: Double, fixed: Int) + //多个账户的分段分成定义 + type ShareMap = scala.collection.mutable.Map[String, Array[ShareRatio]] + type FixedMap = scala.collection.mutable.Map[String,Double] + /** + * 签署分成合约的输入参数 + * @param account_sale 提交销售数据的账号 + * @param + */ + case class IPTSignShare(account_sale :String, product_id: String, account_remain :String, tpl_param: ShareMap) + //固定分账比例 + case class IPTSignFixed(account_sale :String, product_id: String, account_remain :String, ratio:Map[String,Double] ) + //触发分账的输入参数 + //TODO 如何防止重复提交? + case class IPTSplit(account_sale :String, product_id:String, amount:Int) + + case class IPTConfirm(account: String, tx_id:String) +} + diff --git a/src/main/scala/rep/storage/test/testmap.scala b/src/main/scala/rep/storage/test/testmap.scala index f0119ad5..b1cf9364 100644 --- a/src/main/scala/rep/storage/test/testmap.scala +++ b/src/main/scala/rep/storage/test/testmap.scala @@ -256,7 +256,7 @@ def testTreeMapclear{ println(tm) } - + case class myobject(txid:String,name:String) def main(args: Array[String]): Unit = { @@ -271,6 +271,6 @@ case class myobject(txid:String,name:String) val tmp = as.distinct println("tmpsize="+tmp.length+",assize="+as.length) - + } } \ No newline at end of file diff --git a/src/main/scala/rep/storage/util/pathUtil.java b/src/main/scala/rep/storage/util/pathUtil.java index d6bf84b7..9558f27c 100644 --- a/src/main/scala/rep/storage/util/pathUtil.java +++ b/src/main/scala/rep/storage/util/pathUtil.java @@ -119,6 +119,7 @@ public class pathUtil { } return l; } + public static int bytesToInt(byte[] inputs){ if(inputs == null) return 0; diff --git a/src/main/scala/sha0bfbe2faf858dd495e712fb0f897dd66082f06b879fa21a80fcc2acbc199b8d7.scala b/src/main/scala/sha0bfbe2faf858dd495e712fb0f897dd66082f06b879fa21a80fcc2acbc199b8d7.scala new file mode 100644 index 00000000..febb6254 --- /dev/null +++ b/src/main/scala/sha0bfbe2faf858dd495e712fb0f897dd66082f06b879fa21a80fcc2acbc199b8d7.scala @@ -0,0 +1,127 @@ +import org.json4s._ +import org.json4s.jackson.JsonMethods._ +import rep.sc.contract._ +import rep.storage.FakeStorage.Key + +/** + * 资产管理合约 + */ + +class sha0bfbe2faf858dd495e712fb0f897dd66082f06b879fa21a80fcc2acbc199b8d7 extends IContract{ + case class Transfer(from:String, to:String, amount:Int) +// case class Proof(key:String, content:String) + case class ReplaceCert(cert:String, addr:String) +// case class Cert(cert:String, info:String) + + implicit val formats = DefaultFormats + + def init(ctx: ContractContext){ + println(s"tid: $ctx.t.txid") + } + + def set(ctx: ContractContext, data:Map[String,Int]):Object={ + println(s"set data:$data") + for((k,v)<-data){ + ctx.api.setVal(k, v) + } + null + } + + + def read(ctx: ContractContext, key: String):Any={ + ctx.api.getVal(key) + } + + def loadCert(ctx: ContractContext, cert: String): Unit = { + ctx.api.loadCert(cert); + print("cert:"+cert); + } + + def write(ctx: ContractContext, data:Map[String,Int]):Object = { + for((k,v)<-data){ + ctx.api.setVal(k, v) + } + null + } + + def put_proof(ctx: ContractContext, data:Map[String,Any]):Object={ + //先检查该hash是否已经存在,如果已存在,抛异常 + for((k,v)<-data){ + var pv0 = ctx.api.getVal(k) + if(pv0 != null) + throw new Exception("["+k+"]已存在,当前值["+pv0+"]"); + ctx.api.setVal(k,v); + print("putProof:"+k+":"+v); + } + "put_proof ok" + } + + + + def signup(ctx: ContractContext, data:Map[String,String]):Object = { + var addr = "" + for((k,v)<-data){ + ctx.api.check(ctx.t.cert.toStringUtf8,ctx.t) + addr = ctx.api.signup(k,v) + } + addr + } + + def destroyCert(ctx: ContractContext, certAddr: String): Object = { + println(s"destroy cert->addr:$certAddr") + ctx.api.check(ctx.t.cert.toStringUtf8,ctx.t) //ctx中自带交易内容 + ctx.api.destroyCert(certAddr); + "destory scuccess" + } + + def replaceCert(ctx: ContractContext, data:ReplaceCert): Object = { + val cert = data.cert + val addr = data.addr + ctx.api.check(ctx.t.cert.toStringUtf8,ctx.t) + ctx.api.replaceCert(cert,addr); // 返回短地址 + } + + def transfer(ctx: ContractContext, data:Transfer):Object={ + val sfrom = ctx.api.getVal(data.from) + var dfrom =sfrom.toString.toInt + if(dfrom < data.amount) + throw new Exception("余额不足") + var dto = ctx.api.getVal(data.to).toString.toInt + //if(dto==null) dto = 0; + + ctx.api.setVal(data.from,dfrom - data.amount) + ctx.api.setVal(data.to,dto + data.amount) + "transfer ok" + } + + /** + * 根据action,找到对应的method,并将传入的json字符串parse为method需要的传入参数 + */ + def onAction(ctx: ContractContext,action:String, sdata:String ):Object={ + //println(s"onAction---") + //return "transfer ok" + val json = parse(sdata) + + action match { + case "transfer" => + println(s"transfer oook") + transfer(ctx,json.extract[Transfer]) + case "set" => + println(s"set") + set(ctx, json.extract[Map[String,Int]]) + case "put_proof" => + println(s"put_proof") + put_proof(ctx, json.extract[Map[String,Any]]) + case "signup" => + println(s"signup") + signup(ctx, json.extract[Map[String,String]]) + case "destroyCert" => + println(s"destroyCert") + destroyCert(ctx, json.extract[String]) + case "replaceCert" => + println(s"replaceCert") + replaceCert(ctx, json.extract[ReplaceCert]) + } + } + +} \ No newline at end of file diff --git a/src/test/scala/rep/sc/SplitSpec.scala b/src/test/scala/rep/sc/SplitSpec.scala new file mode 100644 index 00000000..0f8acf17 --- /dev/null +++ b/src/test/scala/rep/sc/SplitSpec.scala @@ -0,0 +1,93 @@ +package rep.sc +import org.scalatest._ +import rep.sc.tpl.SupplyTPL +import prop._ +import scala.collection.immutable._ +import scala.collection.mutable.Map + +import org.json4s._ +import org.json4s.JsonDSL._ +import org.json4s.jackson.JsonMethods._ +import org.json4s.{DefaultFormats, Formats, jackson} +import org.json4s.native.Serialization.writePretty +import org.json4s.native.Serialization +import org.json4s.native.Serialization.{read, write} + + +class SplitSpec extends PropSpec with TableDrivenPropertyChecks with Matchers { + import rep.sc.tpl.SupplyType._ + import rep.sc.tpl.SupplyType.FixedMap + implicit val formats = DefaultFormats + //implicit val formats = Serialization.formats(NoTypeHints) + + val fm :FixedMap = Map("A" -> 0.2, "B"-> 0.2, "C"-> 0.1, "D" -> 0.1) + val sm :ShareMap = Map("A" -> Array(new ShareRatio(0,100,0.1,0), new ShareRatio(100,10000,0.15,0)), + "B" -> Array(new ShareRatio(0,10000,0,10)), + "C" -> Array(new ShareRatio(0,10000,0.1,0)), + "D" -> Array(new ShareRatio(0,100,0,10), new ShareRatio(100,10000,0.15,0))) + val account_remain = "R" + val account_sales = "S" + val product_id = "P201806270001" + + val s1 = new SupplyTPL + + val examples = + Table( + "sr", + 100, + 200, + 500, + 1000 + ) + property("JSon format works for IPTSignShare") { + val ipt1 = new IPTSignShare(account_sales,product_id,account_remain,sm) + val jstr = writePretty(ipt1) + println(s"${jstr}") + val ipt2 = read[IPTSignShare](jstr) + ipt2.account_remain should be (ipt1.account_remain) + ipt2.tpl_param("A").length should be (ipt1.tpl_param("A").length) + ipt2.tpl_param("A")(0) should be (ipt1.tpl_param("A")(0)) + } + property("JSon format works for IPTSignFixed") { + val ipt1 = new IPTSignFixed(account_sales,product_id,account_remain,fm) + val jstr = write(ipt1) + println(s"${jstr}") + val ipt2 = read[IPTSignFixed](jstr) + ipt2.account_remain should be (ipt1.account_remain) + ipt2.ratio should be (ipt1.ratio) + } + property("JSon format works for IPTSplit") { + val ipt1 = new IPTSplit(account_sales,product_id,1000) + val jstr = write(ipt1) + println(s"${jstr}") + val ipt2 = read[IPTSplit](jstr) + ipt2.product_id should be (ipt1.product_id) + } + property(" Splitting sales-revenue by splitFixedRatio") { + forAll(examples) { sr => + val rm = s1.splitFixedRatio(sr, account_remain,fm) + var total = 0 + print(s"total:${sr} ") + for ((k, v) <- rm) { + total += v + print(s" ${k}: ${v} ") + } + println("") + total should be (sr) + } + } + property(" Splitting sales-revenue by splitShare") { + forAll(examples) { sr => + val rm = s1.splitShare(sr, account_remain,sm) + var total = 0 + print(s"total:${sr} ") + for ((k, v) <- rm) { + total += v + print(s" ${k}: ${v} ") + } + println("") + total should be (sr) + } + } + +} \ No newline at end of file diff --git a/src/test/scala/rep/sc/SupplySpec.scala b/src/test/scala/rep/sc/SupplySpec.scala new file mode 100644 index 00000000..5febde88 --- /dev/null +++ b/src/test/scala/rep/sc/SupplySpec.scala @@ -0,0 +1,187 @@ +/* + * Copyright 2018 Blockchain Technology and Application Joint Lab, Fintech Research Center of ISCAS. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BA SIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rep.sc + +import org.scalatest.{ BeforeAndAfterAll, FlatSpecLike, Matchers } +import akka.actor.ActorSystem +import akka.testkit.TestKit + +import scala.concurrent.Await +import scala.concurrent.duration._ +import rep.protos.peer._ +import rep.app.system.ClusterSystem +import rep.app.system.ClusterSystem.InitType + +import rep.network.PeerHelper.transactionCreator +import org.json4s.{ DefaultFormats, jackson } +import de.heikoseeberger.akkahttpjson4s.Json4sSupport +import org.json4s._ +import rep.network.module.ModuleManager +import rep.storage.ImpDataAccess +import rep.utils.Json4s._ +import rep.sc.contract._ +import java.io.IOException +import java.io.PrintWriter +import java.io.FileWriter +import java.io.File + +import scala.collection.mutable.Map +import org.json4s.{DefaultFormats, Formats, jackson} +import org.json4s.native.Serialization.writePretty +import org.json4s.native.Serialization +import org.json4s.native.Serialization.{read, write} + +/** 合约容器实现的单元测试 + * @author c4w + * @param _system 测试用例所在的actor System. + * + */ +class SupplySpec(_system: ActorSystem) + extends TestKit(_system) + with Matchers + with FlatSpecLike + with BeforeAndAfterAll { + + import rep.sc.TransProcessor.DoTransaction + import rep.sc.Sandbox.DoTransactionResult + + import akka.testkit.TestProbe + import akka.testkit.TestActorRef + import Json4sSupport._ + import rep.sc.tpl.SupplyType._ + + implicit val serialization = jackson.Serialization + // or native.Serialization + implicit val formats = DefaultFormats + + def this() = this(ActorSystem("SandBoxSpec", new ClusterSystem("1", InitType.MULTI_INIT, false).getConf)) + + override def afterAll: Unit = Await.ready(system.terminate(), Duration.Inf) + + //Scala实现的资产管理合约测试,包括合约的部署、调用、结果返回验证 + "container" should "deploy supply contract and call it for splitting then" in { + val sysName = "1" + val dbTag = "1" + //建立PeerManager实例是为了调用transactionCreator(需要用到密钥签名),无他 + val pm = system.actorOf(ModuleManager.props("pm", sysName)) + //加载合约脚本 + val s1 = scala.io.Source.fromFile("src/main/scala/rep/sc/tpl/SupplyTPL.scala") + val l1 = try s1.mkString finally s1.close() + + val fm :FixedMap= Map("A" -> 0.2, "B"-> 0.2, "C"-> 0.1, "D" -> 0.1) + val sm :ShareMap = Map("A" -> Array(new ShareRatio(0,100,0.1,0), new ShareRatio(100,10000,0.15,0)), + "B" -> Array(new ShareRatio(0,10000,0,10)), + "C" -> Array(new ShareRatio(0,10000,0.1,0)), + "D" -> Array(new ShareRatio(0,100,0,10), new ShareRatio(100,10000,0.15,0))) + val account_remain = "R" + val account_sales1 = "S1" + val account_sales2 = "S2" + val product_id = "P201806270001" + + //构造签约交易合约模版1输入json字符串,销售1选择了合约模版1 + val ipt2 = new IPTSignFixed(account_sales1,product_id,account_remain,fm) + val l2 = write(ipt2) + + //构造签约交易合约模版2输入json字符串,,销售2选择了合约模版2 + val ipt3 = new IPTSignShare(account_sales2,product_id,account_remain,sm) + val l3 = writePretty(ipt3) + + //准备探针以验证调用返回结果 + val probe = TestProbe() + val db = ImpDataAccess.GetDataAccess(sysName) + var sandbox = system.actorOf(TransProcessor.props("sandbox", "", probe.ref)) + //生成deploy交易 + val t1 = transactionCreator(sysName, rep.protos.peer.Transaction.Type.CHAINCODE_DEPLOY, + "", "", List(), l1, None, rep.protos.peer.ChaincodeSpec.CodeType.CODE_SCALA) + + val msg_send1 = new DoTransaction(t1, probe.ref, "") + probe.send(sandbox, msg_send1) + val msg_recv1 = probe.expectMsgType[Sandbox.DoTransactionResult](1000.seconds) + val ol1 = msg_recv1.ol + val ol1str = compactJson(ol1) + + //生成invoke交易 + //获取deploy生成的chainCodeId + //初始化资产 + val cname = t1.payload.get.chaincodeID.get.name + val t2 = transactionCreator(sysName, rep.protos.peer.Transaction.Type.CHAINCODE_INVOKE, + "", ACTION.SignFixed, Seq(l2), "", Option(cname)) + + val msg_send2 = new DoTransaction(t2, probe.ref, "") + probe.send(sandbox, msg_send2) + val msg_recv2 = probe.expectMsgType[Sandbox.DoTransactionResult](1000.seconds) + + val t3 = transactionCreator(sysName, rep.protos.peer.Transaction.Type.CHAINCODE_INVOKE, + "", ACTION.SignShare, Seq(l3), "", Option(cname)) + val msg_send3 = new DoTransaction(t3, probe.ref, "") + probe.send(sandbox, msg_send3) + val msg_recv3 = probe.expectMsgType[Sandbox.DoTransactionResult](1000.seconds) + + //测试各种金额下的分账结果 + val sr = Array(100, 200, 500, 1000) + for (el<- sr) { + //构造分账交易 + val ipt4 = new IPTSplit(account_sales1,product_id,el) + val l4 = write(ipt4) + val t4 = transactionCreator(sysName, rep.protos.peer.Transaction.Type.CHAINCODE_INVOKE, + "", ACTION.Split, Seq(l4), "", Option(cname)) + val msg_send4 = new DoTransaction(t4, probe.ref, "") + + probe.send(sandbox, msg_send4) + val msg_recv4 = probe.expectMsgType[Sandbox.DoTransactionResult](1000.seconds) + val ol4 = msg_recv4.ol + val ol4str = compactJson(ol4) + println(s"oper log:${ol4str}") + //获得交易返回值 + val jv4 = msg_recv4.r.asInstanceOf[JValue] + val rv4 = jv4.extract[Map[String,Int]] + println(rv4) + //分账之后总额应保持一致 + var total = 0 + for ((k, v) <- rv4) { + total += v + } + total should be(el) + } + + for (el<- sr) { + //构造分账交易 + val ipt4 = new IPTSplit(account_sales2,product_id,el) + val l4 = write(ipt4) + val t4 = transactionCreator(sysName, rep.protos.peer.Transaction.Type.CHAINCODE_INVOKE, + "", ACTION.Split, Seq(l4), "", Option(cname)) + val msg_send4 = new DoTransaction(t4, probe.ref, "") + + probe.send(sandbox, msg_send4) + val msg_recv4 = probe.expectMsgType[Sandbox.DoTransactionResult](1000.seconds) + val ol4 = msg_recv4.ol + val ol4str = compactJson(ol4) + println(s"oper log:${ol4str}") + //获得交易返回值 + //获得交易返回值 + val jv4 = msg_recv4.r.asInstanceOf[JValue] + val rv4 = jv4.extract[Map[String,Int]] + println(rv4) + //分账之后总额应保持一致 + var total = 0 + for ((k, v) <- rv4) { + total += v + } + total should be(el) + } + + } +}