mirror of
https://github.com/corda/corda.git
synced 2024-12-20 05:28:21 +00:00
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:
parent
33055702a8
commit
4e6edd012a
@ -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.
|
||||||
|
@ -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()) {
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user