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
import net.corda.core.DeleteForDJVM
import net.corda.core.DoNotImplement
import net.corda.core.KeepForDJVM
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.Vault
import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.transactions.LedgerTransaction
/**
@ -17,7 +19,36 @@ import net.corda.core.transactions.LedgerTransaction
*/
@CordaSerializable
@KeepForDJVM
@DoNotImplement
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.
*/
@ -28,6 +59,11 @@ sealed class StatePointer<T : ContractState> {
*/
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]
* or return an exception.
@ -54,7 +90,21 @@ sealed class StatePointer<T : ContractState> {
* throw a [TransactionResolutionException]
*/
@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.
*/
@ -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.
*/
@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
* [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()) {
val nextStatePointer = statePointerQueue.pop()
val hub = serviceHub
if (hub != null) {
if (hub != null && nextStatePointer.isResolved) {
val resolvedStateAndRef = nextStatePointer.resolve(hub)
// Don't add dupe reference states because CoreTransaction doesn't allow it.
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).
* ``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).
See :doc:`checkpoint-tooling` for more information.

View File

@ -38,7 +38,7 @@ class ResolveStatePointersTest {
) : LinearState
@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 barTwo = Bar(listOf(myself.party), 2, LinearPointer(barOne.linearId, barOne::class.java))
@ -67,18 +67,78 @@ class ResolveStatePointersTest {
}
@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 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)
val pointer = LinearPointer(linearId, 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 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())
// Resolve the StateRef to the actual state.