Skip to content
19 changes: 18 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,23 @@ buildscript { //properties that you need to build the project
mavenLocal()
mavenCentral()
jcenter()
maven { url "${artifactory_contextUrl}/corda-releases" }
maven {
url "https://software.r3.com/artifactory/dc-lib-dev"
credentials {
username = System.getenv('CORDA_ARTIFACTORY_USERNAME') ?: System.getProperty('corda.artifactory.username')
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD') ?: System.getProperty('corda.artifactory.password')
}
}
maven { url "${artifactory_contextUrl}/corda" }
maven { url "${artifactory_contextUrl}/corda-lib" }
}

dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "net.corda.plugins:cordapp:$corda_gradle_plugins_version"
classpath "net.corda.plugins:cordformation:$corda_gradle_plugins_version"
classpath "net.corda.plugins:quasar-utils:$corda_gradle_plugins_version"
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
}
}

Expand Down Expand Up @@ -41,6 +50,7 @@ allprojects { //Properties that you need to compile your project (The applicatio

apply from: "${rootProject.projectDir}/repositories.gradle"
apply plugin: 'kotlin'
apply plugin: "kotlin-jpa"

repositories {
mavenLocal()
Expand All @@ -49,6 +59,13 @@ allprojects { //Properties that you need to compile your project (The applicatio
maven { url "${artifactory_contextUrl}/corda" }
maven { url "https://jitpack.io" }
maven { url "${artifactory_contextUrl}/corda-lib" }
maven {
url "https://software.r3.com/artifactory/dc-lib-dev"
credentials {
username = System.getenv('CORDA_ARTIFACTORY_USERNAME') ?: System.getProperty('corda.artifactory.username')
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD') ?: System.getProperty('corda.artifactory.password')
}
}
}

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.r3.corda.lib.reissuance.contracts

import com.r3.corda.lib.reissuance.states.ReissuableState
import com.r3.corda.lib.reissuance.states.ReissuanceLock
import com.r3.corda.lib.reissuance.states.ReissuanceRequest
import net.corda.core.contracts.*
Expand Down Expand Up @@ -84,8 +85,17 @@ class ReissuanceLockContract<T>: Contract where T: ContractState {
reissuanceLock.status == ReissuanceLock.ReissuanceLockStatus.ACTIVE)

// verify state data
"StatesAndRef objects in ReissuanceLock must be the same as re-issued states" using (
reissuanceLock.originalStates.map { it.state.data } == otherOutputs.map { it.data })
if (firstReissuedState.state.data is ReissuableState<*>) {
reissuanceLock.originalStates.forEachIndexed { index, stateAndRef ->
val state = stateAndRef.state.data as ReissuableState<ContractState>
val reissuedState = otherOutputs[index].data
"StatesAndRef objects in ReissuanceLock must be the same as re-issued states" using (
state.isEqualForReissuance(reissuedState))
}
} else {
"StatesAndRef objects in ReissuanceLock must be the same as re-issued states" using (
reissuanceLock.originalStates.map { it.state.data } == otherOutputs.map { it.data })
}

// verify encumbrance
reissuanceLock.originalStates.forEach {
Expand All @@ -94,7 +104,6 @@ class ReissuanceLockContract<T>: Contract where T: ContractState {
otherOutputs.forEach {
"Output other than ReissuanceRequest and ReissuanceLock must be encumbered" using (it.encumbrance != null)
}

}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.r3.corda.lib.reissuance.states

import net.corda.core.contracts.ContractState

/**
* The ReissuableState interface is an optional interface that states can implement if they
* need to change any of their properties during reissuance.
*
* Note: if reissued states are identical to the states being destroyed, then this interface
* is not required.
*
* For example, a token that has a counter which increments each time it is used, but needs
* to be reset upon reissuance.
*
* The methods that this interface supports are intended to:
* - Allow reissuance to create a new state to issue from an existing state [createReissuance]
* - Allow reissuance to test that a reissued state is equal to an existing state for the
* purposes of reissuance [isEqualForReissuance].
*
*/
interface ReissuableState<T : ContractState> {

/**
* Create a reissuable version of a state. This allows the developer to adjust fields which
* will not be the same before and after reissuance.
*/
fun createReissuance() : T

/**
* Compare to another state of the same type, to evaluate whether for reissuance purposes
* the state is the same. This allows a developer to ignore fields which they do not expect
* to be the same before and after reissuance.
*
* @param otherState
*/
fun isEqualForReissuance(otherState : T) : Boolean
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.r3.corda.lib.reissuance.utils

import net.corda.core.crypto.SecureHash
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.serialize
import net.corda.core.transactions.SignedTransaction
import java.io.ByteArrayOutputStream
Expand All @@ -25,11 +23,3 @@ fun convertSignedTransactionToByteArray(

return baos.toByteArray()
}

fun findSignedTransactionTrandsactionById(
serviceHub: ServiceHub,
transactionId: SecureHash
): SignedTransaction? {
return serviceHub.validatedTransactions.track().snapshot
.findLast { it.tx.id == transactionId }
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ import net.corda.core.transactions.TransactionBuilder
@InitiatingFlow
@StartableByRPC
class CreateSimpleDummyState(
private val owner: Party
private val owner: Party,
private val notary : Party? = null
): FlowLogic<SecureHash>() {
@Suspendable
override fun call(): SecureHash {
val issuer = ourIdentity
val signers = listOf(issuer.owningKey)

val transactionBuilder = TransactionBuilder(notary = getPreferredNotary(serviceHub))
val notaryToUse = notary ?: getPreferredNotary(serviceHub)
val transactionBuilder = TransactionBuilder(notary = notaryToUse)
transactionBuilder.addOutputState(SimpleDummyState(owner))
transactionBuilder.addCommand(SimpleDummyStateContract.Commands.Create(), signers)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class DeleteSimpleDummyState(
@Suspendable
override fun call(): SecureHash {
val signers = listOf(ourIdentity.owningKey)
val notary = getPreferredNotary(serviceHub)
val notary = originalStateAndRef.state.notary

val transactionBuilder = TransactionBuilder(notary = notary)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class DeleteSimpleDummyStateAndCreateDummyStateWithInvalidEqualsMethod(
@Suspendable
override fun call(): SecureHash {
val signers = listOf(ourIdentity.owningKey)
val notary = getPreferredNotary(serviceHub)
val notary = originalStateAndRef.state.notary

val transactionBuilder = TransactionBuilder(notary = notary)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class UpdateSimpleDummyState(

val signers = setOf(owner.owningKey, newOwner.owningKey).toList() // old and new owner might be the same

val transactionBuilder = TransactionBuilder(notary = getPreferredNotary(serviceHub))
val transactionBuilder = TransactionBuilder(notary = simpleDummyStateStateAndRef.state.notary)
transactionBuilder.addInputState(simpleDummyStateStateAndRef)
transactionBuilder.addOutputState(SimpleDummyState(newOwner))
transactionBuilder.addCommand(SimpleDummyStateContract.Commands.Update(), signers)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ abstract class AbstractFlowTest {

lateinit var mockNet: InternalMockNetwork

lateinit var notaryNode: TestStartedNode
lateinit var issuerNode: TestStartedNode
lateinit var acceptorNode: TestStartedNode
lateinit var aliceNode: TestStartedNode
Expand Down Expand Up @@ -101,10 +100,13 @@ abstract class AbstractFlowTest {
lateinit var debbieLegalName: CordaX500Name
lateinit var employeeLegalName: CordaX500Name

lateinit var allNotaries: List<TestStartedNode>

lateinit var issuedTokenType: IssuedTokenType

val notary1Name = CordaX500Name("Notary1", "Zurich", "CH")
val notary2Name = CordaX500Name("Notary2", "Zurich", "CH")

lateinit var notary2Party : Party

@Before
fun setup() {
mockNet = InternalMockNetwork(
Expand All @@ -121,15 +123,19 @@ abstract class AbstractFlowTest {
findCordapp("com.r3.corda.lib.reissuance.flows"),
findCordapp("com.r3.corda.lib.reissuance.dummy_flows")
),
notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME, false)),
notarySpecs = listOf(notary1Name, notary2Name).map { MockNetworkNotarySpec(it) },
initialNetworkParameters = testNetworkParameters(
minimumPlatformVersion = 8 // 4.6
)
)

allNotaries = mockNet.notaryNodes
notaryNode = mockNet.notaryNodes.first()
notaryParty = notaryNode.info.singleIdentity()
notaryParty = mockNet.notaryNodes.single {
it.info.singleIdentity().name == notary1Name
}.info.singleIdentity()

notary2Party = mockNet.notaryNodes.single {
it.info.singleIdentity().name == notary2Name
}.info.singleIdentity()

}

Expand Down Expand Up @@ -286,6 +292,16 @@ abstract class AbstractFlowTest {
)
}

fun createSimpleDummyStateOnNotary(
owner: Party,
notary : Party
): SecureHash {
return runFlow(
issuerNode,
CreateSimpleDummyState(owner, notary)
)
}

fun createSimpleDummyStateForAccount(
node: TestStartedNode,
owner: AbstractParty
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,34 @@ class DeleteReissuedStatesAndLockTest: AbstractFlowTest() {
verifyDeletedReissuedStatesAndLock(statesToReissue)
}

@Test
fun `Re-issued SimpleDummyState and corresponding ReissuanceLock are deleted on notary 2`() {
initialiseParties()
createSimpleDummyStateOnNotary(aliceParty, notary2Party)

val statesToReissue = getStateAndRefs<SimpleDummyState>(aliceNode) // there is just 1
createReissuanceRequestAndShareRequiredTransactions(
aliceNode,
statesToReissue,
SimpleDummyStateContract.Commands.Create(),
issuerParty
)

val reissuanceRequest = getStateAndRefs<ReissuanceRequest>(issuerNode)[0]
reissueRequestedStates<SimpleDummyState>(issuerNode, reissuanceRequest, listOf())

val reissuedSimpleDummyStates = getStateAndRefs<SimpleDummyState>(aliceNode, encumbered = true)
val lockState = getStateAndRefs<ReissuanceLock<SimpleDummyState>>(aliceNode)[0]
deleteReissuedStatesAndLock(
aliceNode,
lockState,
reissuedSimpleDummyStates,
SimpleDummyStateContract.Commands.Delete()
)

verifyDeletedReissuedStatesAndLock(statesToReissue)
}

@Test
fun `Re-issued DummyStateRequiringAcceptance is unencumbered after the original state are deleted`() {
initialiseParties()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,24 @@ class ReissueStatesTest: AbstractFlowTest() {
verifyStatesAfterReissuance<SimpleDummyState>()
}

@Test
fun `SimpleDummyState is re-issued on notary 2`() {
initialiseParties()
createSimpleDummyStateOnNotary(aliceParty, notary2Party)

val simpleDummyState = getStateAndRefs<SimpleDummyState>(aliceNode)[0]
createReissuanceRequestAndShareRequiredTransactions(
aliceNode,
listOf(simpleDummyState),
SimpleDummyStateContract.Commands.Create(),
issuerParty
)

val reissuanceRequest = getStateAndRefs<ReissuanceRequest>(issuerNode)[0]
reissueRequestedStates<SimpleDummyState>(issuerNode, reissuanceRequest, listOf())
verifyStatesAfterReissuance<SimpleDummyState>()
}

@Test
fun `DummyStateRequiringAcceptance is re-issued`() {
initialiseParties()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,24 @@ class RejectReissuanceRequestTest: AbstractFlowTest() {
verifyReissuanceRequestRejection<SimpleDummyState>()
}

@Test
fun `SimpleDummyState re-issuance request is rejected on notary2`() {
initialiseParties()
createSimpleDummyStateOnNotary(aliceParty, notary2Party)

val simpleDummyState = getStateAndRefs<SimpleDummyState>(aliceNode)[0]
createReissuanceRequestAndShareRequiredTransactions(
aliceNode,
listOf(simpleDummyState),
SimpleDummyStateContract.Commands.Create(),
issuerParty
)

val reissuanceRequest = getStateAndRefs<ReissuanceRequest>(issuerNode)[0]
rejectReissuanceRequested<SimpleDummyState>(issuerNode, reissuanceRequest)
verifyReissuanceRequestRejection<SimpleDummyState>()
}

@Test
fun `DummyStateRequiringAcceptance re-issuance request is rejected`() {
initialiseParties()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,27 @@ class RequestReissuanceTest: AbstractFlowTest() {
assertThat(simpleDummyStatesAvailableToIssuer, `is`(statesToBeReissued))
}

@Test
fun `SimpleDummyState re-issuance request is created on notary 2`() {
initialiseParties()
createSimpleDummyStateOnNotary(aliceParty, notary2Party)

val issuanceCommandData = SimpleDummyStateContract.Commands.Create()
val statesToBeReissued = getStateAndRefs<SimpleDummyState>(aliceNode) // there is just 1 state
createReissuanceRequestAndShareRequiredTransactions(
aliceNode,
statesToBeReissued,
issuanceCommandData,
issuerParty
)

val reissuanceRequests = getStateAndRefs<ReissuanceRequest>(issuerNode)
verifyReissuanceRequests(reissuanceRequests, issuanceCommandData, statesToBeReissued)

val simpleDummyStatesAvailableToIssuer = getStateAndRefs<SimpleDummyState>(issuerNode)
assertThat(simpleDummyStatesAvailableToIssuer, `is`(statesToBeReissued))
}

@Test
fun `DummyStateRequiringAcceptance re-issuance request is created`() {
initialiseParties()
Expand Down Expand Up @@ -196,7 +217,7 @@ class RequestReissuanceTest: AbstractFlowTest() {
assertThat(simpleDummyStatesAvailableToIssuer, `is`(statesToBeReissued))
}

@Test(expected = TransactionVerificationException::class)
@Test(expected = IllegalArgumentException::class)
fun `Request re-issuance of 0 states can't be created`() {
initialiseParties()
createReissuanceRequestAndShareRequiredTransactions<SimpleDummyState>(
Expand Down
Loading