CORDA-1664: Blob inspector able to display SignedTransaction blobs dumped from a node's db. (#3559)

This commit is contained in:
Shams Asari 2018-07-11 14:50:51 +01:00 committed by GitHub
parent 6ea4f9c1d6
commit ac179aa9ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 98 additions and 19 deletions

View File

@ -10,8 +10,11 @@ dependencies {
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
testCompile(project(':test-utils')) {
exclude module: 'node-api'
exclude module: 'finance'
}
testCompile project(':test-utils')
testCompile "junit:junit:$junit_version"
}
jar {
@ -24,7 +27,7 @@ jar {
manifest {
attributes(
'Automatic-Module-Name': 'net.corda.blobinspector',
'Main-Class': 'net.corda.blobinspector.MainKt'
'Main-Class': 'net.corda.blobinspector.BlobInspectorKt'
)
}
}

View File

@ -7,12 +7,13 @@ import net.corda.client.jackson.JacksonSupport
import net.corda.core.internal.isRegularFile
import net.corda.core.internal.rootMessage
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationFactory
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
import net.corda.core.serialization.internal._contextSerializationEnv
import net.corda.core.utilities.sequence
import net.corda.serialization.internal.AMQP_P2P_CONTEXT
import net.corda.serialization.internal.AMQP_STORAGE_CONTEXT
import net.corda.serialization.internal.CordaSerializationMagic
import net.corda.serialization.internal.SerializationFactoryImpl
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
@ -20,13 +21,14 @@ import net.corda.serialization.internal.amqp.DeserializationInput
import net.corda.serialization.internal.amqp.amqpMagic
import picocli.CommandLine
import picocli.CommandLine.*
import java.io.PrintStream
import java.net.MalformedURLException
import java.net.URL
import java.nio.file.Paths
import kotlin.system.exitProcess
fun main(args: Array<String>) {
val main = Main()
val main = BlobInspector()
try {
CommandLine.run(main, *args)
} catch (e: ExecutionException) {
@ -47,9 +49,9 @@ fun main(args: Array<String>) {
showDefaultValues = true,
description = ["Inspect AMQP serialised binary blobs"]
)
class Main : Runnable {
class BlobInspector : Runnable {
@Parameters(index = "0", paramLabel = "SOURCE", description = ["URL or file path to the blob"], converter = [SourceConverter::class])
private var source: URL? = null
var source: URL? = null
@Option(names = ["--format"], paramLabel = "type", description = ["Output format. Possible values: [YAML, JSON]"])
private var formatType: FormatType = FormatType.YAML
@ -64,7 +66,9 @@ class Main : Runnable {
@Option(names = ["--verbose"], description = ["Enable verbose output"])
var verbose: Boolean = false
override fun run() {
override fun run() = run(System.out)
fun run(out: PrintStream) {
if (verbose) {
System.setProperty("logLevel", "trace")
}
@ -78,39 +82,44 @@ class Main : Runnable {
if (schema) {
val envelope = DeserializationInput.getEnvelope(bytes)
println(envelope.schema)
println()
println(envelope.transformsSchema)
println()
out.println(envelope.schema)
out.println()
out.println(envelope.transformsSchema)
out.println()
}
initialiseSerialization()
val factory = when (formatType) {
FormatType.YAML -> YAMLFactory()
FormatType.JSON -> JsonFactory()
}
val mapper = JacksonSupport.createNonRpcMapper(factory, fullParties)
// Deserialise with the lenient carpenter as we only care for the AMQP field getters
val deserialized = bytes.deserialize<Any>(context = SerializationFactory.defaultFactory.defaultContext.withLenientCarpenter())
println(deserialized.javaClass.name)
mapper.writeValue(System.out, deserialized)
initialiseSerialization()
try {
val deserialized = bytes.deserialize<Any>(context = SerializationDefaults.STORAGE_CONTEXT)
out.println(deserialized.javaClass.name)
mapper.writeValue(out, deserialized)
} finally {
_contextSerializationEnv.set(null)
}
}
private fun initialiseSerialization() {
// Deserialise with the lenient carpenter as we only care for the AMQP field getters
_contextSerializationEnv.set(SerializationEnvironmentImpl(
SerializationFactoryImpl().apply {
registerScheme(AMQPInspectorSerializationScheme)
},
AMQP_P2P_CONTEXT
p2pContext = AMQP_P2P_CONTEXT.withLenientCarpenter(),
storageContext = AMQP_STORAGE_CONTEXT.withLenientCarpenter()
))
}
}
private object AMQPInspectorSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) {
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
return magic == amqpMagic && target == SerializationContext.UseCase.P2P
return magic == amqpMagic
}
override fun rpcClientSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
override fun rpcServerSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()

View File

@ -0,0 +1,67 @@
package net.corda.blobinspector
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.SignedDataWithCert
import net.corda.core.node.NetworkParameters
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.testing.common.internal.checkNotOnClasspath
import org.apache.commons.io.output.WriterOutputStream
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.io.PrintStream
import java.io.StringWriter
import java.nio.charset.StandardCharsets.UTF_8
class BlobInspectorTest {
private val blobInspector = BlobInspector()
@Test
fun `network-parameters file`() {
val output = run("network-parameters")
assertThat(output)
.startsWith(SignedDataWithCert::class.java.name)
.contains(NetworkParameters::class.java.name)
.contains(CordaX500Name("Notary Service", "Zurich", "CH").toString()) // Name of the notary in the network parameters
}
@Test
fun `node-info file`() {
checkNotOnClassPath("net.corda.nodeapi.internal.SignedNodeInfo")
val output = run("node-info")
assertThat(output)
.startsWith("net.corda.nodeapi.internal.SignedNodeInfo")
.contains(CordaX500Name("BankOfCorda", "New York", "US").toString())
}
@Test
fun `WireTransaction with Cash state`() {
checkNotOnClassPath("net.corda.finance.contracts.asset.Cash\$State")
val output = run("cash-wtx.blob")
assertThat(output)
.startsWith(WireTransaction::class.java.name)
.contains("net.corda.finance.contracts.asset.Cash\$State")
}
@Test
fun `SignedTransaction with Cash state taken from node db`() {
checkNotOnClassPath("net.corda.finance.contracts.asset.Cash\$State")
val output = run("cash-stx-db.blob")
assertThat(output)
.startsWith(SignedTransaction::class.java.name)
.contains("net.corda.finance.contracts.asset.Cash\$State")
}
private fun run(resourceName: String): String {
blobInspector.source = javaClass.getResource(resourceName)
val writer = StringWriter()
blobInspector.run(PrintStream(WriterOutputStream(writer, UTF_8)))
val output = writer.toString()
println(output)
return output
}
private fun checkNotOnClassPath(className: String) {
checkNotOnClasspath(className) { "The Blob Inspector does not have this as a dependency." }
}
}