mirror of
https://github.com/corda/corda.git
synced 2025-03-15 00:36:49 +00:00
Merge pull request #1485 from corda/stefano-merge-201810171215
Merge OS - ENT 201810171230
This commit is contained in:
commit
fe5d0da6e8
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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.")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
)
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
}
|
||||
}
|
@ -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}")
|
||||
|
Loading…
x
Reference in New Issue
Block a user