Skip to content

Commit

Permalink
When transferring objects from the server to the client, send contrac…
Browse files Browse the repository at this point in the history
…t names too so the client knows what kind of stub to instantiate.
  • Loading branch information
mcoblenz committed Aug 23, 2019
1 parent 81eb41a commit 4f66e9e
Show file tree
Hide file tree
Showing 12 changed files with 124 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public byte[] doTransaction(String transactionName, ArrayList<String> args, Stri
throw new ChaincodeClientTransactionFailedException(output);
}

return output.trim().getBytes();
byte[] base64EncodeBytes = output.trim().getBytes();
return Base64.getDecoder().decode(base64EncodeBytes);
}
}
Binary file modified docs/repository/edu/cmu/cs/obsidian/runtime/0.1/runtime-0.1.jar
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
7bc1804643e15c728f4265513d67077e
294c7aa926cc15c35fc20ff1fe7186ef
Original file line number Diff line number Diff line change
@@ -1 +1 @@
450a4297ff76fb9a74d7e1e92b04e3be7dc46550
73e3e0747cc12e29dcaab1df7de91573d611c282
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
<versions>
<version>0.1</version>
</versions>
<lastUpdated>20190808143405</lastUpdated>
<lastUpdated>20190819154821</lastUpdated>
</versioning>
</metadata>
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1ce26d55fed600caea300eab01ef231f
a05f410ad52766af26997c47af64beeb
Original file line number Diff line number Diff line change
@@ -1 +1 @@
adb424278cbd66408b01c3cd2f3ed62a83d5c19b
d016fd81100ae83d74e5315cf66d31607d758321
12 changes: 12 additions & 0 deletions resources/protos/InterfaceImplementerWrapper.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
syntax = "proto3";

option java_outer_classname = "InterfaceImplementerWrapperOuterClass";

option java_package = "org.hyperledger.fabric.example";


message InterfaceImplementerWrapper {
string __className = 1;

string __guid = 2;
}
33 changes: 29 additions & 4 deletions src/main/scala/edu/cmu/cs/obsidian/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ object Main {
val mainName = findMainContractName(checkedTable.ast)

val protobufs: Seq[(Protobuf, String)] = ProtobufGen.translateProgram(checkedTable.ast, sourceFilename)
val protobufOutputPath = outputPath.resolve("protos")

// Each import results in a .proto file, which needs to be compiled.
for (p <- protobufs) {
Expand All @@ -358,11 +359,10 @@ object Main {

protobuf.build(protobufPath.toFile, protobufOuterClassName)


// Invoke protoc to compile from protobuf to Java.
val protoPath = protobufPath.getParent.toString + " " + protobufPath.toString
val protoPath = protobufPath.getParent.toString
val protocInvocation: String =
"protoc --java_out=" + srcDir + " --proto_path=" + protoPath
"protoc --java_out=" + srcDir + " -I=" + protoPath + " " + protobufPath.toString

try {
val exitCode = protocInvocation.!
Expand All @@ -382,7 +382,6 @@ object Main {
case None =>
Paths.get(mainName)
}
val protobufOutputPath = outputPath.resolve("protos")
val temp = protobufOutputPath.toFile
if (temp.exists()) {
FileUtils.deleteDirectory(temp)
Expand All @@ -393,8 +392,34 @@ object Main {
val destFile = Paths.get(s"$protobufOutputPath")
val newDest = Paths.get(destFile.toString() + File.separator + sourceFile.getFileName())
Files.copy(sourceFile, newDest, StandardCopyOption.REPLACE_EXISTING)

}

// Compile the wrapper protobuf file.
val wrapperProtoPath = compilerPath().resolve("resources")
.resolve("protos")
// Invoke protoc to compile from protobuf to Java.
val wrapperProtocInvocation: String =
"protoc --java_out=" + srcDir + " -I=" + wrapperProtoPath + " InterfaceImplementerWrapper.proto"

try {
val exitCode = wrapperProtocInvocation.!
if (exitCode != 0) {
println("`" + wrapperProtocInvocation + "` exited abnormally: " + exitCode)
return false
}
} catch {
case e: Throwable => println("Error running protoc: " + e)
}

// Copy the InterfaceImplementerWrapper.proto file.
val wrapperProtoPathSrc = compilerPath().resolve("resources")
.resolve("protos")
.resolve("InterfaceImplementerWrapper.proto")
val wrapperProtoPathDest = protobufOutputPath.resolve("InterfaceImplementerWrapper.proto")
Files.copy(wrapperProtoPathSrc, wrapperProtoPathDest, StandardCopyOption.REPLACE_EXISTING)


generateFabricCode(mainName, options.outputPath, srcDir)
} catch {
case e:
Expand Down
70 changes: 57 additions & 13 deletions src/main/scala/edu/cmu/cs/obsidian/codegen/CodeGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ class CodeGen (val target: Target, table: SymbolTable) {
}

// Returns a pair of an error-checking block option and the resulting expression.
private def unmarshallExprExpectingUUIDObjects(marshalledExpr: IJExpression, typ: ObsidianType, errorBlock: JBlock): IJExpression = {
private def unmarshallExprExpectingUUIDObjects(marshalledExpr: IJExpression, typ: ObsidianType, errorBlock: JBlock, regularBlock: JBlock): IJExpression = {
typ match {
case IntType() =>
val charset = model.ref("java.nio.charset.StandardCharsets").staticRef("UTF_8")
Expand All @@ -277,16 +277,24 @@ class CodeGen (val target: Target, table: SymbolTable) {
JExpr._new(stringClass).arg(marshalledExpr).arg(charset)
// this case encompasses [AstContractType] and [AstStateType]
case _ =>
val targetClass = resolveType(typ, table).asInstanceOf[AbstractJClass]
val constructorInvocation = JExpr._new(targetClass)
val wrapper = interfaceWrapperType().staticInvoke("parseFrom").arg(marshalledExpr)
val wrapperDecl = regularBlock.decl(interfaceWrapperType(), "wrapper", wrapper)

val className = wrapperDecl.invoke("getClassName")
val targetClass = model.directClass("java.lang.Class").staticInvoke("forName").arg(className.plus("__Stub__"))

val connectionManagerClass = model.directClass("edu.cmu.cs.obsidian.client.ChaincodeClientConnectionManager")
// I have the JDirectClass, but I need an IJExpression.
val connectionManagerClassObj = connectionManagerClass.dotclass()

val constructor = targetClass.invoke("getConstructor").arg(connectionManagerClassObj).arg(javaStringType.dotclass())
val constructorInvocation = constructor.invoke("newInstance")
constructorInvocation.arg(JExpr.ref("connectionManager"))

val stringClass = model.ref("java.lang.String")
val charset = model.ref("java.nio.charset.StandardCharsets").staticRef("UTF_8")
val guidString = JExpr._new(stringClass).arg(marshalledExpr).arg(charset)
val guidString = wrapperDecl.invoke("getGuid")

constructorInvocation.arg(guidString)
constructorInvocation
JExpr.cast(resolveType(typ, table), constructorInvocation)
}
}

Expand Down Expand Up @@ -435,13 +443,13 @@ class CodeGen (val target: Target, table: SymbolTable) {
doTransactionInvocation.arg(JExpr.invoke("__getGUID")) // pass UUID so server knows what object to invoke the transaction on
doTransactionInvocation.arg(transaction.retType.isDefined)

if (transaction.retType.isDefined) {
if (obsidianRetType.isDefined) {
// return result
val marshalledResultDecl = tryBlock.body().decl(newClass.owner().ref("byte[]"), "marshalledResult", doTransactionInvocation)

val errorBlock = new JBlock()

val deserializedArg = unmarshallExprExpectingUUIDObjects(marshalledResultDecl, obsidianRetType.get, errorBlock)
val deserializedArg = unmarshallExprExpectingUUIDObjects(marshalledResultDecl, obsidianRetType.get, errorBlock, tryBlock.body())

if (!errorBlock.isEmpty) {
tryBlock.body().add(errorBlock)
Expand All @@ -462,6 +470,26 @@ class CodeGen (val target: Target, table: SymbolTable) {
val bugCatchBlock = tryBlock._catch(model.directClass("edu.cmu.cs.obsidian.client.ChaincodeClientTransactionBugException"))
bugCatchBlock.body()._throw(JExpr._new(model.directClass("edu.cmu.cs.obsidian.client.ChaincodeClientAbortTransactionException")))

if (obsidianRetType.isDefined && obsidianRetType.get.isInstanceOf[NonPrimitiveType]) {
// In this case, the unmarshalling method is going to do runtime reflection to figure out what kind of object
// this is, in which case some additional exceptions are possible.
val classNotFoundBlock = tryBlock._catch(model.directClass("java.lang.ClassNotFoundException"))
classNotFoundBlock.body()._throw(JExpr._new(model.directClass("edu.cmu.cs.obsidian.client.ChaincodeClientAbortTransactionException")))

val noSuchMethodBlock = tryBlock._catch(model.directClass("java.lang.NoSuchMethodException"))
noSuchMethodBlock.body()._throw(JExpr._new(model.directClass("edu.cmu.cs.obsidian.client.ChaincodeClientAbortTransactionException")))

val instantiationExceptionBlock = tryBlock._catch(model.directClass("java.lang.InstantiationException"))
instantiationExceptionBlock.body()._throw(JExpr._new(model.directClass("edu.cmu.cs.obsidian.client.ChaincodeClientAbortTransactionException")))

val illegalAccessExceptionBlock = tryBlock._catch(model.directClass("java.lang.IllegalAccessException"))
illegalAccessExceptionBlock.body()._throw(JExpr._new(model.directClass("edu.cmu.cs.obsidian.client.ChaincodeClientAbortTransactionException")))

val invocationTargetExceptionBlock = tryBlock._catch(model.directClass("java.lang.reflect.InvocationTargetException"))
invocationTargetExceptionBlock.body()._throw(JExpr._new(model.directClass("edu.cmu.cs.obsidian.client.ChaincodeClientAbortTransactionException")))

}

meth
}

Expand Down Expand Up @@ -953,7 +981,7 @@ class CodeGen (val target: Target, table: SymbolTable) {

aContract.params.foreach(p => {
val paramName = genericParamName(p)
newDispatcher.param(model.directClass("java.lang.String"), paramName)
newDispatcher.param(javaStringType(), paramName)
newDispatcher.body().assign(JExpr.refthis(paramName), JExpr.ref(paramName))

p.gVar.permissionVar match {
Expand Down Expand Up @@ -1107,6 +1135,8 @@ class CodeGen (val target: Target, table: SymbolTable) {
def obsidianSerializedSetType: JNarrowedClass = setType().narrow(obsidianSerialized)
def obsidianSerializedHashSetType: JNarrowedClass = hashSetType().narrow(obsidianSerialized)

def interfaceWrapperType() = model.ref("org.hyperledger.fabric.example.InterfaceImplementerWrapperOuterClass.InterfaceImplementerWrapper")

def isPerm(e: IJExpression, p: Permission): IJExpression =
JExpr.lit(p.toString).invoke("equals").arg(e)

Expand Down Expand Up @@ -1679,7 +1709,21 @@ class CodeGen (val target: Target, table: SymbolTable) {
val charset = model.ref("java.nio.charset.StandardCharsets").staticRef("UTF_8")
invocation.arg(charset)
// case _ => returnObj.invoke("__archiveBytes")
case _ => returnObj.invoke("__getGUID").invoke("getBytes")
case _ =>
// It would be nice if we could only send the wrapper if there's a possibility that
// this class was obtained by substitution from a type variable.
// But that information is gone now, and if/when we support subclassing, we'll need to address that case too.
// For now, do the simple thing, and send class information for all object references.
val wrapperBuilderClass = model.ref("org.hyperledger.fabric.example.InterfaceImplementerWrapperOuterClass.InterfaceImplementerWrapper.Builder")
val wrapperBuilder = enoughArgs.decl(wrapperBuilderClass, "returnWrapperBuilder", interfaceWrapperType().staticInvoke("newBuilder"))
enoughArgs.invoke(wrapperBuilder, "setGuid").arg(returnObj.invoke("__getGUID"))
enoughArgs.invoke(wrapperBuilder, "setClassName").arg(returnObj.invoke("getClass").invoke("getName"))

val encoder = model.directClass("java.util.Base64").staticInvoke("getEncoder")
val bytes = wrapperBuilder.invoke("build").invoke("toByteArray")
encoder.invoke("encode").arg(bytes)

//returnObj.invoke("__getGUID").invoke("getBytes")
}

)
Expand Down Expand Up @@ -2939,7 +2983,7 @@ class CodeGen (val target: Target, table: SymbolTable) {

for (param <- tx.params) {
generify(meth, param)
meth.param(model.directClass("java.lang.String"), genericParamName(param))
meth.param(javaStringType(), genericParamName(param))

param.gVar.permissionVar match {
case Some(pVar) =>
Expand Down Expand Up @@ -3054,7 +3098,7 @@ class CodeGen (val target: Target, table: SymbolTable) {
case States(states) =>
// Generates:
// new HashSet<String>(Arrays.asList(states)).contains(e.getState().toString())
JExpr._new(model.directClass("java.util.HashSet").narrow(model.directClass("java.lang.String")))
JExpr._new(model.directClass("java.util.HashSet").narrow(javaStringType()))
.arg(withArgs(model.directClass("java.util.Arrays").staticInvoke("asList"),
states.map(JExpr.lit).toSeq))
.invoke("contains").arg(invokeGetState(jEx, loadSerialization = true).invoke("toString"))
Expand Down
6 changes: 6 additions & 0 deletions src/main/scala/edu/cmu/cs/obsidian/protobuf/Protobuf.scala
Original file line number Diff line number Diff line change
Expand Up @@ -129,5 +129,11 @@ case class ObjectType(typeName: String) extends FieldType {
}
}

case class BytesType() extends FieldType {
def typeString() = {
"bytes"
}
}



15 changes: 13 additions & 2 deletions src/main/scala/edu/cmu/cs/obsidian/protobuf/ProtobufGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import java.io.File
import edu.cmu.cs.obsidian.parser._
import edu.cmu.cs.obsidian.util.Util
import edu.cmu.cs.obsidian.typecheck._
import edu.cmu.cs.obsidian.protobuf._

class Unimplemented extends Exception {}

Expand Down Expand Up @@ -60,9 +61,11 @@ object ProtobufGen {
}
)



val declsWithGUID =
ProtobufField(edu.cmu.cs.obsidian.protobuf.StringType(), "__guid") ::
genericParams(aContract) ++ interfaceParams(aContract) ++ decls
(ProtobufField(edu.cmu.cs.obsidian.protobuf.StringType(), "__guid") ::
genericParams(aContract) ++ interfaceParams(aContract) ++ decls)

val contractMessage = if (stateNames.nonEmpty) {
val oneOfOptions = stateNames.map((stateName: String) =>
Expand All @@ -75,6 +78,14 @@ object ProtobufGen {
ProtobufMessage(declsWithGUID, aContract.name)
}

val contractWrapperMessage =
if (aContract.bound == ContractType.topContractType)
List[ProtobufDeclaration]()
else {

List()
}

val contractOrGUIDFields : List[(FieldType, String)] = List[(FieldType, String)]((ObjectType(aContract.name), "obj"),
(edu.cmu.cs.obsidian.protobuf.StringType(), "guid"))
val contractOrGUIDMessage = new ProtobufMessage(Seq(new ProtobufOneOf("either", contractOrGUIDFields)),
Expand Down

0 comments on commit 4f66e9e

Please sign in to comment.