CORDA-3133 (Version 2) (#5428)

* CORDA-3133 [v1]
This proposal is a little more flexible by design. It adds a property to the state pointer class, allowing them to be referenced in transactions on a per-state-pointer basis.

* CORDA-3133
 - Updated `resolveAtTransaction` to `isResolved`.
 - Moved `isResolved` out of the constructor into an abstract property.
 - Added deprecation constructor declaration for backwards compatibility.

* CORDA-3133 (version 2)
 - Added required changes to state pointers as per PR comments.
 - Added unit tests to ensure isResolved can be used to configure when state pointers should be resolved to reference inputs.

* CORDA-3133 (version 2)
 - fixed unit tests.
 - added comment to changelog.rst.
 - added helper functions to `StatePointer` to allow easier creation of static and linear pointers.
This commit is contained in:
Matthew Layton 2019-09-11 08:51:27 +01:00 committed by Roger Willis
parent 33055702a8
commit 4e6edd012a
4 changed files with 133 additions and 7 deletions

View File

@ -1,12 +1,14 @@
package net.corda.core.contracts package net.corda.core.contracts
import net.corda.core.DeleteForDJVM import net.corda.core.DeleteForDJVM
import net.corda.core.DoNotImplement
import net.corda.core.KeepForDJVM import net.corda.core.KeepForDJVM
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import net.corda.core.node.services.queryBy import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
/** /**
@ -17,7 +19,36 @@ import net.corda.core.transactions.LedgerTransaction
*/ */
@CordaSerializable @CordaSerializable
@KeepForDJVM @KeepForDJVM
@DoNotImplement
sealed class StatePointer<T : ContractState> { sealed class StatePointer<T : ContractState> {
companion object {
/**
* Creates a [StaticPointer] to the specified contract state.
*
* @param stateAndRef The [StateAndRef] instance from which to construct a static pointer.
* @param isResolved Determines whether the state pointer should be resolved to a reference input when included in a transaction.
* @return Returns a [StaticPointer] to the specified contract state.
*/
inline fun <reified T : ContractState> staticPointer(
stateAndRef: StateAndRef<T>,
isResolved: Boolean = false
) = StaticPointer(stateAndRef.ref, T::class.java, isResolved)
/**
* Creates a [LinearPointer] to the specified linear state.
*
* @param state The [LinearState] instance from which to construct a linear pointer.
* @param isResolved Determines whether the state pointer should be resolved to a reference input when included in a transaction.
* @return Returns a [LinearPointer] to the specified linear state.
*/
inline fun <reified T : LinearState> linearPointer(
state: T,
isResolved: Boolean = true
) = LinearPointer(state.linearId, T::class.java, isResolved)
}
/** /**
* An identifier for the [ContractState] that this [StatePointer] points to. * An identifier for the [ContractState] that this [StatePointer] points to.
*/ */
@ -28,6 +59,11 @@ sealed class StatePointer<T : ContractState> {
*/ */
abstract val type: Class<T> abstract val type: Class<T>
/**
* Determines whether the state pointer should be resolved to a reference input when used in a transaction.
*/
abstract val isResolved: Boolean
/** /**
* Resolves a [StatePointer] to a [StateAndRef] via a vault query. This method will either return a [StateAndRef] * Resolves a [StatePointer] to a [StateAndRef] via a vault query. This method will either return a [StateAndRef]
* or return an exception. * or return an exception.
@ -54,7 +90,21 @@ sealed class StatePointer<T : ContractState> {
* throw a [TransactionResolutionException] * throw a [TransactionResolutionException]
*/ */
@KeepForDJVM @KeepForDJVM
class StaticPointer<T : ContractState>(override val pointer: StateRef, override val type: Class<T>) : StatePointer<T>() { class StaticPointer<T : ContractState>(
override val pointer: StateRef,
override val type: Class<T>,
override val isResolved: Boolean = false
) : StatePointer<T>() {
/**
* Allows this class to be evolved through backwards compatibility with version 1 of this class.
*
* @param pointer The state reference that this points to.
* @param type The underlying [LinearState] type that this points to.
*/
@DeprecatedConstructorForDeserialization(version = 1)
constructor(pointer: StateRef, type: Class<T>) : this(pointer, type, false)
/** /**
* Resolves a [StaticPointer] to a [StateAndRef] via a [StateRef] look-up. * Resolves a [StaticPointer] to a [StateAndRef] via a [StateRef] look-up.
*/ */
@ -99,7 +149,21 @@ class StaticPointer<T : ContractState>(override val pointer: StateRef, override
* of the [LinearState] is available. See reference states documentation on docs.corda.net for more info. * of the [LinearState] is available. See reference states documentation on docs.corda.net for more info.
*/ */
@KeepForDJVM @KeepForDJVM
class LinearPointer<T : LinearState>(override val pointer: UniqueIdentifier, override val type: Class<T>) : StatePointer<T>() { class LinearPointer<T : LinearState>(
override val pointer: UniqueIdentifier,
override val type: Class<T>,
override val isResolved: Boolean = true
) : StatePointer<T>() {
/**
* Allows this class to be evolved through backwards compatibility with version 1 of this class.
*
* @param pointer The unique identifier that this points to.
* @param type The underlying [LinearState] type that this points to.
*/
@DeprecatedConstructorForDeserialization(version = 1)
constructor(pointer: UniqueIdentifier, type: Class<T>) : this(pointer, type, true)
/** /**
* Resolves a [LinearPointer] using the [UniqueIdentifier] contained in the [pointer] property. Returns a * Resolves a [LinearPointer] using the [UniqueIdentifier] contained in the [pointer] property. Returns a
* [StateAndRef] containing the latest version of the [LinearState] that the node calling [resolve] is aware of. * [StateAndRef] containing the latest version of the [LinearState] that the node calling [resolve] is aware of.

View File

@ -593,7 +593,7 @@ open class TransactionBuilder(
while (statePointerQueue.isNotEmpty()) { while (statePointerQueue.isNotEmpty()) {
val nextStatePointer = statePointerQueue.pop() val nextStatePointer = statePointerQueue.pop()
val hub = serviceHub val hub = serviceHub
if (hub != null) { if (hub != null && nextStatePointer.isResolved) {
val resolvedStateAndRef = nextStatePointer.resolve(hub) val resolvedStateAndRef = nextStatePointer.resolve(hub)
// Don't add dupe reference states because CoreTransaction doesn't allow it. // Don't add dupe reference states because CoreTransaction doesn't allow it.
if (resolvedStateAndRef.ref !in referenceStates()) { if (resolvedStateAndRef.ref !in referenceStates()) {

View File

@ -15,6 +15,8 @@ Unreleased
* Introduced a new API on ``KeyManagementService`` which facilitates lookups of ``PublicKey`` s to ``externalId`` s (Account IDs). * Introduced a new API on ``KeyManagementService`` which facilitates lookups of ``PublicKey`` s to ``externalId`` s (Account IDs).
* ``StatePointer`` has been marked as ```@DoNotImplement``, which was an omission in the original release.
* Introduced a new low level flow diagnostics tool: checkpoint agent (that can be used standalone or in conjunction with the ``checkpoints dump`` shell command). * Introduced a new low level flow diagnostics tool: checkpoint agent (that can be used standalone or in conjunction with the ``checkpoints dump`` shell command).
See :doc:`checkpoint-tooling` for more information. See :doc:`checkpoint-tooling` for more information.

View File

@ -38,7 +38,7 @@ class ResolveStatePointersTest {
) : LinearState ) : LinearState
@BelongsToContract(DummyContract::class) @BelongsToContract(DummyContract::class)
private data class Foo<T : LinearState>(val baz: LinearPointer<T>, override val participants: List<AbstractParty>) : ContractState private data class Foo<T : ContractState>(val baz: StatePointer<T>, override val participants: List<AbstractParty>) : ContractState
private val barOne = Bar(listOf(myself.party), 1) private val barOne = Bar(listOf(myself.party), 1)
private val barTwo = Bar(listOf(myself.party), 2, LinearPointer(barOne.linearId, barOne::class.java)) private val barTwo = Bar(listOf(myself.party), 2, LinearPointer(barOne.linearId, barOne::class.java))
@ -67,18 +67,78 @@ class ResolveStatePointersTest {
} }
@Test @Test
fun `resolve state pointers and check reference state is added to transaction`() { fun `resolve linear pointers and check reference state is not added to transaction when isResolved is false`() {
val stateAndRef = createPointedToState(barOne) val stateAndRef = createPointedToState(barOne)
val linearId = stateAndRef.state.data.linearId val linearId = stateAndRef.state.data.linearId
// Add a new state containing a linear pointer. // Add a new state containing a linear pointer.
val tx = TransactionBuilder(notary = notary.party, serviceHub = services).apply { val tx = TransactionBuilder(notary = notary.party, serviceHub = services).apply {
val pointer = LinearPointer(linearId, barOne::class.java) val pointer = LinearPointer(linearId, barOne::class.java, false)
addOutputState(Foo(pointer, listOf(myself.party)), DummyContract.PROGRAM_ID) addOutputState(Foo(pointer, listOf(myself.party)), DummyContract.PROGRAM_ID)
addCommand(Command(DummyContract.Commands.Create(), myself.party.owningKey)) addCommand(Command(DummyContract.Commands.Create(), myself.party.owningKey))
} }
// Check the StateRef for the pointed-to state is added as a reference. // Check the StateRef for the pointed-to state is not added as a reference.
assert(tx.referenceStates().none { it == stateAndRef.ref })
// Resolve the StateRef to the actual state.
val ltx = tx.toLedgerTransaction(services)
assertEquals(emptyList(), ltx.referenceStates)
}
@Test
fun `resolve linear pointers and check reference state is added to transaction when isResolved is true`() {
val stateAndRef = createPointedToState(barOne)
val linearId = stateAndRef.state.data.linearId
// Add a new state containing a linear pointer.
val tx = TransactionBuilder(notary = notary.party, serviceHub = services).apply {
val pointer = LinearPointer(linearId, barOne::class.java, true)
addOutputState(Foo(pointer, listOf(myself.party)), DummyContract.PROGRAM_ID)
addCommand(Command(DummyContract.Commands.Create(), myself.party.owningKey))
}
// Check the StateRef for the pointed-to state is not added as a reference.
assertEquals(stateAndRef.ref, tx.referenceStates().single())
// Resolve the StateRef to the actual state.
val ltx = tx.toLedgerTransaction(services)
assertEquals(barOne, ltx.referenceStates.single())
}
@Test
fun `resolve static pointers and check reference state is not added to transaction when isResolved is false`() {
val stateAndRef = createPointedToState(barOne)
val stateRef = stateAndRef.ref
// Add a new state containing a linear pointer.
val tx = TransactionBuilder(notary = notary.party, serviceHub = services).apply {
val pointer = StaticPointer(stateRef, barOne::class.java, false)
addOutputState(Foo(pointer, listOf(myself.party)), DummyContract.PROGRAM_ID)
addCommand(Command(DummyContract.Commands.Create(), myself.party.owningKey))
}
// Check the StateRef for the pointed-to state is not added as a reference.
assert(tx.referenceStates().none { it == stateAndRef.ref })
// Resolve the StateRef to the actual state.
val ltx = tx.toLedgerTransaction(services)
assertEquals(emptyList(), ltx.referenceStates)
}
@Test
fun `resolve static pointers and check reference state is added to transaction when isResolved is true`() {
val stateAndRef = createPointedToState(barOne)
val stateRef = stateAndRef.ref
// Add a new state containing a linear pointer.
val tx = TransactionBuilder(notary = notary.party, serviceHub = services).apply {
val pointer = StaticPointer(stateRef, barOne::class.java, true)
addOutputState(Foo(pointer, listOf(myself.party)), DummyContract.PROGRAM_ID)
addCommand(Command(DummyContract.Commands.Create(), myself.party.owningKey))
}
// Check the StateRef for the pointed-to state is not added as a reference.
assertEquals(stateAndRef.ref, tx.referenceStates().single()) assertEquals(stateAndRef.ref, tx.referenceStates().single())
// Resolve the StateRef to the actual state. // Resolve the StateRef to the actual state.