mirror of
https://github.com/corda/corda.git
synced 2025-04-07 19:34:41 +00:00
Merged in rnicoll-obligation-infrastructure (pull request #167)
Add obligation infrastructure
This commit is contained in:
commit
39d60bc74b
@ -69,13 +69,20 @@ class Cash : FungibleAsset<Currency>() {
|
||||
|
||||
// Just for grouping
|
||||
interface Commands : CommandData {
|
||||
class Move() : TypeOnlyCommandData(), FungibleAsset.Commands.Move
|
||||
/**
|
||||
* A command stating that money has been moved, optionally to fulfil another contract.
|
||||
*
|
||||
* @param contractHash the hash of the contract this cash is settling, to ensure one cash contract cannot be
|
||||
* used to settle multiple contracts. May be null, if this is not relevant to any other contract in the
|
||||
* same transaction
|
||||
*/
|
||||
data class Move(override val contractHash: SecureHash? = null) : FungibleAsset.Commands.Move, Commands
|
||||
|
||||
/**
|
||||
* Allows new cash states to be issued into existence: the nonce ("number used once") ensures the transaction
|
||||
* has a unique ID even when there are no inputs.
|
||||
*/
|
||||
data class Issue(override val nonce: Long = newSecureRandom().nextLong()) : FungibleAsset.Commands.Issue
|
||||
data class Issue(override val nonce: Long = newSecureRandom().nextLong()) : FungibleAsset.Commands.Issue, Commands
|
||||
|
||||
/**
|
||||
* A command stating that money has been withdrawn from the shared ledger and is now accounted for
|
||||
|
@ -41,13 +41,13 @@ abstract class FungibleAsset<T> : Contract {
|
||||
|
||||
// Just for grouping
|
||||
interface Commands : CommandData {
|
||||
interface Move : Commands
|
||||
interface Move : MoveCommand, Commands
|
||||
|
||||
/**
|
||||
* Allows new asset states to be issued into existence: the nonce ("number used once") ensures the transaction
|
||||
* has a unique ID even when there are no inputs.
|
||||
*/
|
||||
interface Issue : Commands { val nonce: Long }
|
||||
interface Issue : IssueCommand, Commands
|
||||
|
||||
/**
|
||||
* A command stating that money has been withdrawn from the shared ledger and is now accounted for
|
||||
@ -89,7 +89,7 @@ abstract class FungibleAsset<T> : Contract {
|
||||
(inputAmount == outputAmount + amountExitingLedger)
|
||||
}
|
||||
|
||||
verifyMoveCommands<Commands.Move>(inputs, tx)
|
||||
verifyMoveCommand<Commands.Move>(inputs, tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ fun List<AuthenticatedObject<CommandData>>.getTimestampByName(vararg names: Stri
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
// TODO: Can we have a common Move command for all contracts and avoid the reified type parameter here?
|
||||
inline fun <reified T : CommandData> verifyMoveCommands(inputs: List<OwnableState>, tx: TransactionForContract) {
|
||||
inline fun <reified T : CommandData> verifyMoveCommand(inputs: List<OwnableState>, tx: TransactionForContract) {
|
||||
// Now check the digital signatures on the move command. Every input has an owning public key, and we must
|
||||
// see a signature from each of those keys. The actual signatures have been verified against the transaction
|
||||
// data by the platform before execution.
|
||||
|
@ -228,6 +228,21 @@ data class Command(val value: CommandData, val signers: List<PublicKey>) {
|
||||
override fun toString() = "${commandDataToString()} with pubkeys ${signers.map { it.toStringShort() }}"
|
||||
}
|
||||
|
||||
/** A common issue command, to enforce that issue commands have a nonce value. */
|
||||
interface IssueCommand : CommandData {
|
||||
val nonce: Long
|
||||
}
|
||||
|
||||
/** A common move command for contracts which can change owner. */
|
||||
interface MoveCommand : CommandData {
|
||||
/**
|
||||
* Contract code the moved state(s) are for the attention of, for example to indicate that the states are moved in
|
||||
* order to settle an obligation contract's state object(s).
|
||||
*/
|
||||
// TODO: Replace SecureHash here with a general contract constraints object
|
||||
val contractHash: SecureHash?
|
||||
}
|
||||
|
||||
/** Wraps an object that was signed by a public key, which may be a well known/recognised institutional key. */
|
||||
data class AuthenticatedObject<out T : Any>(
|
||||
val signers: List<PublicKey>,
|
||||
|
@ -16,6 +16,8 @@ import com.r3corda.core.crypto.generateKeyPair
|
||||
import com.r3corda.core.crypto.sha256
|
||||
import com.r3corda.core.node.AttachmentsClassLoader
|
||||
import com.r3corda.core.node.services.AttachmentStorage
|
||||
import com.r3corda.core.utilities.NonEmptySet
|
||||
import com.r3corda.core.utilities.NonEmptySetSerializer
|
||||
import de.javakaffee.kryoserializers.ArraysAsListSerializer
|
||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||
@ -350,6 +352,9 @@ fun createKryo(k: Kryo = Kryo()): Kryo {
|
||||
register(Issued::class.java, ImmutableClassSerializer(Issued::class))
|
||||
register(TransactionState::class.java, ImmutableClassSerializer(TransactionState::class))
|
||||
|
||||
// This ensures a NonEmptySetSerializer is constructed with an initial value.
|
||||
register(NonEmptySet::class.java, NonEmptySetSerializer)
|
||||
|
||||
noReferencesWithin<WireTransaction>()
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,20 @@
|
||||
package com.r3corda.core.utilities
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo
|
||||
import com.esotericsoftware.kryo.Serializer
|
||||
import com.esotericsoftware.kryo.io.Input
|
||||
import com.esotericsoftware.kryo.io.Output
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* A set which is constrained to ensure it can never be empty. An initial value must be provided at
|
||||
* construction, and attempting to remove the last element will cause an IllegalStateException.
|
||||
* The underlying set is exposed for Kryo to access, but should not be accessed directly.
|
||||
*/
|
||||
class NonEmptySet<T>(initial: T, private val set: MutableSet<T> = mutableSetOf()) : MutableSet<T> {
|
||||
class NonEmptySet<T>(initial: T) : MutableSet<T> {
|
||||
private val set: MutableSet<T> = HashSet<T>()
|
||||
|
||||
init {
|
||||
require (set.isEmpty()) { "Provided set must be empty." }
|
||||
set.add(initial)
|
||||
}
|
||||
|
||||
@ -80,4 +88,28 @@ fun <T> nonEmptySetOf(initial: T, vararg elements: T): NonEmptySet<T> {
|
||||
// We add the first element twice, but it's a set, so who cares
|
||||
set.addAll(elements)
|
||||
return set
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom serializer which understands it has to read in an item before
|
||||
* trying to construct the set.
|
||||
*/
|
||||
object NonEmptySetSerializer : Serializer<NonEmptySet<Any>>() {
|
||||
override fun write(kryo: Kryo, output: Output, obj: NonEmptySet<Any>) {
|
||||
// Write out the contents as normal
|
||||
output.writeInt(obj.size)
|
||||
obj.forEach { kryo.writeClassAndObject(output, it) }
|
||||
}
|
||||
|
||||
override fun read(kryo: Kryo, input: Input, type: Class<NonEmptySet<Any>>): NonEmptySet<Any> {
|
||||
val size = input.readInt()
|
||||
require(size >= 1) { "Size is positive" }
|
||||
// TODO: Is there an upper limit we can apply to how big one of these could be?
|
||||
val first = kryo.readClassAndObject(input)
|
||||
// Read the first item and use it to construct the NonEmptySet
|
||||
val set = NonEmptySet(first)
|
||||
// Read in the rest of the set
|
||||
for (i in 2..size) { set.add(kryo.readClassAndObject(input)) }
|
||||
return set
|
||||
}
|
||||
}
|
@ -8,6 +8,8 @@ import com.google.common.collect.testing.testers.CollectionAddAllTester
|
||||
import com.google.common.collect.testing.testers.CollectionClearTester
|
||||
import com.google.common.collect.testing.testers.CollectionRemoveAllTester
|
||||
import com.google.common.collect.testing.testers.CollectionRetainAllTester
|
||||
import com.r3corda.core.serialization.deserialize
|
||||
import com.r3corda.core.serialization.serialize
|
||||
import junit.framework.TestSuite
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@ -17,7 +19,8 @@ import kotlin.test.assertEquals
|
||||
@RunWith(Suite::class)
|
||||
@Suite.SuiteClasses(
|
||||
NonEmptySetTest.Guava::class,
|
||||
NonEmptySetTest.Remove::class
|
||||
NonEmptySetTest.Remove::class,
|
||||
NonEmptySetTest.Serializer::class
|
||||
)
|
||||
class NonEmptySetTest {
|
||||
/**
|
||||
@ -93,6 +96,20 @@ class NonEmptySetTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test serialization/deserialization.
|
||||
*/
|
||||
class Serializer {
|
||||
@Test
|
||||
fun `serialize deserialize`() {
|
||||
val expected: NonEmptySet<Int> = nonEmptySetOf(-17, 22, 17)
|
||||
val serialized = expected.serialize().bits
|
||||
val actual = serialized.deserialize<NonEmptySet<Int>>()
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user