Merge pull request #3644 from corda/flow-test-rationalisation

Flow test rationalisation
This commit is contained in:
Dominic Fox 2018-07-19 15:33:43 +01:00 committed by GitHub
commit 7a18dbb8ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 832 additions and 474 deletions

View File

@ -39,6 +39,7 @@ buildscript {
ext.fileupload_version = '1.3.3' ext.fileupload_version = '1.3.3'
ext.junit_version = '4.12' ext.junit_version = '4.12'
ext.mockito_version = '2.18.3' ext.mockito_version = '2.18.3'
ext.hamkrest_version = '1.4.2.2'
ext.jopt_simple_version = '5.0.2' ext.jopt_simple_version = '5.0.2'
ext.jansi_version = '1.14' ext.jansi_version = '1.14'
ext.hibernate_version = '5.2.6.Final' ext.hibernate_version = '5.2.6.Final'

View File

@ -69,7 +69,7 @@ dependencies {
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
// Hamkrest, for fluent, composable matchers // Hamkrest, for fluent, composable matchers
testCompile 'com.natpryce:hamkrest:1.4.2.2' testCompile "com.natpryce:hamkrest:$hamkrest_version"
// Quasar, for suspendable fibres. // Quasar, for suspendable fibres.
compileOnly("$quasar_group:quasar-core:$quasar_version:jdk8") { compileOnly("$quasar_group:quasar-core:$quasar_version:jdk8") {

View File

@ -1,7 +1,7 @@
package net.corda.core.contracts package net.corda.core.contracts
import net.corda.finance.*
import net.corda.core.contracts.Amount.Companion.sumOrZero import net.corda.core.contracts.Amount.Companion.sumOrZero
import net.corda.finance.*
import org.junit.Test import org.junit.Test
import java.math.BigDecimal import java.math.BigDecimal
import java.util.* import java.util.*

View File

@ -2,8 +2,12 @@ package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import com.natpryce.hamkrest.* import com.natpryce.hamkrest.*
import com.natpryce.hamkrest.assertion.assert
import net.corda.core.contracts.Attachment import net.corda.core.contracts.Attachment
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.matchers.flow.willReturn
import net.corda.core.flows.matchers.flow.willThrow
import net.corda.core.flows.mixins.WithMockNet
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.FetchAttachmentsFlow import net.corda.core.internal.FetchAttachmentsFlow
@ -16,25 +20,23 @@ import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNodeParameters import net.corda.testing.node.internal.InternalMockNodeParameters
import net.corda.testing.node.internal.startFlow
import org.junit.AfterClass import org.junit.AfterClass
import org.junit.Test import org.junit.Test
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.util.*
import java.util.jar.JarOutputStream import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import com.natpryce.hamkrest.assertion.assert
import net.corda.core.matchers.*
class AttachmentTests { class AttachmentTests : WithMockNet {
companion object { companion object {
val mockNet = InternalMockNetwork() val classMockNet = InternalMockNetwork()
@JvmStatic @JvmStatic
@AfterClass @AfterClass
fun cleanUp() = mockNet.stopNodes() fun cleanUp() = classMockNet.stopNodes()
} }
override val mockNet = classMockNet
// Test nodes // Test nodes
private val aliceNode = makeNode(ALICE_NAME) private val aliceNode = makeNode(ALICE_NAME)
private val bobNode = makeNode(BOB_NAME) private val bobNode = makeNode(BOB_NAME)
@ -48,7 +50,7 @@ class AttachmentTests {
// Get node one to run a flow to fetch it and insert it. // Get node one to run a flow to fetch it and insert it.
assert.that( assert.that(
bobNode.startAttachmentFlow(id, alice), bobNode.startAttachmentFlow(id, alice),
succeedsWith(noAttachments())) willReturn(noAttachments()))
// Verify it was inserted into node one's store. // Verify it was inserted into node one's store.
val attachment = bobNode.getAttachmentWithId(id) val attachment = bobNode.getAttachmentWithId(id)
@ -59,7 +61,7 @@ class AttachmentTests {
assert.that( assert.that(
bobNode.startAttachmentFlow(id, alice), bobNode.startAttachmentFlow(id, alice),
succeedsWith(soleAttachment(attachment))) willReturn(soleAttachment(attachment)))
} }
@Test @Test
@ -69,10 +71,14 @@ class AttachmentTests {
// Get node one to fetch a non-existent attachment. // Get node one to fetch a non-existent attachment.
assert.that( assert.that(
bobNode.startAttachmentFlow(hash, alice), bobNode.startAttachmentFlow(hash, alice),
failsWith<FetchDataFlow.HashNotFound>( willThrow(withRequestedHash(hash)))
has("requested hash", { it.requested }, equalTo(hash))))
} }
fun withRequestedHash(expected: SecureHash) = has(
"requested hash",
FetchDataFlow.HashNotFound::requested,
equalTo(expected))
@Test @Test
fun maliciousResponse() { fun maliciousResponse() {
// Make a node that doesn't do sanity checking at load time. // Make a node that doesn't do sanity checking at load time.
@ -93,7 +99,7 @@ class AttachmentTests {
// Get n1 to fetch the attachment. Should receive corrupted bytes. // Get n1 to fetch the attachment. Should receive corrupted bytes.
assert.that( assert.that(
bobNode.startAttachmentFlow(id, badAlice), bobNode.startAttachmentFlow(id, badAlice),
failsWith<FetchDataFlow.DownloadedVsRequestedDataMismatch>() willThrow<FetchDataFlow.DownloadedVsRequestedDataMismatch>()
) )
} }
@ -113,22 +119,20 @@ class AttachmentTests {
} }
//region Generators //region Generators
private fun makeNode(name: CordaX500Name) = override fun makeNode(name: CordaX500Name) =
mockNet.createPartyNode(randomiseName(name)).apply { mockNet.createPartyNode(randomise(name)).apply {
registerInitiatedFlow(FetchAttachmentsResponse::class.java) registerInitiatedFlow(FetchAttachmentsResponse::class.java)
} }
// Makes a node that doesn't do sanity checking at load time. // Makes a node that doesn't do sanity checking at load time.
private fun makeBadNode(name: CordaX500Name) = mockNet.createNode( private fun makeBadNode(name: CordaX500Name) = mockNet.createNode(
InternalMockNodeParameters(legalName = randomiseName(name)), InternalMockNodeParameters(legalName = randomise(name)),
nodeFactory = { args -> nodeFactory = { args ->
object : InternalMockNetwork.MockNode(args) { object : InternalMockNetwork.MockNode(args) {
override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = false } override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = false }
} }
}).apply { registerInitiatedFlow(FetchAttachmentsResponse::class.java) } }).apply { registerInitiatedFlow(FetchAttachmentsResponse::class.java) }
private fun randomiseName(name: CordaX500Name) = name.copy(commonName = "${name.commonName}_${UUID.randomUUID()}")
private fun fakeAttachment(): ByteArray = private fun fakeAttachment(): ByteArray =
ByteArrayOutputStream().use { baos -> ByteArrayOutputStream().use { baos ->
JarOutputStream(baos).use { jos -> JarOutputStream(baos).use { jos ->
@ -144,24 +148,19 @@ class AttachmentTests {
//endregion //endregion
//region Operations //region Operations
private fun StartedNode<*>.importAttachment(attachment: ByteArray) = database.transaction { private fun StartedNode<*>.importAttachment(attachment: ByteArray) =
attachments.importAttachment(attachment.inputStream(), "test", null) attachments.importAttachment(attachment.inputStream(), "test", null)
.andRunNetwork()
private fun StartedNode<*>.updateAttachment(attachment: NodeAttachmentService.DBAttachment) = database.transaction {
session.update(attachment)
}.andRunNetwork() }.andRunNetwork()
private fun StartedNode<*>.updateAttachment(attachment: NodeAttachmentService.DBAttachment) = private fun StartedNode<*>.startAttachmentFlow(hash: SecureHash, otherSide: Party) = startFlowAndRunNetwork(
database.transaction { session.update(attachment) }.andRunNetwork() InitiatingFetchAttachmentsFlow(otherSide, setOf(hash)))
private fun StartedNode<*>.startAttachmentFlow(hash: SecureHash, otherSide: Party) = services.startFlow( private fun StartedNode<*>.getAttachmentWithId(id: SecureHash) =
InitiatingFetchAttachmentsFlow(otherSide, setOf(hash))).andRunNetwork()
private fun StartedNode<*>.getAttachmentWithId(id: SecureHash) = database.transaction {
attachments.openAttachment(id)!! attachments.openAttachment(id)!!
}
private fun <T : Any> T.andRunNetwork(): T {
mockNet.runNetwork()
return this
}
//endregion //endregion
//region Matchers //region Matchers

View File

@ -1,64 +1,105 @@
package net.corda.core.flows package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import com.natpryce.hamkrest.assertion.assert
import net.corda.core.contracts.Command import net.corda.core.contracts.Command
import net.corda.core.contracts.StateAndContract import net.corda.core.contracts.StateAndContract
import net.corda.core.contracts.requireThat import net.corda.core.contracts.requireThat
import net.corda.core.flows.matchers.flow.willReturn
import net.corda.core.flows.matchers.flow.willThrow
import net.corda.core.flows.mixins.WithContracts
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.identity.excludeHostNode import net.corda.core.identity.excludeHostNode
import net.corda.core.identity.groupAbstractPartyByWellKnownParty import net.corda.core.identity.groupAbstractPartyByWellKnownParty
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.StartedNode import net.corda.node.internal.StartedNode
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.*
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME
import net.corda.testing.core.TestIdentity
import net.corda.testing.core.singleIdentity
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNetwork.MockNode import org.junit.AfterClass
import net.corda.testing.node.internal.startFlow
import org.junit.After
import org.junit.Before
import org.junit.Test import org.junit.Test
import kotlin.test.assertFailsWith
class CollectSignaturesFlowTests { class CollectSignaturesFlowTests : WithContracts {
companion object { companion object {
private val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")) private val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
private val miniCorpServices = MockServices(listOf("net.corda.testing.contracts"), miniCorp, rigorousMock())
private val classMockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts", "net.corda.core.flows"))
private const val MAGIC_NUMBER = 1337
@JvmStatic
@AfterClass
fun tearDown() = classMockNet.stopNodes()
} }
private lateinit var mockNet: InternalMockNetwork override val mockNet = classMockNet
private lateinit var aliceNode: StartedNode<MockNode>
private lateinit var bobNode: StartedNode<MockNode>
private lateinit var charlieNode: StartedNode<MockNode>
private lateinit var alice: Party
private lateinit var bob: Party
private lateinit var charlie: Party
private lateinit var notary: Party
@Before private val aliceNode = makeNode(ALICE_NAME)
fun setup() { private val bobNode = makeNode(BOB_NAME)
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts", "net.corda.core.flows")) private val charlieNode = makeNode(CHARLIE_NAME)
aliceNode = mockNet.createPartyNode(ALICE_NAME)
bobNode = mockNet.createPartyNode(BOB_NAME) private val alice = aliceNode.info.singleIdentity()
charlieNode = mockNet.createPartyNode(CHARLIE_NAME) private val bob = bobNode.info.singleIdentity()
alice = aliceNode.info.singleIdentity() private val charlie = charlieNode.info.singleIdentity()
bob = bobNode.info.singleIdentity()
charlie = charlieNode.info.singleIdentity() @Test
notary = mockNet.defaultNotaryIdentity fun `successfully collects three signatures`() {
val bConfidentialIdentity = bobNode.createConfidentialIdentity(bob)
aliceNode.verifyAndRegister(bConfidentialIdentity)
assert.that(
aliceNode.startTestFlow(alice, bConfidentialIdentity.party, charlie),
willReturn(requiredSignatures(3))
)
} }
@After @Test
fun tearDown() { fun `no need to collect any signatures`() {
mockNet.stopNodes() val ptx = aliceNode.signDummyContract(alice.ref(1))
assert.that(
aliceNode.collectSignatures(ptx),
willReturn(requiredSignatures(1))
)
} }
@Test
fun `fails when not signed by initiator`() {
val ptx = miniCorpServices.signDummyContract(alice.ref(1))
assert.that(
aliceNode.collectSignatures(ptx),
willThrow(errorMessage("The Initiator of CollectSignaturesFlow must have signed the transaction.")))
}
@Test
fun `passes with multiple initial signatures`() {
val signedByA = aliceNode.signDummyContract(
alice.ref(1),
MAGIC_NUMBER,
bob.ref(2),
bob.ref(3))
val signedByBoth = bobNode.addSignatureTo(signedByA)
assert.that(
aliceNode.collectSignatures(signedByBoth),
willReturn(requiredSignatures(2))
)
}
//region Operators
private fun StartedNode<*>.startTestFlow(vararg party: Party) =
startFlowAndRunNetwork(
TestFlow.Initiator(DummyContract.MultiOwnerState(
MAGIC_NUMBER,
listOf(*party)),
mockNet.defaultNotaryIdentity))
//region Test Flow
// With this flow, the initiator starts the "CollectTransactionFlow". It is then the responders responsibility to // With this flow, the initiator starts the "CollectTransactionFlow". It is then the responders responsibility to
// override "checkTransaction" and add whatever logic their require to verify the SignedTransaction they are // override "checkTransaction" and add whatever logic their require to verify the SignedTransaction they are
// receiving off the wire. // receiving off the wire.
@ -89,7 +130,7 @@ class CollectSignaturesFlowTests {
"There should only be one output state" using (tx.outputs.size == 1) "There should only be one output state" using (tx.outputs.size == 1)
"There should only be one output state" using (tx.inputs.isEmpty()) "There should only be one output state" using (tx.inputs.isEmpty())
val magicNumberState = ltx.outputsOfType<DummyContract.MultiOwnerState>().single() val magicNumberState = ltx.outputsOfType<DummyContract.MultiOwnerState>().single()
"Must be 1337 or greater" using (magicNumberState.magicNumber >= 1337) "Must be $MAGIC_NUMBER or greater" using (magicNumberState.magicNumber >= MAGIC_NUMBER)
} }
} }
@ -98,64 +139,5 @@ class CollectSignaturesFlowTests {
} }
} }
} }
//region
@Test
fun `successfully collects two signatures`() {
val bConfidentialIdentity = bobNode.database.transaction {
val bobCert = bobNode.services.myInfo.legalIdentitiesAndCerts.single { it.name == bob.name }
bobNode.services.keyManagementService.freshKeyAndCert(bobCert, false)
}
aliceNode.database.transaction {
// Normally this is handled by TransactionKeyFlow, but here we have to manually let A know about the identity
aliceNode.services.identityService.verifyAndRegisterIdentity(bConfidentialIdentity)
}
val magicNumber = 1337
val parties = listOf(alice, bConfidentialIdentity.party, charlie)
val state = DummyContract.MultiOwnerState(magicNumber, parties)
val flow = aliceNode.services.startFlow(TestFlow.Initiator(state, notary))
mockNet.runNetwork()
val result = flow.resultFuture.getOrThrow()
result.verifyRequiredSignatures()
println(result.tx)
println(result.sigs)
}
@Test
fun `no need to collect any signatures`() {
val onePartyDummyContract = DummyContract.generateInitial(1337, notary, alice.ref(1))
val ptx = aliceNode.services.signInitialTransaction(onePartyDummyContract)
val flow = aliceNode.services.startFlow(CollectSignaturesFlow(ptx, emptySet()))
mockNet.runNetwork()
val result = flow.resultFuture.getOrThrow()
result.verifyRequiredSignatures()
println(result.tx)
println(result.sigs)
}
@Test
fun `fails when not signed by initiator`() {
val onePartyDummyContract = DummyContract.generateInitial(1337, notary, alice.ref(1))
val miniCorpServices = MockServices(listOf("net.corda.testing.contracts"), miniCorp, rigorousMock())
val ptx = miniCorpServices.signInitialTransaction(onePartyDummyContract)
val flow = aliceNode.services.startFlow(CollectSignaturesFlow(ptx, emptySet()))
mockNet.runNetwork()
assertFailsWith<IllegalArgumentException>("The Initiator of CollectSignaturesFlow must have signed the transaction.") {
flow.resultFuture.getOrThrow()
}
}
@Test
fun `passes with multiple initial signatures`() {
val twoPartyDummyContract = DummyContract.generateInitial(1337, notary,
alice.ref(1),
bob.ref(2),
bob.ref(3))
val signedByA = aliceNode.services.signInitialTransaction(twoPartyDummyContract)
val signedByBoth = bobNode.services.addSignature(signedByA)
val flow = aliceNode.services.startFlow(CollectSignaturesFlow(signedByBoth, emptySet()))
mockNet.runNetwork()
val result = flow.resultFuture.getOrThrow()
println(result.tx)
println(result.sigs)
}
} }

View File

@ -0,0 +1,141 @@
package net.corda.core.flows
import com.natpryce.hamkrest.and
import com.natpryce.hamkrest.anything
import com.natpryce.hamkrest.assertion.assert
import com.natpryce.hamkrest.has
import com.natpryce.hamkrest.isA
import net.corda.core.CordaRuntimeException
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
import net.corda.core.flows.matchers.rpc.willReturn
import net.corda.core.flows.matchers.rpc.willThrow
import net.corda.core.flows.mixins.WithContracts
import net.corda.core.flows.mixins.WithFinality
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.transactions.ContractUpgradeLedgerTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.node.internal.StartedNode
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyContractV2
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.User
import net.corda.testing.node.internal.*
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
import org.junit.AfterClass
import org.junit.Test
class ContractUpgradeFlowRPCTest : WithContracts, WithFinality {
companion object {
private val classMockNet = InternalMockNetwork(cordappPackages = listOf(
"net.corda.testing.contracts",
"net.corda.finance.contracts.asset",
"net.corda.core.flows"))
@JvmStatic
@AfterClass
fun tearDown() = classMockNet.stopNodes()
}
override val mockNet = classMockNet
private val aliceNode = makeNode(ALICE_NAME)
private val bobNode = makeNode(BOB_NAME)
private val alice = aliceNode.info.singleIdentity()
private val bob = bobNode.info.singleIdentity()
@Test
fun `2 parties contract upgrade using RPC`() = rpcDriver {
val testUser = createTestUser()
val rpcA = startProxy(aliceNode, testUser)
val rpcB = startProxy(bobNode, testUser)
// Create, sign and finalise dummy contract.
val signedByA = aliceNode.signDummyContract(alice.ref(1), 0, bob.ref(1))
val stx = bobNode.addSignatureTo(signedByA)
assert.that(rpcA.finalise(stx, bob), willReturn())
val atx = aliceNode.getValidatedTransaction(stx)
val btx = bobNode.getValidatedTransaction(stx)
// Cannot upgrade contract without prior authorisation from counterparty
assert.that(
rpcA.initiateDummyContractUpgrade(atx),
willThrow<CordaRuntimeException>())
// Party B authorises the contract state upgrade, and immediately deauthorises the same.
assert.that(rpcB.authoriseDummyContractUpgrade(btx), willReturn())
assert.that(rpcB.deauthoriseContractUpgrade(btx), willReturn())
// Cannot upgrade contract if counterparty has deauthorised a previously-given authority
assert.that(
rpcA.initiateDummyContractUpgrade(atx),
willThrow<CordaRuntimeException>())
// Party B authorise the contract state upgrade.
assert.that(rpcB.authoriseDummyContractUpgrade(btx), willReturn())
// Party A initiates contract upgrade flow, expected to succeed this time.
assert.that(
rpcA.initiateDummyContractUpgrade(atx),
willReturn(
aliceNode.hasDummyContractUpgradeTransaction()
and bobNode.hasDummyContractUpgradeTransaction()))
}
//region RPC DSL
private fun RPCDriverDSL.startProxy(node: StartedNode<MockNode>, user: User): CordaRPCOps {
return startRpcClient<CordaRPCOps>(
rpcAddress = startRpcServer(
rpcUser = user,
ops = node.rpcOps
).get().broker.hostAndPort!!,
username = user.username,
password = user.password
).get()
}
private fun RPCDriverDSL.createTestUser() = rpcTestUser.copy(permissions = setOf(
startFlow<WithFinality.FinalityInvoker>(),
startFlow<ContractUpgradeFlow.Initiate<*, *>>(),
startFlow<ContractUpgradeFlow.Authorise>(),
startFlow<ContractUpgradeFlow.Deauthorise>()
))
//endregion
//region Operations
private fun CordaRPCOps.initiateDummyContractUpgrade(tx: SignedTransaction) =
initiateContractUpgrade(tx, DummyContractV2::class)
private fun CordaRPCOps.authoriseDummyContractUpgrade(tx: SignedTransaction) =
authoriseContractUpgrade(tx, DummyContractV2::class)
//endregion
//region Matchers
private fun StartedNode<*>.hasDummyContractUpgradeTransaction() =
hasContractUpgradeTransaction<DummyContract.State, DummyContractV2.State>()
private inline fun <reified FROM : Any, reified TO: Any> StartedNode<*>.hasContractUpgradeTransaction() =
has<StateAndRef<ContractState>, ContractUpgradeLedgerTransaction>(
"a contract upgrade transaction",
{ getContractUpgradeTransaction(it) },
isUpgrade<FROM, TO>())
private fun StartedNode<*>.getContractUpgradeTransaction(state: StateAndRef<ContractState>) =
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() =
has<ContractUpgradeLedgerTransaction, Any>("input data", { it.inputs.single().state.data }, isA<T>(anything))
private inline fun <reified T: Any> isUpgradeTo() =
has<ContractUpgradeLedgerTransaction, Any>("output data", { it.outputs.single().data }, isA<T>(anything))
//endregion
}

View File

@ -1,17 +1,17 @@
package net.corda.core.flows package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable import com.natpryce.hamkrest.*
import net.corda.core.CordaRuntimeException import com.natpryce.hamkrest.assertion.assert
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.flows.matchers.flow.willReturn
import net.corda.core.flows.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.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.internal.Emoji import net.corda.core.internal.Emoji
import net.corda.core.messaging.CordaRPCOps import net.corda.core.transactions.ContractUpgradeLedgerTransaction
import net.corda.core.messaging.startFlow
import net.corda.core.node.services.queryBy
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.finance.USD import net.corda.finance.USD
@ -19,217 +19,126 @@ import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.Cash import net.corda.finance.contracts.asset.Cash
import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashIssueFlow
import net.corda.node.internal.StartedNode import net.corda.node.internal.StartedNode
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyContractV2 import net.corda.testing.contracts.DummyContractV2
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity import net.corda.testing.core.singleIdentity
import net.corda.testing.node.User import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.* import net.corda.testing.node.internal.startFlow
import net.corda.testing.node.internal.InternalMockNetwork.MockNode import org.junit.AfterClass
import org.junit.After
import org.junit.Before
import org.junit.Test import org.junit.Test
import java.util.* import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class ContractUpgradeFlowTest { class ContractUpgradeFlowTest : WithContracts, WithFinality {
private lateinit var mockNet: InternalMockNetwork companion object {
private lateinit var aliceNode: StartedNode<MockNode> private val classMockNet = InternalMockNetwork(cordappPackages = listOf(
private lateinit var bobNode: StartedNode<MockNode> "net.corda.testing.contracts",
private lateinit var notary: Party "net.corda.finance.contracts.asset",
private lateinit var alice: Party "net.corda.core.flows"))
private lateinit var bob: Party
@Before @JvmStatic
fun setup() { @AfterClass
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts", "net.corda.finance.contracts.asset", "net.corda.core.flows")) fun tearDown() = classMockNet.stopNodes()
aliceNode = mockNet.createPartyNode(ALICE_NAME)
bobNode = mockNet.createPartyNode(BOB_NAME)
notary = mockNet.defaultNotaryIdentity
alice = aliceNode.info.singleIdentity()
bob = bobNode.info.singleIdentity()
// Process registration
mockNet.runNetwork()
} }
@After override val mockNet = classMockNet
fun tearDown() {
mockNet.stopNodes() private val aliceNode = makeNode(ALICE_NAME)
} private val bobNode = makeNode(BOB_NAME)
private val alice = aliceNode.info.singleIdentity()
private val bob = bobNode.info.singleIdentity()
private val notary = mockNet.defaultNotaryIdentity
@Test @Test
fun `2 parties contract upgrade`() { fun `2 parties contract upgrade`() {
// Create dummy contract. // Create dummy contract.
val twoPartyDummyContract = DummyContract.generateInitial(0, notary, alice.ref(1), bob.ref(1)) val signedByA = aliceNode.signDummyContract(alice.ref(1),0, bob.ref(1))
val signedByA = aliceNode.services.signInitialTransaction(twoPartyDummyContract) val stx = bobNode.addSignatureTo(signedByA)
val stx = bobNode.services.addSignature(signedByA)
aliceNode.services.startFlow(FinalityFlow(stx, setOf(bob))) aliceNode.finalise(stx, bob)
mockNet.runNetwork()
val atx = aliceNode.database.transaction { aliceNode.services.validatedTransactions.getTransaction(stx.id) } val atx = aliceNode.getValidatedTransaction(stx)
val btx = bobNode.database.transaction { bobNode.services.validatedTransactions.getTransaction(stx.id) } val btx = bobNode.getValidatedTransaction(stx)
requireNotNull(atx)
requireNotNull(btx)
// The request is expected to be rejected because party B hasn't authorised the upgrade yet. // The request is expected to be rejected because party B hasn't authorised the upgrade yet.
val rejectedFuture = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(atx!!.tx.outRef(0), DummyContractV2::class.java)) assert.that(
mockNet.runNetwork() aliceNode.initiateDummyContractUpgrade(atx),
assertFailsWith(UnexpectedFlowEndException::class) { rejectedFuture.resultFuture.getOrThrow() } willThrow<UnexpectedFlowEndException>())
// Party B authorise the contract state upgrade, and immediately deauthorise the same. // Party B authorise the contract state upgrade, and immediately deauthorise the same.
bobNode.services.startFlow(ContractUpgradeFlow.Authorise(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)).resultFuture.getOrThrow() assert.that(bobNode.authoriseDummyContractUpgrade(btx), willReturn())
bobNode.services.startFlow(ContractUpgradeFlow.Deauthorise(btx.tx.outRef<ContractState>(0).ref)).resultFuture.getOrThrow() assert.that(bobNode.deauthoriseContractUpgrade(btx), willReturn())
// The request is expected to be rejected because party B has subsequently deauthorised and a previously authorised upgrade. // The request is expected to be rejected because party B has subsequently deauthorised a previously authorised upgrade.
val deauthorisedFuture = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(atx.tx.outRef(0), DummyContractV2::class.java)) assert.that(
mockNet.runNetwork() aliceNode.initiateDummyContractUpgrade(atx),
assertFailsWith(UnexpectedFlowEndException::class) { deauthorisedFuture.resultFuture.getOrThrow() } willThrow<UnexpectedFlowEndException>())
// Party B authorise the contract state upgrade // Party B authorise the contract state upgrade
bobNode.services.startFlow(ContractUpgradeFlow.Authorise(btx.tx.outRef<ContractState>(0), DummyContractV2::class.java)).resultFuture.getOrThrow() assert.that(bobNode.authoriseDummyContractUpgrade(btx), willReturn())
// Party A initiates contract upgrade flow, expected to succeed this time. // Party A initiates contract upgrade flow, expected to succeed this time.
val resultFuture = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(atx.tx.outRef(0), DummyContractV2::class.java)) assert.that(
mockNet.runNetwork() aliceNode.initiateDummyContractUpgrade(atx),
willReturn(
val result = resultFuture.resultFuture.getOrThrow() aliceNode.hasDummyContractUpgradeTransaction()
and bobNode.hasDummyContractUpgradeTransaction()))
fun check(node: StartedNode<MockNode>) {
val upgradeTx = node.database.transaction {
val wtx = node.services.validatedTransactions.getTransaction(result.ref.txhash)
wtx!!.resolveContractUpgradeTransaction(node.services)
}
assertTrue(upgradeTx.inputs.single().state.data is DummyContract.State)
assertTrue(upgradeTx.outputs.single().data is DummyContractV2.State)
}
check(aliceNode)
check(bobNode)
} }
private fun RPCDriverDSL.startProxy(node: StartedNode<MockNode>, user: User): CordaRPCOps { private fun StartedNode<*>.issueCash(amount: Amount<Currency> = Amount(1000, USD)) =
return startRpcClient<CordaRPCOps>( services.startFlow(CashIssueFlow(amount, OpaqueBytes.of(1), notary))
rpcAddress = startRpcServer( .andRunNetwork()
rpcUser = user, .resultFuture.getOrThrow()
ops = node.rpcOps
).get().broker.hostAndPort!!,
username = user.username,
password = user.password
).get()
}
@Test private fun StartedNode<*>.getBaseStateFromVault() = getStateFromVault(ContractState::class)
fun `2 parties contract upgrade using RPC`() {
rpcDriver {
// Create dummy contract.
val twoPartyDummyContract = DummyContract.generateInitial(0, notary, alice.ref(1), bob.ref(1))
val signedByA = aliceNode.services.signInitialTransaction(twoPartyDummyContract)
val stx = bobNode.services.addSignature(signedByA)
val user = rpcTestUser.copy(permissions = setOf( private fun StartedNode<*>.getCashStateFromVault() = getStateFromVault(CashV2.State::class)
startFlow<FinalityInvoker>(),
startFlow<ContractUpgradeFlow.Initiate<*, *>>(),
startFlow<ContractUpgradeFlow.Authorise>(),
startFlow<ContractUpgradeFlow.Deauthorise>()
))
val rpcA = startProxy(aliceNode, user)
val rpcB = startProxy(bobNode, user)
val handle = rpcA.startFlow(::FinalityInvoker, stx, setOf(bob))
mockNet.runNetwork()
handle.returnValue.getOrThrow()
val atx = aliceNode.database.transaction { aliceNode.services.validatedTransactions.getTransaction(stx.id) } private fun hasIssuedAmount(expected: Amount<Issued<Currency>>) =
val btx = bobNode.database.transaction { bobNode.services.validatedTransactions.getTransaction(stx.id) } hasContractState(has(CashV2.State::amount, equalTo(expected)))
requireNotNull(atx)
requireNotNull(btx)
val rejectedFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Initiate(stateAndRef, upgrade) }, private fun belongsTo(vararg recipients: AbstractParty) =
atx!!.tx.outRef<DummyContract.State>(0), hasContractState(has(CashV2.State::owners, equalTo(recipients.toList())))
DummyContractV2::class.java).returnValue
mockNet.runNetwork() private fun <T : ContractState> hasContractState(expectation: Matcher<T>) =
assertFailsWith(CordaRuntimeException::class) { rejectedFuture.getOrThrow() } has<StateAndRef<T>, T>(
"contract state",
// Party B authorise the contract state upgrade, and immediately deauthorise the same. { it.state.data },
rpcB.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Authorise(stateAndRef, upgrade) }, expectation)
btx!!.tx.outRef<ContractState>(0),
DummyContractV2::class.java).returnValue
rpcB.startFlow({ stateRef -> ContractUpgradeFlow.Deauthorise(stateRef) },
btx.tx.outRef<ContractState>(0).ref).returnValue
// The request is expected to be rejected because party B has subsequently deauthorised and a previously authorised upgrade.
val deauthorisedFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Initiate(stateAndRef, upgrade) },
atx.tx.outRef<DummyContract.State>(0),
DummyContractV2::class.java).returnValue
mockNet.runNetwork()
assertFailsWith(CordaRuntimeException::class) { deauthorisedFuture.getOrThrow() }
// Party B authorise the contract state upgrade.
rpcB.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Authorise(stateAndRef, upgrade) },
btx.tx.outRef<ContractState>(0),
DummyContractV2::class.java).returnValue
// Party A initiates contract upgrade flow, expected to succeed this time.
val resultFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Initiate(stateAndRef, upgrade) },
atx.tx.outRef<DummyContract.State>(0),
DummyContractV2::class.java).returnValue
mockNet.runNetwork()
val result = resultFuture.getOrThrow()
// Check results.
listOf(aliceNode, bobNode).forEach {
val upgradeTx = aliceNode.database.transaction {
val wtx = aliceNode.services.validatedTransactions.getTransaction(result.ref.txhash)
wtx!!.resolveContractUpgradeTransaction(aliceNode.services)
}
assertTrue(upgradeTx.inputs.single().state.data is DummyContract.State)
assertTrue(upgradeTx.outputs.single().data is DummyContractV2.State)
}
}
}
@Test @Test
fun `upgrade Cash to v2`() { fun `upgrade Cash to v2`() {
// Create some cash. // Create some cash.
val chosenIdentity = alice val cashFlowResult = aliceNode.issueCash()
val result = aliceNode.services.startFlow(CashIssueFlow(Amount(1000, USD), OpaqueBytes.of(1), notary)) val anonymisedRecipient = cashFlowResult.recipient!!
mockNet.runNetwork() val stateAndRef = cashFlowResult.stx.tx.outRef<Cash.State>(0)
val stx = result.resultFuture.getOrThrow().stx
val anonymisedRecipient = result.resultFuture.get().recipient!!
val stateAndRef = stx.tx.outRef<Cash.State>(0)
val baseState = aliceNode.database.transaction { aliceNode.services.vaultService.queryBy<ContractState>().states.single() }
assertTrue(baseState.state.data is Cash.State, "Contract state is old version.")
// Starts contract upgrade flow.
val upgradeResult = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(stateAndRef, CashV2::class.java))
mockNet.runNetwork()
upgradeResult.resultFuture.getOrThrow()
// Get contract state from the vault.
val upgradedStateFromVault = aliceNode.database.transaction { aliceNode.services.vaultService.queryBy<CashV2.State>().states.single() }
assertEquals(Amount(1000000, USD).`issued by`(chosenIdentity.ref(1)), upgradedStateFromVault.state.data.amount, "Upgraded cash contain the correct amount.")
assertEquals<Collection<AbstractParty>>(listOf(anonymisedRecipient), upgradedStateFromVault.state.data.owners, "Upgraded cash belongs to the right owner.")
// Make sure the upgraded state can be spent
val movedState = upgradedStateFromVault.state.data.copy(amount = upgradedStateFromVault.state.data.amount.times(2))
val spendUpgradedTx = aliceNode.services.signInitialTransaction(
TransactionBuilder(notary)
.addInputState(upgradedStateFromVault)
.addOutputState(
upgradedStateFromVault.state.copy(data = movedState)
)
.addCommand(CashV2.Move(), alice.owningKey)
) // The un-upgraded state is Cash.State
aliceNode.services.startFlow(FinalityFlow(spendUpgradedTx)).resultFuture.apply { assert.that(aliceNode.getBaseStateFromVault(), hasContractState(isA<Cash.State>(anything)))
mockNet.runNetwork()
get() // Starts contract upgrade flow.
assert.that(aliceNode.initiateContractUpgrade(stateAndRef, CashV2::class), willReturn())
// Get contract state from the vault.
val upgradedState = aliceNode.getCashStateFromVault()
assert.that(upgradedState,
hasIssuedAmount(Amount(1000000, USD) `issued by` (alice.ref(1)))
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)
)
addCommand(CashV2.Move(), alice.owningKey)
} }
val movedStateFromVault = aliceNode.database.transaction { aliceNode.services.vaultService.queryBy<CashV2.State>().states.single() }
assertEquals(movedState, movedStateFromVault.state.data) assert.that(aliceNode.finalise(spendUpgradedTx), willReturn())
assert.that(aliceNode.getCashStateFromVault(), hasContractState(equalTo(movedState)))
} }
class CashV2 : UpgradedContractWithLegacyConstraint<Cash.State, CashV2.State> { class CashV2 : UpgradedContractWithLegacyConstraint<Cash.State, CashV2.State> {
@ -254,10 +163,35 @@ class ContractUpgradeFlowTest {
override fun verify(tx: LedgerTransaction) {} override fun verify(tx: LedgerTransaction) {}
} }
@StartableByRPC //region Operations
class FinalityInvoker(private val transaction: SignedTransaction, private fun StartedNode<*>.initiateDummyContractUpgrade(tx: SignedTransaction) =
private val extraRecipients: Set<Party>) : FlowLogic<SignedTransaction>() { initiateContractUpgrade(tx, DummyContractV2::class)
@Suspendable
override fun call(): SignedTransaction = subFlow(FinalityFlow(transaction, extraRecipients)) private fun StartedNode<*>.authoriseDummyContractUpgrade(tx: SignedTransaction) =
} authoriseContractUpgrade(tx, DummyContractV2::class)
//endregion
//region Matchers
private fun StartedNode<*>.hasDummyContractUpgradeTransaction() =
hasContractUpgradeTransaction<DummyContract.State, DummyContractV2.State>()
private inline fun <reified FROM : Any, reified TO: Any> StartedNode<*>.hasContractUpgradeTransaction() =
has<StateAndRef<ContractState>, ContractUpgradeLedgerTransaction>(
"a contract upgrade transaction",
{ getContractUpgradeTransaction(it) },
isUpgrade<FROM, TO>())
private fun StartedNode<*>.getContractUpgradeTransaction(state: StateAndRef<ContractState>) =
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() =
has<ContractUpgradeLedgerTransaction, Any>("input data", { it.inputs.single().state.data }, isA<T>(anything))
private inline fun <reified T: Any> isUpgradeTo() =
has<ContractUpgradeLedgerTransaction, Any>("output data", { it.outputs.single().data }, isA<T>(anything))
//endregion
} }

View File

@ -13,7 +13,6 @@ import net.corda.core.internal.rootCause
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import org.assertj.core.api.Assertions.catchThrowable import org.assertj.core.api.Assertions.catchThrowable
import org.hamcrest.Matchers.lessThanOrEqualTo import org.hamcrest.Matchers.lessThanOrEqualTo
import org.junit.After
import org.junit.Assert.assertThat import org.junit.Assert.assertThat
import org.junit.Test import org.junit.Test
import java.util.* import java.util.*
@ -31,30 +30,9 @@ class FastThreadLocalTest {
} }
private val expensiveObjCount = AtomicInteger() private val expensiveObjCount = AtomicInteger()
private lateinit var pool: ExecutorService
private lateinit var scheduler: FiberExecutorScheduler
private fun init(threadCount: Int, threadImpl: (Runnable) -> Thread) {
pool = Executors.newFixedThreadPool(threadCount, threadImpl)
scheduler = FiberExecutorScheduler(null, pool)
}
@After
fun poolShutdown() = try {
pool.shutdown()
} catch (e: UninitializedPropertyAccessException) {
// Do nothing.
}
@After
fun schedulerShutdown() = try {
scheduler.shutdown()
} catch (e: UninitializedPropertyAccessException) {
// Do nothing.
}
@Test @Test
fun `ThreadLocal with plain old Thread is fiber-local`() { fun `ThreadLocal with plain old Thread is fiber-local`() = scheduled(3, ::Thread) {
init(3, ::Thread)
val threadLocal = object : ThreadLocal<ExpensiveObj>() { val threadLocal = object : ThreadLocal<ExpensiveObj>() {
override fun initialValue() = ExpensiveObj() override fun initialValue() = ExpensiveObj()
} }
@ -63,8 +41,7 @@ class FastThreadLocalTest {
} }
@Test @Test
fun `ThreadLocal with FastThreadLocalThread is fiber-local`() { fun `ThreadLocal with FastThreadLocalThread is fiber-local`() = scheduled(3, ::FastThreadLocalThread) {
init(3, ::FastThreadLocalThread)
val threadLocal = object : ThreadLocal<ExpensiveObj>() { val threadLocal = object : ThreadLocal<ExpensiveObj>() {
override fun initialValue() = ExpensiveObj() override fun initialValue() = ExpensiveObj()
} }
@ -73,8 +50,7 @@ class FastThreadLocalTest {
} }
@Test @Test
fun `FastThreadLocal with plain old Thread is fiber-local`() { fun `FastThreadLocal with plain old Thread is fiber-local`() = scheduled(3, ::Thread) {
init(3, ::Thread)
val threadLocal = object : FastThreadLocal<ExpensiveObj>() { val threadLocal = object : FastThreadLocal<ExpensiveObj>() {
override fun initialValue() = ExpensiveObj() override fun initialValue() = ExpensiveObj()
} }
@ -83,8 +59,8 @@ class FastThreadLocalTest {
} }
@Test @Test
fun `FastThreadLocal with FastThreadLocalThread is not fiber-local`() { fun `FastThreadLocal with FastThreadLocalThread is not fiber-local`() =
init(3, ::FastThreadLocalThread) scheduled(3, ::FastThreadLocalThread) {
val threadLocal = object : FastThreadLocal<ExpensiveObj>() { val threadLocal = object : FastThreadLocal<ExpensiveObj>() {
override fun initialValue() = ExpensiveObj() override fun initialValue() = ExpensiveObj()
} }
@ -93,7 +69,7 @@ class FastThreadLocalTest {
} }
/** @return the number of times a different expensive object was obtained post-suspend. */ /** @return the number of times a different expensive object was obtained post-suspend. */
private fun runFibers(fiberCount: Int, threadLocalGet: () -> ExpensiveObj): Int { private fun SchedulerContext.runFibers(fiberCount: Int, threadLocalGet: () -> ExpensiveObj): Int {
val fibers = (0 until fiberCount).map { Fiber(scheduler, FiberTask(threadLocalGet)) } val fibers = (0 until fiberCount).map { Fiber(scheduler, FiberTask(threadLocalGet)) }
val startedFibers = fibers.map { it.start() } val startedFibers = fibers.map { it.start() }
return startedFibers.map { it.get() }.count { it } return startedFibers.map { it.get() }.count { it }
@ -127,8 +103,7 @@ class FastThreadLocalTest {
}::get) }::get)
} }
private fun contentIsNotSerialized(threadLocalGet: () -> UnserializableObj) { private fun contentIsNotSerialized(threadLocalGet: () -> UnserializableObj) = scheduled(1, ::FastThreadLocalThread) {
init(1, ::FastThreadLocalThread)
// Use false like AbstractKryoSerializationScheme, the default of true doesn't work at all: // Use false like AbstractKryoSerializationScheme, the default of true doesn't work at all:
val serializer = Fiber.getFiberSerializer(false) val serializer = Fiber.getFiberSerializer(false)
val returnValue = UUID.randomUUID() val returnValue = UUID.randomUUID()
@ -162,4 +137,21 @@ class FastThreadLocalTest {
return returnValue return returnValue
} }
} }
private data class SchedulerContext(private val pool: ExecutorService, val scheduler: FiberExecutorScheduler) {
fun shutdown() {
pool.shutdown()
scheduler.shutdown()
}
}
private fun scheduled(threadCount: Int, threadImpl: (Runnable) -> Thread, test: SchedulerContext.() -> Unit) {
val pool = Executors.newFixedThreadPool(threadCount, threadImpl)
val ctx = SchedulerContext(pool, FiberExecutorScheduler(null, pool))
try {
ctx.test()
} finally {
ctx.shutdown()
}
}
} }

View File

@ -1,74 +1,68 @@
package net.corda.core.flows package net.corda.core.flows
import com.natpryce.hamkrest.and
import com.natpryce.hamkrest.assertion.assert
import net.corda.core.flows.matchers.flow.willReturn
import net.corda.core.flows.matchers.flow.willThrow
import net.corda.core.flows.mixins.WithFinality
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.finance.POUNDS import net.corda.finance.POUNDS
import net.corda.finance.contracts.asset.Cash import net.corda.finance.contracts.asset.Cash
import net.corda.finance.issuedBy import net.corda.finance.issuedBy
import net.corda.node.internal.StartedNode
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.node.MockNetwork import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.StartedMockNode import org.junit.AfterClass
import org.junit.After
import org.junit.Before
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class FinalityFlowTests { class FinalityFlowTests : WithFinality {
companion object { companion object {
private val CHARLIE = TestIdentity(CHARLIE_NAME, 90).party private val CHARLIE = TestIdentity(CHARLIE_NAME, 90).party
private val classMockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset"))
@JvmStatic
@AfterClass
fun tearDown() = classMockNet.stopNodes()
} }
private lateinit var mockNet: MockNetwork override val mockNet = classMockNet
private lateinit var aliceNode: StartedMockNode
private lateinit var bobNode: StartedMockNode
private lateinit var alice: Party
private lateinit var bob: Party
private lateinit var notary: Party
@Before private val aliceNode = makeNode(ALICE_NAME)
fun setup() { private val bobNode = makeNode(BOB_NAME)
mockNet = MockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset"))
aliceNode = mockNet.createPartyNode(ALICE_NAME)
bobNode = mockNet.createPartyNode(BOB_NAME)
alice = aliceNode.info.singleIdentity()
bob = bobNode.info.singleIdentity()
notary = mockNet.defaultNotaryIdentity
}
@After private val alice = aliceNode.info.singleIdentity()
fun tearDown() { private val bob = bobNode.info.singleIdentity()
mockNet.stopNodes() private val notary = mockNet.defaultNotaryIdentity
}
@Test @Test
fun `finalise a simple transaction`() { fun `finalise a simple transaction`() {
val amount = 1000.POUNDS.issuedBy(alice.ref(0)) val stx = aliceNode.signCashTransactionWith(bob)
val builder = TransactionBuilder(notary)
Cash().generateIssue(builder, amount, bob, notary) assert.that(
val stx = aliceNode.services.signInitialTransaction(builder) aliceNode.finalise(stx),
val flow = aliceNode.startFlow(FinalityFlow(stx)) willReturn(
mockNet.runNetwork() requiredSignatures(1)
val notarisedTx = flow.getOrThrow() and visibleTo(bobNode)))
notarisedTx.verifyRequiredSignatures()
val transactionSeenByB = bobNode.transaction {
bobNode.services.validatedTransactions.getTransaction(notarisedTx.id)
}
assertEquals(notarisedTx, transactionSeenByB)
} }
@Test @Test
fun `reject a transaction with unknown parties`() { fun `reject a transaction with unknown parties`() {
val amount = 1000.POUNDS.issuedBy(alice.ref(0)) // Charlie isn't part of this network, so node A won't recognise them
val fakeIdentity = CHARLIE // Charlie isn't part of this network, so node A won't recognise them val stx = aliceNode.signCashTransactionWith(CHARLIE)
val builder = TransactionBuilder(notary)
Cash().generateIssue(builder, amount, fakeIdentity, notary) assert.that(
val stx = aliceNode.services.signInitialTransaction(builder) aliceNode.finalise(stx),
val flow = aliceNode.startFlow(FinalityFlow(stx)) willThrow<IllegalArgumentException>())
mockNet.runNetwork()
assertFailsWith<IllegalArgumentException> {
flow.getOrThrow()
}
} }
private fun StartedNode<*>.signCashTransactionWith(other: Party): SignedTransaction {
val amount = 1000.POUNDS.issuedBy(alice.ref(0))
val builder = TransactionBuilder(notary)
Cash().generateIssue(builder, amount, other, notary)
return services.signInitialTransaction(builder)
}
} }

View File

@ -1,25 +1,34 @@
package net.corda.core.flows package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import com.natpryce.hamkrest.assertion.assert
import com.natpryce.hamkrest.equalTo
import com.natpryce.hamkrest.isA
import net.corda.core.flows.matchers.flow.willReturn
import net.corda.core.flows.mixins.WithMockNet
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap import net.corda.core.utilities.unwrap
import net.corda.testing.core.singleIdentity import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.startFlow
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.After import org.junit.AfterClass
import org.junit.Test import org.junit.Test
class ReceiveMultipleFlowTests {
private val mockNet = InternalMockNetwork() class ReceiveMultipleFlowTests : WithMockNet {
private val nodes = (0..2).map { mockNet.createPartyNode() } companion object {
@After private val classMockNet = InternalMockNetwork()
fun stopNodes() {
mockNet.stopNodes() @JvmStatic
@AfterClass
fun stopNodes() = classMockNet.stopNodes()
} }
override val mockNet = classMockNet
private val nodes = (0..2).map { mockNet.createPartyNode() }
@Test @Test
fun showcase_flows_as_closures() { fun showcase_flows_as_closures() {
val answer = 10.0 val answer = 10.0
@ -49,10 +58,9 @@ class ReceiveMultipleFlowTests {
} as FlowLogic<Unit> } as FlowLogic<Unit>
} }
val flow = nodes[0].services.startFlow(initiatingFlow) assert.that(
mockNet.runNetwork() nodes[0].startFlowAndRunNetwork(initiatingFlow),
val receivedAnswer = flow.resultFuture.getOrThrow() willReturn(answer as Any))
assertThat(receivedAnswer).isEqualTo(answer)
} }
@Test @Test
@ -61,10 +69,10 @@ class ReceiveMultipleFlowTests {
nodes[1].registerAnswer(AlgorithmDefinition::class, doubleValue) nodes[1].registerAnswer(AlgorithmDefinition::class, doubleValue)
val stringValue = "Thriller" val stringValue = "Thriller"
nodes[2].registerAnswer(AlgorithmDefinition::class, stringValue) nodes[2].registerAnswer(AlgorithmDefinition::class, stringValue)
val flow = nodes[0].services.startFlow(ParallelAlgorithmMap(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity()))
mockNet.runNetwork() assert.that(
val result = flow.resultFuture.getOrThrow() nodes[0].startFlowAndRunNetwork(ParallelAlgorithmMap(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity())),
assertThat(result).isEqualTo(doubleValue * stringValue.length) willReturn(doubleValue * stringValue.length))
} }
@Test @Test
@ -73,12 +81,10 @@ class ReceiveMultipleFlowTests {
nodes[1].registerAnswer(ParallelAlgorithmList::class, value1) nodes[1].registerAnswer(ParallelAlgorithmList::class, value1)
val value2 = 6.0 val value2 = 6.0
nodes[2].registerAnswer(ParallelAlgorithmList::class, value2) nodes[2].registerAnswer(ParallelAlgorithmList::class, value2)
val flow = nodes[0].services.startFlow(ParallelAlgorithmList(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity()))
mockNet.runNetwork() assert.that(
val data = flow.resultFuture.getOrThrow() nodes[0].startFlowAndRunNetwork(ParallelAlgorithmList(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity())),
assertThat(data[0]).isEqualTo(value1) willReturn(listOf(value1, value2)))
assertThat(data[1]).isEqualTo(value2)
assertThat(data.fold(1.0) { a, b -> a * b }).isEqualTo(value1 * value2)
} }
class ParallelAlgorithmMap(doubleMember: Party, stringMember: Party) : AlgorithmDefinition(doubleMember, stringMember) { class ParallelAlgorithmMap(doubleMember: Party, stringMember: Party) : AlgorithmDefinition(doubleMember, stringMember) {

View File

@ -0,0 +1,72 @@
package net.corda.core.flows.matchers
import com.natpryce.hamkrest.MatchResult
import com.natpryce.hamkrest.Matcher
import com.natpryce.hamkrest.equalTo
import net.corda.core.utilities.getOrThrow
import java.util.concurrent.Future
/**
* Matches a Flow that succeeds with a result matched by the given matcher
*/
fun <T> willReturn() = object : Matcher<Future<T>> {
override val description: String = "is a future that will succeed"
override fun invoke(actual: Future<T>): MatchResult = try {
actual.getOrThrow()
MatchResult.Match
} catch (e: Exception) {
MatchResult.Mismatch("Failed with $e")
}
}
fun <T> willReturn(expected: T): Matcher<Future<out T?>> = willReturn(equalTo(expected))
/**
* Matches a Flow that succeeds with a result matched by the given matcher
*/
fun <T> willReturn(successMatcher: Matcher<T>) = object : Matcher<Future<out T>> {
override val description: String = "is a future that will succeed with a value that ${successMatcher.description}"
override fun invoke(actual: Future<out T>): MatchResult = try {
successMatcher(actual.getOrThrow())
} catch (e: Exception) {
MatchResult.Mismatch("Failed with $e")
}
}
/**
* Matches a Flow that fails, with an exception matched by the given matcher.
*/
inline fun <reified E: Exception> willThrow(failureMatcher: Matcher<E>) = object : Matcher<Future<*>> {
override val description: String
get() = "is a future that will fail with a ${E::class.java.simpleName} that ${failureMatcher.description}"
override fun invoke(actual: Future<*>): MatchResult = try {
actual.getOrThrow()
MatchResult.Mismatch("Succeeded")
} catch (e: Exception) {
when(e) {
is E -> failureMatcher(e)
else -> MatchResult.Mismatch("Failure class was ${e.javaClass}")
}
}
}
/**
* Matches a Flow that fails, with an exception of the specified type.
*/
inline fun <reified E: Exception> willThrow() = object : Matcher<Future<*>> {
override val description: String
get() = "is a future that will fail with a ${E::class.java}"
override fun invoke(actual: Future<*>): MatchResult = try {
actual.getOrThrow()
MatchResult.Mismatch("Succeeded")
} catch (e: Exception) {
when(e) {
is E -> MatchResult.Match
else -> MatchResult.Mismatch("Failure class was ${e.javaClass}")
}
}
}

View File

@ -0,0 +1,36 @@
package net.corda.core.flows.matchers.flow
import com.natpryce.hamkrest.Matcher
import com.natpryce.hamkrest.equalTo
import com.natpryce.hamkrest.has
import net.corda.core.flows.matchers.willThrow
import net.corda.core.flows.matchers.willReturn
import net.corda.core.internal.FlowStateMachine
/**
* Matches a Flow that succeeds with a result matched by the given matcher
*/
fun <T> willReturn() = has(FlowStateMachine<T>::resultFuture, willReturn())
fun <T> willReturn(expected: T): Matcher<FlowStateMachine<out T?>> = net.corda.core.flows.matchers.flow.willReturn(equalTo(expected))
/**
* Matches a Flow that succeeds with a result matched by the given matcher
*/
fun <T> willReturn(successMatcher: Matcher<T>) = has(
FlowStateMachine<out T>::resultFuture,
willReturn(successMatcher))
/**
* Matches a Flow that fails, with an exception matched by the given matcher.
*/
inline fun <reified E: Exception> willThrow(failureMatcher: Matcher<E>) = has(
FlowStateMachine<*>::resultFuture,
willThrow(failureMatcher))
/**
* Matches a Flow that fails, with an exception of the specified type.
*/
inline fun <reified E: Exception> willThrow() = has(
FlowStateMachine<*>::resultFuture,
willThrow<E>())

View File

@ -0,0 +1,31 @@
package net.corda.core.flows.matchers.rpc
import com.natpryce.hamkrest.Matcher
import com.natpryce.hamkrest.has
import net.corda.core.flows.matchers.willThrow
import net.corda.core.flows.matchers.willReturn
import net.corda.core.messaging.FlowHandle
/**
* Matches a flow handle that succeeds with a result matched by the given matcher
*/
fun <T> willReturn() = has(FlowHandle<T>::returnValue, willReturn())
/**
* Matches a flow handle that succeeds with a result matched by the given matcher
*/
fun <T> willReturn(successMatcher: Matcher<T>) = has(FlowHandle<out T>::returnValue, willReturn(successMatcher))
/**
* Matches a flow handle that fails, with an exception matched by the given matcher.
*/
inline fun <reified E: Exception> willThrow(failureMatcher: Matcher<E>) = has(
FlowHandle<*>::returnValue,
willThrow(failureMatcher))
/**
* Matches a flow handle that fails, with an exception of the specified type.
*/
inline fun <reified E: Exception> willThrow() = has(
FlowHandle<*>::returnValue,
willThrow<E>())

View File

@ -0,0 +1,83 @@
package net.corda.core.flows.mixins
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.PartyAndReference
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.UpgradedContract
import net.corda.core.flows.CollectSignaturesFlow
import net.corda.core.flows.ContractUpgradeFlow
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow
import net.corda.core.node.ServiceHub
import net.corda.core.transactions.SignedTransaction
import net.corda.node.internal.StartedNode
import net.corda.testing.contracts.DummyContract
import kotlin.reflect.KClass
/**
* Mix this interface into a test class to get useful generator and operation functions for working with dummy contracts
*/
interface WithContracts : WithMockNet {
//region Generators
fun createDummyContract(owner: PartyAndReference, magicNumber: Int = 0, vararg others: PartyAndReference) =
DummyContract.generateInitial(
magicNumber,
mockNet.defaultNotaryIdentity,
owner,
*others)
//region
//region Operations
fun StartedNode<*>.signDummyContract(owner: PartyAndReference, magicNumber: Int = 0, vararg others: PartyAndReference) =
services.signDummyContract(owner, magicNumber, *others).andRunNetwork()
fun ServiceHub.signDummyContract(owner: PartyAndReference, magicNumber: Int = 0, vararg others: PartyAndReference) =
signInitialTransaction(createDummyContract(owner, magicNumber, *others))
fun StartedNode<*>.collectSignatures(ptx: SignedTransaction) =
startFlowAndRunNetwork(CollectSignaturesFlow(ptx, emptySet()))
fun StartedNode<*>.addSignatureTo(ptx: SignedTransaction) =
services.addSignature(ptx).andRunNetwork()
fun <T : UpgradedContract<*, *>>
StartedNode<*>.initiateContractUpgrade(tx: SignedTransaction, toClass: KClass<T>) =
initiateContractUpgrade(tx.tx.outRef(0), toClass)
fun <S : ContractState, T : UpgradedContract<S, *>>
StartedNode<*>.initiateContractUpgrade(stateAndRef: StateAndRef<S>, toClass: KClass<T>) =
startFlowAndRunNetwork(ContractUpgradeFlow.Initiate(stateAndRef, toClass.java))
fun <T : UpgradedContract<*, *>> StartedNode<*>.authoriseContractUpgrade(
tx: SignedTransaction, toClass: KClass<T>) =
startFlow(
ContractUpgradeFlow.Authorise(tx.tx.outRef<ContractState>(0), toClass.java)
)
fun StartedNode<*>.deauthoriseContractUpgrade(tx: SignedTransaction) = startFlow(
ContractUpgradeFlow.Deauthorise(tx.tx.outRef<ContractState>(0).ref)
)
// RPC versions of the above
fun <S : ContractState, T : UpgradedContract<S, *>> CordaRPCOps.initiateContractUpgrade(
tx: SignedTransaction, toClass: KClass<T>) =
startFlow(
{ stateAndRef, upgrade -> ContractUpgradeFlow.Initiate(stateAndRef, upgrade) },
tx.tx.outRef<S>(0),
toClass.java)
.andRunNetwork()
fun <S : ContractState, T : UpgradedContract<S, *>> CordaRPCOps.authoriseContractUpgrade(
tx: SignedTransaction, toClass: KClass<T>) =
startFlow(
{ stateAndRef, upgrade -> ContractUpgradeFlow.Authorise(stateAndRef, upgrade) },
tx.tx.outRef<S>(0),
toClass.java)
fun CordaRPCOps.deauthoriseContractUpgrade(tx: SignedTransaction) =
startFlow(
{ stateRef -> ContractUpgradeFlow.Deauthorise(stateRef) },
tx.tx.outRef<ContractState>(0).ref)
//region
}

View File

@ -0,0 +1,44 @@
package net.corda.core.flows.mixins
import co.paralleluniverse.fibers.Suspendable
import com.natpryce.hamkrest.Matcher
import com.natpryce.hamkrest.equalTo
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow
import net.corda.core.transactions.SignedTransaction
import net.corda.node.internal.StartedNode
import net.corda.testing.core.singleIdentity
interface WithFinality : WithMockNet {
//region Operations
fun StartedNode<*>.finalise(stx: SignedTransaction, vararg additionalParties: Party) =
startFlowAndRunNetwork(FinalityFlow(stx, additionalParties.toSet()))
fun StartedNode<*>.getValidatedTransaction(stx: SignedTransaction) =
services.validatedTransactions.getTransaction(stx.id)!!
fun CordaRPCOps.finalise(stx: SignedTransaction, vararg parties: Party) =
startFlow(::FinalityInvoker, stx, parties.toSet())
.andRunNetwork()
//endregion
//region Matchers
fun visibleTo(other: StartedNode<*>) = object : Matcher<SignedTransaction> {
override val description = "has a transaction visible to ${other.info.singleIdentity()}"
override fun invoke(actual: SignedTransaction) =
equalTo(actual)(other.getValidatedTransaction(actual))
}
//endregion
@StartableByRPC
class FinalityInvoker(private val transaction: SignedTransaction,
private val extraRecipients: Set<Party>) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction = subFlow(FinalityFlow(transaction, extraRecipients))
}
}

View File

@ -0,0 +1,96 @@
package net.corda.core.flows.mixins
import com.natpryce.hamkrest.*
import net.corda.core.contracts.ContractState
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.FlowStateMachine
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.node.internal.StartedNode
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.startFlow
import java.util.*
import kotlin.reflect.KClass
/**
* Mix this interface into a test to provide functions useful for working with a mock network
*/
interface WithMockNet {
val mockNet: InternalMockNetwork
/**
* Create a node using a randomised version of the given name
*/
fun makeNode(name: CordaX500Name) = mockNet.createPartyNode(randomise(name))
/**
* Randomise a party name to avoid clashes with other tests
*/
fun randomise(name: CordaX500Name) = name.copy(commonName = "${name.commonName}_${UUID.randomUUID()}")
/**
* Run the mock network before proceeding
*/
fun <T: Any> T.andRunNetwork(): T = apply { mockNet.runNetwork() }
//region Operations
/**
* Sign an initial transaction
*/
fun StartedNode<*>.signInitialTransaction(build: TransactionBuilder.() -> TransactionBuilder) =
services.signInitialTransaction(TransactionBuilder(mockNet.defaultNotaryIdentity).build())
/**
* Retrieve the sole instance of a state of a particular class from the node's vault
*/
fun <S: ContractState> StartedNode<*>.getStateFromVault(stateClass: KClass<S>) =
services.vaultService.queryBy(stateClass.java).states.single()
/**
* Start a flow
*/
fun <T> StartedNode<*>.startFlow(logic: FlowLogic<T>): FlowStateMachine<T> = services.startFlow(logic)
/**
* Start a flow and run the network immediately afterwards
*/
fun <T> StartedNode<*>.startFlowAndRunNetwork(logic: FlowLogic<T>): FlowStateMachine<T> =
startFlow(logic).andRunNetwork()
fun StartedNode<*>.createConfidentialIdentity(party: Party) =
services.keyManagementService.freshKeyAndCert(
services.myInfo.legalIdentitiesAndCerts.single { it.name == party.name },
false)
fun StartedNode<*>.verifyAndRegister(identity: PartyAndCertificate) =
services.identityService.verifyAndRegisterIdentity(identity)
//endregion
//region Matchers
/**
* The transaction has the required number of verified signatures
*/
fun requiredSignatures(count: Int = 1) = object : Matcher<SignedTransaction> {
override val description: String = "A transaction with valid required signatures"
override fun invoke(actual: SignedTransaction): MatchResult = try {
actual.verifyRequiredSignatures()
has(SignedTransaction::sigs, hasSize(equalTo(count)))(actual)
} catch (e: Exception) {
MatchResult.Mismatch("$e")
}
}
/**
* The exception has the expected error message
*/
fun errorMessage(expected: String) = has(
Exception::message,
equalTo(expected))
//endregion
}

View File

@ -1,56 +0,0 @@
package net.corda.core.matchers
import com.natpryce.hamkrest.MatchResult
import com.natpryce.hamkrest.Matcher
import net.corda.core.internal.FlowStateMachine
import net.corda.core.utilities.getOrThrow
/**
* Matches a Flow that succeeds with a result matched by the given matcher
*/
fun <T> succeedsWith(successMatcher: Matcher<T>) = object : Matcher<FlowStateMachine<T>> {
override val description: String
get() = "A flow that succeeds with ${successMatcher.description}"
override fun invoke(actual: FlowStateMachine<T>): MatchResult = try {
successMatcher(actual.resultFuture.getOrThrow())
} catch (e: Exception) {
MatchResult.Mismatch("Failed with $e")
}
}
/**
* Matches a Flow that fails, with an exception matched by the given matcher.
*/
inline fun <reified E: Exception> failsWith(failureMatcher: Matcher<E>) = object : Matcher<FlowStateMachine<*>> {
override val description: String
get() = "A flow that fails with a ${E::class.java} that ${failureMatcher.description}"
override fun invoke(actual: FlowStateMachine<*>): MatchResult = try {
actual.resultFuture.getOrThrow()
MatchResult.Mismatch("Succeeded")
} catch (e: Exception) {
when(e) {
is E -> failureMatcher(e)
else -> MatchResult.Mismatch("Failure class was ${e.javaClass}")
}
}
}
/**
* Matches a Flow that fails, with an exception of the specified type.
*/
inline fun <reified E: Exception> failsWith() = object : Matcher<FlowStateMachine<*>> {
override val description: String
get() = "A flow that fails with a ${E::class.java}"
override fun invoke(actual: FlowStateMachine<*>): MatchResult = try {
actual.resultFuture.getOrThrow()
MatchResult.Mismatch("Succeeded")
} catch (e: Exception) {
when(e) {
is E -> MatchResult.Match
else -> MatchResult.Mismatch("Failure class was ${e.javaClass}")
}
}
}

View File

@ -8,7 +8,10 @@ import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.finance.POUNDS import net.corda.finance.POUNDS
import net.corda.testing.core.* import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import net.corda.testing.core.generateStateRef
import net.corda.testing.internal.TEST_TX_TIME import net.corda.testing.internal.TEST_TX_TIME
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices

View File

@ -1,8 +1,8 @@
package net.corda.core.utilities package net.corda.core.utilities
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import org.assertj.core.api.Assertions.assertThatThrownBy
class NetworkHostAndPortTest { class NetworkHostAndPortTest {
/** /**

View File

@ -1,11 +1,11 @@
package net.corda.core.utilities package net.corda.core.utilities
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import java.util.* import java.util.*
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFails import kotlin.test.assertFails
import org.assertj.core.api.Assertions.*
class ProgressTrackerTest { class ProgressTrackerTest {
object SimpleSteps { object SimpleSteps {