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).
This commit is contained in:
josecoll 2017-11-17 14:18:16 +00:00 committed by GitHub
parent 19aba62fc6
commit f5c9fd8f44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 75 additions and 22 deletions

View File

@ -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)

View File

@ -45,9 +45,17 @@ interface NamedByHash {
*/
@CordaSerializable
data class Issued<out P : Any>(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

View File

@ -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)

View File

@ -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()
}

View File

@ -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()

View File

@ -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

View File

@ -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()
}

View File

@ -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()
}

View File

@ -55,7 +55,7 @@ class DummyFungibleContract : OnLedgerAsset<Currency, DummyFungibleContract.Comm
_quantity = this.amount.quantity,
currency = this.amount.token.product.currencyCode,
_issuerParty = this.amount.token.issuer.party,
_issuerRef = this.amount.token.issuer.reference.bytes
_issuerRef = this.amount.token.issuer.reference
)
is SampleCashSchemaV3 -> SampleCashSchemaV3.PersistentCashState(
participants = this.participants.toMutableSet(),

View File

@ -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()
}

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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)
}

View File

@ -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<ByteArray>(VarbinaryTypeDescriptor.INSTANCE, PrimitiveByteArrayTypeDescriptor.INSTANCE) {
override fun getRegistrationKeys(): Array<String> {
return arrayOf(name, "ByteArray", ByteArray::class.java.name)
}
override fun getName(): String {
return "corda-wrapper-binary"
}
}
}

View File

@ -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<AbstractParty>) :