Introduce OpaqueBytes, a simple wrapper around the JVM level byte[] which fixes hashCode and equals to use value-type identity rather than instance identity. Allows for more natural code.

This commit is contained in:
Mike Hearn 2015-11-03 16:00:43 +01:00
parent 12f5ddb0aa
commit 8f46fb4406
5 changed files with 26 additions and 16 deletions

View File

@ -5,7 +5,6 @@ import java.util.*
// //
// Cash // Cash
// TODO: Think about state merging: when does it make sense to merge multiple cash states from the same issuer? // TODO: Think about state merging: when does it make sense to merge multiple cash states from the same issuer?
// TODO: Does multi-currency also make sense? Probably? // TODO: Does multi-currency also make sense? Probably?
// TODO: Implement a generate function. // TODO: Implement a generate function.
@ -19,7 +18,7 @@ data class CashState(
val issuingInstitution: Institution, val issuingInstitution: Institution,
/** Whatever internal ID the bank needs in order to locate that deposit, may be encrypted (propagated) */ /** Whatever internal ID the bank needs in order to locate that deposit, may be encrypted (propagated) */
val depositReference: ByteArray, val depositReference: OpaqueBytes,
val amount: Amount, val amount: Amount,
@ -69,8 +68,7 @@ class CashContract : Contract {
requireThat { requireThat {
"for issuer ${issuer.name} the amounts balance" by (inputAmount == outputAmount + amountExitingLedger) "for issuer ${issuer.name} the amounts balance" by (inputAmount == outputAmount + amountExitingLedger)
// TODO: Introduce a byte array wrapper that makes == do what we expect (Kotlin does not do this for us) "for issuer ${issuer.name} the deposit references are the same" by outputs.all { it.depositReference == depositReference }
"for issuer ${issuer.name} the deposit references are the same" by outputs.all { Arrays.equals(it.depositReference, depositReference) }
} }
} }

View File

@ -1,9 +1,10 @@
import com.google.common.io.BaseEncoding import com.google.common.io.BaseEncoding
import java.security.MessageDigest import java.security.MessageDigest
import java.security.PublicKey import java.security.PublicKey
import java.util.*
// "sealed" here means there can't be any subclasses other than the ones defined here. // "sealed" here means there can't be any subclasses other than the ones defined here.
sealed class SecureHash(val bits: ByteArray) { sealed class SecureHash(bits: ByteArray) : OpaqueBytes(bits) {
class SHA256(bits: ByteArray) : SecureHash(bits) { class SHA256(bits: ByteArray) : SecureHash(bits) {
init { require(bits.size == 32) } init { require(bits.size == 32) }
} }
@ -28,7 +29,7 @@ sealed class SecureHash(val bits: ByteArray) {
* A wrapper around a digital signature. The covering field is a generic tag usable by whatever is interpreting the * A wrapper around a digital signature. The covering field is a generic tag usable by whatever is interpreting the
* signature. * signature.
*/ */
sealed class DigitalSignature(val bits: ByteArray, val covering: Int) { sealed class DigitalSignature(bits: ByteArray, val covering: Int) : OpaqueBytes(bits) {
/** A digital signature that identifies who the public key is owned by */ /** A digital signature that identifies who the public key is owned by */
open class WithKey(val by: PublicKey, bits: ByteArray, covering: Int) : DigitalSignature(bits, covering) open class WithKey(val by: PublicKey, bits: ByteArray, covering: Int) : DigitalSignature(bits, covering)
class LegallyIdentifiable(val signer: Institution, bits: ByteArray, covering: Int) : WithKey(signer.owningKey, bits, covering) class LegallyIdentifiable(val signer: Institution, bits: ByteArray, covering: Int) : WithKey(signer.owningKey, bits, covering)

View File

@ -1,6 +1,5 @@
import java.security.PublicKey import java.security.PublicKey
import java.security.Timestamp import java.security.Timestamp
import java.util.*
/** /**
* A contract state (or just "state") contains opaque data used by a contract program. It can be thought of as a disk * A contract state (or just "state") contains opaque data used by a contract program. It can be thought of as a disk
@ -67,7 +66,7 @@ data class SignedCommand(
val commandDataSignature: DigitalSignature.WithKey, val commandDataSignature: DigitalSignature.WithKey,
/** Command data, deserialized to an implementation of [Command] */ /** Command data, deserialized to an implementation of [Command] */
val serialized: ByteArray, val serialized: OpaqueBytes,
/** Identifies what command the serialized data contains (should maybe be a hash too) */ /** Identifies what command the serialized data contains (should maybe be a hash too) */
val classID: String, val classID: String,
/** Hash of a derivative of the transaction data, so this command can only ever apply to one transaction */ /** Hash of a derivative of the transaction data, so this command can only ever apply to one transaction */

View File

@ -1,9 +1,22 @@
import java.math.BigInteger import com.google.common.io.BaseEncoding
import java.security.PublicKey
import java.util.* import java.util.*
import kotlin.test.assertTrue
import kotlin.test.fail
/** A simple class that wraps a byte array and makes the equals/hashCode/toString methods work as you actually expect */
open class OpaqueBytes(val bits: ByteArray) {
companion object {
fun of(vararg b: Byte) = OpaqueBytes(byteArrayOf(*b))
}
override fun equals(other: Any?): Boolean{
if (this === other) return true
if (other !is OpaqueBytes) return false
return Arrays.equals(bits, other.bits)
}
override fun hashCode() = Arrays.hashCode(bits)
override fun toString() = "[" + BaseEncoding.base16().encode(bits) + "]"
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //

View File

@ -4,11 +4,10 @@ import org.junit.Test
// 1. No duplicate input states // 1. No duplicate input states
// 2. There must be at least one input state (note: not "one of the type the contract wants") // 2. There must be at least one input state (note: not "one of the type the contract wants")
class CashTests { class CashTests {
val inState = CashState( val inState = CashState(
issuingInstitution = MEGA_CORP, issuingInstitution = MEGA_CORP,
depositReference = byteArrayOf(1), depositReference = OpaqueBytes.of(1),
amount = 1000.DOLLARS, amount = 1000.DOLLARS,
owner = DUMMY_PUBKEY_1 owner = DUMMY_PUBKEY_1
) )
@ -100,8 +99,8 @@ class CashTests {
// Can't change deposit reference when splitting. // Can't change deposit reference when splitting.
transaction { transaction {
input { inState } input { inState }
output { outState.copy(depositReference = byteArrayOf(0), amount = inState.amount / 2) } output { outState.copy(depositReference = OpaqueBytes.of(0), amount = inState.amount / 2) }
output { outState.copy(depositReference = byteArrayOf(1), amount = inState.amount / 2) } output { outState.copy(depositReference = OpaqueBytes.of(1), amount = inState.amount / 2) }
contract `fails requirement` "the deposit references are the same" contract `fails requirement` "the deposit references are the same"
} }
// Can't mix currencies. // Can't mix currencies.