mirror of
https://github.com/corda/corda.git
synced 2025-02-07 03:29:19 +00:00
Merge remote-tracking branch 'open/master' into os-merge-2907250
# Conflicts: # build.gradle # client/rpc/src/main/kotlin/net/corda/client/rpc/CordaRPCClient.kt # node-api/build.gradle # node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/StringBufferSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/AllButBlacklisted.kt # serialization/src/main/kotlin/net/corda/serialization/internal/AttachmentsClassLoader.kt # serialization/src/main/kotlin/net/corda/serialization/internal/DefaultWhitelist.kt # serialization/src/main/kotlin/net/corda/serialization/internal/GeneratedAttachment.kt # serialization/src/main/kotlin/net/corda/serialization/internal/OrdinalIO.kt # serialization/src/main/kotlin/net/corda/serialization/internal/SerializationFormat.kt # serialization/src/main/kotlin/net/corda/serialization/internal/SerializationScheme.kt # serialization/src/main/kotlin/net/corda/serialization/internal/SerializeAsTokenContextImpl.kt # serialization/src/main/kotlin/net/corda/serialization/internal/SharedContexts.kt # serialization/src/main/kotlin/net/corda/serialization/internal/UseCaseAwareness.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPDescriptorRegistry.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPPrimitiveSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/AMQPSerializerFactories.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ArraySerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/CollectionSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/CorDappCustomSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/CustomSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializationInput.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializedGenericArrayType.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/DeserializedParameterizedType.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EnumEvolutionSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EnumSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/Envelope.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/FingerPrinter.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/MapSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ObjectSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertySerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertySerializers.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/Schema.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationHelper.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializationOutput.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SerializerFactory.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SingletonSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/SupportedTransforms.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/TransformTypes.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/TransformsSchema.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/BigDecimalSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/BigIntegerSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/BitSetSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/CertPathSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/ClassSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/ContractAttachmentSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/CurrencySerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/DurationSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/EnumSetSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/InputStreamSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/InstantSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/LocalDateSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/LocalDateTimeSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/LocalTimeSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/MonthDaySerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/OffsetDateTimeSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/OffsetTimeSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/OpaqueBytesSubSequenceSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/PeriodSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/PrivateKeySerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/PublicKeySerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/SimpleStringSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/ThrowableSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/X509CRLSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/X509CertificateSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/YearMonthSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/YearSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/ZoneIdSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/amqp/custom/ZonedDateTimeSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenter.kt # serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/Exceptions.kt # serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/MetaCarpenter.kt # serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/Schema.kt # serialization/src/main/kotlin/net/corda/serialization/internal/carpenter/SchemaFields.kt # serialization/src/main/kotlin/net/corda/serialization/internal/kryo/CordaClassResolver.kt # serialization/src/main/kotlin/net/corda/serialization/internal/kryo/CordaClosureSerializer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/kryo/DefaultKryoCustomizer.kt # serialization/src/main/kotlin/net/corda/serialization/internal/kryo/Kryo.kt # serialization/src/main/kotlin/net/corda/serialization/internal/kryo/KryoSerializationScheme.kt # serialization/src/main/kotlin/net/corda/serialization/internal/kryo/SerializeAsTokenSerializer.kt # serialization/src/test/java/net/corda/serialization/internal/ForbiddenLambdaSerializationTests.java # serialization/src/test/java/net/corda/serialization/internal/LambdaCheckpointSerializationTest.java # serialization/src/test/java/net/corda/serialization/internal/amqp/ErrorMessageTests.java # serialization/src/test/java/net/corda/serialization/internal/amqp/JavaGenericsTest.java # serialization/src/test/java/net/corda/serialization/internal/amqp/JavaPrivatePropertyTests.java # serialization/src/test/java/net/corda/serialization/internal/amqp/JavaSerialiseEnumTests.java # serialization/src/test/java/net/corda/serialization/internal/amqp/JavaSerializationOutputTests.java # serialization/src/test/java/net/corda/serialization/internal/amqp/ListsSerializationJavaTest.java # serialization/src/test/java/net/corda/serialization/internal/amqp/SetterConstructorTests.java # serialization/src/test/kotlin/net/corda/serialization/internal/AttachmentsClassLoaderTests.kt # serialization/src/test/kotlin/net/corda/serialization/internal/ContractAttachmentSerializerTest.kt # serialization/src/test/kotlin/net/corda/serialization/internal/CordaClassResolverTests.kt # serialization/src/test/kotlin/net/corda/serialization/internal/ListsSerializationTest.kt # serialization/src/test/kotlin/net/corda/serialization/internal/MapsSerializationTest.kt # serialization/src/test/kotlin/net/corda/serialization/internal/PrivateKeySerializationTest.kt # serialization/src/test/kotlin/net/corda/serialization/internal/SerializationTokenTest.kt # serialization/src/test/kotlin/net/corda/serialization/internal/SetsSerializationTest.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/CorDappSerializerTests.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeAndReturnEnvelopeTests.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeMapTests.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeNeedingCarpentryOfEnumsTest.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeNeedingCarpentryTests.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializeSimpleTypesTests.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/DeserializedParameterizedTypeTests.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EnumEvolvabilityTests.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EnumEvolveTests.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EnumTests.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/ErrorMessagesTests.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializerGetterTesting.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/EvolvabilityTests.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/FingerPrinterTesting.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/GenericsTests.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/OverridePKSerializerTest.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/PrivatePropertyTests.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/SerializationPropertyOrdering.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/SerializeAndReturnSchemaTest.kt # serialization/src/test/kotlin/net/corda/serialization/internal/amqp/StaticInitialisationOfSerializedObjectTest.kt # serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenterTest.kt # serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenterTestUtils.kt # serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/ClassCarpenterWhitelistTest.kt # serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt # serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/EnumClassTests.kt # serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/InheritanceSchemaToClassCarpenterTests.kt # serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt # serialization/src/test/kotlin/net/corda/serialization/internal/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt # serialization/src/test/kotlin/net/corda/serialization/internal/kryo/KryoStreamsTest.kt # serialization/src/test/kotlin/net/corda/serialization/internal/kryo/KryoTests.kt # settings.gradle
This commit is contained in:
commit
152848730d
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@ -141,6 +141,8 @@
|
||||
<module name="samples_test" target="1.8" />
|
||||
<module name="sandbox_main" target="1.8" />
|
||||
<module name="sandbox_test" target="1.8" />
|
||||
<module name="serialization_main" target="1.8" />
|
||||
<module name="serialization_test" target="1.8" />
|
||||
<module name="shell_integrationTest" target="1.8" />
|
||||
<module name="shell_main" target="1.8" />
|
||||
<module name="shell_test" target="1.8" />
|
||||
|
@ -1,11 +1,11 @@
|
||||
# List of Contributors
|
||||
|
||||
We'd like to thank the following people for contributing to Corda, either by
|
||||
contributing to the design of Corda during the architecture review sessions of the
|
||||
R3 Architecture Working Group and during design reviews since Corda has been
|
||||
open-sourced, or by contributing code via pull requests. Some people have
|
||||
moved to a different organisation since their contribution. Please forgive any
|
||||
omissions, and create a pull request, or email <james@r3.com>, if you wish to
|
||||
We'd like to thank the following people for contributing to Corda, either by
|
||||
contributing to the design of Corda during the architecture review sessions of the
|
||||
R3 Architecture Working Group and during design reviews since Corda has been
|
||||
open-sourced, or by contributing code via pull requests. Some people have
|
||||
moved to a different organisation since their contribution. Please forgive any
|
||||
omissions, and create a pull request, or email <james@r3.com>, if you wish to
|
||||
see changes to this list.
|
||||
|
||||
* acetheultimate
|
||||
@ -164,6 +164,7 @@ see changes to this list.
|
||||
* Simon Taylor (Barclays)
|
||||
* Sofus Mortensen (Digital Asset Holdings)
|
||||
* stevenroose
|
||||
* Stanly Johnson (Servntire Global)
|
||||
* Szymon Sztuka (R3)
|
||||
* tb-pq
|
||||
* Thiago Rafael Ferreira (Scorpius IT Solutions)
|
||||
@ -175,7 +176,7 @@ see changes to this list.
|
||||
* tomconte
|
||||
* Tommy Lillehagen (R3)
|
||||
* tomtau
|
||||
* Tudor Malene (R3)
|
||||
* Tudor Malene (R3)
|
||||
* Tushar Singh Bora
|
||||
* varunkm
|
||||
* verymahler
|
||||
|
@ -190,6 +190,7 @@ allprojects {
|
||||
apiVersion = "1.2"
|
||||
jvmTarget = "1.8"
|
||||
javaParameters = true // Useful for reflection.
|
||||
freeCompilerArgs = ['-Xenable-jvm-default']
|
||||
}
|
||||
}
|
||||
|
||||
@ -378,7 +379,7 @@ bintrayConfig {
|
||||
projectUrl = 'https://github.com/corda/corda'
|
||||
gpgSign = true
|
||||
gpgPassphrase = System.getenv('CORDA_BINTRAY_GPG_PASSPHRASE')
|
||||
publications = ['corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver', 'corda-node-driver', 'corda-confidential-identities', 'doorman', 'doorman-hsm', 'corda-shell', 'corda-bridgeserver', 'corda-ptflows', 'jmeter-corda', 'migration-tool']
|
||||
publications = ['corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver', 'corda-node-driver', 'corda-confidential-identities', 'doorman', 'doorman-hsm', 'corda-shell', 'corda-serialization', 'corda-bridgeserver', 'corda-ptflows', 'jmeter-corda', 'migration-tool']
|
||||
license {
|
||||
name = 'Apache-2.0'
|
||||
url = 'https://www.apache.org/licenses/LICENSE-2.0'
|
||||
|
@ -15,7 +15,7 @@ apply plugin: 'net.corda.plugins.api-scanner'
|
||||
apply plugin: 'com.jfrog.artifactory'
|
||||
|
||||
dependencies {
|
||||
compile project(':core')
|
||||
compile project(':serialization')
|
||||
testCompile project(':test-utils')
|
||||
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
|
@ -10,17 +10,18 @@
|
||||
|
||||
package net.corda.client.jackson
|
||||
|
||||
import com.fasterxml.jackson.annotation.*
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.core.*
|
||||
import com.fasterxml.jackson.databind.*
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize
|
||||
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||
import com.fasterxml.jackson.module.kotlin.convertValue
|
||||
import net.corda.client.jackson.internal.CordaModule
|
||||
import net.corda.client.jackson.internal.ToStringSerialize
|
||||
import net.corda.client.jackson.internal.jsonObject
|
||||
import net.corda.client.jackson.internal.readValueAs
|
||||
import net.corda.core.CordaInternal
|
||||
@ -30,23 +31,24 @@ import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.identity.*
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.CertRole
|
||||
import net.corda.core.internal.DigitalSignatureWithCert
|
||||
import net.corda.core.internal.VisibleForTesting
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.CoreTransaction
|
||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.parsePublicKeyBase58
|
||||
import net.corda.core.utilities.toBase58String
|
||||
import org.bouncycastle.asn1.x509.KeyPurposeId
|
||||
import java.lang.reflect.Modifier
|
||||
import java.math.BigDecimal
|
||||
@ -103,31 +105,9 @@ object JacksonSupport {
|
||||
override fun nodeInfoFromParty(party: AbstractParty): NodeInfo? = null
|
||||
}
|
||||
|
||||
val cordaModule: Module by lazy {
|
||||
SimpleModule("core").apply {
|
||||
setMixInAnnotation(BigDecimal::class.java, BigDecimalMixin::class.java)
|
||||
setMixInAnnotation(X500Principal::class.java, X500PrincipalMixin::class.java)
|
||||
setMixInAnnotation(X509Certificate::class.java, X509CertificateMixin::class.java)
|
||||
setMixInAnnotation(PartyAndCertificate::class.java, PartyAndCertificateSerializerMixin::class.java)
|
||||
setMixInAnnotation(NetworkHostAndPort::class.java, NetworkHostAndPortMixin::class.java)
|
||||
setMixInAnnotation(CordaX500Name::class.java, CordaX500NameMixin::class.java)
|
||||
setMixInAnnotation(Amount::class.java, AmountMixin::class.java)
|
||||
setMixInAnnotation(AbstractParty::class.java, AbstractPartyMixin::class.java)
|
||||
setMixInAnnotation(AnonymousParty::class.java, AnonymousPartyMixin::class.java)
|
||||
setMixInAnnotation(Party::class.java, PartyMixin::class.java)
|
||||
setMixInAnnotation(PublicKey::class.java, PublicKeyMixin::class.java)
|
||||
setMixInAnnotation(ByteSequence::class.java, ByteSequenceMixin::class.java)
|
||||
setMixInAnnotation(SecureHash.SHA256::class.java, SecureHashSHA256Mixin::class.java)
|
||||
setMixInAnnotation(SerializedBytes::class.java, SerializedBytesMixin::class.java)
|
||||
setMixInAnnotation(DigitalSignature.WithKey::class.java, ByteSequenceWithPropertiesMixin::class.java)
|
||||
setMixInAnnotation(DigitalSignatureWithCert::class.java, ByteSequenceWithPropertiesMixin::class.java)
|
||||
setMixInAnnotation(TransactionSignature::class.java, ByteSequenceWithPropertiesMixin::class.java)
|
||||
setMixInAnnotation(SignedTransaction::class.java, SignedTransactionMixin2::class.java)
|
||||
setMixInAnnotation(WireTransaction::class.java, WireTransactionMixin::class.java)
|
||||
setMixInAnnotation(CertPath::class.java, CertPathMixin::class.java)
|
||||
setMixInAnnotation(NodeInfo::class.java, NodeInfoMixin::class.java)
|
||||
}
|
||||
}
|
||||
@Suppress("unused")
|
||||
@Deprecated("Do not use this as it's not thread safe. Instead get a ObjectMapper instance with one of the create*Mapper methods.")
|
||||
val cordaModule: Module by lazy(::CordaModule)
|
||||
|
||||
/**
|
||||
* Creates a Jackson ObjectMapper that uses RPC to deserialise parties from string names.
|
||||
@ -182,15 +162,16 @@ object JacksonSupport {
|
||||
registerModule(JavaTimeModule().apply {
|
||||
addSerializer(Date::class.java, DateSerializer)
|
||||
})
|
||||
registerModule(cordaModule)
|
||||
registerModule(CordaModule())
|
||||
registerModule(KotlinModule())
|
||||
|
||||
addMixIn(BigDecimal::class.java, BigDecimalMixin::class.java)
|
||||
addMixIn(X500Principal::class.java, X500PrincipalMixin::class.java)
|
||||
addMixIn(X509Certificate::class.java, X509CertificateMixin::class.java)
|
||||
addMixIn(CertPath::class.java, CertPathMixin::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@JacksonAnnotationsInside
|
||||
@JsonSerialize(using = com.fasterxml.jackson.databind.ser.std.ToStringSerializer::class)
|
||||
private annotation class ToStringSerialize
|
||||
|
||||
@ToStringSerialize
|
||||
@JsonDeserialize(using = NumberDeserializers.BigDecimalDeserializer::class)
|
||||
private interface BigDecimalMixin
|
||||
@ -201,77 +182,6 @@ object JacksonSupport {
|
||||
}
|
||||
}
|
||||
|
||||
@ToStringSerialize
|
||||
@JsonDeserialize(using = NetworkHostAndPortDeserializer::class)
|
||||
private interface NetworkHostAndPortMixin
|
||||
|
||||
private class NetworkHostAndPortDeserializer : JsonDeserializer<NetworkHostAndPort>() {
|
||||
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NetworkHostAndPort {
|
||||
return NetworkHostAndPort.parse(parser.text)
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerialize(using = PartyAndCertificateSerializer::class)
|
||||
// TODO Add deserialization which follows the same lookup logic as Party
|
||||
private interface PartyAndCertificateSerializerMixin
|
||||
|
||||
private class PartyAndCertificateSerializer : JsonSerializer<PartyAndCertificate>() {
|
||||
override fun serialize(value: PartyAndCertificate, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
gen.jsonObject {
|
||||
writeObjectField("name", value.name)
|
||||
writeObjectField("owningKey", value.owningKey)
|
||||
// TODO Add configurable option to output the certPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerialize(using = SignedTransactionSerializer::class)
|
||||
@JsonDeserialize(using = SignedTransactionDeserializer::class)
|
||||
private interface SignedTransactionMixin2
|
||||
|
||||
private class SignedTransactionSerializer : JsonSerializer<SignedTransaction>() {
|
||||
override fun serialize(value: SignedTransaction, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
gen.writeObject(SignedTransactionWrapper(value.txBits.bytes, value.sigs))
|
||||
}
|
||||
}
|
||||
|
||||
private class SignedTransactionDeserializer : JsonDeserializer<SignedTransaction>() {
|
||||
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SignedTransaction {
|
||||
val wrapper = parser.readValueAs<SignedTransactionWrapper>()
|
||||
return SignedTransaction(SerializedBytes(wrapper.txBits), wrapper.signatures)
|
||||
}
|
||||
}
|
||||
|
||||
private class SignedTransactionWrapper(val txBits: ByteArray, val signatures: List<TransactionSignature>)
|
||||
|
||||
@JsonSerialize(using = SerializedBytesSerializer::class)
|
||||
@JsonDeserialize(using = SerializedBytesDeserializer::class)
|
||||
private class SerializedBytesMixin
|
||||
|
||||
private class SerializedBytesSerializer : JsonSerializer<SerializedBytes<*>>() {
|
||||
override fun serialize(value: SerializedBytes<*>, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
val deserialized = value.deserialize<Any>()
|
||||
gen.jsonObject {
|
||||
writeStringField("class", deserialized.javaClass.name)
|
||||
writeObjectField("deserialized", deserialized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SerializedBytesDeserializer : JsonDeserializer<SerializedBytes<*>>() {
|
||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): SerializedBytes<Any> {
|
||||
return if (parser.currentToken == JsonToken.START_OBJECT) {
|
||||
val mapper = parser.codec as ObjectMapper
|
||||
val json = parser.readValueAsTree<ObjectNode>()
|
||||
val clazz = context.findClass(json["class"].textValue())
|
||||
val pojo = mapper.convertValue(json["deserialized"], clazz)
|
||||
pojo.serialize()
|
||||
} else {
|
||||
SerializedBytes(parser.binaryValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ToStringSerialize
|
||||
private interface X500PrincipalMixin
|
||||
|
||||
@ -358,13 +268,6 @@ object JacksonSupport {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonDeserialize(using = PartyDeserializer::class)
|
||||
private interface AbstractPartyMixin
|
||||
|
||||
@JsonSerialize(using = AnonymousPartySerializer::class)
|
||||
@JsonDeserialize(using = AnonymousPartyDeserializer::class)
|
||||
private interface AnonymousPartyMixin
|
||||
|
||||
@Deprecated("This is an internal class, do not use")
|
||||
object AnonymousPartySerializer : JsonSerializer<AnonymousParty>() {
|
||||
override fun serialize(value: AnonymousParty, generator: JsonGenerator, provider: SerializerProvider) {
|
||||
@ -379,9 +282,6 @@ object JacksonSupport {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerialize(using = PartySerializer::class)
|
||||
private interface PartyMixin
|
||||
|
||||
@Deprecated("This is an internal class, do not use")
|
||||
object PartySerializer : JsonSerializer<Party>() {
|
||||
override fun serialize(value: Party, generator: JsonGenerator, provider: SerializerProvider) {
|
||||
@ -416,10 +316,6 @@ object JacksonSupport {
|
||||
}
|
||||
}
|
||||
|
||||
@ToStringSerialize
|
||||
@JsonDeserialize(using = CordaX500NameDeserializer::class)
|
||||
private interface CordaX500NameMixin
|
||||
|
||||
@Deprecated("This is an internal class, do not use")
|
||||
object CordaX500NameDeserializer : JsonDeserializer<CordaX500Name>() {
|
||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): CordaX500Name {
|
||||
@ -431,10 +327,6 @@ object JacksonSupport {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties("legalIdentities") // This is already covered by legalIdentitiesAndCerts
|
||||
@JsonDeserialize(using = NodeInfoDeserializer::class)
|
||||
private interface NodeInfoMixin
|
||||
|
||||
@Deprecated("This is an internal class, do not use")
|
||||
object NodeInfoDeserializer : JsonDeserializer<NodeInfo>() {
|
||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): NodeInfo {
|
||||
@ -444,10 +336,6 @@ object JacksonSupport {
|
||||
}
|
||||
}
|
||||
|
||||
@ToStringSerialize
|
||||
@JsonDeserialize(using = SecureHashDeserializer::class)
|
||||
private interface SecureHashSHA256Mixin
|
||||
|
||||
@Deprecated("This is an internal class, do not use")
|
||||
class SecureHashDeserializer<T : SecureHash> : JsonDeserializer<T>() {
|
||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): T {
|
||||
@ -459,10 +347,6 @@ object JacksonSupport {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerialize(using = PublicKeySerializer::class)
|
||||
@JsonDeserialize(using = PublicKeyDeserializer::class)
|
||||
private interface PublicKeyMixin
|
||||
|
||||
@Deprecated("This is an internal class, do not use")
|
||||
object PublicKeySerializer : JsonSerializer<PublicKey>() {
|
||||
override fun serialize(value: PublicKey, generator: JsonGenerator, provider: SerializerProvider) {
|
||||
@ -481,10 +365,6 @@ object JacksonSupport {
|
||||
}
|
||||
}
|
||||
|
||||
@ToStringSerialize
|
||||
@JsonDeserialize(using = AmountDeserializer::class)
|
||||
private interface AmountMixin
|
||||
|
||||
@Deprecated("This is an internal class, do not use")
|
||||
object AmountDeserializer : JsonDeserializer<Amount<*>>() {
|
||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): Amount<*> {
|
||||
@ -499,22 +379,6 @@ object JacksonSupport {
|
||||
|
||||
private data class CurrencyAmountWrapper(val quantity: Long, val token: Currency)
|
||||
|
||||
@JsonDeserialize(using = OpaqueBytesDeserializer::class)
|
||||
private interface ByteSequenceMixin {
|
||||
@Suppress("unused")
|
||||
@JsonValue
|
||||
fun copyBytes(): ByteArray
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties("offset", "size")
|
||||
@JsonSerialize
|
||||
@JsonDeserialize
|
||||
private interface ByteSequenceWithPropertiesMixin {
|
||||
@Suppress("unused")
|
||||
@JsonValue(false)
|
||||
fun copyBytes(): ByteArray
|
||||
}
|
||||
|
||||
@Deprecated("This is an internal class, do not use")
|
||||
object OpaqueBytesDeserializer : JsonDeserializer<OpaqueBytes>() {
|
||||
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): OpaqueBytes {
|
||||
|
@ -0,0 +1,194 @@
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package net.corda.client.jackson.internal
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.core.JsonToken
|
||||
import com.fasterxml.jackson.databind.*
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter
|
||||
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier
|
||||
import net.corda.client.jackson.JacksonSupport
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.identity.*
|
||||
import net.corda.core.internal.DigitalSignatureWithCert
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.ByteSequence
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory
|
||||
import net.corda.serialization.internal.amqp.constructorForDeserialization
|
||||
import net.corda.serialization.internal.amqp.createSerializerFactoryFactory
|
||||
import net.corda.serialization.internal.amqp.propertiesForSerialization
|
||||
import java.security.PublicKey
|
||||
|
||||
class CordaModule : SimpleModule("corda-core") {
|
||||
override fun setupModule(context: SetupContext) {
|
||||
super.setupModule(context)
|
||||
|
||||
context.addBeanSerializerModifier(CordaSerializableBeanSerializerModifier())
|
||||
|
||||
context.setMixInAnnotations(PartyAndCertificate::class.java, PartyAndCertificateSerializerMixin::class.java)
|
||||
context.setMixInAnnotations(NetworkHostAndPort::class.java, NetworkHostAndPortMixin::class.java)
|
||||
context.setMixInAnnotations(CordaX500Name::class.java, CordaX500NameMixin::class.java)
|
||||
context.setMixInAnnotations(Amount::class.java, AmountMixin::class.java)
|
||||
context.setMixInAnnotations(AbstractParty::class.java, AbstractPartyMixin::class.java)
|
||||
context.setMixInAnnotations(AnonymousParty::class.java, AnonymousPartyMixin::class.java)
|
||||
context.setMixInAnnotations(Party::class.java, PartyMixin::class.java)
|
||||
context.setMixInAnnotations(PublicKey::class.java, PublicKeyMixin::class.java)
|
||||
context.setMixInAnnotations(ByteSequence::class.java, ByteSequenceMixin::class.java)
|
||||
context.setMixInAnnotations(SecureHash.SHA256::class.java, SecureHashSHA256Mixin::class.java)
|
||||
context.setMixInAnnotations(SerializedBytes::class.java, SerializedBytesMixin::class.java)
|
||||
context.setMixInAnnotations(DigitalSignature.WithKey::class.java, ByteSequenceWithPropertiesMixin::class.java)
|
||||
context.setMixInAnnotations(DigitalSignatureWithCert::class.java, ByteSequenceWithPropertiesMixin::class.java)
|
||||
context.setMixInAnnotations(TransactionSignature::class.java, ByteSequenceWithPropertiesMixin::class.java)
|
||||
context.setMixInAnnotations(SignedTransaction::class.java, SignedTransactionMixin2::class.java)
|
||||
context.setMixInAnnotations(WireTransaction::class.java, JacksonSupport.WireTransactionMixin::class.java)
|
||||
context.setMixInAnnotations(NodeInfo::class.java, NodeInfoMixin::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the same properties that AMQP serialization uses if the POJO is @CordaSerializable
|
||||
*/
|
||||
private class CordaSerializableBeanSerializerModifier : BeanSerializerModifier() {
|
||||
// We need a SerializerFactory when scanning for properties but don't actually use it so any will do
|
||||
private val serializerFactory = SerializerFactory(AllWhitelist, Thread.currentThread().contextClassLoader)
|
||||
|
||||
override fun changeProperties(config: SerializationConfig,
|
||||
beanDesc: BeanDescription,
|
||||
beanProperties: MutableList<BeanPropertyWriter>): MutableList<BeanPropertyWriter> {
|
||||
// TODO We're assuming here that Jackson gives us a superset of all the properties. Either confirm this or
|
||||
// make sure the returned beanProperties are exactly the AMQP properties
|
||||
if (beanDesc.beanClass.isAnnotationPresent(CordaSerializable::class.java)) {
|
||||
val ctor = constructorForDeserialization(beanDesc.beanClass)
|
||||
val amqpProperties = propertiesForSerialization(ctor, beanDesc.beanClass, serializerFactory).serializationOrder
|
||||
beanProperties.removeIf { bean -> amqpProperties.none { amqp -> amqp.serializer.name == bean.name } }
|
||||
}
|
||||
return beanProperties
|
||||
}
|
||||
}
|
||||
|
||||
@ToStringSerialize
|
||||
@JsonDeserialize(using = NetworkHostAndPortDeserializer::class)
|
||||
private interface NetworkHostAndPortMixin
|
||||
|
||||
private class NetworkHostAndPortDeserializer : JsonDeserializer<NetworkHostAndPort>() {
|
||||
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext) = NetworkHostAndPort.parse(parser.text)
|
||||
}
|
||||
|
||||
@JsonSerialize(using = PartyAndCertificateSerializer::class)
|
||||
// TODO Add deserialization which follows the same lookup logic as Party
|
||||
private interface PartyAndCertificateSerializerMixin
|
||||
|
||||
private class PartyAndCertificateSerializer : JsonSerializer<PartyAndCertificate>() {
|
||||
override fun serialize(value: PartyAndCertificate, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
gen.jsonObject {
|
||||
writeObjectField("name", value.name)
|
||||
writeObjectField("owningKey", value.owningKey)
|
||||
// TODO Add configurable option to output the certPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerialize(using = SignedTransactionSerializer::class)
|
||||
@JsonDeserialize(using = SignedTransactionDeserializer::class)
|
||||
private interface SignedTransactionMixin2
|
||||
|
||||
private class SignedTransactionSerializer : JsonSerializer<SignedTransaction>() {
|
||||
override fun serialize(value: SignedTransaction, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
gen.writeObject(SignedTransactionWrapper(value.txBits.bytes, value.sigs))
|
||||
}
|
||||
}
|
||||
|
||||
private class SignedTransactionDeserializer : JsonDeserializer<SignedTransaction>() {
|
||||
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): SignedTransaction {
|
||||
val wrapper = parser.readValueAs<SignedTransactionWrapper>()
|
||||
return SignedTransaction(SerializedBytes(wrapper.txBits), wrapper.signatures)
|
||||
}
|
||||
}
|
||||
|
||||
private class SignedTransactionWrapper(val txBits: ByteArray, val signatures: List<TransactionSignature>)
|
||||
|
||||
@JsonSerialize(using = SerializedBytesSerializer::class)
|
||||
@JsonDeserialize(using = SerializedBytesDeserializer::class)
|
||||
private class SerializedBytesMixin
|
||||
|
||||
private class SerializedBytesSerializer : JsonSerializer<SerializedBytes<*>>() {
|
||||
override fun serialize(value: SerializedBytes<*>, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
val deserialized = value.deserialize<Any>()
|
||||
gen.jsonObject {
|
||||
writeStringField("class", deserialized.javaClass.name)
|
||||
writeObjectField("deserialized", deserialized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SerializedBytesDeserializer : JsonDeserializer<SerializedBytes<*>>() {
|
||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): SerializedBytes<Any> {
|
||||
return if (parser.currentToken == JsonToken.START_OBJECT) {
|
||||
val mapper = parser.codec as ObjectMapper
|
||||
val json = parser.readValueAsTree<ObjectNode>()
|
||||
val clazz = context.findClass(json["class"].textValue())
|
||||
val pojo = mapper.convertValue(json["deserialized"], clazz)
|
||||
pojo.serialize()
|
||||
} else {
|
||||
SerializedBytes(parser.binaryValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsonDeserialize(using = JacksonSupport.PartyDeserializer::class)
|
||||
private interface AbstractPartyMixin
|
||||
|
||||
@JsonSerialize(using = JacksonSupport.AnonymousPartySerializer::class)
|
||||
@JsonDeserialize(using = JacksonSupport.AnonymousPartyDeserializer::class)
|
||||
private interface AnonymousPartyMixin
|
||||
|
||||
@JsonSerialize(using = JacksonSupport.PartySerializer::class)
|
||||
private interface PartyMixin
|
||||
|
||||
@ToStringSerialize
|
||||
@JsonDeserialize(using = JacksonSupport.CordaX500NameDeserializer::class)
|
||||
private interface CordaX500NameMixin
|
||||
|
||||
@JsonDeserialize(using = JacksonSupport.NodeInfoDeserializer::class)
|
||||
private interface NodeInfoMixin
|
||||
|
||||
@ToStringSerialize
|
||||
@JsonDeserialize(using = JacksonSupport.SecureHashDeserializer::class)
|
||||
private interface SecureHashSHA256Mixin
|
||||
|
||||
@JsonSerialize(using = JacksonSupport.PublicKeySerializer::class)
|
||||
@JsonDeserialize(using = JacksonSupport.PublicKeyDeserializer::class)
|
||||
private interface PublicKeyMixin
|
||||
|
||||
@ToStringSerialize
|
||||
@JsonDeserialize(using = JacksonSupport.AmountDeserializer::class)
|
||||
private interface AmountMixin
|
||||
|
||||
@JsonDeserialize(using = JacksonSupport.OpaqueBytesDeserializer::class)
|
||||
private interface ByteSequenceMixin {
|
||||
@Suppress("unused")
|
||||
@JsonValue
|
||||
fun copyBytes(): ByteArray
|
||||
}
|
||||
|
||||
@JsonSerialize
|
||||
@JsonDeserialize
|
||||
private interface ByteSequenceWithPropertiesMixin {
|
||||
@Suppress("unused")
|
||||
@JsonValue(false)
|
||||
fun copyBytes(): ByteArray
|
||||
}
|
@ -1,19 +1,14 @@
|
||||
package net.corda.client.jackson.internal
|
||||
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.JsonSerializer
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer
|
||||
import com.fasterxml.jackson.module.kotlin.convertValue
|
||||
|
||||
inline fun <reified T : Any> SimpleModule.addSerAndDeser(serializer: JsonSerializer<in T>, deserializer: JsonDeserializer<T>) {
|
||||
addSerializer(T::class.java, serializer)
|
||||
addDeserializer(T::class.java, deserializer)
|
||||
}
|
||||
|
||||
inline fun JsonGenerator.jsonObject(fieldName: String? = null, gen: JsonGenerator.() -> Unit) {
|
||||
fieldName?.let { writeFieldName(it) }
|
||||
writeStartObject()
|
||||
@ -24,3 +19,7 @@ inline fun JsonGenerator.jsonObject(fieldName: String? = null, gen: JsonGenerato
|
||||
inline fun <reified T> JsonParser.readValueAs(): T = readValueAs(T::class.java)
|
||||
|
||||
inline fun <reified T : Any> JsonNode.valueAs(mapper: ObjectMapper): T = mapper.convertValue(this)
|
||||
|
||||
@JacksonAnnotationsInside
|
||||
@JsonSerialize(using = ToStringSerializer::class)
|
||||
annotation class ToStringSerialize
|
@ -415,6 +415,15 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
|
||||
testToStringSerialisation(X500Principal("CN=Common,L=London,O=Org,C=UK"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `@CordaSerializable class which has non-c'tor properties`() {
|
||||
val data = NonCtorPropertiesData(4434)
|
||||
val json = mapper.valueToTree<ObjectNode>(data)
|
||||
val (value) = json.assertHasOnlyFields("value")
|
||||
assertThat(value.intValue()).isEqualTo(4434)
|
||||
assertThat(mapper.convertValue<NonCtorPropertiesData>(json)).isEqualTo(data)
|
||||
}
|
||||
|
||||
private fun makeDummyStx(): SignedTransaction {
|
||||
val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1))
|
||||
.toWireTransaction(services)
|
||||
@ -442,6 +451,12 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
|
||||
@CordaSerializable
|
||||
private data class SubTestData(val value: Int)
|
||||
|
||||
@CordaSerializable
|
||||
private data class NonCtorPropertiesData(val value: Int) {
|
||||
@Suppress("unused")
|
||||
val nonCtor: Int get() = value
|
||||
}
|
||||
|
||||
private class TestPartyObjectMapper : JacksonSupport.PartyObjectMapper {
|
||||
val identities = ArrayList<Party>()
|
||||
val nodes = ArrayList<NodeInfo>()
|
||||
|
@ -80,7 +80,8 @@ class NodeMonitorModel {
|
||||
|
||||
// Only execute using "runLater()" if JavaFX been initialized.
|
||||
// It may not be initialized in the unit test.
|
||||
if(initialized.value.get()) {
|
||||
// Also if we are already in the JavaFX thread - perform direct invocation without postponing it.
|
||||
if(initialized.value.get() && !Platform.isFxApplicationThread()) {
|
||||
Platform.runLater(op)
|
||||
} else {
|
||||
op()
|
||||
|
@ -10,10 +10,11 @@
|
||||
|
||||
package net.corda.client.rpc
|
||||
|
||||
|
||||
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
|
||||
import net.corda.client.rpc.internal.RPCClient
|
||||
import net.corda.client.rpc.internal.CordaRPCClientConfigurationImpl
|
||||
import net.corda.client.rpc.internal.RPCClient
|
||||
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
|
||||
import net.corda.core.context.Actor
|
||||
import net.corda.core.context.Trace
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
@ -22,7 +23,7 @@ import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport
|
||||
import net.corda.nodeapi.ConnectionDirection
|
||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||
import net.corda.nodeapi.internal.serialization.AMQP_RPC_CLIENT_CONTEXT
|
||||
import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
|
||||
import java.time.Duration
|
||||
|
||||
/**
|
||||
|
@ -7,12 +7,12 @@ import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import net.corda.core.serialization.internal.SerializationEnvironment
|
||||
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
||||
import net.corda.core.serialization.internal.nodeSerializationEnv
|
||||
import net.corda.nodeapi.internal.serialization.*
|
||||
import net.corda.nodeapi.internal.serialization.amqp.AbstractAMQPSerializationScheme
|
||||
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||
import net.corda.nodeapi.internal.serialization.amqp.amqpMagic
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import net.corda.nodeapi.internal.serialization.amqp.custom.RxNotificationSerializer
|
||||
import net.corda.serialization.internal.*
|
||||
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory
|
||||
import net.corda.serialization.internal.amqp.amqpMagic
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* When set as the serialization scheme for a process, sets it to be the Corda AMQP implementation.
|
||||
|
@ -2,9 +2,8 @@ package net.corda.client.rpc.internal.serialization.amqp
|
||||
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.toFuture
|
||||
import net.corda.core.toObservable
|
||||
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
|
||||
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||
import net.corda.serialization.internal.amqp.CustomSerializer
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory
|
||||
import rx.Observable
|
||||
import java.io.NotSerializableException
|
||||
|
||||
|
@ -5,7 +5,7 @@ import net.corda.client.rpc.internal.ObservableContext
|
||||
import net.corda.core.context.Trace
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.nodeapi.RPCApi
|
||||
import net.corda.nodeapi.internal.serialization.amqp.*
|
||||
import net.corda.serialization.internal.amqp.*
|
||||
import org.apache.qpid.proton.codec.Data
|
||||
import rx.Notification
|
||||
import rx.Observable
|
||||
|
@ -31,7 +31,7 @@
|
||||
<PatternLayout pattern="%msg%n" />
|
||||
</Console>
|
||||
|
||||
<!-- Will generate up to 10 log files for a given day. During every rollover it will delete
|
||||
<!-- Will generate up to 100 log files for a given day. During every rollover it will delete
|
||||
those that are older than 60 days, but keep the most recent 10 GB -->
|
||||
<RollingFile name="RollingFile-Appender"
|
||||
fileName="${sys:log-path}/${log-name}.log"
|
||||
@ -44,7 +44,7 @@
|
||||
<SizeBasedTriggeringPolicy size="10MB"/>
|
||||
</Policies>
|
||||
|
||||
<DefaultRolloverStrategy min="1" max="10">
|
||||
<DefaultRolloverStrategy min="1" max="100">
|
||||
<Delete basePath="${archive}" maxDepth="1">
|
||||
<IfFileName glob="${log-name}*.log.gz"/>
|
||||
<IfLastModified age="60d">
|
||||
|
@ -62,7 +62,6 @@ class FinalityFlow(val transaction: SignedTransaction,
|
||||
// Lookup the resolved transactions and use them to map each signed transaction to the list of participants.
|
||||
// Then send to the notary if needed, record locally and distribute.
|
||||
val parties = getPartiesToSend(verifyTx())
|
||||
progressTracker.currentStep = NOTARISING
|
||||
val notarised = notariseAndRecord()
|
||||
|
||||
// Each transaction has its own set of recipients, but extra recipients get them all.
|
||||
@ -80,6 +79,7 @@ class FinalityFlow(val transaction: SignedTransaction,
|
||||
@Suspendable
|
||||
private fun notariseAndRecord(): SignedTransaction {
|
||||
val notarised = if (needsNotarySignature(transaction)) {
|
||||
progressTracker.currentStep = NOTARISING
|
||||
val notarySignatures = subFlow(NotaryFlow.Client(transaction))
|
||||
transaction + notarySignatures
|
||||
} else {
|
||||
|
@ -59,6 +59,8 @@ object Emoji {
|
||||
val CODE_DEVELOPER: String = codePointsString(0x1F469, 0x200D, 0x1F4BB)
|
||||
@JvmStatic
|
||||
val CODE_WARNING_SIGN: String = codePointsString(0x26A0, 0xFE0F)
|
||||
@JvmStatic
|
||||
val CROSS_MARK_BUTTON: String = codePointsString(0x274E)
|
||||
|
||||
/**
|
||||
* When non-null, toString() methods are allowed to use emoji in the output as we're going to render them to a
|
||||
@ -86,6 +88,7 @@ object Emoji {
|
||||
val rightArrow: String get() = if (emojiMode.get() != null) "$CODE_RIGHT_ARROW " else "▶︎"
|
||||
val skullAndCrossbones: String get() = if (emojiMode.get() != null) "$CODE_SKULL_AND_CROSSBONES " else "☂"
|
||||
val noEntry: String get() = if (emojiMode.get() != null) "$CODE_NO_ENTRY " else "✘"
|
||||
val notRun: String get() = if (emojiMode.get() != null) "$CROSS_MARK_BUTTON " else "-"
|
||||
|
||||
inline fun <T> renderIfSupported(body: () -> T): T {
|
||||
if (hasEmojiTerminal)
|
||||
|
@ -158,8 +158,15 @@ class ProgressTracker(vararg steps: Step) {
|
||||
val currentStepRecursive: Step
|
||||
get() = getChildProgressTracker(currentStep)?.currentStepRecursive ?: currentStep
|
||||
|
||||
/** Returns the current step, descending into children to find the deepest started step we are up to. */
|
||||
private val currentStartedStepRecursive: Step
|
||||
get() {
|
||||
val step = getChildProgressTracker(currentStep)?.currentStartedStepRecursive ?: currentStep
|
||||
return if (step == UNSTARTED) currentStep else step
|
||||
}
|
||||
|
||||
private fun currentStepRecursiveWithoutUnstarted(): Step {
|
||||
val stepRecursive = getChildProgressTracker(currentStep)?.currentStepRecursive
|
||||
val stepRecursive = getChildProgressTracker(currentStep)?.currentStartedStepRecursive
|
||||
return if (stepRecursive == null || stepRecursive == UNSTARTED) currentStep else stepRecursive
|
||||
}
|
||||
|
||||
|
@ -12,12 +12,12 @@ package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.nodeapi.internal.serialization.AMQP_RPC_CLIENT_CONTEXT
|
||||
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||
import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput
|
||||
import net.corda.nodeapi.internal.serialization.amqp.SerializationOutput
|
||||
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||
import net.corda.nodeapi.internal.serialization.amqp.custom.PublicKeySerializer
|
||||
import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
import net.corda.serialization.internal.amqp.DeserializationInput
|
||||
import net.corda.serialization.internal.amqp.SerializationOutput
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory
|
||||
import net.corda.serialization.internal.amqp.custom.PublicKeySerializer
|
||||
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.core.TestIdentity
|
||||
|
@ -13,9 +13,9 @@ package net.corda.core.utilities
|
||||
import com.esotericsoftware.kryo.KryoException
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.nodeapi.internal.serialization.KRYO_CHECKPOINT_CONTEXT
|
||||
import net.corda.nodeapi.internal.serialization.SerializationContextImpl
|
||||
import net.corda.nodeapi.internal.serialization.kryo.kryoMagic
|
||||
import net.corda.serialization.internal.KRYO_CHECKPOINT_CONTEXT
|
||||
import net.corda.serialization.internal.SerializationContextImpl
|
||||
import net.corda.serialization.internal.kryo.kryoMagic
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
@ -34,8 +34,8 @@ class KotlinUtilsTest {
|
||||
@Rule
|
||||
val expectedEx: ExpectedException = ExpectedException.none()
|
||||
|
||||
val KRYO_CHECKPOINT_NOWHITELIST_CONTEXT = SerializationContextImpl(kryoMagic,
|
||||
SerializationDefaults.javaClass.classLoader,
|
||||
private val KRYO_CHECKPOINT_NOWHITELIST_CONTEXT = SerializationContextImpl(kryoMagic,
|
||||
javaClass.classLoader,
|
||||
EmptyWhitelist,
|
||||
emptyMap(),
|
||||
true,
|
||||
|
@ -141,6 +141,41 @@ class ProgressTrackerTest {
|
||||
assertThat(stepsTreeNotification).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `steps tree index counts two levels of children steps`() {
|
||||
pt.setChildProgressTracker(SimpleSteps.FOUR, pt2)
|
||||
pt2.setChildProgressTracker(ChildSteps.SEA, pt3)
|
||||
val allSteps = pt.allSteps
|
||||
|
||||
// Capture notifications.
|
||||
val stepsIndexNotifications = LinkedList<Int>()
|
||||
pt.stepsTreeIndexChanges.subscribe {
|
||||
stepsIndexNotifications += it
|
||||
}
|
||||
val stepsTreeNotification = LinkedList<List<Pair<Int, String>>>()
|
||||
pt.stepsTreeChanges.subscribe {
|
||||
stepsTreeNotification += it
|
||||
}
|
||||
|
||||
fun assertCurrentStepsTree(index: Int, step: ProgressTracker.Step) {
|
||||
assertEquals(index, pt.stepsTreeIndex)
|
||||
assertEquals(step, allSteps[pt.stepsTreeIndex].second)
|
||||
}
|
||||
|
||||
pt.currentStep = SimpleSteps.ONE
|
||||
assertCurrentStepsTree(0, SimpleSteps.ONE)
|
||||
|
||||
pt.currentStep = SimpleSteps.FOUR
|
||||
assertCurrentStepsTree(3, SimpleSteps.FOUR)
|
||||
|
||||
pt2.currentStep = ChildSteps.SEA
|
||||
assertCurrentStepsTree(6, ChildSteps.SEA)
|
||||
|
||||
// Assert no structure changes and proper steps propagation.
|
||||
assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(0, 3, 6))
|
||||
assertThat(stepsTreeNotification).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `structure changes are pushed down when progress trackers are added`() {
|
||||
pt.setChildProgressTracker(SimpleSteps.TWO, pt2)
|
||||
|
@ -50,7 +50,7 @@ be structured one parameter per line.
|
||||
|
||||
Code is vertically dense, blank lines in methods are used sparingly. This is so more code can fit on screen at once.
|
||||
|
||||
We use spaces and not tabs.
|
||||
We use spaces and not tabs, with indents being 4 spaces wide.
|
||||
|
||||
1.2 Naming
|
||||
----------
|
||||
|
@ -1,40 +0,0 @@
|
||||
# Design Decision: Notary Backend - Galera or Permazen Raft
|
||||
|
||||
## Background / Context
|
||||
|
||||
We have evaluated Galera and Permazen as a possible replacement for Atomix CopyCat for the storage backend of our Notary
|
||||
Service, more specificalyl the Uniqueness Provider.
|
||||
|
||||
## Options Analysis
|
||||
|
||||
### A. Galera Cluster
|
||||
|
||||
#### Advantages
|
||||
|
||||
1. Wider user base. In a survey of 478 OpenStack deployments, 32% decided to use Galera Cluster in production, see p. 47
|
||||
of the [survey](https://www.openstack.org/assets/survey/April2017SurveyReport.pdf).
|
||||
|
||||
2. Very little additional work needed.
|
||||
|
||||
3. Entrerprise support.
|
||||
|
||||
#### Disadvantages
|
||||
|
||||
1. Harder to customize.
|
||||
|
||||
### B. Permazen Raft KV Database
|
||||
|
||||
#### Advantages
|
||||
|
||||
1. Customizable.
|
||||
2. Slightly faster in our tests.
|
||||
3. Simpler to deploy (embedded in the Corda node).
|
||||
|
||||
#### Disadvantages
|
||||
|
||||
1. Not ready out of the box, needs rate limiting, follower might run OOM during snapshot transfer.
|
||||
2. No large community behind it.
|
||||
|
||||
## Recommendation and justification
|
||||
|
||||
Proceed with Option A
|
@ -1,226 +0,0 @@
|
||||
# HA Notary Service
|
||||
|
||||
## Overview
|
||||
|
||||
The distributed notary service tracks spent contract states and prevents double spending. For high-availability (HA),
|
||||
the backing data store is replicated across a cluster of machines in different data centers. In this model, the cluster
|
||||
is meant to be operated by a single party, and only crash faults are tolerated.
|
||||
|
||||
## Background
|
||||
|
||||
We have an existing HA notary service based on Atomix CopyCat, which an open source state machine replication library
|
||||
that implemets the Raft consensus algorithm. However, it doesn't scale well with the number of spent input states, since
|
||||
CopyCat takes periodic snapshots of the state machine and the snapshots have to fit in memory.
|
||||
|
||||
As an alternative, we propose using a more traditional MySQL database-based approach, using Galera Cluster, which
|
||||
provides synchronous multi-master replication. Galera Cluster is based on a MySQL server with Write-Set replication
|
||||
(wsrep) API, and the Galera Replication Plugin. Through the wsrep API Galera provides [certification-based replication](http://galeracluster.com/documentation-webpages/certificationbasedreplication.html). It works roughly as
|
||||
follows:
|
||||
|
||||
1. A single database node executes a transaction optimistically until it reaches the commit point.
|
||||
2. Changes made by the trasaction are collected into a write-set.
|
||||
3. The write-set broadcasted to the cluster.
|
||||
4. Every other node determines whether it can apply the write-set without conflicts.
|
||||
5. In case of conflict, the initial node rolls back the transaction.
|
||||
|
||||
There are different Galera Cluster implementations, and we chose the Percona XtraDB cluster, as they were historically
|
||||
more focused on performance than the competition.
|
||||
|
||||
### Decisions
|
||||
|
||||
- We are replacing the Atomix CopyCat Raft service.
|
||||
- We are using a Percona cluster for Corda Connect.
|
||||
- We keep investigating a more scalable solution, based on Permazen or a custom implementation.
|
||||
- In the long term, we are interested in providing a BFT solution, perhaps leveraging SGX.
|
||||
|
||||
.. toctree::
|
||||
|
||||
decisions/decision.md
|
||||
|
||||
#### Advantages of Percona
|
||||
|
||||
- Production ready
|
||||
- Works out of the box
|
||||
- Backed by a company, enterprise and a community support are available
|
||||
- Running stable at 30 tx/second (with 10 input states / tx), see figure below, in the section about the long running test
|
||||
|
||||
#### Disadvantages of Percona
|
||||
|
||||
- Performance deteriorates over time. This happens because Galera only works with the InnoDB storage engine, internally
|
||||
backed by a B+ tree. Since we use state references as primary keys, table insterts results in random B+ tree inserts,
|
||||
which doesn't scale well.
|
||||
|
||||
## Scope
|
||||
|
||||
### Goals
|
||||
|
||||
* We need a stable notary implementation.
|
||||
* The implementation has to be easy to operate.
|
||||
* We know that the switching costs to a more scalable solution are minimal.
|
||||
* We take periodic backups of the consumed states and we test the recovery.
|
||||
* We remain flexible and open to future requirements.
|
||||
|
||||
### Non-Goals
|
||||
|
||||
* For the time being, we don't need a solution that is shardable (for now, all replicas can hold all the state).
|
||||
* We don't require a solution that can handle throughput beyond 15 tx/second.
|
||||
* We don't design and implement a custom solution in the short term.
|
||||
* We don't need rate limiting and fairness.
|
||||
|
||||
## Design
|
||||
|
||||
![Overview](overview.svg)
|
||||
|
||||
The HA notary service relies on the underlying MySQL uniqueness provider on top of a Percona XtraDB Cluster to prevent
|
||||
double spending of input states. The exact data center locations are to be determined. Our notary service replicas
|
||||
connect via JDBC to the replicated MySQL service.
|
||||
|
||||
Percona XtraDB Cluster is based on Percona Server and the Galera replication library that provides a multi master
|
||||
cluster based on synchronous replication. The cluster is as good as its slowest node.
|
||||
|
||||
## Main Data Structure
|
||||
|
||||
The table below details the data base schema.
|
||||
|
||||
| Field name | Type | Description |
|
||||
| --------------------- | ------------ | ---------------------------------------- |
|
||||
| issue_tx_id | Binary(32) | The ID of the transaction that created the state |
|
||||
| issue_tx_output_id | Int unsigned | Where in the transaction the state was created |
|
||||
| consuming_tx_id | Binary(32) | The ID of the transaction that consumes the input state |
|
||||
| consuming_tx_input_id | Int unsigned | Where in the transaction the state is consumed |
|
||||
| consuming_party | Blob | Who is requesting the notarisation (~1 kByte) |
|
||||
| commit_time | Timestamp | When this row is committed |
|
||||
|
||||
## Functional
|
||||
|
||||
The notary service relies on the MySQL uniqueness provider to prevent double spending. The MySQL database holds a single
|
||||
table as described above. For HA, the data is synchronously replicated to several nodes by the Galera replication
|
||||
plugin.
|
||||
|
||||
During notarisation, the uniqueness provider attempts to commit all input states of the Corda transaction in a single
|
||||
database transaction. If at least one input state has been previously spent, the entire database transaction fails with
|
||||
a batch exception. Unspent states can still be spent in a different later transaction. In case of double spend attempts,
|
||||
the uniqueness provider queries the database for details where the conflicting states have been spent. The consuming
|
||||
transaction ID, position of the input in the transaction and the requesting party are collected for all conflicting
|
||||
inputs, wrapped in a uniqueness exception, thrown by the uniqueness provider. This exception is handled by the notary
|
||||
service and turned into a notary exception.
|
||||
|
||||
We are using the Hikari connection pool to connect the notary services to all nodes of our Percona cluster. The
|
||||
connection pool can be monitored via JMX.
|
||||
|
||||
### Deployment
|
||||
|
||||
We are planning to run a five node Percona cluster that can tolerate two simultaneous node failures. In case we need to
|
||||
provide more storage or upgrade to better hardware we can take a single node down for maintenance and still tolerate one
|
||||
unplanned failure.
|
||||
|
||||
#### Monitoring cluster membership changes
|
||||
|
||||
We setup a [notification command](http://galeracluster.com/documentation-webpages/notificationcmd.html) that gets called
|
||||
whenever the node registers a change.
|
||||
|
||||
### Management
|
||||
|
||||
#### Disaster Recovery
|
||||
|
||||
Our disaster recovery strategy covers the following risks:
|
||||
1. **Host Failure**. For the 5 node cluster we can tolerate 2 host failures without interrupting operation. This includes both machine and disk failures.
|
||||
2. **DC Failure**. The cluster will be distributed across 3 data centers in a 2+2+1 configuration. A loss of one data center can be tolerated without interrupting service operation.
|
||||
3. **Data Corruption/Loss**. In cases of data corruption or loss that is replicated across the cluster (for example, accidental data deletion or modification by an administrator) backups will be used to restore the cluster state. In this scenario service downtime will be incurred.
|
||||
|
||||
#### Backup and Recovery
|
||||
|
||||
Recovery Point Objective: 0
|
||||
|
||||
Recovery Time Objective: 1h
|
||||
|
||||
Any data loss incurred by the notary service will lead to a compromised ledger, since participants would be able to
|
||||
double-spend already notarised states. Note that the backup & recovery procedure is only required for mitigating data
|
||||
loss that gets replicated to the entire cluster.
|
||||
|
||||
This can be achieved by combining periodic backups of the entire database state, and the MySQL [binary
|
||||
log](https://dev.mysql.com/doc/refman/5.7/en/binary-log.html). The binary log contains a log of all executed SQL
|
||||
statements, which can be replayed onto a backup to restore the most up-to-date state. In case of an accidental statement
|
||||
that removes data (e.g. DROP TABLE), the binary log can be replayed only up to the offending statement.
|
||||
|
||||
Scenarios where data corruption is caused by a malicious administrator selectively modifying or removing table rows are
|
||||
out of scope.
|
||||
|
||||
See [Galera's backup documentation](http://galeracluster.com/documentation-webpages/backingupthecluster.html)
|
||||
|
||||
#### Monitoring
|
||||
|
||||
See the [Percona Management and Monitoring](https://www.percona.com/doc/percona-monitoring-and-management/index.html) documentation.
|
||||
|
||||
* Throughput in Tx / second
|
||||
* Throughput in Input states / second
|
||||
* Double spend attempts / time
|
||||
* High level statistics, e.g. number of double spend attempts in the last 24 hours by two parties
|
||||
* Double spend attempts per party
|
||||
* Latency p50, p99
|
||||
* Number of input states in DB
|
||||
* Size of DB
|
||||
* Replication Queues, see [monitoring Galera](http://galeracluster.com/documentation-webpages/monitoringthecluster.html)
|
||||
|
||||
#### Alerting
|
||||
|
||||
Alerts are triggered based on relevant metrics, like number of active members in the cluster and size of write queues of
|
||||
individual nodes. We are configuring PMM to forward alerts to PagerDuty, where we do the routing to the operators who
|
||||
are on call. We configure email alerting and slack integration as additional channels.
|
||||
|
||||
## Security
|
||||
|
||||
SSL encrypted links between the nodes of the Galera cluster and the notary service and the Galera cluster. See the [SSL
|
||||
config documentation](http://galeracluster.com/documentation-webpages/sslconfig.html).
|
||||
|
||||
The managed disks on Azure [are encrypted](https://azure.microsoft.com/en-gb/blog/azure-managed-disks-sse/) with keys
|
||||
managed by Microsoft. We have to trust our cloud provider anyways, so we don't do our own disk encryption.
|
||||
|
||||
## Testing the throughput of the uniqueness provider
|
||||
|
||||
We are using a custom load test flow that includes double spend attempts. The application metrics are forwarded to
|
||||
Graphite and our Percona cluster is monitored by Percona's metrics and monitoring tool (PMM).
|
||||
|
||||
In our tests, the number of input states is Poisson-distributed with an average four input states per transaction. To
|
||||
increase throughput in terms of notarised input states per second, we could batch transactions in the future. We tested
|
||||
batching with batch sizes of up to 1000 input states per batch. And reached a throughput of 2k input states / second for
|
||||
batch sizes 250-1000. When we detect a double spend attempt, we could send through individual transactions to find the
|
||||
source of the conflict or bisect the batch.
|
||||
|
||||
## Long running test
|
||||
|
||||
![throughput](txs.png)
|
||||
|
||||
The figure above shows the throughput in transactions per second over four days, while writing to the cluster with up to
|
||||
three clients. The dips occur while we take nodes off-line to simulate failure and to upgrade the disks. In the last
|
||||
phase of the test all nodes were equipped with managed 1TB SSDs and and the cluster notarised at more than 300 input
|
||||
states per second while holding more than 100 M input states in the DB.
|
||||
|
||||
Glitches in throughput can occur when the write queue of a node is filling up. I'm assuming this is due to increased
|
||||
disk latency when the cloud SAN disk is busy with other operations. When the maximum write queue size is reached, the
|
||||
slow node isn't accepting writes any more and sends out flow control messages to its peers to stop replicating (I'm
|
||||
assuming this leads to messages being queued in their send queue). The queue sizes are monitored by the PMM tool and we
|
||||
can setup alerts based on a configured maximum write queue size or when we see "flow control messages".
|
||||
|
||||
We found that managed SSDs of 1TB in size performed better than a RAID 10 array of four 128GB SSDs. The latency of the
|
||||
1TB SSDs was stable around 8ms, while we have observed latency spikes up to 64ms on the smaller SSDs. The disk load on
|
||||
the slowest node in terms of disk latency was around 6-8 outstanding writes during the last phase of the test. Setting
|
||||
up a RAID 10 was a mistake, for best performance we should have used a RAID 0 configuration, since the Azure disks are
|
||||
replicated.
|
||||
|
||||
![disk load](diskload.png)
|
||||
|
||||
### Recommended Cloud Server Configuration
|
||||
|
||||
We recommend `Standard DS13 v2 (8 cores, 56 GB memory)` servers with 1 TB managed SSD disks attached. To make the setup
|
||||
more cost effective, we can run on more affordable cloud instances, when we have lower demands in terms of throughput.
|
||||
The optimum is yet to be found. It is possible to upgrade or downgrade the nodes of the cluster, one node at a time.
|
||||
|
||||
Be prepared to kill and replace the slowest node of the cluster, especially in the cloud, since the Galera cluster will
|
||||
not perform better than the slowest node. The same goes for SAN disks. If you are unlucky and your disk has high
|
||||
latency, try replacing it with a new one. Maybe your get better performance with your new disk.
|
||||
|
||||
### Disk upgrade using LVM
|
||||
|
||||
We recommend using LVM in production for convenience and flexibility. During our long running test we performed a hot
|
||||
disk upgrade using LVM.
|
Binary file not shown.
Before Width: | Height: | Size: 126 KiB |
@ -1,30 +0,0 @@
|
||||
|
||||
graph G {
|
||||
concentrate=true;
|
||||
graph [dpi=100, fontname="helvetica" ];
|
||||
node [fontname="helvetica"];
|
||||
edge [fontname="helvetica"];
|
||||
|
||||
|
||||
subgraph cluster0 {
|
||||
label="Galera Cluster";
|
||||
color="lightgrey";
|
||||
|
||||
GaleraNode1 [label="Node 1\nSouth UK"];
|
||||
GaleraNode2 [label="Node 2\nIreland"];
|
||||
GaleraNode3 [label="Node 3\nNetherlands"];
|
||||
|
||||
GaleraNode1 -- GaleraNode2;
|
||||
GaleraNode2 -- GaleraNode3;
|
||||
GaleraNode1 -- GaleraNode3;
|
||||
}
|
||||
|
||||
subgraph cluster1 {
|
||||
label="Corda";
|
||||
color="lightgrey";
|
||||
NotaryNode [label="Notary Node\nJDBC Uniqueness Provider"];
|
||||
}
|
||||
|
||||
NotaryNode -- GaleraNode1;
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 40 KiB |
Binary file not shown.
Before Width: | Height: | Size: 111 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 11 KiB |
Binary file not shown.
Before Width: | Height: | Size: 538 KiB |
@ -64,7 +64,6 @@ We look forward to seeing what you can do with Corda!
|
||||
design/hadr/design.md
|
||||
design/kafka-notary/design.md
|
||||
design/monitoring-management/design.md
|
||||
design/notary-service-ha/design.md
|
||||
|
||||
.. toctree::
|
||||
:caption: Participate
|
||||
|
@ -4,8 +4,8 @@ import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.serialization.EncodingWhitelist
|
||||
import net.corda.core.serialization.SerializationEncoding
|
||||
import net.corda.core.utilities.ByteSequence
|
||||
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
||||
import net.corda.nodeapi.internal.serialization.amqp.*
|
||||
import net.corda.serialization.internal.SerializationFactoryImpl
|
||||
import net.corda.serialization.internal.amqp.*
|
||||
import org.apache.qpid.proton.amqp.Binary
|
||||
import org.apache.qpid.proton.amqp.DescribedType
|
||||
import org.apache.qpid.proton.amqp.Symbol
|
||||
|
@ -1,10 +1,10 @@
|
||||
package net.corda.blobinspector
|
||||
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||
import net.corda.nodeapi.internal.serialization.amqp.SerializationOutput
|
||||
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
import net.corda.serialization.internal.amqp.SerializationOutput
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory
|
||||
import net.corda.serialization.internal.AMQP_P2P_CONTEXT
|
||||
import org.junit.Test
|
||||
|
||||
|
||||
|
121
experimental/corda-utils/README.md
Normal file
121
experimental/corda-utils/README.md
Normal file
@ -0,0 +1,121 @@
|
||||
# Introduction
|
||||
This project holds different Corda-related utility code.
|
||||
|
||||
## Utils
|
||||
Utils.kt contains various extension functions and other short utility code that aid
|
||||
development on Corda. The code is mostly self-explanatory -- the only exception may
|
||||
be `StateRefHere` which can be used in situations where multiple states are produced
|
||||
in one transaction, and one state needs to refer to the others, e.g. something like this:
|
||||
```
|
||||
val tx = TransactionBuilder(//...
|
||||
// ...
|
||||
tx.addOutputState(innerState, contractClassName)
|
||||
val innerStateRef = StateRefHere(null, tx.outputStates().count() - 1)
|
||||
tx.addOutputState(OuterState(innerStateRef = innerStateRef), contractClassName)
|
||||
// ...
|
||||
|
||||
```
|
||||
|
||||
## StatusTransitions
|
||||
StatusTransitions.kt contains utility code related to FSM-style defining possible transactions that can happen
|
||||
with the respect to the contained status and roles of participants. Here's a simple example for illustration.
|
||||
We are going to track package delivery status, so we first define all roles of participants and possible statuses
|
||||
each package could have:
|
||||
```
|
||||
enum class PackageDeliveryRole {
|
||||
Sender,
|
||||
Receiver,
|
||||
Courier
|
||||
}
|
||||
|
||||
enum class DeliveryStatus {
|
||||
InTransit,
|
||||
Delivered,
|
||||
Returned
|
||||
}
|
||||
```
|
||||
|
||||
The information about each package is held in PackageState: it contains its involved parties, status, linearId,
|
||||
current location, and information related to delivery attempts:
|
||||
```
|
||||
import net.corda.core.contracts.CommandData
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.LinearState
|
||||
import net.corda.core.contracts.UniqueIdentifier
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import java.time.Instant
|
||||
|
||||
data class PackageState(val sender: Party,
|
||||
val receiver: Party,
|
||||
val deliveryCompany: Party,
|
||||
val currentLocation: String,
|
||||
override val status: DeliveryStatus,
|
||||
val deliveryAttempts: Int = 0,
|
||||
val lastDeliveryAttempt: Instant? = null,
|
||||
override val linearId: UniqueIdentifier): LinearState, StatusTrackingContractState<DeliveryStatus, PackageDeliveryRole> {
|
||||
|
||||
override fun roleToParty(role: PackageDeliveryRole): Party {
|
||||
return when (role) {
|
||||
PackageDeliveryRole.Sender -> sender
|
||||
PackageDeliveryRole.Receiver -> receiver
|
||||
PackageDeliveryRole.Courier -> deliveryCompany
|
||||
}
|
||||
}
|
||||
|
||||
override val participants: List<AbstractParty> = listOf(sender, receiver, deliveryCompany)
|
||||
}
|
||||
```
|
||||
We can then define operations one can do with this state, who can do them and under what circumstances (i.e. from what status):
|
||||
```
|
||||
sealed class DeliveryCommand: CommandData {
|
||||
object Send: DeliveryCommand()
|
||||
object Transport: DeliveryCommand()
|
||||
object ConfirmReceipt: DeliveryCommand()
|
||||
object AttemptedDelivery: DeliveryCommand()
|
||||
object Return: DeliveryCommand()
|
||||
}
|
||||
|
||||
class PackageDelivery: Contract {
|
||||
companion object {
|
||||
val transitions = StatusTransitions(PackageState::class,
|
||||
DeliveryCommand.Send.txDef(PackageDeliveryRole.Sender, null, listOf(DeliveryStatus.InTransit)),
|
||||
DeliveryCommand.Transport.txDef(PackageDeliveryRole.Courier, DeliveryStatus.InTransit, listOf(DeliveryStatus.InTransit)),
|
||||
DeliveryCommand.AttemptedDelivery.txDef(PackageDeliveryRole.Courier, DeliveryStatus.InTransit, listOf(DeliveryStatus.InTransit)),
|
||||
DeliveryCommand.ConfirmReceipt.txDef(PackageDeliveryRole.Receiver, DeliveryStatus.InTransit, listOf(DeliveryStatus.Delivered)),
|
||||
DeliveryCommand.Return.txDef(PackageDeliveryRole.Courier, DeliveryStatus.InTransit, listOf(DeliveryStatus.Returned)))
|
||||
}
|
||||
override fun verify(tx: LedgerTransaction) {
|
||||
transitions.verify(tx)
|
||||
// ...
|
||||
// other checks -- linearId is preserved, attributes are updated correctly for given commands, return is only allowed after 3 attempts, etc.
|
||||
}
|
||||
}
|
||||
```
|
||||
This definition gives us some basic generic verification -- e.g. that package receipt confirmations need to be signed by package receivers.
|
||||
In addition that, we could visualize the defined transitions in a PUML diagram:
|
||||
|
||||
```
|
||||
PackageDelivery.transitions.printGraph().printedPUML
|
||||
```
|
||||
|
||||
Which will result in:
|
||||
```
|
||||
@startuml
|
||||
title PackageState
|
||||
[*] --> InTransit : Send (by Sender)
|
||||
InTransit --> InTransit : Transport (by Courier)
|
||||
InTransit --> InTransit : AttemptedDelivery (by Courier)
|
||||
InTransit --> Delivered : ConfirmReceipt (by Receiver)
|
||||
InTransit --> Returned : Return (by Courier)
|
||||
@enduml
|
||||
```
|
||||
![Generated PlantUML model](http://www.plantuml.com:80/plantuml/png/VSsn2i8m58NXlK-HKOM-W8DKwk8chPiunEOemIGDjoU5lhqIHP12jn_k-RZLG2rCtXMqT50dtJtr0oqrKLmsLrMMEtKCPz5Xi5HRrI8OjRfDEI3hudUSJNF5NfZtTP_4BeCz2Hy9Su2p8sHQWjyDp1lMVRXRyGqwsCYiSezpre19GbQV_FzH8PZatGi0)
|
||||
|
||||
## Future plans
|
||||
Depending on particular use cases, this utility library may be enhanced in different ways. Here are a few ideas:
|
||||
|
||||
* More generic verification (e.g. verifying numbers of produced and consumed states of a particular type)
|
||||
* More convenient syntax, not abusing nulls so much, etc.
|
||||
* ...
|
27
experimental/corda-utils/build.gradle
Normal file
27
experimental/corda-utils/build.gradle
Normal file
@ -0,0 +1,27 @@
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'idea'
|
||||
|
||||
sourceSets {
|
||||
integrationTest {
|
||||
kotlin {
|
||||
compileClasspath += main.output + test.output
|
||||
runtimeClasspath += main.output + test.output
|
||||
srcDir file('src/integration-test/kotlin')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
integrationTestCompile.extendsFrom testCompile
|
||||
integrationTestRuntime.extendsFrom testRuntime
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
compile project(':core')
|
||||
compile project(':node-api')
|
||||
testCompile project(':test-utils')
|
||||
testCompile project(':node-driver')
|
||||
|
||||
testCompile "junit:junit:$junit_version"
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package io.cryptoblk.core
|
||||
|
||||
import net.corda.core.contracts.CommandData
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Contract state that records changes of some [status] on the ledger and roles of parties that are participants
|
||||
* in that state using [roleToParty].
|
||||
*/
|
||||
interface StatusTrackingContractState<out S, in R> : ContractState {
|
||||
val status: S
|
||||
fun roleToParty(role: R): Party
|
||||
}
|
||||
|
||||
/**
|
||||
* Definition of finite state transition: for a particular command in a TX, it defines what transitions can be done
|
||||
* [from] what status [to] what statuses, and who needs to sign them ([signer]).
|
||||
* If [from] is null, it means there doesn't need to be any input; if [to] is null, it mean there doesn't need to be any output.
|
||||
* If [signer] is null, it means anyone can sign it.
|
||||
*/
|
||||
data class TransitionDef<out S, out R>(val cmd: Class<*>, val signer: R?, val from: S?, val to: List<S?>)
|
||||
|
||||
/**
|
||||
* Holds visualized PUML graph in [printedPUML] and the relevant state class name in [stateClassName].
|
||||
*/
|
||||
data class PrintedTransitionGraph(val stateClassName: String, val printedPUML: String)
|
||||
|
||||
/**
|
||||
* Shorthand for defining transitions directly from the command class
|
||||
*/
|
||||
fun <S, R> CommandData.txDef(signer: R? = null, from: S?, to: List<S?>):
|
||||
TransitionDef<S, R> = TransitionDef(this::class.java, signer, from, to)
|
||||
|
||||
/**
|
||||
* For a given [stateClass] that tracks a status, it holds all possible transitions in [ts].
|
||||
* This can be used for generic [verify] in contract code as well as for visualizing the state transition graph in PUML ([printGraph]).
|
||||
*/
|
||||
class StatusTransitions<out S, in R, T : StatusTrackingContractState<S, R>>(private val stateClass: KClass<T>,
|
||||
private vararg val ts: TransitionDef<S, R>) {
|
||||
|
||||
private val allowedCmds = ts.map { it.cmd }.toSet()
|
||||
|
||||
private fun matchingTransitions(input: S?, output: S?, command: CommandData): List<TransitionDef<S, R>> {
|
||||
val options = ts.filter {
|
||||
(it.from == input) && (output in it.to) && (it.cmd == command.javaClass)
|
||||
}
|
||||
if (options.isEmpty()) throw IllegalStateException("Transition [$input -(${command.javaClass.simpleName})-> $output] not allowed")
|
||||
return options
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic verification based on provided [TransitionDef]s
|
||||
*/
|
||||
fun verify(tx: LedgerTransaction) {
|
||||
val relevantCmds = tx.commands.filter { allowedCmds.contains(it.value.javaClass) }
|
||||
require(relevantCmds.isNotEmpty()) { "Transaction must have at least one Command relevant to its defined transitions" }
|
||||
|
||||
relevantCmds.forEach { cmd ->
|
||||
val ins = tx.inputsOfType(stateClass.java)
|
||||
val inputStates = if (ins.isEmpty()) listOf(null) else ins
|
||||
val outs = tx.outputsOfType(stateClass.java)
|
||||
val outputStates = if (outs.isEmpty()) listOf(null) else outs
|
||||
|
||||
// for each combination of in x out which should normally be at most 1...
|
||||
inputStates.forEach { inp ->
|
||||
outputStates.forEach { outp ->
|
||||
assert((inp != null) || (outp != null))
|
||||
val options = matchingTransitions(inp?.status, outp?.status, cmd.value)
|
||||
|
||||
val signerGroup = options.groupBy { it.signer }.entries.singleOrNull()
|
||||
?: throw IllegalStateException("Cannot have different signers in StatusTransitions for the same command.")
|
||||
val signer = signerGroup.key
|
||||
if (signer != null) {
|
||||
// which state determines who is the signer? by default the input, unless it's the initial transition
|
||||
val state = (inp ?: outp)!!
|
||||
val signerParty = state.roleToParty(signer)
|
||||
if (!cmd.signers.contains(signerParty.owningKey))
|
||||
throw IllegalStateException("Command ${cmd.value.javaClass} must be signed by $signer")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun printGraph(): PrintedTransitionGraph {
|
||||
val sb = StringBuilder()
|
||||
sb.append("@startuml\n")
|
||||
if (stateClass.simpleName != null) sb.append("title ${stateClass.simpleName}\n")
|
||||
ts.forEach { txDef ->
|
||||
val fromStatus = txDef.from?.toString() ?: "[*]"
|
||||
txDef.to.forEach { to ->
|
||||
val toStatus = (to ?: "[*]").toString()
|
||||
val cmd = txDef.cmd.simpleName
|
||||
val signer = txDef.signer?.toString() ?: "anyone involved"
|
||||
|
||||
sb.append("$fromStatus --> $toStatus : $cmd (by $signer)\n")
|
||||
}
|
||||
}
|
||||
sb.append("@enduml")
|
||||
return PrintedTransitionGraph(stateClass.simpleName ?: "", sb.toString())
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package io.cryptoblk.core
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FinalityFlow
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.services.queryBy
|
||||
import net.corda.core.node.services.vault.QueryCriteria
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
|
||||
inline fun <reified T : ContractState> ServiceHub.queryStateByRef(ref: StateRef): StateAndRef<T> {
|
||||
val results = vaultService.queryBy<T>(QueryCriteria.VaultQueryCriteria(stateRefs = kotlin.collections.listOf(ref)))
|
||||
return results.states.firstOrNull() ?: throw IllegalArgumentException("State (type=${T::class}) corresponding to the reference $ref not found (or is spent).")
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand when a single party signs a TX and then returns a result that uses the signed TX (e.g. includes the TX id)
|
||||
*/
|
||||
@Suspendable
|
||||
fun <R> FlowLogic<R>.finalize(tx: TransactionBuilder, returnWithSignedTx: (stx: SignedTransaction) -> R): R {
|
||||
val stx = serviceHub.signInitialTransaction(tx)
|
||||
subFlow(FinalityFlow(stx)) // it'll send to all participants in the state by default
|
||||
return returnWithSignedTx(stx)
|
||||
}
|
||||
|
||||
/**
|
||||
* Corda fails when it tries to store the same attachment hash twice. And it's convenient to also do nothing if no attachment is provided.
|
||||
* This doesn't fix the same-attachment problem completely but should at least help in testing with the same file.
|
||||
*/
|
||||
fun TransactionBuilder.addAttachmentOnce(att: SecureHash?): TransactionBuilder {
|
||||
if (att == null) return this
|
||||
if (att !in this.attachments())
|
||||
this.addAttachment(att)
|
||||
return this
|
||||
}
|
||||
|
||||
// checks the instance type, so the cast is safe
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <reified T : ContractState> List<StateAndRef<ContractState>>.entriesOfType(): List<StateAndRef<T>> = this.mapNotNull {
|
||||
if (T::class.java.isInstance(it.state.data)) it as StateAndRef<T> else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when multiple objects may be created in the same transaction and need to refer to each other. If a state
|
||||
* contains this object as a reference to another object and txhash is null, the same txhash as of the containing/outer state
|
||||
* should be used. If txhash is not null, then this works exactly like StateRef.
|
||||
*
|
||||
* WARNING:
|
||||
* - if the outer state gets updated but its referenced state does not (in the same tx) then
|
||||
* - this reference in parent state must be updated with the real txhash: [StateRefHere.copyWith]
|
||||
* - otherwise it will be unresolvable (could be solved by disallowing copy on this)
|
||||
*/
|
||||
// do not make it a data class
|
||||
@CordaSerializable
|
||||
class StateRefHere(val txhash: SecureHash?, val index: Int) {
|
||||
constructor(ref: StateRef) : this(ref.txhash, ref.index)
|
||||
|
||||
fun toStateRef(parent: SecureHash) = StateRef(txhash ?: parent, index)
|
||||
|
||||
// not standard copy
|
||||
fun copyWith(parent: SecureHash): StateRefHere {
|
||||
return StateRefHere(txhash ?: parent, index)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is StateRefHere) return false
|
||||
return (this.txhash == other.txhash) && (this.index == other.index)
|
||||
}
|
||||
}
|
@ -0,0 +1,301 @@
|
||||
package io.cryptoblk.core
|
||||
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.TypeOnlyCommandData
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.ledger
|
||||
import org.junit.Test
|
||||
|
||||
private val ALICE_ID = TestIdentity(CordaX500Name.parse("L=London,O=Alice Ltd,OU=Trade,C=GB"))
|
||||
private val BOB_ID = TestIdentity(CordaX500Name.parse("L=London,O=Bob Ltd,OU=Trade,C=GB"))
|
||||
private val BIGCORP_ID = TestIdentity(CordaX500Name.parse("L=New York,O=Bigcorp Ltd,OU=Trade,C=US"))
|
||||
private val ALICE = ALICE_ID.party
|
||||
private val BOB = BOB_ID.party
|
||||
private val BIG_CORP = BIGCORP_ID.party
|
||||
private val ALICE_PUBKEY = ALICE_ID.publicKey
|
||||
private val BOB_PUBKEY = BOB_ID.publicKey
|
||||
private val BIG_CORP_PUBKEY = BIGCORP_ID.publicKey
|
||||
|
||||
private enum class PartyRole {
|
||||
Adder,
|
||||
MultiplierAndRandomiser,
|
||||
Randomiser
|
||||
}
|
||||
|
||||
private class IntegerTestState(override val status: String) : StatusTrackingContractState<String, PartyRole> {
|
||||
override fun roleToParty(role: PartyRole): Party {
|
||||
return when (role) {
|
||||
PartyRole.Adder -> BIG_CORP
|
||||
PartyRole.MultiplierAndRandomiser -> BOB
|
||||
PartyRole.Randomiser -> ALICE
|
||||
}
|
||||
}
|
||||
|
||||
override val participants: List<AbstractParty>
|
||||
get() = listOf(ALICE, BOB, BIG_CORP)
|
||||
}
|
||||
|
||||
private sealed class Operations: TypeOnlyCommandData() {
|
||||
object Add1 : Operations()
|
||||
object Add10 : Operations()
|
||||
object Multiply2 : Operations()
|
||||
object Randomise : Operations()
|
||||
object Close : Operations()
|
||||
object AnotherCommand : Operations()
|
||||
}
|
||||
|
||||
class TestIntegerContract: Contract {
|
||||
companion object {
|
||||
private val fsTransitions = StatusTransitions(IntegerTestState::class,
|
||||
Operations.Add1.txDef(PartyRole.Adder, null, listOf("1")),
|
||||
Operations.Add1.txDef(PartyRole.Adder, "1", listOf("2")),
|
||||
Operations.Add10.txDef(PartyRole.Adder, "1", listOf("11")),
|
||||
Operations.Multiply2.txDef(PartyRole.MultiplierAndRandomiser, "2", listOf("4")),
|
||||
Operations.Multiply2.txDef(PartyRole.MultiplierAndRandomiser, "11", listOf("22")),
|
||||
Operations.Randomise.txDef(PartyRole.Randomiser, "2", listOf("8", "9", "1", "11")),
|
||||
Operations.Randomise.txDef(PartyRole.Randomiser, "11", listOf("2", "11", "4")),
|
||||
Operations.Randomise.txDef(PartyRole.MultiplierAndRandomiser, "11", listOf("22")),
|
||||
Operations.Close.txDef(PartyRole.Randomiser, "9", listOf(null))
|
||||
)
|
||||
}
|
||||
|
||||
override fun verify(tx: LedgerTransaction) {
|
||||
fsTransitions.verify(tx)
|
||||
}
|
||||
}
|
||||
|
||||
private class TestOwnedIntegerState(override val status: String): StatusTrackingContractState<String, PartyRole> {
|
||||
override val participants: List<AbstractParty>
|
||||
get() = listOf(ALICE, BOB)
|
||||
|
||||
override fun roleToParty(role: PartyRole): Party {
|
||||
return if (status == "0") ALICE else BOB
|
||||
}
|
||||
}
|
||||
|
||||
class TestOwnedIntegerContract: Contract {
|
||||
companion object {
|
||||
private val fsTransitions = StatusTransitions(TestOwnedIntegerState::class,
|
||||
Operations.Add1.txDef(PartyRole.Adder, null, listOf("0")),
|
||||
Operations.Add1.txDef(PartyRole.Adder, "0", listOf("1")),
|
||||
Operations.Add1.txDef(PartyRole.Adder, "1", listOf("2")),
|
||||
Operations.Multiply2.txDef(PartyRole.MultiplierAndRandomiser, "10", listOf("20")),
|
||||
Operations.Multiply2.txDef(PartyRole.Adder, "10", listOf("20")) // bug for the test
|
||||
)
|
||||
}
|
||||
|
||||
override fun verify(tx: LedgerTransaction) {
|
||||
fsTransitions.verify(tx)
|
||||
}
|
||||
}
|
||||
|
||||
class StatusTransitionsTest {
|
||||
|
||||
companion object {
|
||||
private val integerContract = TestIntegerContract::class.qualifiedName!!
|
||||
private val ownedIntegerContract = TestOwnedIntegerContract::class.qualifiedName!!
|
||||
private val ledgerServices = MockServices(ALICE_ID, BOB_ID, BIGCORP_ID)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `basic correct cases`() {
|
||||
ledgerServices.ledger {
|
||||
transaction {
|
||||
output(integerContract, IntegerTestState("1"))
|
||||
command(BIG_CORP_PUBKEY, Operations.Add1)
|
||||
|
||||
verifies()
|
||||
}
|
||||
transaction {
|
||||
input(integerContract, IntegerTestState("1"))
|
||||
output(integerContract, IntegerTestState("2"))
|
||||
command(BIG_CORP_PUBKEY, Operations.Add1)
|
||||
|
||||
verifies()
|
||||
}
|
||||
transaction {
|
||||
input(integerContract, IntegerTestState("2"))
|
||||
output(integerContract, IntegerTestState("9"))
|
||||
command(ALICE_PUBKEY, Operations.Randomise)
|
||||
|
||||
verifies()
|
||||
}
|
||||
transaction {
|
||||
input(integerContract, IntegerTestState("9"))
|
||||
command(ALICE_PUBKEY, Operations.Close)
|
||||
|
||||
verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `disallowed output`() {
|
||||
ledgerServices.ledger {
|
||||
transaction {
|
||||
input(integerContract, IntegerTestState("1"))
|
||||
output(integerContract, IntegerTestState("3"))
|
||||
command(BIG_CORP_PUBKEY, Operations.Add1)
|
||||
|
||||
fails()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `disallowed command`() {
|
||||
ledgerServices.ledger {
|
||||
transaction {
|
||||
input(integerContract, IntegerTestState("1"))
|
||||
output(integerContract, IntegerTestState("2"))
|
||||
command(BIG_CORP_PUBKEY, Operations.Multiply2)
|
||||
|
||||
fails()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `disallowed signer`() {
|
||||
ledgerServices.ledger {
|
||||
transaction {
|
||||
input(integerContract, IntegerTestState("1"))
|
||||
output(integerContract, IntegerTestState("2"))
|
||||
command(ALICE_PUBKEY, Operations.Add1)
|
||||
|
||||
fails()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `irrelevant commands fail`() {
|
||||
ledgerServices.ledger {
|
||||
transaction {
|
||||
output(integerContract, IntegerTestState("8"))
|
||||
command(ALICE_PUBKEY, Operations.AnotherCommand)
|
||||
|
||||
failsWith("at least one Command relevant")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `multiple relevant commands accepted`() {
|
||||
ledgerServices.ledger {
|
||||
transaction {
|
||||
input(integerContract, IntegerTestState("11"))
|
||||
output(integerContract, IntegerTestState("22"))
|
||||
command(BOB_PUBKEY, Operations.Randomise)
|
||||
command(BOB_PUBKEY, Operations.Multiply2)
|
||||
|
||||
verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `multiple relevant commands failed`() {
|
||||
ledgerServices.ledger {
|
||||
transaction {
|
||||
input(integerContract, IntegerTestState("2"))
|
||||
output(integerContract, IntegerTestState("4"))
|
||||
command(BOB_PUBKEY, Operations.Randomise)
|
||||
command(BOB_PUBKEY, Operations.Multiply2)
|
||||
|
||||
fails()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `multiple inputs failed`() {
|
||||
ledgerServices.ledger {
|
||||
transaction {
|
||||
input(integerContract, IntegerTestState("1"))
|
||||
input(integerContract, IntegerTestState("2"))
|
||||
output(integerContract, IntegerTestState("11"))
|
||||
command(BIG_CORP_PUBKEY, Operations.Add10)
|
||||
|
||||
fails()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `multiple outputs failed`() {
|
||||
ledgerServices.ledger {
|
||||
transaction {
|
||||
input(integerContract, IntegerTestState("1"))
|
||||
output(integerContract, IntegerTestState("2"))
|
||||
output(integerContract, IntegerTestState("11"))
|
||||
command(BIG_CORP_PUBKEY, Operations.Add10)
|
||||
|
||||
fails()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `role change signer correct`() {
|
||||
ledgerServices.ledger {
|
||||
transaction {
|
||||
output(ownedIntegerContract, TestOwnedIntegerState("0"))
|
||||
command(ALICE_PUBKEY, Operations.Add1)
|
||||
|
||||
verifies()
|
||||
}
|
||||
transaction {
|
||||
input(ownedIntegerContract, TestOwnedIntegerState("0"))
|
||||
output(ownedIntegerContract, TestOwnedIntegerState("1"))
|
||||
command(ALICE_PUBKEY, Operations.Add1)
|
||||
|
||||
verifies()
|
||||
}
|
||||
transaction {
|
||||
input(ownedIntegerContract, TestOwnedIntegerState("1"))
|
||||
output(ownedIntegerContract, TestOwnedIntegerState("2"))
|
||||
command(ALICE_PUBKEY, Operations.Add1)
|
||||
|
||||
fails()
|
||||
}
|
||||
transaction {
|
||||
input(ownedIntegerContract, TestOwnedIntegerState("1"))
|
||||
output(ownedIntegerContract, TestOwnedIntegerState("2"))
|
||||
command(BOB_PUBKEY, Operations.Add1)
|
||||
|
||||
verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `multiple signers disallowed`() {
|
||||
ledgerServices.ledger {
|
||||
transaction {
|
||||
input(ownedIntegerContract, TestOwnedIntegerState("10"))
|
||||
output(ownedIntegerContract, TestOwnedIntegerState("20"))
|
||||
command(ALICE_PUBKEY, Operations.Multiply2)
|
||||
|
||||
failsWith("Cannot have different signers")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `spend disallowed`() {
|
||||
ledgerServices.ledger {
|
||||
transaction {
|
||||
input(integerContract, IntegerTestState("1"))
|
||||
command(ALICE_PUBKEY, Operations.Close)
|
||||
|
||||
fails()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -162,7 +162,7 @@ abstract class OnLedgerAsset<T : Any, out C : CommandData, S : FungibleAsset<T>>
|
||||
delta > 0 -> {
|
||||
// The states from the current issuer more than covers this payment.
|
||||
outputStates += deriveState(templateState, Amount(remainingToPay, token), party)
|
||||
remainingFromEachIssuer[0] = Pair(token, Amount(delta, token))
|
||||
remainingFromEachIssuer[remainingFromEachIssuer.lastIndex] = Pair(token, Amount(delta, token))
|
||||
remainingToPay = 0
|
||||
}
|
||||
delta == 0L -> {
|
||||
|
@ -24,6 +24,7 @@ import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.After
|
||||
import org.junit.Test
|
||||
import java.util.Collections.nCopies
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
class CashSelectionH2ImplTest {
|
||||
private val mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance", "net.corda.core.schemas", "net.corda.finance.sampleschemas"))
|
||||
@ -63,4 +64,19 @@ class CashSelectionH2ImplTest {
|
||||
assertThatThrownBy { flow2.getOrThrow() }.isInstanceOf(CashException::class.java)
|
||||
assertThatThrownBy { flow3.getOrThrow() }.isInstanceOf(CashException::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `select pennies amount from cash states with more than two different issuers and expect change`() {
|
||||
val node = mockNet.createNode()
|
||||
val notary = mockNet.defaultNotaryIdentity
|
||||
|
||||
// Issue some cash
|
||||
node.startFlow(CashIssueFlow(1.POUNDS, OpaqueBytes.of(1), notary)).getOrThrow()
|
||||
node.startFlow(CashIssueFlow(1.POUNDS, OpaqueBytes.of(2), notary)).getOrThrow()
|
||||
node.startFlow(CashIssueFlow(1000.POUNDS, OpaqueBytes.of(3), notary)).getOrThrow()
|
||||
|
||||
// Make a payment
|
||||
val paymentResult = node.startFlow(CashPaymentFlow(999.POUNDS, node.info.legalIdentities[0], false)).getOrThrow()
|
||||
assertNotNull(paymentResult.recipient)
|
||||
}
|
||||
}
|
@ -19,10 +19,10 @@ description 'Corda node API'
|
||||
|
||||
dependencies {
|
||||
compile project(":core")
|
||||
compile project(":serialization") // TODO Remove this once the NetworkBootstrapper class is moved into the tools:bootstrapper module
|
||||
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
|
||||
// TODO: remove the forced update of commons-collections and beanutils when artemis updates them
|
||||
compile "org.apache.commons:commons-collections4:${commons_collections_version}"
|
||||
@ -41,12 +41,6 @@ dependencies {
|
||||
// TypeSafe Config: for simple and human friendly config files.
|
||||
compile "com.typesafe:config:$typesafe_config_version"
|
||||
|
||||
// Kryo: object graph serialization.
|
||||
compile "com.esotericsoftware:kryo:4.0.0"
|
||||
compile "de.javakaffee:kryo-serializers:0.41"
|
||||
compile "org.ow2.asm:asm:$asm_version"
|
||||
|
||||
// For AMQP serialisation.
|
||||
compile "org.apache.qpid:proton-j:$protonj_version"
|
||||
|
||||
// SQL connection pooling library
|
||||
@ -61,12 +55,9 @@ dependencies {
|
||||
shadow "org.apache.curator:curator-recipes:${curator_version}"
|
||||
testCompile "org.apache.curator:curator-test:${curator_version}"
|
||||
|
||||
// FastClasspathScanner: classpath scanning - needed for the NetworkBootstrapper and AMQP.
|
||||
// FastClasspathScanner: classpath scanning - needed for the NetworkBootstrapper.
|
||||
compile "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version"
|
||||
|
||||
// Pure-Java Snappy compression
|
||||
compile "org.iq80.snappy:snappy:$snappy_version"
|
||||
|
||||
compile "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
|
||||
|
||||
// For caches rather than guava
|
||||
@ -75,6 +66,7 @@ dependencies {
|
||||
// Unit testing helpers.
|
||||
testCompile "junit:junit:$junit_version"
|
||||
testCompile "org.assertj:assertj-core:$assertj_version"
|
||||
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
testCompile project(':node-driver')
|
||||
|
||||
compile ("org.apache.activemq:artemis-amqp-protocol:${artemis_version}") {
|
||||
|
@ -10,9 +10,11 @@
|
||||
|
||||
package net.corda.nodeapi.internal.network
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.core.contracts.ContractClassName
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.internal.concurrent.fork
|
||||
@ -28,18 +30,15 @@ import net.corda.core.serialization.internal._contextSerializationEnv
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.nodeapi.internal.ContractsJar
|
||||
import net.corda.nodeapi.internal.ContractsJarFile
|
||||
import net.corda.nodeapi.internal.DEV_ROOT_CA
|
||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||
import net.corda.nodeapi.internal.*
|
||||
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier.Companion.NODE_INFO_FILE_NAME_PREFIX
|
||||
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
|
||||
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
|
||||
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
||||
import net.corda.nodeapi.internal.serialization.amqp.AbstractAMQPSerializationScheme
|
||||
import net.corda.nodeapi.internal.serialization.amqp.amqpMagic
|
||||
import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
|
||||
import net.corda.nodeapi.internal.serialization.kryo.kryoMagic
|
||||
import net.corda.serialization.internal.AMQP_P2P_CONTEXT
|
||||
import net.corda.serialization.internal.CordaSerializationMagic
|
||||
import net.corda.serialization.internal.SerializationFactoryImpl
|
||||
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
|
||||
import net.corda.serialization.internal.amqp.amqpMagic
|
||||
import net.corda.serialization.internal.kryo.AbstractKryoSerializationScheme
|
||||
import net.corda.serialization.internal.kryo.kryoMagic
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
|
||||
@ -47,6 +46,10 @@ import java.time.Instant
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeoutException
|
||||
import kotlin.streams.toList
|
||||
import kotlin.collections.HashSet
|
||||
import kotlin.collections.component1
|
||||
import kotlin.collections.component2
|
||||
import kotlin.collections.set
|
||||
|
||||
/**
|
||||
* Class to bootstrap a local network of Corda nodes on the same filesystem.
|
||||
@ -72,6 +75,43 @@ class NetworkBootstrapper {
|
||||
}
|
||||
}
|
||||
|
||||
sealed class NotaryCluster {
|
||||
data class BFT(val name: CordaX500Name) : NotaryCluster()
|
||||
data class CFT(val name: CordaX500Name) : NotaryCluster()
|
||||
}
|
||||
|
||||
data class DirectoryAndConfig(val directory: Path, val config: Config)
|
||||
|
||||
private fun notaryClusters(configs: Map<Path, Config>): Map<NotaryCluster, List<Path>> {
|
||||
val clusteredNotaries = configs.flatMap { (path, config) ->
|
||||
if (config.hasPath("notary.serviceLegalName")) {
|
||||
listOf(CordaX500Name.parse(config.getString("notary.serviceLegalName")) to DirectoryAndConfig(path, config))
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
return clusteredNotaries.groupBy { it.first }.map { (k, vs) ->
|
||||
val cs = vs.map { it.second.config }
|
||||
if (cs.any { it.hasPath("notary.bftSMaRt") }) {
|
||||
require(cs.all { it.hasPath("notary.bftSMaRt") }) { "Mix of BFT and non-BFT notaries with service name $k" }
|
||||
NotaryCluster.BFT(k) to vs.map { it.second.directory }
|
||||
} else {
|
||||
NotaryCluster.CFT(k) to vs.map { it.second.directory }
|
||||
}
|
||||
}.toMap()
|
||||
}
|
||||
|
||||
private fun generateServiceIdentitiesForNotaryClusters(configs: Map<Path, Config>) {
|
||||
notaryClusters(configs).forEach { (cluster, directories) ->
|
||||
when (cluster) {
|
||||
is NotaryCluster.BFT ->
|
||||
DevIdentityGenerator.generateDistributedNotaryCompositeIdentity(directories, cluster.name, threshold = 1 + 2 * directories.size / 3)
|
||||
is NotaryCluster.CFT ->
|
||||
DevIdentityGenerator.generateDistributedNotarySingularIdentity(directories, cluster.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun bootstrap(directory: Path, cordappJars: List<Path>) {
|
||||
directory.createDirectories()
|
||||
println("Bootstrapping local network in $directory")
|
||||
@ -79,18 +119,22 @@ class NetworkBootstrapper {
|
||||
val nodeDirs = directory.list { paths -> paths.filter { (it / "corda.jar").exists() }.toList() }
|
||||
require(nodeDirs.isNotEmpty()) { "No nodes found" }
|
||||
println("Nodes found in the following sub-directories: ${nodeDirs.map { it.fileName }}")
|
||||
val configs = nodeDirs.associateBy({ it }, { ConfigFactory.parseFile((it / "node.conf").toFile()) })
|
||||
generateServiceIdentitiesForNotaryClusters(configs)
|
||||
val processes = startNodeInfoGeneration(nodeDirs)
|
||||
initialiseSerialization()
|
||||
try {
|
||||
println("Waiting for all nodes to generate their node-info files...")
|
||||
val nodeInfoFiles = gatherNodeInfoFiles(processes, nodeDirs)
|
||||
println("Checking for duplicate nodes")
|
||||
checkForDuplicateLegalNames(nodeInfoFiles)
|
||||
println("Distributing all node-info files to all nodes")
|
||||
distributeNodeInfos(nodeDirs, nodeInfoFiles)
|
||||
print("Loading existing network parameters... ")
|
||||
val existingNetParams = loadNetworkParameters(nodeDirs)
|
||||
println(existingNetParams ?: "none found")
|
||||
println("Gathering notary identities")
|
||||
val notaryInfos = gatherNotaryInfos(nodeInfoFiles)
|
||||
val notaryInfos = gatherNotaryInfos(nodeInfoFiles, configs)
|
||||
println("Generating contract implementations whitelist")
|
||||
val newWhitelist = generateWhitelist(existingNetParams, readExcludeWhitelist(directory), cordappJars.map(::ContractsJarFile))
|
||||
val netParams = installNetworkParameters(notaryInfos, newWhitelist, existingNetParams, nodeDirs)
|
||||
@ -170,12 +214,28 @@ class NetworkBootstrapper {
|
||||
}
|
||||
}
|
||||
|
||||
private fun gatherNotaryInfos(nodeInfoFiles: List<Path>): List<NotaryInfo> {
|
||||
/*the function checks for duplicate myLegalName in the all the *_node.conf files
|
||||
All the myLegalName values are added to a HashSet - this helps detect duplicate values.
|
||||
If a duplicate name is found the process is aborted with an error message
|
||||
*/
|
||||
private fun checkForDuplicateLegalNames(nodeInfoFiles: List<Path>) {
|
||||
val legalNames = HashSet<String>()
|
||||
for (nodeInfoFile in nodeInfoFiles) {
|
||||
val nodeConfig = ConfigFactory.parseFile((nodeInfoFile.parent / "node.conf").toFile())
|
||||
val legalName = nodeConfig.getString("myLegalName")
|
||||
if(!legalNames.add(legalName)){
|
||||
println("Duplicate Node Found - ensure every node has a unique legal name");
|
||||
throw IllegalArgumentException("Duplicate Node Found - $legalName");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun gatherNotaryInfos(nodeInfoFiles: List<Path>, configs: Map<Path, Config>): List<NotaryInfo> {
|
||||
return nodeInfoFiles.mapNotNull { nodeInfoFile ->
|
||||
// The config contains the notary type
|
||||
val nodeConfig = ConfigFactory.parseFile((nodeInfoFile.parent / "node.conf").toFile())
|
||||
val nodeConfig = configs[nodeInfoFile.parent]!!
|
||||
if (nodeConfig.hasPath("notary")) {
|
||||
val validating = nodeConfig.getConfig("notary").getBoolean("validating")
|
||||
val validating = nodeConfig.getBoolean("notary.validating")
|
||||
// And the node-info file contains the notary's identity
|
||||
val nodeInfo = nodeInfoFile.readObject<SignedNodeInfo>().verified()
|
||||
NotaryInfo(nodeInfo.notaryIdentity(), validating)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.nodeapi.internal.serialization.amqp.custom
|
||||
|
||||
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
|
||||
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||
import net.corda.serialization.internal.amqp.CustomSerializer
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory
|
||||
import rx.Notification
|
||||
|
||||
class RxNotificationSerializer(
|
||||
|
@ -15,4 +15,4 @@ import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
|
||||
/**
|
||||
* A serializer for [StringBuffer].
|
||||
*/
|
||||
object StringBufferSerializer : CustomSerializer.ToString<StringBuffer>(StringBuffer::class.java)
|
||||
object StringBufferSerializer : CustomSerializer.ToString<StringBuffer>(StringBuffer::class.java)
|
||||
|
@ -21,10 +21,10 @@ import net.corda.core.serialization.serialize
|
||||
import net.corda.node.serialization.amqp.AMQPServerSerializationScheme
|
||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||
import net.corda.nodeapi.internal.createDevKeyStores
|
||||
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||
import net.corda.nodeapi.internal.serialization.SerializationContextImpl
|
||||
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
||||
import net.corda.nodeapi.internal.serialization.amqp.amqpMagic
|
||||
import net.corda.serialization.internal.SerializationContextImpl
|
||||
import net.corda.serialization.internal.SerializationFactoryImpl
|
||||
import net.corda.serialization.internal.amqp.amqpMagic
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.TestIdentity
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -52,7 +52,7 @@ import net.corda.nodeapi.internal.ShutdownHook
|
||||
import net.corda.nodeapi.internal.addShutdownHook
|
||||
import net.corda.nodeapi.internal.bridging.BridgeControlListener
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.serialization.*
|
||||
import net.corda.serialization.internal.*
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import rx.Scheduler
|
||||
|
@ -43,7 +43,7 @@ import net.corda.node.VersionInfo
|
||||
import net.corda.node.internal.classloading.requireAnnotation
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.nodeapi.internal.coreContractClasses
|
||||
import net.corda.nodeapi.internal.serialization.DefaultWhitelist
|
||||
import net.corda.serialization.internal.DefaultWhitelist
|
||||
import org.apache.commons.collections4.map.LRUMap
|
||||
import java.lang.reflect.Modifier
|
||||
import java.net.JarURLConnection
|
||||
|
@ -4,11 +4,10 @@ import net.corda.core.cordapp.Cordapp
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
|
||||
import net.corda.nodeapi.internal.serialization.amqp.AbstractAMQPSerializationScheme
|
||||
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||
import net.corda.nodeapi.internal.serialization.amqp.custom.RxNotificationSerializer
|
||||
|
||||
import net.corda.serialization.internal.CordaSerializationMagic
|
||||
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
|
@ -2,8 +2,8 @@ package net.corda.node.serialization.amqp
|
||||
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.toObservable
|
||||
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
|
||||
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||
import net.corda.serialization.internal.amqp.CustomSerializer
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory
|
||||
import rx.Observable
|
||||
import java.io.NotSerializableException
|
||||
|
||||
|
@ -6,14 +6,12 @@ import net.corda.core.utilities.loggerFor
|
||||
import net.corda.node.services.messaging.ObservableContextInterface
|
||||
import net.corda.node.services.messaging.ObservableSubscription
|
||||
import net.corda.nodeapi.RPCApi
|
||||
import net.corda.nodeapi.internal.serialization.amqp.*
|
||||
import net.corda.serialization.internal.amqp.*
|
||||
import org.apache.qpid.proton.codec.Data
|
||||
|
||||
import rx.Notification
|
||||
import rx.Observable
|
||||
import rx.Subscriber
|
||||
import java.io.NotSerializableException
|
||||
|
||||
import java.lang.reflect.Type
|
||||
|
||||
/**
|
||||
|
@ -12,9 +12,9 @@ package net.corda.node.serialization.kryo
|
||||
|
||||
import com.esotericsoftware.kryo.pool.KryoPool
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
|
||||
import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
|
||||
import net.corda.nodeapi.internal.serialization.kryo.kryoMagic
|
||||
import net.corda.serialization.internal.CordaSerializationMagic
|
||||
import net.corda.serialization.internal.kryo.AbstractKryoSerializationScheme
|
||||
import net.corda.serialization.internal.kryo.kryoMagic
|
||||
|
||||
class KryoServerSerializationScheme : AbstractKryoSerializationScheme() {
|
||||
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
|
||||
|
@ -24,8 +24,8 @@ import net.corda.node.serialization.amqp.AMQPServerSerializationScheme
|
||||
import net.corda.nodeapi.internal.NodeInfoAndSigned
|
||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
|
||||
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
|
||||
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
||||
import net.corda.serialization.internal.AMQP_P2P_CONTEXT
|
||||
import net.corda.serialization.internal.SerializationFactoryImpl
|
||||
import rx.Observable
|
||||
import rx.Scheduler
|
||||
import java.nio.file.Path
|
||||
|
@ -47,8 +47,8 @@ import net.corda.node.services.statemachine.transitions.StateMachine
|
||||
import net.corda.node.services.statemachine.transitions.StateMachineConfiguration
|
||||
import net.corda.node.utilities.AffinityExecutor
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl
|
||||
import net.corda.nodeapi.internal.serialization.withTokenContext
|
||||
import net.corda.serialization.internal.SerializeAsTokenContextImpl
|
||||
import net.corda.serialization.internal.withTokenContext
|
||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||
import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
|
@ -40,7 +40,7 @@ import net.corda.core.utilities.debug
|
||||
import net.corda.node.utilities.AppendOnlyPersistentMap
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.currentDBSession
|
||||
import net.corda.nodeapi.internal.serialization.CordaSerializationEncoding
|
||||
import net.corda.serialization.internal.CordaSerializationEncoding
|
||||
import java.time.Clock
|
||||
|
||||
/**
|
||||
|
@ -69,7 +69,7 @@ class CordappLoaderTest {
|
||||
assertThat(actualCordapp.schedulableFlows).isEmpty()
|
||||
assertThat(actualCordapp.services).isEmpty()
|
||||
assertThat(actualCordapp.serializationWhitelists).hasSize(1)
|
||||
assertThat(actualCordapp.serializationWhitelists.first().javaClass.name).isEqualTo("net.corda.nodeapi.internal.serialization.DefaultWhitelist")
|
||||
assertThat(actualCordapp.serializationWhitelists.first().javaClass.name).isEqualTo("net.corda.serialization.internal.DefaultWhitelist")
|
||||
assertThat(actualCordapp.jarPath).isEqualTo(isolatedJAR)
|
||||
}
|
||||
|
||||
|
@ -1,23 +1,20 @@
|
||||
package net.corda.node.internal.serialization
|
||||
|
||||
import net.corda.client.rpc.internal.ObservableContext as ClientObservableContext
|
||||
import net.corda.core.internal.ThreadBox
|
||||
import net.corda.core.context.Trace
|
||||
import net.corda.node.internal.serialization.testutils.AMQPRoundTripRPCSerializationScheme
|
||||
import net.corda.node.internal.serialization.testutils.TestObservableContext as ServerObservableContext
|
||||
import net.corda.node.services.messaging.ObservableSubscription
|
||||
import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput
|
||||
import net.corda.nodeapi.internal.serialization.amqp.SerializationOutput
|
||||
|
||||
import co.paralleluniverse.common.util.SameThreadExecutor
|
||||
import com.github.benmanes.caffeine.cache.Cache
|
||||
import com.github.benmanes.caffeine.cache.Caffeine
|
||||
import com.github.benmanes.caffeine.cache.RemovalListener
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import net.corda.client.rpc.internal.serialization.amqp.RpcClientObservableSerializer
|
||||
import net.corda.core.context.Trace
|
||||
import net.corda.core.internal.ThreadBox
|
||||
import net.corda.node.internal.serialization.testutils.AMQPRoundTripRPCSerializationScheme
|
||||
import net.corda.node.internal.serialization.testutils.serializationContext
|
||||
import net.corda.node.serialization.amqp.RpcServerObservableSerializer
|
||||
import net.corda.node.services.messaging.ObservableSubscription
|
||||
import net.corda.nodeapi.RPCApi
|
||||
import net.corda.serialization.internal.amqp.DeserializationInput
|
||||
import net.corda.serialization.internal.amqp.SerializationOutput
|
||||
import org.apache.activemq.artemis.api.core.SimpleString
|
||||
import org.junit.Test
|
||||
import rx.Notification
|
||||
@ -28,6 +25,8 @@ import java.time.Instant
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.TimeUnit
|
||||
import net.corda.client.rpc.internal.ObservableContext as ClientObservableContext
|
||||
import net.corda.node.internal.serialization.testutils.TestObservableContext as ServerObservableContext
|
||||
|
||||
class RoundTripObservableSerializerTests {
|
||||
private fun getID() = Trace.InvocationId("test1", Instant.now())
|
||||
|
@ -4,13 +4,13 @@ import com.github.benmanes.caffeine.cache.Cache
|
||||
import com.github.benmanes.caffeine.cache.Caffeine
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import net.corda.core.context.Trace
|
||||
import net.corda.node.internal.serialization.testutils.*
|
||||
import net.corda.node.internal.serialization.testutils.TestObservableContext
|
||||
import net.corda.node.internal.serialization.testutils.serializationContext
|
||||
import net.corda.node.serialization.amqp.RpcServerObservableSerializer
|
||||
import net.corda.node.services.messaging.ObservableSubscription
|
||||
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||
import net.corda.nodeapi.internal.serialization.amqp.SerializationOutput
|
||||
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||
|
||||
import net.corda.serialization.internal.amqp.SerializationOutput
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
import org.apache.activemq.artemis.api.core.SimpleString
|
||||
import org.junit.Test
|
||||
import rx.Observable
|
||||
|
@ -2,17 +2,15 @@ package net.corda.node.internal.serialization.testutils
|
||||
|
||||
import net.corda.client.rpc.internal.serialization.amqp.RpcClientObservableSerializer
|
||||
import net.corda.core.context.Trace
|
||||
import net.corda.core.cordapp.Cordapp
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import net.corda.node.serialization.amqp.RpcServerObservableSerializer
|
||||
import net.corda.nodeapi.RPCApi
|
||||
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
|
||||
import net.corda.nodeapi.internal.serialization.amqp.AbstractAMQPSerializationScheme
|
||||
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import net.corda.serialization.internal.CordaSerializationMagic
|
||||
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
import net.corda.client.rpc.internal.ObservableContext as ClientObservableContext
|
||||
|
||||
/**
|
||||
|
@ -1,9 +1,9 @@
|
||||
package net.corda.node.internal.serialization.testutils
|
||||
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||
import net.corda.nodeapi.internal.serialization.SerializationContextImpl
|
||||
import net.corda.nodeapi.internal.serialization.amqp.amqpMagic
|
||||
import net.corda.serialization.internal.SerializationContextImpl
|
||||
import net.corda.serialization.internal.amqp.amqpMagic
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
|
||||
val serializationProperties: MutableMap<Any, Any> = mutableMapOf()
|
||||
|
||||
|
@ -96,10 +96,5 @@ class BFTNotaryCordform : CordformDefinition() {
|
||||
}
|
||||
|
||||
override fun setup(context: CordformContext) {
|
||||
DevIdentityGenerator.generateDistributedNotaryCompositeIdentity(
|
||||
notaryNames.map { context.baseDirectory(it.toString()) },
|
||||
clusterName,
|
||||
minCorrectReplicas(clusterSize)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -89,9 +89,5 @@ class RaftNotaryCordform : CordformDefinition() {
|
||||
}
|
||||
|
||||
override fun setup(context: CordformContext) {
|
||||
DevIdentityGenerator.generateDistributedNotarySingularIdentity(
|
||||
notaryNames.map { context.baseDirectory(it.toString()) },
|
||||
clusterName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import com.opengamma.strata.product.common.BuySell
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.packageName
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.nodeapi.internal.serialization.amqp.AbstractAMQPSerializationScheme
|
||||
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
|
||||
import net.corda.testing.core.DUMMY_BANK_A_NAME
|
||||
import net.corda.testing.core.DUMMY_BANK_B_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
|
58
serialization/build.gradle
Normal file
58
serialization/build.gradle
Normal file
@ -0,0 +1,58 @@
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
apply plugin: 'com.jfrog.artifactory'
|
||||
|
||||
description 'Corda serialization'
|
||||
|
||||
dependencies {
|
||||
compile project(":core")
|
||||
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
|
||||
compile "org.apache.activemq:artemis-commons:${artemis_version}"
|
||||
|
||||
// Kryo: object graph serialization.
|
||||
compile "com.esotericsoftware:kryo:4.0.0"
|
||||
compile "de.javakaffee:kryo-serializers:0.41"
|
||||
compile "org.ow2.asm:asm:$asm_version"
|
||||
|
||||
// For AMQP serialisation.
|
||||
compile "org.apache.qpid:proton-j:$protonj_version"
|
||||
|
||||
// FastClasspathScanner: classpath scanning
|
||||
compile "io.github.lukehutch:fast-classpath-scanner:$fast_classpath_scanner_version"
|
||||
|
||||
// Pure-Java Snappy compression
|
||||
compile "org.iq80.snappy:snappy:$snappy_version"
|
||||
|
||||
// For caches rather than guava
|
||||
compile "com.github.ben-manes.caffeine:caffeine:$caffeine_version"
|
||||
|
||||
// Unit testing helpers.
|
||||
testCompile "junit:junit:$junit_version"
|
||||
testCompile "org.assertj:assertj-core:$assertj_version"
|
||||
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
testCompile project(':node-driver')
|
||||
}
|
||||
|
||||
configurations {
|
||||
testArtifacts.extendsFrom testRuntime
|
||||
}
|
||||
|
||||
task testJar(type: Jar) {
|
||||
classifier "tests"
|
||||
from sourceSets.test.output
|
||||
}
|
||||
|
||||
artifacts {
|
||||
testArtifacts testJar
|
||||
}
|
||||
|
||||
jar {
|
||||
baseName 'corda-serialization'
|
||||
}
|
||||
|
||||
publish {
|
||||
name jar.baseName
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.nodeapi.internal.serialization
|
||||
package net.corda.serialization.internal
|
||||
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
import sun.misc.Unsafe
|
||||
@ -42,7 +42,7 @@ import kotlin.collections.LinkedHashSet
|
||||
* Inheritance works for blacklisted items, but one can specifically exclude classes from blacklisting as well.
|
||||
* Note: Custom serializer registration trumps white/black lists. So if a given type has a custom serializer and has its name
|
||||
* in the blacklist - it will still be serialized as specified by custom serializer.
|
||||
* For more details, see [net.corda.nodeapi.internal.serialization.CordaClassResolver.getRegistration]
|
||||
* For more details, see [net.corda.serialization.internal.CordaClassResolver.getRegistration]
|
||||
*/
|
||||
object AllButBlacklisted : ClassWhitelist {
|
||||
|
@ -8,7 +8,7 @@
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.nodeapi.internal
|
||||
package net.corda.serialization.internal
|
||||
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.ContractAttachment
|
@ -1,6 +1,6 @@
|
||||
@file:JvmName("ByteBufferStreams")
|
||||
|
||||
package net.corda.nodeapi.internal.serialization
|
||||
package net.corda.serialization.internal
|
||||
|
||||
import net.corda.core.internal.LazyPool
|
||||
import java.io.ByteArrayOutputStream
|
@ -0,0 +1,11 @@
|
||||
package net.corda.serialization.internal
|
||||
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
|
||||
interface MutableClassWhitelist : ClassWhitelist {
|
||||
fun add(entry: Class<*>)
|
||||
}
|
||||
|
||||
object AllWhitelist : ClassWhitelist {
|
||||
override fun hasListed(type: Class<*>): Boolean = true
|
||||
}
|
@ -10,12 +10,14 @@
|
||||
|
||||
@file:JvmName("ClientContexts")
|
||||
|
||||
package net.corda.nodeapi.internal.serialization
|
||||
package net.corda.serialization.internal
|
||||
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializationDefaults
|
||||
import net.corda.nodeapi.internal.serialization.amqp.amqpMagic
|
||||
import net.corda.nodeapi.internal.serialization.kryo.kryoMagic
|
||||
import net.corda.serialization.internal.amqp.amqpMagic
|
||||
import net.corda.serialization.internal.kryo.BuiltInExceptionsWhitelist
|
||||
import net.corda.serialization.internal.kryo.GlobalTransientClassWhiteList
|
||||
import net.corda.serialization.internal.kryo.kryoMagic
|
||||
|
||||
/*
|
||||
* Serialisation contexts for the client.
|
@ -8,7 +8,7 @@
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.nodeapi.internal.serialization
|
||||
package net.corda.serialization.internal
|
||||
|
||||
import com.esotericsoftware.kryo.KryoException
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
@ -8,7 +8,7 @@
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.nodeapi.internal.serialization
|
||||
package net.corda.serialization.internal
|
||||
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.internal.AbstractAttachment
|
@ -8,7 +8,7 @@
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.nodeapi.internal.serialization
|
||||
package net.corda.serialization.internal
|
||||
|
||||
import java.io.EOFException
|
||||
import java.io.InputStream
|
||||
@ -18,9 +18,9 @@ import java.nio.ByteBuffer
|
||||
class OrdinalBits(private val ordinal: Int) {
|
||||
interface OrdinalWriter {
|
||||
val bits: OrdinalBits
|
||||
val encodedSize get() = 1
|
||||
fun writeTo(stream: OutputStream) = stream.write(bits.ordinal)
|
||||
fun putTo(buffer: ByteBuffer) = buffer.put(bits.ordinal.toByte())!!
|
||||
@JvmDefault val encodedSize get() = 1
|
||||
@JvmDefault fun writeTo(stream: OutputStream) = stream.write(bits.ordinal)
|
||||
@JvmDefault fun putTo(buffer: ByteBuffer) = buffer.put(bits.ordinal.toByte())!!
|
||||
}
|
||||
|
||||
init {
|
@ -8,13 +8,13 @@
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.nodeapi.internal.serialization
|
||||
package net.corda.serialization.internal
|
||||
|
||||
import net.corda.core.internal.VisibleForTesting
|
||||
import net.corda.core.serialization.SerializationEncoding
|
||||
import net.corda.core.utilities.ByteSequence
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.nodeapi.internal.serialization.OrdinalBits.OrdinalWriter
|
||||
import net.corda.serialization.internal.OrdinalBits.OrdinalWriter
|
||||
import org.iq80.snappy.SnappyFramedInputStream
|
||||
import org.iq80.snappy.SnappyFramedOutputStream
|
||||
import java.io.InputStream
|
@ -8,7 +8,7 @@
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.nodeapi.internal.serialization
|
||||
package net.corda.serialization.internal
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache
|
||||
import com.github.benmanes.caffeine.cache.Caffeine
|
||||
@ -17,9 +17,8 @@ import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.internal.copyBytes
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.utilities.ByteSequence
|
||||
import net.corda.nodeapi.internal.AttachmentsClassLoader
|
||||
import net.corda.nodeapi.internal.serialization.amqp.amqpMagic
|
||||
import net.corda.nodeapi.internal.serialization.kryo.kryoMagic
|
||||
import net.corda.serialization.internal.amqp.amqpMagic
|
||||
import net.corda.serialization.internal.kryo.kryoMagic
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.NotSerializableException
|
||||
import java.util.*
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user