diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt index e85439a601..10f101bdc7 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt @@ -64,8 +64,10 @@ abstract class FlowLogic { /** * Return the outermost [FlowLogic] instance, or null if not in a flow. */ - @Suppress("unused") @JvmStatic - val currentTopLevel: FlowLogic<*>? get() = (Strand.currentStrand() as? FlowStateMachine<*>)?.logic + @Suppress("unused") + @JvmStatic + val currentTopLevel: FlowLogic<*>? + get() = (Strand.currentStrand() as? FlowStateMachine<*>)?.logic /** * If on a flow, suspends the flow and only wakes it up after at least [duration] time has passed. Otherwise, @@ -123,10 +125,11 @@ abstract class FlowLogic { * Note: The current implementation returns the single identity of the node. This will change once multiple identities * is implemented. */ - val ourIdentityAndCert: PartyAndCertificate get() { - return serviceHub.myInfo.legalIdentitiesAndCerts.find { it.party == stateMachine.ourIdentity } - ?: throw IllegalStateException("Identity specified by ${stateMachine.id} (${stateMachine.ourIdentity}) is not one of ours!") - } + val ourIdentityAndCert: PartyAndCertificate + get() { + return serviceHub.myInfo.legalIdentitiesAndCerts.find { it.party == stateMachine.ourIdentity } + ?: throw IllegalStateException("Identity specified by ${stateMachine.id} (${stateMachine.ourIdentity}) is not one of ours!") + } /** * Specifies the identity to use for this flow. This will be one of the multiple identities that belong to this node. @@ -141,9 +144,11 @@ abstract class FlowLogic { // Used to implement the deprecated send/receive functions using Party. When such a deprecated function is used we // create a fresh session for the Party, put it here and use it in subsequent deprecated calls. private val deprecatedPartySessionMap = HashMap() + private fun getDeprecatedSessionForParty(party: Party): FlowSession { return deprecatedPartySessionMap.getOrPut(party) { initiateFlow(party) } } + /** * Returns a [FlowInfo] object describing the flow [otherParty] is using. With [FlowInfo.flowVersion] it * provides the necessary information needed for the evolution of flows and enabling backwards compatibility. @@ -342,7 +347,7 @@ abstract class FlowLogic { * Note that this has to return a tracker before the flow is invoked. You can't change your mind half way * through. */ - open val progressTracker: ProgressTracker? = null + open val progressTracker: ProgressTracker? = ProgressTracker.DEFAULT_TRACKER() /** * This is where you fill out your business logic. @@ -383,7 +388,7 @@ abstract class FlowLogic { * * @return Returns null if this flow has no progress tracker. */ - fun trackStepsTree(): DataFeed>, List>>? { + fun trackStepsTree(): DataFeed>, List>>? { // TODO this is not threadsafe, needs an atomic get-step-and-subscribe return progressTracker?.let { DataFeed(it.allStepsLabels, it.stepsTreeChanges) diff --git a/core/src/main/kotlin/net/corda/core/internal/CertRole.kt b/core/src/main/kotlin/net/corda/core/internal/CertRole.kt index b53f8977e8..f2946d3c72 100644 --- a/core/src/main/kotlin/net/corda/core/internal/CertRole.kt +++ b/core/src/main/kotlin/net/corda/core/internal/CertRole.kt @@ -26,20 +26,25 @@ import java.security.cert.X509Certificate enum class CertRole(val validParents: NonEmptySet, val isIdentity: Boolean, val isWellKnown: Boolean) : ASN1Encodable { /** Signing certificate for the Doorman CA. */ DOORMAN_CA(NonEmptySet.of(null), false, false), + /** Signing certificate for the network map. */ NETWORK_MAP(NonEmptySet.of(null), false, false), + /** Well known (publicly visible) identity of a service (such as notary). */ SERVICE_IDENTITY(NonEmptySet.of(DOORMAN_CA), true, true), + /** Node level CA from which the TLS and well known identity certificates are issued. */ NODE_CA(NonEmptySet.of(DOORMAN_CA), false, false), + + // [DOORMAN_CA] is also added as a valid parent of [TLS] and [LEGAL_IDENTITY] for backwards compatibility purposes + // (eg. if we decide [TLS] has its own [ROOT_CA] and [DOORMAN_CA] directly issues [TLS] and [LEGAL_IDENTITY]; thus, + // there won't be a requirement for [NODE_CA]). /** Transport layer security certificate for a node. */ - TLS(NonEmptySet.of(NODE_CA), false, false), + TLS(NonEmptySet.of(DOORMAN_CA, NODE_CA), false, false), + /** Well known (publicly visible) identity of a legal entity. */ - // TODO: at the moment, Legal Identity certs are issued by Node CA only. However, [DOORMAN_CA] is also added - // as a valid parent of [LEGAL_IDENTITY] for backwards compatibility purposes (eg. if we decide TLS has its - // own Root CA and Doorman CA directly issues Legal Identities; thus, there won't be a requirement for - // Node CA). Consider removing [DOORMAN_CA] from [validParents] when the model is finalised. LEGAL_IDENTITY(NonEmptySet.of(DOORMAN_CA, NODE_CA), true, true), + /** Confidential (limited visibility) identity of a legal entity. */ CONFIDENTIAL_LEGAL_IDENTITY(NonEmptySet.of(LEGAL_IDENTITY), true, false); @@ -88,4 +93,4 @@ enum class CertRole(val validParents: NonEmptySet, val isIdentity: Bo fun isValidParent(parent: CertRole?): Boolean = parent in validParents override fun toASN1Primitive(): ASN1Primitive = ASN1Integer(this.ordinal + 1L) -} \ No newline at end of file +} diff --git a/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt b/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt index f646f0569a..7741500c31 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/ProgressTracker.kt @@ -30,10 +30,11 @@ import java.util.* * using the [Observable] subscribeOn call. */ @CordaSerializable -class ProgressTracker(vararg steps: Step) { +class ProgressTracker(vararg inputSteps: Step) { + @CordaSerializable sealed class Change(val progressTracker: ProgressTracker) { - data class Position(val tracker: ProgressTracker, val newStep: Step) : Change(tracker) { + data class Position(val tracker: ProgressTracker, val newStep: Step) : Change(tracker) { override fun toString() = newStep.label } @@ -64,6 +65,10 @@ class ProgressTracker(vararg steps: Step) { override fun equals(other: Any?) = other === UNSTARTED } + object STARTING : Step("Starting") { + override fun equals(other: Any?) = other === STARTING + } + object DONE : Step("Done") { override fun equals(other: Any?) = other === DONE } @@ -74,7 +79,7 @@ class ProgressTracker(vararg steps: Step) { private val childProgressTrackers = mutableMapOf() /** The steps in this tracker, same as the steps passed to the constructor but with UNSTARTED and DONE inserted. */ - val steps = arrayOf(UNSTARTED, *steps, DONE) + val steps = arrayOf(UNSTARTED, STARTING, *inputSteps, DONE) private var _allStepsCache: List> = _allSteps() @@ -83,42 +88,16 @@ class ProgressTracker(vararg steps: Step) { private val _stepsTreeChanges by transient { PublishSubject.create>>() } private val _stepsTreeIndexChanges by transient { PublishSubject.create() } - - - init { - steps.forEach { - val childTracker = it.childProgressTracker() - if (childTracker != null) { - setChildProgressTracker(it, childTracker) - } - } - } - - /** The zero-based index of the current step in the [steps] array (i.e. with UNSTARTED and DONE) */ - var stepIndex: Int = 0 - private set(value) { - field = value - } - - /** The zero-bases index of the current step in a [allStepsLabels] list */ - var stepsTreeIndex: Int = -1 - private set(value) { - field = value - _stepsTreeIndexChanges.onNext(value) - } - - /** - * Reading returns the value of steps[stepIndex], writing moves the position of the current tracker. Once moved to - * the [DONE] state, this tracker is finished and the current step cannot be moved again. - */ var currentStep: Step get() = steps[stepIndex] set(value) { - check(!hasEnded) { "Cannot rewind a progress tracker once it has ended" } + check((value === DONE && hasEnded) || !hasEnded) { + "Cannot rewind a progress tracker once it has ended" + } if (currentStep == value) return val index = steps.indexOf(value) - require(index != -1, { "Step ${value.label} not found in progress tracker." }) + require(index != -1) { "Step ${value.label} not found in progress tracker." } if (index < stepIndex) { // We are going backwards: unlink and unsubscribe from any child nodes that we're rolling back @@ -144,6 +123,39 @@ class ProgressTracker(vararg steps: Step) { } } + + init { + steps.forEach { + configureChildTrackerForStep(it) + } + this.currentStep = UNSTARTED + } + + private fun configureChildTrackerForStep(it: Step) { + val childTracker = it.childProgressTracker() + if (childTracker != null) { + setChildProgressTracker(it, childTracker) + } + } + + /** The zero-based index of the current step in the [steps] array (i.e. with UNSTARTED and DONE) */ + var stepIndex: Int = 0 + private set(value) { + field = value + } + + /** The zero-bases index of the current step in a [allStepsLabels] list */ + var stepsTreeIndex: Int = -1 + private set(value) { + field = value + _stepsTreeIndexChanges.onNext(value) + } + + /** + * Reading returns the value of steps[stepIndex], writing moves the position of the current tracker. Once moved to + * the [DONE] state, this tracker is finished and the current step cannot be moved again. + */ + /** Returns the current step, descending into children to find the deepest step we are up to. */ val currentStepRecursive: Step get() = getChildProgressTracker(currentStep)?.currentStepRecursive ?: currentStep @@ -263,7 +275,7 @@ class ProgressTracker(vararg steps: Step) { /** * An observable stream of changes to the [allStepsLabels] */ - val stepsTreeChanges: Observable>> get() = _stepsTreeChanges + val stepsTreeChanges: Observable>> get() = _stepsTreeChanges /** * An observable stream of changes to the [stepsTreeIndex] @@ -272,6 +284,10 @@ class ProgressTracker(vararg steps: Step) { /** Returns true if the progress tracker has ended, either by reaching the [DONE] step or prematurely with an error */ val hasEnded: Boolean get() = _changes.hasCompleted() || _changes.hasThrowable() + + companion object { + val DEFAULT_TRACKER = { ProgressTracker() } + } } // TODO: Expose the concept of errors. // TODO: It'd be helpful if this class was at least partly thread safe. diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt index e42a6dc891..a6e7f5d106 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -3,15 +3,12 @@ package net.corda.core.flows import com.natpryce.hamkrest.* import com.natpryce.hamkrest.assertion.assert import net.corda.core.contracts.* -import net.corda.testing.internal.matchers.flow.willReturn -import net.corda.testing.internal.matchers.flow.willThrow import net.corda.core.flows.mixins.WithContracts import net.corda.core.flows.mixins.WithFinality import net.corda.core.identity.AbstractParty import net.corda.core.internal.Emoji import net.corda.core.transactions.ContractUpgradeLedgerTransaction import net.corda.core.transactions.LedgerTransaction -import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow import net.corda.finance.USD @@ -20,9 +17,12 @@ import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.CashIssueFlow import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContractV2 +import net.corda.testing.contracts.DummyContractV3 import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.singleIdentity +import net.corda.testing.internal.matchers.flow.willReturn +import net.corda.testing.internal.matchers.flow.willThrow import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.TestStartedNode import net.corda.testing.node.internal.cordappsForPackages @@ -57,54 +57,67 @@ class ContractUpgradeFlowTest : WithContracts, WithFinality { aliceNode.finalise(stx, bob) - val atx = aliceNode.getValidatedTransaction(stx) - val btx = bobNode.getValidatedTransaction(stx) + val aliceTx = aliceNode.getValidatedTransaction(stx) + val bobTx = bobNode.getValidatedTransaction(stx) // The request is expected to be rejected because party B hasn't authorised the upgrade yet. assert.that( - aliceNode.initiateDummyContractUpgrade(atx), + aliceNode.initiateContractUpgrade(aliceTx, DummyContractV2::class), willThrow()) - // Party B authorise the contract state upgrade, and immediately deauthorise the same. - assert.that(bobNode.authoriseDummyContractUpgrade(btx), willReturn()) - assert.that(bobNode.deauthoriseContractUpgrade(btx), willReturn()) + // Party B authorises the contract state upgrade, and immediately de-authorises the same. + assert.that(bobNode.authoriseContractUpgrade(bobTx, DummyContractV2::class), willReturn()) + assert.that(bobNode.deauthoriseContractUpgrade(bobTx), willReturn()) - // The request is expected to be rejected because party B has subsequently deauthorised a previously authorised upgrade. + // The request is expected to be rejected because party B has subsequently de-authorised a previously authorised upgrade. assert.that( - aliceNode.initiateDummyContractUpgrade(atx), + aliceNode.initiateContractUpgrade(aliceTx, DummyContractV2::class), willThrow()) - // Party B authorise the contract state upgrade - assert.that(bobNode.authoriseDummyContractUpgrade(btx), willReturn()) + // Party B authorises the contract state upgrade. + assert.that(bobNode.authoriseContractUpgrade(bobTx, DummyContractV2::class), willReturn()) // Party A initiates contract upgrade flow, expected to succeed this time. assert.that( - aliceNode.initiateDummyContractUpgrade(atx), + aliceNode.initiateContractUpgrade(aliceTx, DummyContractV2::class), willReturn( - aliceNode.hasDummyContractUpgradeTransaction() - and bobNode.hasDummyContractUpgradeTransaction())) + aliceNode.hasContractUpgradeTransaction() + and bobNode.hasContractUpgradeTransaction())) + + val upgradedState = aliceNode.getStateFromVault(DummyContractV2.State::class) + + // We now test that the upgraded state can be upgraded further, to V3. + // Party B authorises the contract state upgrade. + assert.that(bobNode.authoriseContractUpgrade(upgradedState, DummyContractV3::class), willReturn()) + + // Party A initiates contract upgrade flow which is expected to succeed. + assert.that( + aliceNode.initiateContractUpgrade(upgradedState, DummyContractV3::class), + willReturn( + aliceNode.hasContractUpgradeTransaction() + and bobNode.hasContractUpgradeTransaction())) } private fun TestStartedNode.issueCash(amount: Amount = Amount(1000, USD)) = - services.startFlow(CashIssueFlow(amount, OpaqueBytes.of(1), notary)) - .andRunNetwork() - .resultFuture.getOrThrow() + services.startFlow(CashIssueFlow(amount, OpaqueBytes.of(1), notary)) + .andRunNetwork() + .resultFuture.getOrThrow() private fun TestStartedNode.getBaseStateFromVault() = getStateFromVault(ContractState::class) private fun TestStartedNode.getCashStateFromVault() = getStateFromVault(CashV2.State::class) private fun hasIssuedAmount(expected: Amount>) = - hasContractState(has(CashV2.State::amount, equalTo(expected))) + hasContractState(has(CashV2.State::amount, equalTo(expected))) private fun belongsTo(vararg recipients: AbstractParty) = - hasContractState(has(CashV2.State::owners, equalTo(recipients.toList()))) + hasContractState(has(CashV2.State::owners, equalTo(recipients.toList()))) private fun hasContractState(expectation: Matcher) = - has, T>( - "contract state", - { it.state.data }, - expectation) + has, T>( + "contract state", + { it.state.data }, + expectation) @Test fun `upgrade Cash to v2`() { @@ -123,14 +136,14 @@ class ContractUpgradeFlowTest : WithContracts, WithFinality { val upgradedState = aliceNode.getCashStateFromVault() assert.that(upgradedState, hasIssuedAmount(Amount(1000000, USD) `issued by` (alice.ref(1))) - and belongsTo(anonymisedRecipient)) + and belongsTo(anonymisedRecipient)) // Make sure the upgraded state can be spent val movedState = upgradedState.state.data.copy(amount = upgradedState.state.data.amount.times(2)) val spendUpgradedTx = aliceNode.signInitialTransaction { addInputState(upgradedState) addOutputState( - upgradedState.state.copy(data = movedState) + upgradedState.state.copy(data = movedState) ) addCommand(CashV2.Move(), alice.owningKey) } @@ -162,35 +175,24 @@ class ContractUpgradeFlowTest : WithContracts, WithFinality { override fun verify(tx: LedgerTransaction) {} } - //region Operations - private fun TestStartedNode.initiateDummyContractUpgrade(tx: SignedTransaction) = - initiateContractUpgrade(tx, DummyContractV2::class) - - private fun TestStartedNode.authoriseDummyContractUpgrade(tx: SignedTransaction) = - authoriseContractUpgrade(tx, DummyContractV2::class) - //endregion - //region Matchers - private fun TestStartedNode.hasDummyContractUpgradeTransaction() = - hasContractUpgradeTransaction() - - private inline fun TestStartedNode.hasContractUpgradeTransaction() = - has, ContractUpgradeLedgerTransaction>( - "a contract upgrade transaction", - { getContractUpgradeTransaction(it) }, - isUpgrade()) + private inline fun TestStartedNode.hasContractUpgradeTransaction() = + has, ContractUpgradeLedgerTransaction>( + "a contract upgrade transaction", + { getContractUpgradeTransaction(it) }, + isUpgrade()) private fun TestStartedNode.getContractUpgradeTransaction(state: StateAndRef) = - services.validatedTransactions.getTransaction(state.ref.txhash)!! - .resolveContractUpgradeTransaction(services) + services.validatedTransactions.getTransaction(state.ref.txhash)!! + .resolveContractUpgradeTransaction(services) private inline fun isUpgrade() = isUpgradeFrom() and isUpgradeTo() - private inline fun isUpgradeFrom() = + private inline fun isUpgradeFrom() = has("input data", { it.inputs.single().state.data }, isA(anything)) - private inline fun isUpgradeTo() = + private inline fun isUpgradeTo() = has("output data", { it.outputs.single().data }, isA(anything)) //endregion } diff --git a/core/src/test/kotlin/net/corda/core/flows/mixins/WithContracts.kt b/core/src/test/kotlin/net/corda/core/flows/mixins/WithContracts.kt index c5794007fe..7206b1ce76 100644 --- a/core/src/test/kotlin/net/corda/core/flows/mixins/WithContracts.kt +++ b/core/src/test/kotlin/net/corda/core/flows/mixins/WithContracts.kt @@ -51,9 +51,13 @@ interface WithContracts : WithMockNet { fun > TestStartedNode.authoriseContractUpgrade( tx: SignedTransaction, toClass: KClass) = - startFlow( - ContractUpgradeFlow.Authorise(tx.tx.outRef(0), toClass.java) - ) + authoriseContractUpgrade(tx.tx.outRef(0), toClass) + + fun > TestStartedNode.authoriseContractUpgrade( + stateAndRef: StateAndRef, toClass: KClass) = + startFlow( + ContractUpgradeFlow.Authorise(stateAndRef, toClass.java) + ) fun TestStartedNode.deauthoriseContractUpgrade(tx: SignedTransaction) = startFlow( ContractUpgradeFlow.Deauthorise(tx.tx.outRef(0).ref) diff --git a/core/src/test/kotlin/net/corda/core/internal/CertRoleTests.kt b/core/src/test/kotlin/net/corda/core/internal/CertRoleTests.kt index 60f81927c8..49653648de 100644 --- a/core/src/test/kotlin/net/corda/core/internal/CertRoleTests.kt +++ b/core/src/test/kotlin/net/corda/core/internal/CertRoleTests.kt @@ -1,9 +1,12 @@ package net.corda.core.internal +import net.corda.core.crypto.Crypto +import net.corda.nodeapi.internal.crypto.CertificateType +import net.corda.nodeapi.internal.crypto.X509Utilities import org.bouncycastle.asn1.ASN1Integer import org.junit.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith +import javax.security.auth.x500.X500Principal +import kotlin.test.* class CertRoleTests { @Test @@ -22,4 +25,74 @@ class CertRoleTests { // Outside of the range of integers assertFailsWith { CertRole.getInstance(ASN1Integer(Integer.MAX_VALUE + 1L)) } } -} \ No newline at end of file + + @Test + fun `check cert roles verify for various cert hierarchies`(){ + + // Testing for various certificate hierarchies (with or without NodeCA). + // ROOT -> Intermediate Root -> Doorman -> NodeCA -> Legal Identity cert -> Confidential key cert + // -> NodeCA -> TLS + // -> Legal Identity cert -> Confidential key cert + // -> TLS + val rootSubject = X500Principal("CN=Root,O=R3 Ltd,L=London,C=GB") + val intermediateRootSubject = X500Principal("CN=Intermediate Root,O=R3 Ltd,L=London,C=GB") + val doormanSubject = X500Principal("CN=Doorman,O=R3 Ltd,L=London,C=GB") + val nodeSubject = X500Principal("CN=Node,O=R3 Ltd,L=London,C=GB") + + val rootKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val rootCert = X509Utilities.createSelfSignedCACertificate(rootSubject, rootKeyPair) + val rootCertRole = CertRole.extract(rootCert) + + val intermediateRootKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + // Note that [CertificateType.ROOT_CA] is used for both root and intermediate root. + val intermediateRootCert = X509Utilities.createCertificate(CertificateType.ROOT_CA, rootCert, rootKeyPair, intermediateRootSubject, intermediateRootKeyPair.public) + val intermediateRootCertRole = CertRole.extract(intermediateRootCert) + + val doormanKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + // Note that [CertificateType.INTERMEDIATE_CA] has actually role = CertRole.DOORMAN_CA, see [CertificateType] in [X509Utilities]. + val doormanCert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateRootCert, intermediateRootKeyPair, doormanSubject, doormanKeyPair.public) + val doormanCertRole = CertRole.extract(doormanCert)!! + + val nodeCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val nodeCACert = X509Utilities.createCertificate(CertificateType.NODE_CA, doormanCert, doormanKeyPair, nodeSubject, nodeCAKeyPair.public) + val nodeCACertRole = CertRole.extract(nodeCACert)!! + + val tlsKeyPairFromNodeCA = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val tlsCertFromNodeCA = X509Utilities.createCertificate(CertificateType.TLS, nodeCACert, nodeCAKeyPair, nodeSubject, tlsKeyPairFromNodeCA.public) + val tlsCertFromNodeCARole = CertRole.extract(tlsCertFromNodeCA)!! + + val tlsKeyPairFromDoorman = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val tlsCertFromDoorman = X509Utilities.createCertificate(CertificateType.TLS, doormanCert, doormanKeyPair, nodeSubject, tlsKeyPairFromDoorman.public) + val tlsCertFromDoormanRole = CertRole.extract(tlsCertFromDoorman)!! + + val legalIDKeyPairFromNodeCA = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val legalIDCertFromNodeCA = X509Utilities.createCertificate(CertificateType.LEGAL_IDENTITY, nodeCACert, nodeCAKeyPair, nodeSubject, legalIDKeyPairFromNodeCA.public) + val legalIDCertFromNodeCARole = CertRole.extract(legalIDCertFromNodeCA)!! + + val legalIDKeyPairFromDoorman = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val legalIDCertFromDoorman = X509Utilities.createCertificate(CertificateType.LEGAL_IDENTITY, doormanCert, doormanKeyPair, nodeSubject, legalIDKeyPairFromDoorman.public) + val legalIDCertFromDoormanRole = CertRole.extract(legalIDCertFromDoorman)!! + + val confidentialKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val confidentialCert = X509Utilities.createCertificate(CertificateType.CONFIDENTIAL_LEGAL_IDENTITY, legalIDCertFromNodeCA, legalIDKeyPairFromNodeCA, nodeSubject, confidentialKeyPair.public) + val confidentialCertRole = CertRole.extract(confidentialCert)!! + + assertNull(rootCertRole) + assertNull(intermediateRootCertRole) + assertEquals(tlsCertFromNodeCARole, tlsCertFromDoormanRole) + assertEquals(legalIDCertFromNodeCARole, legalIDCertFromDoormanRole) + + assertTrue { doormanCertRole.isValidParent(intermediateRootCertRole) } // Doorman is signed by Intermediate Root. + assertTrue { nodeCACertRole.isValidParent(doormanCertRole) } // NodeCA is signed by Doorman. + assertTrue { tlsCertFromNodeCARole.isValidParent(nodeCACertRole) } // TLS is signed by NodeCA. + assertTrue { tlsCertFromDoormanRole.isValidParent(doormanCertRole) } // TLS can also be signed by Doorman. + assertTrue { legalIDCertFromNodeCARole.isValidParent(nodeCACertRole) } // Legal Identity is signed by NodeCA. + assertTrue { legalIDCertFromDoormanRole.isValidParent(doormanCertRole) } // Legal Identity can also be signed by Doorman. + assertTrue { confidentialCertRole.isValidParent(legalIDCertFromNodeCARole) } // Confidential key cert is signed by Legal Identity. + + assertFalse { legalIDCertFromDoormanRole.isValidParent(tlsCertFromDoormanRole) } // Legal Identity cannot be signed by TLS. + assertFalse { tlsCertFromNodeCARole.isValidParent(legalIDCertFromNodeCARole) } // TLS cannot be signed by Legal Identity. + assertFalse { confidentialCertRole.isValidParent(nodeCACertRole) } // Confidential key cert cannot be signed by NodeCA. + assertFalse { confidentialCertRole.isValidParent(doormanCertRole) } // Confidential key cert cannot be signed by Doorman. + } +} diff --git a/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt b/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt index 6a682faff6..e476df1a8b 100644 --- a/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt +++ b/core/src/test/kotlin/net/corda/core/utilities/ProgressTrackerTest.kt @@ -50,11 +50,11 @@ class ProgressTrackerTest { assertEquals(0, pt.stepIndex) var stepNotification: ProgressTracker.Step? = null pt.changes.subscribe { stepNotification = (it as? ProgressTracker.Change.Position)?.newStep } - + assertEquals(ProgressTracker.UNSTARTED, pt.currentStep) + assertEquals(ProgressTracker.STARTING, pt.nextStep()) assertEquals(SimpleSteps.ONE, pt.nextStep()) - assertEquals(1, pt.stepIndex) + assertEquals(2, pt.stepIndex) assertEquals(SimpleSteps.ONE, stepNotification) - assertEquals(SimpleSteps.TWO, pt.nextStep()) assertEquals(SimpleSteps.THREE, pt.nextStep()) assertEquals(SimpleSteps.FOUR, pt.nextStep()) @@ -87,8 +87,10 @@ class ProgressTrackerTest { assertEquals(SimpleSteps.TWO, (stepNotification.pollFirst() as ProgressTracker.Change.Structural).parent) assertNextStep(SimpleSteps.TWO) + assertEquals(pt2.currentStep, ProgressTracker.UNSTARTED) + assertEquals(ProgressTracker.STARTING, pt2.nextStep()) assertEquals(ChildSteps.AYY, pt2.nextStep()) - assertNextStep(ChildSteps.AYY) + assertEquals((stepNotification.last as ProgressTracker.Change.Position).newStep, ChildSteps.AYY) assertEquals(ChildSteps.BEE, pt2.nextStep()) } @@ -115,19 +117,19 @@ class ProgressTrackerTest { // Travel tree. pt.currentStep = SimpleSteps.ONE - assertCurrentStepsTree(0, SimpleSteps.ONE) + assertCurrentStepsTree(1, SimpleSteps.ONE) pt.currentStep = SimpleSteps.TWO - assertCurrentStepsTree(1, SimpleSteps.TWO) + assertCurrentStepsTree(2, SimpleSteps.TWO) pt2.currentStep = ChildSteps.BEE - assertCurrentStepsTree(3, ChildSteps.BEE) + assertCurrentStepsTree(5, ChildSteps.BEE) pt.currentStep = SimpleSteps.THREE - assertCurrentStepsTree(5, SimpleSteps.THREE) + assertCurrentStepsTree(7, SimpleSteps.THREE) // Assert no structure changes and proper steps propagation. - assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(0, 1, 3, 5)) + assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(1, 2, 5, 7)) assertThat(stepsTreeNotification).isEmpty() } @@ -153,16 +155,16 @@ class ProgressTrackerTest { } pt.currentStep = SimpleSteps.ONE - assertCurrentStepsTree(0, SimpleSteps.ONE) + assertCurrentStepsTree(1, SimpleSteps.ONE) pt.currentStep = SimpleSteps.FOUR - assertCurrentStepsTree(3, SimpleSteps.FOUR) + assertCurrentStepsTree(4, SimpleSteps.FOUR) pt2.currentStep = ChildSteps.SEA - assertCurrentStepsTree(6, ChildSteps.SEA) + assertCurrentStepsTree(8, ChildSteps.SEA) // Assert no structure changes and proper steps propagation. - assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(0, 3, 6)) + assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(1, 4, 8)) assertThat(stepsTreeNotification).isEmpty() } @@ -189,18 +191,18 @@ class ProgressTrackerTest { } pt.currentStep = SimpleSteps.TWO - assertCurrentStepsTree(1, SimpleSteps.TWO) + assertCurrentStepsTree(2, SimpleSteps.TWO) pt.currentStep = SimpleSteps.FOUR - assertCurrentStepsTree(6, SimpleSteps.FOUR) + assertCurrentStepsTree(8, SimpleSteps.FOUR) pt.setChildProgressTracker(SimpleSteps.THREE, pt3) - assertCurrentStepsTree(9, SimpleSteps.FOUR) + assertCurrentStepsTree(12, SimpleSteps.FOUR) // Assert no structure changes and proper steps propagation. - assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(1, 6, 9)) + assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(2, 8, 12)) assertThat(stepsTreeNotification).hasSize(2) // 1 change + 1 our initial state } @@ -228,14 +230,14 @@ class ProgressTrackerTest { pt.currentStep = SimpleSteps.TWO pt2.currentStep = ChildSteps.SEA pt3.currentStep = BabySteps.UNOS - assertCurrentStepsTree(4, ChildSteps.SEA) + assertCurrentStepsTree(6, ChildSteps.SEA) pt.setChildProgressTracker(SimpleSteps.TWO, pt3) - assertCurrentStepsTree(2, BabySteps.UNOS) + assertCurrentStepsTree(4, BabySteps.UNOS) // Assert no structure changes and proper steps propagation. - assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(1, 4, 2)) + assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(2, 6, 4)) assertThat(stepsTreeNotification).hasSize(2) // 1 change + 1 our initial state. } diff --git a/node/src/integration-test/kotlin/net/corda/node/flows/FlowRetryTest.kt b/node/src/integration-test/kotlin/net/corda/node/flows/FlowRetryTest.kt index 8ccf2ac1e1..466b99be86 100644 --- a/node/src/integration-test/kotlin/net/corda/node/flows/FlowRetryTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/flows/FlowRetryTest.kt @@ -2,8 +2,10 @@ package net.corda.node.flows import co.paralleluniverse.fibers.Suspendable import net.corda.client.rpc.CordaRPCClient +import net.corda.core.CordaRuntimeException import net.corda.core.flows.* import net.corda.core.identity.Party +import net.corda.core.internal.IdempotentFlow import net.corda.core.messaging.startFlow import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.ProgressTracker @@ -19,6 +21,8 @@ import net.corda.testing.internal.IntegrationTest import net.corda.testing.internal.IntegrationTestSchemas import net.corda.testing.internal.toDatabaseSchemaName import net.corda.testing.node.User +import org.assertj.core.api.Assertions.assertThatExceptionOfType +import org.hibernate.exception.ConstraintViolationException import org.junit.Before import org.junit.ClassRule import org.junit.Test @@ -62,6 +66,42 @@ class FlowRetryTest : IntegrationTest() { assertNotNull(result) assertEquals("$numSessions:$numIterations", result) } + + @Test + fun `flow gives up after number of exceptions, even if this is the first line of the flow`() { + val user = User("mark", "dadada", setOf(Permissions.startFlow())) + assertThatExceptionOfType(CordaRuntimeException::class.java).isThrownBy { + driver(DriverParameters( + startNodesInProcess = isQuasarAgentSpecified(), + notarySpecs = emptyList() + )) { + val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow() + + val result = CordaRPCClient(nodeAHandle.rpcAddress).start(user.username, user.password).use { + it.proxy.startFlow(::RetryFlow).returnValue.getOrThrow() + } + result + } + } + } + + @Test + fun `flow that throws in constructor throw for the RPC client that attempted to start them`() { + val user = User("mark", "dadada", setOf(Permissions.startFlow())) + assertThatExceptionOfType(CordaRuntimeException::class.java).isThrownBy { + driver(DriverParameters( + startNodesInProcess = isQuasarAgentSpecified(), + notarySpecs = emptyList() + )) { + val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow() + + val result = CordaRPCClient(nodeAHandle.rpcAddress).start(user.username, user.password).use { + it.proxy.startFlow(::ThrowingFlow).returnValue.getOrThrow() + } + result + } + } + } } fun isQuasarAgentSpecified(): Boolean { @@ -71,6 +111,8 @@ fun isQuasarAgentSpecified(): Boolean { class ExceptionToCauseRetry : SQLException("deadlock") +class ExceptionToCauseFiniteRetry : ConstraintViolationException("Faked violation", SQLException("Fake"), "Fake name") + @StartableByRPC @InitiatingFlow class InitiatorFlow(private val sessionsCount: Int, private val iterationsCount: Int, private val other: Party) : FlowLogic() { @@ -168,3 +210,42 @@ data class SessionInfo(val sessionNum: Int, val iterationsCount: Int) enum class Step { First, BeforeInitiate, AfterInitiate, AfterInitiateSendReceive, BeforeSend, AfterSend, BeforeReceive, AfterReceive } data class Visited(val sessionNum: Int, val iterationNum: Int, val step: Step) + +@StartableByRPC +class RetryFlow() : FlowLogic(), IdempotentFlow { + companion object { + object FIRST_STEP : ProgressTracker.Step("Step one") + + fun tracker() = ProgressTracker(FIRST_STEP) + } + + override val progressTracker = tracker() + + @Suspendable + override fun call(): String { + progressTracker.currentStep = FIRST_STEP + throw ExceptionToCauseFiniteRetry() + return "Result" + } +} + +@StartableByRPC +class ThrowingFlow() : FlowLogic(), IdempotentFlow { + companion object { + object FIRST_STEP : ProgressTracker.Step("Step one") + + fun tracker() = ProgressTracker(FIRST_STEP) + } + + override val progressTracker = tracker() + + init { + throw IllegalStateException("This flow can never be ") + } + + @Suspendable + override fun call(): String { + progressTracker.currentStep = FIRST_STEP + return "Result" + } +} \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/node/services/TimedFlowMultiThreadedSMMTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/TimedFlowMultiThreadedSMMTests.kt index 41688c417b..5d8f8310e5 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/TimedFlowMultiThreadedSMMTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/TimedFlowMultiThreadedSMMTests.kt @@ -117,7 +117,7 @@ class TimedFlowMultiThreadedSMMTests : IntegrationTest() { private fun whenInvokedDirectlyAndTracked(rpc: CordaRPCConnection, nodeBHandle: NodeHandle) { val flowHandle = rpc.proxy.startTrackedFlow(::TimedInitiatorFlow, nodeBHandle.nodeInfo.singleIdentity()) - val stepsCount = 4 + val stepsCount = 5 assertEquals(stepsCount, flowHandle.stepsTreeFeed!!.snapshot.size, "Expected progress tracker to return the last step") val doneIndex = 3 diff --git a/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt b/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt index df2a1ec019..7b6bbb75a6 100644 --- a/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt +++ b/node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt @@ -56,7 +56,7 @@ class ContractUpgradeHandler(otherSide: FlowSession) : AbstractStateReplacementF // verify outputs matches the proposed upgrade. val ourSTX = serviceHub.validatedTransactions.getTransaction(proposal.stateRef.txhash) requireNotNull(ourSTX) { "We don't have a copy of the referenced state" } - val oldStateAndRef = ourSTX!!.tx.outRef(proposal.stateRef.index) + val oldStateAndRef = ourSTX!!.resolveBaseTransaction(serviceHub).outRef(proposal.stateRef.index) val authorisedUpgrade = serviceHub.contractUpgradeService.getAuthorisedContractUpgrade(oldStateAndRef.ref) ?: throw IllegalStateException("Contract state upgrade is unauthorised. State hash : ${oldStateAndRef.ref}") val proposedTx = stx.coreTransaction as ContractUpgradeWireTransaction val expectedTx = ContractUpgradeUtils.assembleUpgradeTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt, serviceHub) diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt index 0a8e38d0e0..b47cf2b3c1 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt @@ -14,6 +14,7 @@ import net.corda.core.identity.Party import net.corda.core.internal.* import net.corda.core.serialization.internal.CheckpointSerializationContext import net.corda.core.serialization.internal.checkpointSerialize +import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.Try import net.corda.core.utilities.debug import net.corda.core.utilities.trace @@ -209,6 +210,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, @Suspendable override fun run() { + logic.progressTracker?.currentStep = ProgressTracker.STARTING logic.stateMachine = this setLoggingContext() @@ -267,7 +269,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, processEventImmediately( Event.EnterSubFlow(subFlow.javaClass, createSubFlowVersion( - serviceHub.cordappProvider.getCordappForFlow(subFlow), serviceHub.myInfo.platformVersion + serviceHub.cordappProvider.getCordappForFlow(subFlow), serviceHub.myInfo.platformVersion ) ), isDbTransactionOpenOnEntry = true, @@ -439,7 +441,7 @@ val Class>.flowVersionAndInitiatingClass: Pair() { + @Suspendable + override fun call(): String { + return "You Called me!" + } +} + + @Suppress("DEPRECATION") // DOCSTART 1 fun recipient(rpc: CordaRPCOps, webPort: Int) { diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV3.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV3.kt new file mode 100644 index 0000000000..eb8b9a1e97 --- /dev/null +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/DummyContractV3.kt @@ -0,0 +1,36 @@ +package net.corda.testing.contracts + +import net.corda.core.contracts.* +import net.corda.core.identity.AbstractParty +import net.corda.core.transactions.LedgerTransaction + +// The dummy contract doesn't do anything useful. It exists for testing purposes. + +/** + * Dummy contract state for testing of the upgrade process. + */ +class DummyContractV3 : UpgradedContractWithLegacyConstraint { + companion object { + const val PROGRAM_ID: ContractClassName = "net.corda.testing.contracts.DummyContractV3" + } + + override val legacyContract: String = DummyContractV2.PROGRAM_ID + override val legacyContractConstraint: AttachmentConstraint = AlwaysAcceptAttachmentConstraint + + data class State(val magicNumber: Int = 0, val owners: List) : ContractState { + override val participants: List = owners + } + + interface Commands : CommandData { + class Create : TypeOnlyCommandData(), Commands + class Move : TypeOnlyCommandData(), Commands + } + + override fun upgrade(state: DummyContractV2.State): State { + return State(state.magicNumber, state.participants) + } + + override fun verify(tx: LedgerTransaction) { + // Other verifications. + } +} diff --git a/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt b/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt index 6729ab0ea2..55109ff233 100644 --- a/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt +++ b/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt @@ -17,7 +17,10 @@ import net.corda.core.flows.FlowLogic import net.corda.core.internal.* import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.openFuture -import net.corda.core.messaging.* +import net.corda.core.messaging.CordaRPCOps +import net.corda.core.messaging.DataFeed +import net.corda.core.messaging.FlowProgressHandle +import net.corda.core.messaging.StateMachineUpdate import net.corda.nodeapi.internal.pendingFlowsCount import net.corda.tools.shell.utlities.ANSIProgressRenderer import net.corda.tools.shell.utlities.StdoutANSIProgressRenderer @@ -359,11 +362,6 @@ object InteractiveShell { errors.add("${getPrototype()}: Wrong number of arguments (${args.size} provided, ${ctor.genericParameterTypes.size} needed)") continue } - val flow = ctor.newInstance(*args) as FlowLogic<*> - if (flow.progressTracker == null) { - errors.add("A flow must override the progress tracker in order to be run from the shell") - continue - } return invoke(clazz, args) } catch (e: StringToMethodCallParser.UnparseableCallException.MissingParameter) { errors.add("${getPrototype()}: missing parameter ${e.paramName}")