Merge remote-tracking branch 'open/master' into stefano-merge-201810171215

# Conflicts:
#	docs/source/node-database.rst
This commit is contained in:
Stefano Franz 2018-10-17 12:23:01 +01:00
commit 5ca26b1345
15 changed files with 372 additions and 133 deletions

View File

@ -64,8 +64,10 @@ abstract class FlowLogic<out T> {
/**
* 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<out T> {
* 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<out T> {
// 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<Party, FlowSession>()
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<out T> {
* 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<out T> {
*
* @return Returns null if this flow has no progress tracker.
*/
fun trackStepsTree(): DataFeed<List<Pair<Int,String>>, List<Pair<Int,String>>>? {
fun trackStepsTree(): DataFeed<List<Pair<Int, String>>, List<Pair<Int, String>>>? {
// TODO this is not threadsafe, needs an atomic get-step-and-subscribe
return progressTracker?.let {
DataFeed(it.allStepsLabels, it.stepsTreeChanges)

View File

@ -26,20 +26,25 @@ import java.security.cert.X509Certificate
enum class CertRole(val validParents: NonEmptySet<CertRole?>, 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<CertRole?>, val isIdentity: Bo
fun isValidParent(parent: CertRole?): Boolean = parent in validParents
override fun toASN1Primitive(): ASN1Primitive = ASN1Integer(this.ordinal + 1L)
}
}

View File

@ -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<Step, Child>()
/** 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<Pair<Int, Step>> = _allSteps()
@ -83,42 +88,16 @@ class ProgressTracker(vararg steps: Step) {
private val _stepsTreeChanges by transient { PublishSubject.create<List<Pair<Int, String>>>() }
private val _stepsTreeIndexChanges by transient { PublishSubject.create<Int>() }
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<List<Pair<Int,String>>> get() = _stepsTreeChanges
val stepsTreeChanges: Observable<List<Pair<Int, String>>> 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.

View File

@ -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<UnexpectedFlowEndException>())
// 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<UnexpectedFlowEndException>())
// 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<DummyContract.State, DummyContractV2.State>()
and bobNode.hasContractUpgradeTransaction<DummyContract.State, DummyContractV2.State>()))
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<DummyContractV2.State, DummyContractV3.State>()
and bobNode.hasContractUpgradeTransaction<DummyContractV2.State, DummyContractV3.State>()))
}
private fun TestStartedNode.issueCash(amount: Amount<Currency> = 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<Issued<Currency>>) =
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 <T : ContractState> hasContractState(expectation: Matcher<T>) =
has<StateAndRef<T>, T>(
"contract state",
{ it.state.data },
expectation)
has<StateAndRef<T>, 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<DummyContract.State, DummyContractV2.State>()
private inline fun <reified FROM : Any, reified TO: Any> TestStartedNode.hasContractUpgradeTransaction() =
has<StateAndRef<ContractState>, ContractUpgradeLedgerTransaction>(
"a contract upgrade transaction",
{ getContractUpgradeTransaction(it) },
isUpgrade<FROM, TO>())
private inline fun <reified FROM : Any, reified TO : Any> TestStartedNode.hasContractUpgradeTransaction() =
has<StateAndRef<ContractState>, ContractUpgradeLedgerTransaction>(
"a contract upgrade transaction",
{ getContractUpgradeTransaction(it) },
isUpgrade<FROM, TO>())
private fun TestStartedNode.getContractUpgradeTransaction(state: StateAndRef<ContractState>) =
services.validatedTransactions.getTransaction(state.ref.txhash)!!
.resolveContractUpgradeTransaction(services)
services.validatedTransactions.getTransaction(state.ref.txhash)!!
.resolveContractUpgradeTransaction(services)
private inline fun <reified FROM : Any, reified TO : Any> isUpgrade() =
isUpgradeFrom<FROM>() and isUpgradeTo<TO>()
private inline fun <reified T: Any> isUpgradeFrom() =
private inline fun <reified T : Any> isUpgradeFrom() =
has<ContractUpgradeLedgerTransaction, Any>("input data", { it.inputs.single().state.data }, isA<T>(anything))
private inline fun <reified T: Any> isUpgradeTo() =
private inline fun <reified T : Any> isUpgradeTo() =
has<ContractUpgradeLedgerTransaction, Any>("output data", { it.outputs.single().data }, isA<T>(anything))
//endregion
}

View File

@ -51,9 +51,13 @@ interface WithContracts : WithMockNet {
fun <T : UpgradedContract<*, *>> TestStartedNode.authoriseContractUpgrade(
tx: SignedTransaction, toClass: KClass<T>) =
startFlow(
ContractUpgradeFlow.Authorise(tx.tx.outRef<ContractState>(0), toClass.java)
)
authoriseContractUpgrade(tx.tx.outRef(0), toClass)
fun <T : UpgradedContract<*, *>> TestStartedNode.authoriseContractUpgrade(
stateAndRef: StateAndRef<ContractState>, toClass: KClass<T>) =
startFlow(
ContractUpgradeFlow.Authorise(stateAndRef, toClass.java)
)
fun TestStartedNode.deauthoriseContractUpgrade(tx: SignedTransaction) = startFlow(
ContractUpgradeFlow.Deauthorise(tx.tx.outRef<ContractState>(0).ref)

View File

@ -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<IllegalArgumentException> { CertRole.getInstance(ASN1Integer(Integer.MAX_VALUE + 1L)) }
}
}
@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.
}
}

View File

@ -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.
}

View File

@ -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<RetryFlow>()))
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<ThrowingFlow>()))
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<Any>() {
@ -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<String>(), 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<String>(), 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"
}
}

View File

@ -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

View File

@ -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<ContractState>(proposal.stateRef.index)
val oldStateAndRef = ourSTX!!.resolveBaseTransaction(serviceHub).outRef<ContractState>(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)

View File

@ -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<R>(override val id: StateMachineRunId,
@Suspendable
override fun run() {
logic.progressTracker?.currentStep = ProgressTracker.STARTING
logic.stateMachine = this
setLoggingContext()
@ -267,7 +269,7 @@ class FlowStateMachineImpl<R>(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<out FlowLogic<*>>.flowVersionAndInitiatingClass: Pair<Int, Class<out F
current = current.superclass
?: return found
?: throw IllegalArgumentException("$name, as a flow that initiates other flows, must be annotated with " +
"${InitiatingFlow::class.java.name}. See https://docs.corda.net/api-flows.html#flowlogic-annotations.")
"${InitiatingFlow::class.java.name}. See https://docs.corda.net/api-flows.html#flowlogic-annotations.")
}
}

View File

@ -38,7 +38,10 @@ import net.corda.testing.node.internal.*
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType
import org.junit.*
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import rx.Notification
import rx.Observable
import java.time.Instant
@ -423,6 +426,7 @@ class FlowFrameworkTests {
}
assertThat(receiveFlowException.message).doesNotContain("evil bug!")
assertThat(receiveFlowSteps.get()).containsExactly(
Notification.createOnNext(ProgressTracker.STARTING),
Notification.createOnNext(ReceiveFlow.START_STEP),
Notification.createOnError(receiveFlowException)
)

View File

@ -11,6 +11,7 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import net.corda.core.flows.StartableByService
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.internal.Emoji
@ -23,9 +24,9 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.getOrThrow
import net.corda.testing.node.internal.poll
import net.corda.testing.core.DUMMY_BANK_B_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.node.internal.poll
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.URL
@ -131,6 +132,16 @@ class AttachmentDemoFlow(private val otherSide: Party,
}
}
@StartableByRPC
@StartableByService
class NoProgressTrackerShellDemo : FlowLogic<String>() {
@Suspendable
override fun call(): String {
return "You Called me!"
}
}
@Suppress("DEPRECATION")
// DOCSTART 1
fun recipient(rpc: CordaRPCOps, webPort: Int) {

View File

@ -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<DummyContractV2.State, DummyContractV3.State> {
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<AbstractParty>) : ContractState {
override val participants: List<AbstractParty> = 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.
}
}

View File

@ -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}")