From f5c9fd8f4442d43032755f9f90a2b3a150fc461c Mon Sep 17 00:00:00 2001 From: josecoll Date: Fri, 17 Nov 2017 14:18:16 +0000 Subject: [PATCH] Cross provider Issuer Reference database storage (#2032) * consistent storage of Issuer Reference using `ByteArray` Kotlin type in Schema definition and a custom Hibernate Type to map this to a VARBINARY database type. Creation of a new Issued type now also validates maximum size permissible (512). --- core/build.gradle | 3 +++ .../kotlin/net/corda/core/contracts/Structures.kt | 8 ++++++++ .../kotlin/net/corda/core/crypto/SecureHash.kt | 2 ++ .../kotlin/net/corda/core/schemas/CommonSchema.kt | 13 +++++++------ .../asset/cash/selection/CashSelectionH2Impl.kt | 6 ++++-- .../cash/selection/CashSelectionPostgreSQLImpl.kt | 4 ++-- .../net/corda/finance/schemas/CashSchemaV1.kt | 5 ++++- .../finance/schemas/CommercialPaperSchemaV1.kt | 5 ++++- .../contracts/asset/DummyFungibleContract.kt | 2 +- .../corda/finance/schemas/SampleCashSchemaV1.kt | 5 ++++- .../corda/finance/schemas/SampleCashSchemaV2.kt | 5 +++-- .../corda/finance/schemas/SampleCashSchemaV3.kt | 5 ++++- .../schemas/SampleCommercialPaperSchemaV1.kt | 5 ++++- .../schemas/SampleCommercialPaperSchemaV2.kt | 10 +++++++--- .../services/persistence/HibernateConfiguration.kt | 14 ++++++++++++++ .../net/corda/node/services/vault/VaultSchema.kt | 5 ++++- 16 files changed, 75 insertions(+), 22 deletions(-) diff --git a/core/build.gradle b/core/build.gradle index 34d77bf8fc..ab67d23248 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -117,6 +117,9 @@ dependencies { // JPA 2.1 annotations. compile "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final" + + // required to use @Type annotation + compile "org.hibernate:hibernate-core:$hibernate_version" } // TODO Consider moving it to quasar-utils in the future (introduced with PR-1388) diff --git a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt index d0359f0ded..cc1abb0d56 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt @@ -45,9 +45,17 @@ interface NamedByHash { */ @CordaSerializable data class Issued(val issuer: PartyAndReference, val product: P) { + init { + require(issuer.reference.bytes.size <= MAX_ISSUER_REF_SIZE) { "Maximum issuer reference size is $MAX_ISSUER_REF_SIZE." } + } override fun toString() = "$product issued by $issuer" } +/** + * The maximum permissible size of an issuer reference. + */ +const val MAX_ISSUER_REF_SIZE = 512 + /** * Strips the issuer and returns an [Amount] of the raw token directly. This is useful when you are mixing code that * cares about specific issuers with code that will accept any, or which is imposing issuer constraints via some diff --git a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt index 6555ac6af7..9a2348c48f 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt @@ -103,3 +103,5 @@ fun ByteArray.sha256(): SecureHash.SHA256 = SecureHash.sha256(this) * Compute the SHA-256 hash for the contents of the [OpaqueBytes]. */ fun OpaqueBytes.sha256(): SecureHash.SHA256 = SecureHash.sha256(this.bytes) + + diff --git a/core/src/main/kotlin/net/corda/core/schemas/CommonSchema.kt b/core/src/main/kotlin/net/corda/core/schemas/CommonSchema.kt index 1d5559d45d..a630a7b8e3 100644 --- a/core/src/main/kotlin/net/corda/core/schemas/CommonSchema.kt +++ b/core/src/main/kotlin/net/corda/core/schemas/CommonSchema.kt @@ -1,12 +1,12 @@ package net.corda.core.schemas -import net.corda.core.contracts.ContractState -import net.corda.core.contracts.FungibleAsset -import net.corda.core.contracts.OwnableState -import net.corda.core.contracts.UniqueIdentifier +import net.corda.core.contracts.* import net.corda.core.identity.AbstractParty +import org.hibernate.annotations.Type import java.util.* -import javax.persistence.* +import javax.persistence.Column +import javax.persistence.ElementCollection +import javax.persistence.MappedSuperclass /** * JPA representation of the common schema entities @@ -74,7 +74,8 @@ object CommonSchemaV1 : MappedSchema(schemaFamily = CommonSchema.javaClass, vers @Column(name = "issuer_name") var issuer: AbstractParty, - @Column(name = "issuer_reference") + @Column(name = "issuer_ref", length = MAX_ISSUER_REF_SIZE) + @Type(type = "corda-wrapper-binary") var issuerRef: ByteArray ) : PersistentState() } \ No newline at end of file diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt index ef46782996..d8be0af698 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt @@ -4,7 +4,9 @@ import net.corda.core.contracts.Amount import net.corda.core.crypto.toStringShort import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party -import net.corda.core.utilities.* +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.debug +import net.corda.core.utilities.loggerFor import java.sql.Connection import java.sql.DatabaseMetaData import java.sql.ResultSet @@ -60,7 +62,7 @@ class CashSelectionH2Impl : AbstractCashSelection() { if (onlyFromIssuerParties.isNotEmpty()) psSelectJoin.setObject(++pIndex, onlyFromIssuerParties.map { it.owningKey.toStringShort() as Any}.toTypedArray() ) if (withIssuerRefs.isNotEmpty()) - psSelectJoin.setObject(++pIndex, withIssuerRefs.map { it.bytes.toHexString() as Any }.toTypedArray()) + psSelectJoin.setObject(++pIndex, withIssuerRefs.map { it.bytes as Any }.toTypedArray()) log.debug { psSelectJoin.toString() } return psSelectJoin.executeQuery() diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt index a792c60501..d76fde536d 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt @@ -63,13 +63,13 @@ class CashSelectionPostgreSQLImpl : AbstractCashSelection() { paramOffset += 1 } if (onlyFromIssuerParties.isNotEmpty()) { - val issuerKeys = connection.createArrayOf("VARCHAR", onlyFromIssuerParties.map + val issuerKeys = connection.createArrayOf("BYTEA", onlyFromIssuerParties.map { it.owningKey.toBase58String() }.toTypedArray()) statement.setArray(3 + paramOffset, issuerKeys) paramOffset += 1 } if (withIssuerRefs.isNotEmpty()) { - val issuerRefs = connection.createArrayOf("BYTEA", withIssuerRefs.map + val issuerRefs = connection.createArrayOf("VARCHAR", withIssuerRefs.map { it.bytes }.toTypedArray()) statement.setArray(3 + paramOffset, issuerRefs) paramOffset += 1 diff --git a/finance/src/main/kotlin/net/corda/finance/schemas/CashSchemaV1.kt b/finance/src/main/kotlin/net/corda/finance/schemas/CashSchemaV1.kt index 4c39f010e6..351dcc0c70 100644 --- a/finance/src/main/kotlin/net/corda/finance/schemas/CashSchemaV1.kt +++ b/finance/src/main/kotlin/net/corda/finance/schemas/CashSchemaV1.kt @@ -5,6 +5,8 @@ import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.MAX_HASH_HEX_SIZE +import net.corda.core.contracts.MAX_ISSUER_REF_SIZE +import org.hibernate.annotations.Type import javax.persistence.* /** @@ -36,7 +38,8 @@ object CashSchemaV1 : MappedSchema(schemaFamily = CashSchema.javaClass, version @Column(name = "issuer_key_hash", length = MAX_HASH_HEX_SIZE) var issuerPartyHash: String, - @Column(name = "issuer_ref") + @Column(name = "issuer_ref", length = MAX_ISSUER_REF_SIZE) + @Type(type = "corda-wrapper-binary") var issuerRef: ByteArray ) : PersistentState() } diff --git a/finance/src/main/kotlin/net/corda/finance/schemas/CommercialPaperSchemaV1.kt b/finance/src/main/kotlin/net/corda/finance/schemas/CommercialPaperSchemaV1.kt index 6af1da2ce1..c3a9743115 100644 --- a/finance/src/main/kotlin/net/corda/finance/schemas/CommercialPaperSchemaV1.kt +++ b/finance/src/main/kotlin/net/corda/finance/schemas/CommercialPaperSchemaV1.kt @@ -1,9 +1,11 @@ package net.corda.finance.schemas +import net.corda.core.contracts.MAX_ISSUER_REF_SIZE import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.MAX_HASH_HEX_SIZE +import org.hibernate.annotations.Type import java.time.Instant import javax.persistence.Column import javax.persistence.Entity @@ -48,7 +50,8 @@ object CommercialPaperSchemaV1 : MappedSchema(schemaFamily = CommercialPaperSche @Column(name = "face_value_issuer_key_hash", length = MAX_HASH_HEX_SIZE) var faceValueIssuerPartyHash: String, - @Column(name = "face_value_issuer_ref") + @Column(name = "face_value_issuer_ref", length = MAX_ISSUER_REF_SIZE) + @Type(type = "corda-wrapper-binary") var faceValueIssuerRef: ByteArray ) : PersistentState() } diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/DummyFungibleContract.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/DummyFungibleContract.kt index 62702f215d..62f3b7ad8f 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/DummyFungibleContract.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/DummyFungibleContract.kt @@ -55,7 +55,7 @@ class DummyFungibleContract : OnLedgerAsset SampleCashSchemaV3.PersistentCashState( participants = this.participants.toMutableSet(), diff --git a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV1.kt b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV1.kt index 5acd34fe12..7ac073621e 100644 --- a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV1.kt +++ b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV1.kt @@ -1,8 +1,10 @@ package net.corda.finance.schemas +import net.corda.core.contracts.MAX_ISSUER_REF_SIZE import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.utilities.MAX_HASH_HEX_SIZE +import org.hibernate.annotations.Type import javax.persistence.Column import javax.persistence.Entity import javax.persistence.Index @@ -35,7 +37,8 @@ object SampleCashSchemaV1 : MappedSchema(schemaFamily = CashSchema.javaClass, ve @Column(name = "issuer_key_hash", length = MAX_HASH_HEX_SIZE) var issuerPartyHash: String, - @Column(name = "issuer_ref") + @Column(name = "issuer_ref", length = MAX_ISSUER_REF_SIZE) + @Type(type = "corda-wrapper-binary") var issuerRef: ByteArray ) : PersistentState() } diff --git a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV2.kt b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV2.kt index 95108eddfa..8b548f89ce 100644 --- a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV2.kt +++ b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV2.kt @@ -3,6 +3,7 @@ package net.corda.finance.schemas import net.corda.core.identity.AbstractParty import net.corda.core.schemas.CommonSchemaV1 import net.corda.core.schemas.MappedSchema +import net.corda.core.utilities.OpaqueBytes import javax.persistence.Column import javax.persistence.Entity import javax.persistence.Index @@ -32,6 +33,6 @@ object SampleCashSchemaV2 : MappedSchema(schemaFamily = CashSchema.javaClass, ve @Transient val _issuerParty: AbstractParty, @Transient - val _issuerRef: ByteArray - ) : CommonSchemaV1.FungibleState(_participants.toMutableSet(), _owner, _quantity, _issuerParty, _issuerRef) + val _issuerRef: OpaqueBytes + ) : CommonSchemaV1.FungibleState(_participants.toMutableSet(), _owner, _quantity, _issuerParty, _issuerRef.bytes) } diff --git a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV3.kt b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV3.kt index eaae33410f..0a6c0cc289 100644 --- a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV3.kt +++ b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV3.kt @@ -1,8 +1,10 @@ package net.corda.finance.schemas +import net.corda.core.contracts.MAX_ISSUER_REF_SIZE import net.corda.core.identity.AbstractParty import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState +import org.hibernate.annotations.Type import javax.persistence.Column import javax.persistence.ElementCollection import javax.persistence.Entity @@ -37,7 +39,8 @@ object SampleCashSchemaV3 : MappedSchema(schemaFamily = CashSchema.javaClass, ve @Column(name = "issuer_name") var issuer: AbstractParty, - @Column(name = "issuer_ref") + @Column(name = "issuer_ref", length = MAX_ISSUER_REF_SIZE) + @Type(type = "corda-wrapper-binary") var issuerRef: ByteArray ) : PersistentState() } diff --git a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV1.kt b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV1.kt index e73983a5e2..f407bf7fc2 100644 --- a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV1.kt +++ b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV1.kt @@ -1,8 +1,10 @@ package net.corda.finance.schemas +import net.corda.core.contracts.MAX_ISSUER_REF_SIZE import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.utilities.MAX_HASH_HEX_SIZE +import org.hibernate.annotations.Type import java.time.Instant import javax.persistence.Column import javax.persistence.Entity @@ -46,7 +48,8 @@ object SampleCommercialPaperSchemaV1 : MappedSchema(schemaFamily = CommercialPap @Column(name = "face_value_issuer_key_hash", length = MAX_HASH_HEX_SIZE) var faceValueIssuerPartyHash: String, - @Column(name = "face_value_issuer_ref") + @Column(name = "face_value_issuer_ref", length = MAX_ISSUER_REF_SIZE) + @Type(type = "corda-wrapper-binary") var faceValueIssuerRef: ByteArray ) : PersistentState() } diff --git a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV2.kt b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV2.kt index d7bb1d5633..2c7882c048 100644 --- a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV2.kt +++ b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV2.kt @@ -1,9 +1,12 @@ package net.corda.finance.schemas +import net.corda.core.contracts.MAX_ISSUER_REF_SIZE import net.corda.core.identity.AbstractParty import net.corda.core.schemas.CommonSchemaV1 import net.corda.core.schemas.MappedSchema import net.corda.core.utilities.MAX_HASH_HEX_SIZE +import net.corda.core.utilities.OpaqueBytes +import org.hibernate.annotations.Type import java.time.Instant import javax.persistence.Column import javax.persistence.Entity @@ -30,7 +33,8 @@ object SampleCommercialPaperSchemaV2 : MappedSchema(schemaFamily = CommercialPap @Column(name = "face_value_issuer_key_hash", length = MAX_HASH_HEX_SIZE) var faceValueIssuerPartyHash: String, - @Column(name = "face_value_issuer_ref") + @Column(name = "face_value_issuer_ref", length = MAX_ISSUER_REF_SIZE) + @Type(type = "corda-wrapper-binary") var faceValueIssuerRef: ByteArray, /** parent attributes */ @@ -44,6 +48,6 @@ object SampleCommercialPaperSchemaV2 : MappedSchema(schemaFamily = CommercialPap @Transient val _issuerParty: AbstractParty, @Transient - val _issuerRef: ByteArray - ) : CommonSchemaV1.FungibleState(_participants.toMutableSet(), _owner, _quantity, _issuerParty, _issuerRef) + val _issuerRef: OpaqueBytes + ) : CommonSchemaV1.FungibleState(_participants.toMutableSet(), _owner, _quantity, _issuerParty, _issuerRef.bytes) } diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt index f5c7b4b5b5..cd6b93de57 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt @@ -3,6 +3,7 @@ package net.corda.node.services.persistence import net.corda.core.internal.castIfPossible import net.corda.core.node.services.IdentityService import net.corda.core.schemas.MappedSchema +import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.loggerFor import net.corda.core.utilities.toHexString import net.corda.node.services.api.SchemaService @@ -21,6 +22,7 @@ import org.hibernate.type.AbstractSingleColumnStandardBasicType import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry import org.hibernate.type.descriptor.java.PrimitiveByteArrayTypeDescriptor import org.hibernate.type.descriptor.sql.BlobTypeDescriptor +import org.hibernate.type.descriptor.sql.VarbinaryTypeDescriptor import java.sql.Connection import java.util.* import java.util.concurrent.ConcurrentHashMap @@ -83,6 +85,7 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab // Register a tweaked version of `org.hibernate.type.MaterializedBlobType` that truncates logged messages. // to avoid OOM when large blobs might get logged. applyBasicType(CordaMaterializedBlobType, CordaMaterializedBlobType.name) + applyBasicType(CordaWrapperBinaryType, CordaWrapperBinaryType.name) build() } @@ -139,4 +142,15 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab } } } + + // A tweaked version of `org.hibernate.type.WrapperBinaryType` that deals with ByteArray (java primitive byte[] type). + private object CordaWrapperBinaryType : AbstractSingleColumnStandardBasicType(VarbinaryTypeDescriptor.INSTANCE, PrimitiveByteArrayTypeDescriptor.INSTANCE) { + override fun getRegistrationKeys(): Array { + return arrayOf(name, "ByteArray", ByteArray::class.java.name) + } + + override fun getName(): String { + return "corda-wrapper-binary" + } + } } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt b/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt index d98dbb4c0b..e54f2cba23 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt @@ -1,6 +1,7 @@ package net.corda.node.services.vault import net.corda.core.contracts.ContractState +import net.corda.core.contracts.MAX_ISSUER_REF_SIZE import net.corda.core.contracts.UniqueIdentifier import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party @@ -9,6 +10,7 @@ import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.OpaqueBytes +import org.hibernate.annotations.Type import java.io.Serializable import java.time.Instant import java.util.* @@ -131,7 +133,8 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio @Column(name = "issuer_name") var issuer: AbstractParty, - @Column(name = "issuer_reference") + @Column(name = "issuer_ref", length = MAX_ISSUER_REF_SIZE) + @Type(type = "corda-wrapper-binary") var issuerRef: ByteArray ) : PersistentState() { constructor(_owner: AbstractParty, _quantity: Long, _issuerParty: AbstractParty, _issuerRef: OpaqueBytes, _participants: List) :