StatePointer (#4074)

* Introducing linear pointer.

* Added design document.
Added StatePointer interface.
Updated design document.
Updated StatePointer implementation.
Added doc section for state pointer.

* Updated design document.
Added API for StatePointer.

* Update core/src/main/kotlin/net/corda/core/contracts/Structures.kt

Co-Authored-By: roger3cev <roger.willis@r3cev.com>

* Update core/src/main/kotlin/net/corda/core/contracts/Structures.kt

Co-Authored-By: roger3cev <roger.willis@r3cev.com>

* Update core/src/main/kotlin/net/corda/core/contracts/Structures.kt

Co-Authored-By: roger3cev <roger.willis@r3cev.com>

* Update docs/source/design/linear-pointer/design.md

Co-Authored-By: roger3cev <roger.willis@r3cev.com>

* Update docs/source/design/linear-pointer/design.md

Co-Authored-By: roger3cev <roger.willis@r3cev.com>

* Resolve pointers
Added test to check pointers are resolved.
Updated docs and kdocs.
Reverted changes to api-current.txt
Revert "Reverted changes to api-current.txt"
This reverts commit dc1cece91a595a4e772f63917b830c7e1fd0586d.
Fix CI bug.
Made StatePointers type safe.
Resolving StatePointers is now optionally recursive
Addressed review comments.

Fixed compile error.

Addressed review comments.

Fixed bug in state pointer search.
Improved efficiency of state pointer search.

Removed whitespace.

TxBuilder logs warning when no service hub is supplied for resolving pointers as opposed to throwing an exception.

* Addressed review comments.
This commit is contained in:
Roger Willis
2018-11-05 10:33:26 +00:00
committed by GitHub
parent 3260d9f2c4
commit 80591bc6fd
8 changed files with 622 additions and 2 deletions

View File

@ -0,0 +1,158 @@
package net.corda.node.services.transactions
import net.corda.core.contracts.*
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.transactions.TransactionBuilder
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import net.corda.testing.node.MockServices
import net.corda.testing.node.makeTestIdentityService
import org.junit.Rule
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class ResolveStatePointersTest {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
private val myself = TestIdentity(CordaX500Name("Me", "London", "GB"))
private val notary = TestIdentity(DUMMY_NOTARY_NAME, 20)
private val cordapps = listOf("net.corda.testing.contracts")
private val databaseAndServices = MockServices.makeTestDatabaseAndMockServices(
cordappPackages = cordapps,
identityService = makeTestIdentityService(notary.identity, myself.identity),
initialIdentity = myself,
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
)
private val services = databaseAndServices.second
private data class Bar(
override val participants: List<AbstractParty> = listOf(),
val bar: Int = 0,
val nestedPointer: LinearPointer<*>? = null,
override val linearId: UniqueIdentifier = UniqueIdentifier()
) : LinearState
private data class Foo<T : LinearState>(val baz: LinearPointer<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))
private fun createPointedToState(contractState: ContractState): StateAndRef<Bar> {
// Create the pointed to state.
return services.run {
val tx = signInitialTransaction(TransactionBuilder(notary = notary.party, serviceHub = services).apply {
addOutputState(contractState, DummyContract.PROGRAM_ID)
addCommand(Command(DummyContract.Commands.Create(), myself.party.owningKey))
})
recordTransactions(listOf(tx))
tx.tx.outRefsOfType<Bar>().single()
}
}
@Test
fun `resolve state pointers and check reference state is added to transaction`() {
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)
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.
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 `resolving nested pointers is possible`() {
// Create barOne.
createPointedToState(barOne)
// Create another Bar - barTwo - which points to barOne.
val barTwoStateAndRef = createPointedToState(barTwo)
val barTwoLinearId = barTwoStateAndRef.state.data.linearId
// Add a new state containing a linear pointer.
val tx = TransactionBuilder(notary = notary.party, serviceHub = services).apply {
val pointer = LinearPointer(barTwoLinearId, barTwo::class.java)
addOutputState(Foo(pointer, listOf(myself.party)), DummyContract.PROGRAM_ID)
addOutputState(Foo(pointer, listOf()), DummyContract.PROGRAM_ID)
addCommand(Command(DummyContract.Commands.Create(), myself.party.owningKey))
}
tx.toLedgerTransaction(services).referenceStates.forEach { println(it) }
// Check both Bar StateRefs have been added to the transaction.
assertEquals(2, tx.referenceStates().size)
}
@Test
fun `Resolving to an unknown state throws an exception`() {
// Don't create the pointed to state.
// Resolve the pointer for barTwo.
assertFailsWith(IllegalStateException::class) {
barTwo.nestedPointer?.resolve(services)
}
}
@Test
fun `resolving an exited state throws an exception`() {
// Create barOne.
val stateAndRef = createPointedToState(barOne)
// Exit barOne from the ledger.
services.run {
val tx = signInitialTransaction(TransactionBuilder(notary = notary.party, serviceHub = services).apply {
addInputState(stateAndRef)
addCommand(Command(DummyContract.Commands.Move(), myself.party.owningKey))
})
recordTransactions(listOf(tx))
}
assertFailsWith(IllegalStateException::class) {
barTwo.nestedPointer?.resolve(services)
}
}
@Test
fun `resolve linear pointer with correct type`() {
val stateAndRef = createPointedToState(barOne)
val linearPointer = LinearPointer(stateAndRef.state.data.linearId, barOne::class.java)
val resolvedPointer = linearPointer.resolve(services)
assertEquals(stateAndRef::class.java, resolvedPointer::class.java)
}
@Test
fun `resolve state pointer in ledger transaction`() {
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)
addOutputState(Foo(pointer, listOf(myself.party)), DummyContract.PROGRAM_ID)
addCommand(Command(DummyContract.Commands.Create(), myself.party.owningKey))
}
val ltx = tx.toLedgerTransaction(services)
@Suppress("UNCHECKED_CAST")
val foo = ltx.outputs.single().data as Foo<Bar>
assertEquals(stateAndRef, foo.baz.resolve(ltx))
}
}