mirror of
https://github.com/corda/corda.git
synced 2025-05-06 18:48:34 +00:00
CORDA-1664: Blob inspector able to display SignedTransaction blobs dumped from a node's db. (#3559)
This commit is contained in:
parent
6ea4f9c1d6
commit
ac179aa9ab
@ -10,8 +10,11 @@ dependencies {
|
|||||||
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
||||||
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_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 project(':test-utils')
|
||||||
testCompile "junit:junit:$junit_version"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
@ -24,7 +27,7 @@ jar {
|
|||||||
manifest {
|
manifest {
|
||||||
attributes(
|
attributes(
|
||||||
'Automatic-Module-Name': 'net.corda.blobinspector',
|
'Automatic-Module-Name': 'net.corda.blobinspector',
|
||||||
'Main-Class': 'net.corda.blobinspector.MainKt'
|
'Main-Class': 'net.corda.blobinspector.BlobInspectorKt'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,13 @@ import net.corda.client.jackson.JacksonSupport
|
|||||||
import net.corda.core.internal.isRegularFile
|
import net.corda.core.internal.isRegularFile
|
||||||
import net.corda.core.internal.rootMessage
|
import net.corda.core.internal.rootMessage
|
||||||
import net.corda.core.serialization.SerializationContext
|
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.deserialize
|
||||||
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
||||||
import net.corda.core.serialization.internal._contextSerializationEnv
|
import net.corda.core.serialization.internal._contextSerializationEnv
|
||||||
import net.corda.core.utilities.sequence
|
import net.corda.core.utilities.sequence
|
||||||
import net.corda.serialization.internal.AMQP_P2P_CONTEXT
|
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.CordaSerializationMagic
|
||||||
import net.corda.serialization.internal.SerializationFactoryImpl
|
import net.corda.serialization.internal.SerializationFactoryImpl
|
||||||
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
|
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 net.corda.serialization.internal.amqp.amqpMagic
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
import picocli.CommandLine.*
|
import picocli.CommandLine.*
|
||||||
|
import java.io.PrintStream
|
||||||
import java.net.MalformedURLException
|
import java.net.MalformedURLException
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
val main = Main()
|
val main = BlobInspector()
|
||||||
try {
|
try {
|
||||||
CommandLine.run(main, *args)
|
CommandLine.run(main, *args)
|
||||||
} catch (e: ExecutionException) {
|
} catch (e: ExecutionException) {
|
||||||
@ -47,9 +49,9 @@ fun main(args: Array<String>) {
|
|||||||
showDefaultValues = true,
|
showDefaultValues = true,
|
||||||
description = ["Inspect AMQP serialised binary blobs"]
|
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])
|
@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]"])
|
@Option(names = ["--format"], paramLabel = "type", description = ["Output format. Possible values: [YAML, JSON]"])
|
||||||
private var formatType: FormatType = FormatType.YAML
|
private var formatType: FormatType = FormatType.YAML
|
||||||
@ -64,7 +66,9 @@ class Main : Runnable {
|
|||||||
@Option(names = ["--verbose"], description = ["Enable verbose output"])
|
@Option(names = ["--verbose"], description = ["Enable verbose output"])
|
||||||
var verbose: Boolean = false
|
var verbose: Boolean = false
|
||||||
|
|
||||||
override fun run() {
|
override fun run() = run(System.out)
|
||||||
|
|
||||||
|
fun run(out: PrintStream) {
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
System.setProperty("logLevel", "trace")
|
System.setProperty("logLevel", "trace")
|
||||||
}
|
}
|
||||||
@ -78,39 +82,44 @@ class Main : Runnable {
|
|||||||
|
|
||||||
if (schema) {
|
if (schema) {
|
||||||
val envelope = DeserializationInput.getEnvelope(bytes)
|
val envelope = DeserializationInput.getEnvelope(bytes)
|
||||||
println(envelope.schema)
|
out.println(envelope.schema)
|
||||||
println()
|
out.println()
|
||||||
println(envelope.transformsSchema)
|
out.println(envelope.transformsSchema)
|
||||||
println()
|
out.println()
|
||||||
}
|
}
|
||||||
|
|
||||||
initialiseSerialization()
|
|
||||||
|
|
||||||
val factory = when (formatType) {
|
val factory = when (formatType) {
|
||||||
FormatType.YAML -> YAMLFactory()
|
FormatType.YAML -> YAMLFactory()
|
||||||
FormatType.JSON -> JsonFactory()
|
FormatType.JSON -> JsonFactory()
|
||||||
}
|
}
|
||||||
|
|
||||||
val mapper = JacksonSupport.createNonRpcMapper(factory, fullParties)
|
val mapper = JacksonSupport.createNonRpcMapper(factory, fullParties)
|
||||||
|
|
||||||
// Deserialise with the lenient carpenter as we only care for the AMQP field getters
|
initialiseSerialization()
|
||||||
val deserialized = bytes.deserialize<Any>(context = SerializationFactory.defaultFactory.defaultContext.withLenientCarpenter())
|
try {
|
||||||
println(deserialized.javaClass.name)
|
val deserialized = bytes.deserialize<Any>(context = SerializationDefaults.STORAGE_CONTEXT)
|
||||||
mapper.writeValue(System.out, deserialized)
|
out.println(deserialized.javaClass.name)
|
||||||
|
mapper.writeValue(out, deserialized)
|
||||||
|
} finally {
|
||||||
|
_contextSerializationEnv.set(null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initialiseSerialization() {
|
private fun initialiseSerialization() {
|
||||||
|
// Deserialise with the lenient carpenter as we only care for the AMQP field getters
|
||||||
_contextSerializationEnv.set(SerializationEnvironmentImpl(
|
_contextSerializationEnv.set(SerializationEnvironmentImpl(
|
||||||
SerializationFactoryImpl().apply {
|
SerializationFactoryImpl().apply {
|
||||||
registerScheme(AMQPInspectorSerializationScheme)
|
registerScheme(AMQPInspectorSerializationScheme)
|
||||||
},
|
},
|
||||||
AMQP_P2P_CONTEXT
|
p2pContext = AMQP_P2P_CONTEXT.withLenientCarpenter(),
|
||||||
|
storageContext = AMQP_STORAGE_CONTEXT.withLenientCarpenter()
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private object AMQPInspectorSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) {
|
private object AMQPInspectorSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) {
|
||||||
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
|
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 rpcClientSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
|
||||||
override fun rpcServerSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
|
override fun rpcServerSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
|
@ -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." }
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user