[CORDA-2086] Allow transactions to be re-recorded using StatesToRecord.ALL_VISIBLE (#5184)

This commit is contained in:
James Higgs
2019-07-04 14:00:42 +01:00
committed by Shams Asari
parent 479d61cfde
commit 075f68f179
8 changed files with 349 additions and 80 deletions

View File

@ -8,7 +8,6 @@ import net.corda.core.node.StatesToRecord
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.toFuture
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
@ -32,7 +31,7 @@ class ReferencedStatesFlowTests {
@Before
fun setup() {
nodes = (0..1).map {
nodes = (0..2).map {
mockNet.createNode(
parameters = InternalMockNodeParameters(version = VersionInfo(4, "Blah", "Blah", "Blah"))
)
@ -77,7 +76,7 @@ class ReferencedStatesFlowTests {
// 2. Use the "newRefState" a transaction involving another party (nodes[1]) which creates a new state. They should store the new state and the reference state.
val newTx = nodes[0].services.startFlow(UseRefState(nodes[1].info.legalIdentities.first(), newRefState.state.data.linearId)).resultFuture.getOrThrow()
// Wait until node 1 stores the new tx.
nodes[1].services.validatedTransactions.updates.filter { it.id == newTx.id }.toFuture().getOrThrow()
nodes[1].services.validatedTransactions.trackTransaction(newTx.id).getOrThrow()
// Check that nodes[1] has finished recording the transaction (and updating the vault.. hopefully!).
// nodes[1] should have two states. The newly created output of type "Regular.State" and the reference state created by nodes[0].
assertEquals(2, nodes[1].services.vaultService.queryBy<LinearState>().states.size)
@ -108,7 +107,7 @@ class ReferencedStatesFlowTests {
// 2. Use the "newRefState" a transaction involving another party (nodes[1]) which creates a new state. They should store the new state and the reference state.
val newTx = nodes[0].services.startFlow(UseRefState(nodes[1].info.legalIdentities.first(), newRefState.state.data.linearId)).resultFuture.getOrThrow()
// Wait until node 1 stores the new tx.
nodes[1].services.validatedTransactions.updates.filter { it.id == newTx.id }.toFuture().getOrThrow()
nodes[1].services.validatedTransactions.trackTransaction(newTx.id).getOrThrow()
// Check that nodes[1] has finished recording the transaction (and updating the vault.. hopefully!).
val allRefStates = nodes[1].services.vaultService.queryBy<LinearState>()
// nodes[1] should have two states. The newly created output and the reference state created by nodes[0].
@ -125,7 +124,7 @@ class ReferencedStatesFlowTests {
val newTx = nodes[0].services.startFlow(UseRefState(nodes[1].info.legalIdentities.first(), newRefState.state.data.linearId))
.resultFuture.getOrThrow()
// Wait until node 1 stores the new tx.
nodes[1].services.validatedTransactions.updates.filter { it.id == newTx.id }.toFuture().getOrThrow()
nodes[1].services.validatedTransactions.trackTransaction(newTx.id).getOrThrow()
// Check that nodes[1] has finished recording the transaction (and updating the vault.. hopefully!).
// nodes[1] should have two states. The newly created output of type "Regular.State" and the reference state created by nodes[0].
assertEquals(2, nodes[1].services.vaultService.queryBy<LinearState>().states.size)
@ -144,13 +143,13 @@ class ReferencedStatesFlowTests {
assertEquals(Vault.StateStatus.UNCONSUMED, theReferencedStateOnNodeZero.statesMetadata.single().status)
// 3. Update the reference state but don't share the update.
val updatedRefTx = nodes[0].services.startFlow(UpdateRefState(newRefState)).resultFuture.getOrThrow()
nodes[0].services.startFlow(UpdateRefState(newRefState)).resultFuture.getOrThrow()
// 4. Use the evolved state as a reference state.
val updatedTx = nodes[0].services.startFlow(UseRefState(nodes[1].info.legalIdentities.first(), newRefState.state.data.linearId))
.resultFuture.getOrThrow()
// Wait until node 1 stores the new tx.
nodes[1].services.validatedTransactions.updates.filter { it.id == updatedTx.id }.toFuture().getOrThrow()
nodes[1].services.validatedTransactions.trackTransaction(updatedTx.id).getOrThrow()
// Check that nodes[1] has finished recording the transaction (and updating the vault.. hopefully!).
// nodes[1] should have four states. The originals, plus the newly created output of type "Regular.State" and the reference state created by nodes[0].
assertEquals(4, nodes[1].services.vaultService.queryBy<LinearState>(QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL)).states.size)
@ -167,6 +166,37 @@ class ReferencedStatesFlowTests {
assertEquals(Vault.StateStatus.CONSUMED, theOriginalReferencedStateOnNodeZero.statesMetadata.single().status)
}
@Test
fun `check consumed reference state is found if a transaction refers to it`() {
// 1. Create a state to be used as a reference state. Don't share it.
val newRefTx = nodes[0].services.startFlow(CreateRefState()).resultFuture.getOrThrow()
val newRefState = newRefTx.tx.outRefsOfType<RefState.State>().single()
// 2. Use the "newRefState" in a transaction involving another party (nodes[1]) which creates a new state. They should store the new state and the reference state.
val newTx = nodes[0].services.startFlow(UseRefState(nodes[1].info.legalIdentities.first(), newRefState.state.data.linearId))
.resultFuture.getOrThrow()
// Wait until node 1 stores the new tx.
nodes[1].services.validatedTransactions.trackTransaction(newTx.id).getOrThrow()
// Check that nodes[1] has finished recording the transaction (and updating the vault.. hopefully!).
// nodes[1] should have two states. The newly created output of type "Regular.State" and the reference state created by nodes[0].
assertEquals(2, nodes[1].services.vaultService.queryBy<LinearState>().states.size)
// 3. Update the reference state but don't share the update.
val updatedRefTx = nodes[0].services.startFlow(UpdateRefState(newRefState)).resultFuture.getOrThrow()
// 4. Now report the transactions that created the two reference states to a third party.
nodes[0].services.startFlow(ReportTransactionFlow(nodes[2].info.legalIdentities.first(), newRefTx)).resultFuture.getOrThrow()
nodes[0].services.startFlow(ReportTransactionFlow(nodes[2].info.legalIdentities.first(), updatedRefTx)).resultFuture.getOrThrow()
// Check that there are two linear states in the vault (note that one is consumed)
assertEquals(2, nodes[2].services.vaultService.queryBy<LinearState>(QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL)).states.size)
// 5. Report the transaction that uses the consumed reference state
nodes[0].services.startFlow(ReportTransactionFlow(nodes[2].info.legalIdentities.first(), newTx)).resultFuture.getOrThrow()
// There should be 3 linear states in the vault
assertEquals(3, nodes[2].services.vaultService.queryBy<LinearState>(QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL)).states.size)
}
// A dummy reference state contract.
class RefState : Contract {
companion object {
@ -284,4 +314,27 @@ class ReferencedStatesFlowTests {
return subFlow(ReceiveFinalityFlow(otherSession, statesToRecord = StatesToRecord.ONLY_RELEVANT))
}
}
// A flow to report a transaction to a third party.
@InitiatingFlow
@StartableByRPC
class ReportTransactionFlow(private val reportee: Party,
private val signedTx: SignedTransaction) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val session = initiateFlow(reportee)
subFlow(SendTransactionFlow(session, signedTx))
session.receive<Unit>()
}
}
@InitiatedBy(ReportTransactionFlow::class)
class ReceiveReportedTransactionFlow(private val otherSideSession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
subFlow(ReceiveTransactionFlow(otherSideSession, true, StatesToRecord.ALL_VISIBLE))
otherSideSession.send(Unit)
}
}
}