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.junit_version = '4.12'
ext.mockito_version = '2.18.3'
ext.hamkrest_version = '1.4.2.2'
ext.jopt_simple_version = '5.0.2'
ext.jansi_version = '1.14'
ext.hibernate_version = '5.2.6.Final'

View File

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

View File

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

View File

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

View File

@ -1,64 +1,105 @@
package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import com.natpryce.hamkrest.assertion.assert
import net.corda.core.contracts.Command
import net.corda.core.contracts.StateAndContract
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.Party
import net.corda.core.identity.excludeHostNode
import net.corda.core.identity.groupAbstractPartyByWellKnownParty
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.StartedNode
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.ALICE_NAME
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.core.*
import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
import net.corda.testing.node.internal.startFlow
import org.junit.After
import org.junit.Before
import org.junit.AfterClass
import org.junit.Test
import kotlin.test.assertFailsWith
class CollectSignaturesFlowTests {
class CollectSignaturesFlowTests : WithContracts {
companion object {
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
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
override val mockNet = classMockNet
@Before
fun setup() {
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts", "net.corda.core.flows"))
aliceNode = mockNet.createPartyNode(ALICE_NAME)
bobNode = mockNet.createPartyNode(BOB_NAME)
charlieNode = mockNet.createPartyNode(CHARLIE_NAME)
alice = aliceNode.info.singleIdentity()
bob = bobNode.info.singleIdentity()
charlie = charlieNode.info.singleIdentity()
notary = mockNet.defaultNotaryIdentity
private val aliceNode = makeNode(ALICE_NAME)
private val bobNode = makeNode(BOB_NAME)
private val charlieNode = makeNode(CHARLIE_NAME)
private val alice = aliceNode.info.singleIdentity()
private val bob = bobNode.info.singleIdentity()
private val charlie = charlieNode.info.singleIdentity()
@Test
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
fun tearDown() {
mockNet.stopNodes()
@Test
fun `no need to collect any signatures`() {
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
// override "checkTransaction" and add whatever logic their require to verify the SignedTransaction they are
// 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.inputs.isEmpty())
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 {
}
}
}
@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)
}
//region
}

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
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.CordaRuntimeException
import com.natpryce.hamkrest.*
import com.natpryce.hamkrest.assertion.assert
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.Party
import net.corda.core.internal.Emoji
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow
import net.corda.core.node.services.queryBy
import net.corda.core.transactions.ContractUpgradeLedgerTransaction
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
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.flows.CashIssueFlow
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.After
import org.junit.Before
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.startFlow
import org.junit.AfterClass
import org.junit.Test
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class ContractUpgradeFlowTest {
private lateinit var mockNet: InternalMockNetwork
private lateinit var aliceNode: StartedNode<MockNode>
private lateinit var bobNode: StartedNode<MockNode>
private lateinit var notary: Party
private lateinit var alice: Party
private lateinit var bob: Party
class ContractUpgradeFlowTest : WithContracts, WithFinality {
companion object {
private val classMockNet = InternalMockNetwork(cordappPackages = listOf(
"net.corda.testing.contracts",
"net.corda.finance.contracts.asset",
"net.corda.core.flows"))
@Before
fun setup() {
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts", "net.corda.finance.contracts.asset", "net.corda.core.flows"))
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()
@JvmStatic
@AfterClass
fun tearDown() = classMockNet.stopNodes()
}
@After
fun tearDown() {
mockNet.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()
private val notary = mockNet.defaultNotaryIdentity
@Test
fun `2 parties contract upgrade`() {
// 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 signedByA = aliceNode.signDummyContract(alice.ref(1),0, bob.ref(1))
val stx = bobNode.addSignatureTo(signedByA)
aliceNode.services.startFlow(FinalityFlow(stx, setOf(bob)))
mockNet.runNetwork()
aliceNode.finalise(stx, bob)
val atx = aliceNode.database.transaction { aliceNode.services.validatedTransactions.getTransaction(stx.id) }
val btx = bobNode.database.transaction { bobNode.services.validatedTransactions.getTransaction(stx.id) }
requireNotNull(atx)
requireNotNull(btx)
val atx = aliceNode.getValidatedTransaction(stx)
val btx = bobNode.getValidatedTransaction(stx)
// 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))
mockNet.runNetwork()
assertFailsWith(UnexpectedFlowEndException::class) { rejectedFuture.resultFuture.getOrThrow() }
assert.that(
aliceNode.initiateDummyContractUpgrade(atx),
willThrow<UnexpectedFlowEndException>())
// 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()
bobNode.services.startFlow(ContractUpgradeFlow.Deauthorise(btx.tx.outRef<ContractState>(0).ref)).resultFuture.getOrThrow()
assert.that(bobNode.authoriseDummyContractUpgrade(btx), willReturn())
assert.that(bobNode.deauthoriseContractUpgrade(btx), willReturn())
// The request is expected to be rejected because party B has subsequently deauthorised and a previously authorised upgrade.
val deauthorisedFuture = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(atx.tx.outRef(0), DummyContractV2::class.java))
mockNet.runNetwork()
assertFailsWith(UnexpectedFlowEndException::class) { deauthorisedFuture.resultFuture.getOrThrow() }
// The request is expected to be rejected because party B has subsequently deauthorised a previously authorised upgrade.
assert.that(
aliceNode.initiateDummyContractUpgrade(atx),
willThrow<UnexpectedFlowEndException>())
// 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.
val resultFuture = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(atx.tx.outRef(0), DummyContractV2::class.java))
mockNet.runNetwork()
val result = resultFuture.resultFuture.getOrThrow()
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)
assert.that(
aliceNode.initiateDummyContractUpgrade(atx),
willReturn(
aliceNode.hasDummyContractUpgradeTransaction()
and bobNode.hasDummyContractUpgradeTransaction()))
}
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 StartedNode<*>.issueCash(amount: Amount<Currency> = Amount(1000, USD)) =
services.startFlow(CashIssueFlow(amount, OpaqueBytes.of(1), notary))
.andRunNetwork()
.resultFuture.getOrThrow()
@Test
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)
private fun StartedNode<*>.getBaseStateFromVault() = getStateFromVault(ContractState::class)
val user = rpcTestUser.copy(permissions = setOf(
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()
private fun StartedNode<*>.getCashStateFromVault() = getStateFromVault(CashV2.State::class)
val atx = aliceNode.database.transaction { aliceNode.services.validatedTransactions.getTransaction(stx.id) }
val btx = bobNode.database.transaction { bobNode.services.validatedTransactions.getTransaction(stx.id) }
requireNotNull(atx)
requireNotNull(btx)
private fun hasIssuedAmount(expected: Amount<Issued<Currency>>) =
hasContractState(has(CashV2.State::amount, equalTo(expected)))
val rejectedFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Initiate(stateAndRef, upgrade) },
atx!!.tx.outRef<DummyContract.State>(0),
DummyContractV2::class.java).returnValue
private fun belongsTo(vararg recipients: AbstractParty) =
hasContractState(has(CashV2.State::owners, equalTo(recipients.toList())))
mockNet.runNetwork()
assertFailsWith(CordaRuntimeException::class) { rejectedFuture.getOrThrow() }
// Party B authorise the contract state upgrade, and immediately deauthorise the same.
rpcB.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Authorise(stateAndRef, upgrade) },
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)
}
}
}
private fun <T : ContractState> hasContractState(expectation: Matcher<T>) =
has<StateAndRef<T>, T>(
"contract state",
{ it.state.data },
expectation)
@Test
fun `upgrade Cash to v2`() {
// Create some cash.
val chosenIdentity = alice
val result = aliceNode.services.startFlow(CashIssueFlow(Amount(1000, USD), OpaqueBytes.of(1), notary))
mockNet.runNetwork()
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)
val cashFlowResult = aliceNode.issueCash()
val anonymisedRecipient = cashFlowResult.recipient!!
val stateAndRef = cashFlowResult.stx.tx.outRef<Cash.State>(0)
)
aliceNode.services.startFlow(FinalityFlow(spendUpgradedTx)).resultFuture.apply {
mockNet.runNetwork()
get()
// The un-upgraded state is Cash.State
assert.that(aliceNode.getBaseStateFromVault(), hasContractState(isA<Cash.State>(anything)))
// 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> {
@ -254,10 +163,35 @@ class ContractUpgradeFlowTest {
override fun verify(tx: LedgerTransaction) {}
}
@StartableByRPC
class FinalityInvoker(private val transaction: SignedTransaction,
private val extraRecipients: Set<Party>) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction = subFlow(FinalityFlow(transaction, extraRecipients))
}
//region Operations
private fun StartedNode<*>.initiateDummyContractUpgrade(tx: SignedTransaction) =
initiateContractUpgrade(tx, DummyContractV2::class)
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 org.assertj.core.api.Assertions.catchThrowable
import org.hamcrest.Matchers.lessThanOrEqualTo
import org.junit.After
import org.junit.Assert.assertThat
import org.junit.Test
import java.util.*
@ -31,30 +30,9 @@ class FastThreadLocalTest {
}
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
fun `ThreadLocal with plain old Thread is fiber-local`() {
init(3, ::Thread)
fun `ThreadLocal with plain old Thread is fiber-local`() = scheduled(3, ::Thread) {
val threadLocal = object : ThreadLocal<ExpensiveObj>() {
override fun initialValue() = ExpensiveObj()
}
@ -63,8 +41,7 @@ class FastThreadLocalTest {
}
@Test
fun `ThreadLocal with FastThreadLocalThread is fiber-local`() {
init(3, ::FastThreadLocalThread)
fun `ThreadLocal with FastThreadLocalThread is fiber-local`() = scheduled(3, ::FastThreadLocalThread) {
val threadLocal = object : ThreadLocal<ExpensiveObj>() {
override fun initialValue() = ExpensiveObj()
}
@ -73,8 +50,7 @@ class FastThreadLocalTest {
}
@Test
fun `FastThreadLocal with plain old Thread is fiber-local`() {
init(3, ::Thread)
fun `FastThreadLocal with plain old Thread is fiber-local`() = scheduled(3, ::Thread) {
val threadLocal = object : FastThreadLocal<ExpensiveObj>() {
override fun initialValue() = ExpensiveObj()
}
@ -83,8 +59,8 @@ class FastThreadLocalTest {
}
@Test
fun `FastThreadLocal with FastThreadLocalThread is not fiber-local`() {
init(3, ::FastThreadLocalThread)
fun `FastThreadLocal with FastThreadLocalThread is not fiber-local`() =
scheduled(3, ::FastThreadLocalThread) {
val threadLocal = object : FastThreadLocal<ExpensiveObj>() {
override fun initialValue() = ExpensiveObj()
}
@ -93,7 +69,7 @@ class FastThreadLocalTest {
}
/** @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 startedFibers = fibers.map { it.start() }
return startedFibers.map { it.get() }.count { it }
@ -127,8 +103,7 @@ class FastThreadLocalTest {
}::get)
}
private fun contentIsNotSerialized(threadLocalGet: () -> UnserializableObj) {
init(1, ::FastThreadLocalThread)
private fun contentIsNotSerialized(threadLocalGet: () -> UnserializableObj) = scheduled(1, ::FastThreadLocalThread) {
// Use false like AbstractKryoSerializationScheme, the default of true doesn't work at all:
val serializer = Fiber.getFiberSerializer(false)
val returnValue = UUID.randomUUID()
@ -162,4 +137,21 @@ class FastThreadLocalTest {
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
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.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.finance.POUNDS
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.issuedBy
import net.corda.node.internal.StartedNode
import net.corda.testing.core.*
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.StartedMockNode
import org.junit.After
import org.junit.Before
import net.corda.testing.node.internal.InternalMockNetwork
import org.junit.AfterClass
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class FinalityFlowTests {
class FinalityFlowTests : WithFinality {
companion object {
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
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
override val mockNet = classMockNet
@Before
fun setup() {
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
}
private val aliceNode = makeNode(ALICE_NAME)
private val bobNode = makeNode(BOB_NAME)
@After
fun tearDown() {
mockNet.stopNodes()
}
private val alice = aliceNode.info.singleIdentity()
private val bob = bobNode.info.singleIdentity()
private val notary = mockNet.defaultNotaryIdentity
@Test
fun `finalise a simple transaction`() {
val amount = 1000.POUNDS.issuedBy(alice.ref(0))
val builder = TransactionBuilder(notary)
Cash().generateIssue(builder, amount, bob, notary)
val stx = aliceNode.services.signInitialTransaction(builder)
val flow = aliceNode.startFlow(FinalityFlow(stx))
mockNet.runNetwork()
val notarisedTx = flow.getOrThrow()
notarisedTx.verifyRequiredSignatures()
val transactionSeenByB = bobNode.transaction {
bobNode.services.validatedTransactions.getTransaction(notarisedTx.id)
}
assertEquals(notarisedTx, transactionSeenByB)
val stx = aliceNode.signCashTransactionWith(bob)
assert.that(
aliceNode.finalise(stx),
willReturn(
requiredSignatures(1)
and visibleTo(bobNode)))
}
@Test
fun `reject a transaction with unknown parties`() {
val amount = 1000.POUNDS.issuedBy(alice.ref(0))
val fakeIdentity = CHARLIE // Charlie isn't part of this network, so node A won't recognise them
val builder = TransactionBuilder(notary)
Cash().generateIssue(builder, amount, fakeIdentity, notary)
val stx = aliceNode.services.signInitialTransaction(builder)
val flow = aliceNode.startFlow(FinalityFlow(stx))
mockNet.runNetwork()
assertFailsWith<IllegalArgumentException> {
flow.getOrThrow()
}
// Charlie isn't part of this network, so node A won't recognise them
val stx = aliceNode.signCashTransactionWith(CHARLIE)
assert.that(
aliceNode.finalise(stx),
willThrow<IllegalArgumentException>())
}
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
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.utilities.UntrustworthyData
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.startFlow
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.AfterClass
import org.junit.Test
class ReceiveMultipleFlowTests {
private val mockNet = InternalMockNetwork()
private val nodes = (0..2).map { mockNet.createPartyNode() }
@After
fun stopNodes() {
mockNet.stopNodes()
class ReceiveMultipleFlowTests : WithMockNet {
companion object {
private val classMockNet = InternalMockNetwork()
@JvmStatic
@AfterClass
fun stopNodes() = classMockNet.stopNodes()
}
override val mockNet = classMockNet
private val nodes = (0..2).map { mockNet.createPartyNode() }
@Test
fun showcase_flows_as_closures() {
val answer = 10.0
@ -49,10 +58,9 @@ class ReceiveMultipleFlowTests {
} as FlowLogic<Unit>
}
val flow = nodes[0].services.startFlow(initiatingFlow)
mockNet.runNetwork()
val receivedAnswer = flow.resultFuture.getOrThrow()
assertThat(receivedAnswer).isEqualTo(answer)
assert.that(
nodes[0].startFlowAndRunNetwork(initiatingFlow),
willReturn(answer as Any))
}
@Test
@ -61,10 +69,10 @@ class ReceiveMultipleFlowTests {
nodes[1].registerAnswer(AlgorithmDefinition::class, doubleValue)
val stringValue = "Thriller"
nodes[2].registerAnswer(AlgorithmDefinition::class, stringValue)
val flow = nodes[0].services.startFlow(ParallelAlgorithmMap(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity()))
mockNet.runNetwork()
val result = flow.resultFuture.getOrThrow()
assertThat(result).isEqualTo(doubleValue * stringValue.length)
assert.that(
nodes[0].startFlowAndRunNetwork(ParallelAlgorithmMap(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity())),
willReturn(doubleValue * stringValue.length))
}
@Test
@ -73,12 +81,10 @@ class ReceiveMultipleFlowTests {
nodes[1].registerAnswer(ParallelAlgorithmList::class, value1)
val value2 = 6.0
nodes[2].registerAnswer(ParallelAlgorithmList::class, value2)
val flow = nodes[0].services.startFlow(ParallelAlgorithmList(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity()))
mockNet.runNetwork()
val data = flow.resultFuture.getOrThrow()
assertThat(data[0]).isEqualTo(value1)
assertThat(data[1]).isEqualTo(value2)
assertThat(data.fold(1.0) { a, b -> a * b }).isEqualTo(value1 * value2)
assert.that(
nodes[0].startFlowAndRunNetwork(ParallelAlgorithmList(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity())),
willReturn(listOf(value1, value2)))
}
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.utilities.seconds
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.rigorousMock
import net.corda.testing.node.MockServices

View File

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

View File

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