Extract common test functionality into mixin interfaces

This commit is contained in:
Dominic Fox
2018-07-18 13:49:49 +01:00
parent 45514a8764
commit 201628e44f
15 changed files with 636 additions and 386 deletions

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.fails
import net.corda.core.flows.matchers.succeedsWith
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
@ -20,21 +24,20 @@ 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)
@ -69,7 +72,7 @@ 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>( fails<FetchDataFlow.HashNotFound>(
has("requested hash", { it.requested }, equalTo(hash)))) has("requested hash", { it.requested }, equalTo(hash))))
} }
@ -93,7 +96,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>() fails<FetchDataFlow.DownloadedVsRequestedDataMismatch>()
) )
} }
@ -113,22 +116,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 ->
@ -157,11 +158,6 @@ class AttachmentTests {
private fun StartedNode<*>.getAttachmentWithId(id: SecureHash) = database.transaction { 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,45 +1,43 @@
package net.corda.core.flows package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import com.natpryce.hamkrest.* 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.identity.* import net.corda.core.flows.matchers.fails
import net.corda.core.matchers.succeedsWith import net.corda.core.flows.matchers.succeedsWith
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.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
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.startFlow
import org.junit.AfterClass import org.junit.AfterClass
import org.junit.Test import org.junit.Test
import java.util.*
import com.natpryce.hamkrest.assertion.assert
import net.corda.core.contracts.PartyAndReference
import net.corda.core.matchers.failsWith
import net.corda.core.node.ServiceHub
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 miniCorpServices = MockServices(listOf("net.corda.testing.contracts"), miniCorp, rigorousMock())
private val mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts", "net.corda.core.flows")) private val classMockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts", "net.corda.core.flows"))
private const val MAGIC_NUMBER = 1337 private const val MAGIC_NUMBER = 1337
@JvmStatic @JvmStatic
@AfterClass @AfterClass
fun tearDown() = mockNet.stopNodes() fun tearDown() = classMockNet.stopNodes()
} }
override val magicNumber = MAGIC_NUMBER
override val mockNet = classMockNet
private val aliceNode = makeNode(ALICE_NAME) private val aliceNode = makeNode(ALICE_NAME)
private val bobNode = makeNode(BOB_NAME) private val bobNode = makeNode(BOB_NAME)
private val charlieNode = makeNode(CHARLIE_NAME) private val charlieNode = makeNode(CHARLIE_NAME)
@ -75,7 +73,7 @@ class CollectSignaturesFlowTests {
assert.that( assert.that(
aliceNode.collectSignatures(ptx), aliceNode.collectSignatures(ptx),
failsWith(errorMessage("The Initiator of CollectSignaturesFlow must have signed the transaction."))) fails(errorMessage("The Initiator of CollectSignaturesFlow must have signed the transaction.")))
} }
@Test @Test
@ -92,6 +90,15 @@ class CollectSignaturesFlowTests {
) )
} }
//region Operators
private fun StartedNode<*>.startTestFlow(vararg party: Party) =
startFlow(
TestFlow.Initiator(DummyContract.MultiOwnerState(
magicNumber,
listOf(*party)),
mockNet.defaultNotaryIdentity))
.andRunNetwork()
//region Test Flow //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
@ -133,71 +140,4 @@ class CollectSignaturesFlowTests {
} }
} }
//region //region
//region Generators
private fun makeNode(name: CordaX500Name) = mockNet.createPartyNode(randomise(name))
private fun randomise(name: CordaX500Name) = name.copy(commonName = "${name.commonName}_${UUID.randomUUID()}")
//endregion
//region Operations
private fun StartedNode<*>.createConfidentialIdentity(party: Party) = database.transaction {
services.keyManagementService.freshKeyAndCert(
services.myInfo.legalIdentitiesAndCerts.single { it.name == party.name },
false)
}
private fun StartedNode<*>.verifyAndRegister(identity: PartyAndCertificate) = database.transaction {
services.identityService.verifyAndRegisterIdentity(identity)
}
private fun StartedNode<*>.startTestFlow(vararg party: Party) =
services.startFlow(
TestFlow.Initiator(DummyContract.MultiOwnerState(
MAGIC_NUMBER,
listOf(*party)),
mockNet.defaultNotaryIdentity))
.andRunNetwork()
private fun createDummyContract(owner: PartyAndReference, vararg others: PartyAndReference) =
DummyContract.generateInitial(
MAGIC_NUMBER,
mockNet.defaultNotaryIdentity,
owner,
*others)
private fun StartedNode<*>.signDummyContract(owner: PartyAndReference, vararg others: PartyAndReference) =
services.signDummyContract(owner, *others).andRunNetwork()
private fun ServiceHub.signDummyContract(owner: PartyAndReference, vararg others: PartyAndReference) =
signInitialTransaction(createDummyContract(owner, *others))
private fun StartedNode<*>.collectSignatures(ptx: SignedTransaction) =
services.startFlow(CollectSignaturesFlow(ptx, emptySet()))
.andRunNetwork()
private fun StartedNode<*>.addSignatureTo(ptx: SignedTransaction) =
services.addSignature(ptx).andRunNetwork()
private fun <T: Any> T.andRunNetwork(): T {
mockNet.runNetwork()
return this
}
//endregion
//region Matchers
private 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")
}
}
private fun errorMessage(expected: String) = has(
Exception::message,
equalTo(expected))
//endregion
} }

View File

@ -1,17 +1,20 @@
package net.corda.core.flows package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import com.natpryce.hamkrest.*
import com.natpryce.hamkrest.assertion.assert
import net.corda.core.CordaRuntimeException import net.corda.core.CordaRuntimeException
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.flows.matchers.*
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.identity.Party
import net.corda.core.internal.Emoji import net.corda.core.internal.Emoji
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow import net.corda.core.transactions.ContractUpgradeLedgerTransaction
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
@ -28,208 +31,159 @@ import net.corda.testing.core.singleIdentity
import net.corda.testing.node.User import net.corda.testing.node.User
import net.corda.testing.node.internal.* import net.corda.testing.node.internal.*
import net.corda.testing.node.internal.InternalMockNetwork.MockNode import net.corda.testing.node.internal.InternalMockNetwork.MockNode
import org.junit.After import org.junit.AfterClass
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() { override val magicNumber = 0
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), 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() } fails<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), succeeds())
bobNode.services.startFlow(ContractUpgradeFlow.Deauthorise(btx.tx.outRef<ContractState>(0).ref)).resultFuture.getOrThrow() assert.that(bobNode.deauthoriseContractUpgrade(btx), succeeds())
// 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() } fails<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), succeeds())
// 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),
succeedsWith(
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 {
return startRpcClient<CordaRPCOps>(
rpcAddress = startRpcServer(
rpcUser = user,
ops = node.rpcOps
).get().broker.hostAndPort!!,
username = user.username,
password = user.password
).get()
} }
@Test @Test
fun `2 parties contract upgrade using RPC`() { fun `2 parties contract upgrade using RPC`() = rpcDriver {
rpcDriver { val testUser = createTestUser()
// Create dummy contract. val rpcA = startProxy(aliceNode, testUser)
val twoPartyDummyContract = DummyContract.generateInitial(0, notary, alice.ref(1), bob.ref(1)) val rpcB = startProxy(bobNode, testUser)
val signedByA = aliceNode.services.signInitialTransaction(twoPartyDummyContract)
val stx = bobNode.services.addSignature(signedByA)
val user = rpcTestUser.copy(permissions = setOf( // Create, sign and finalise dummy contract.
startFlow<FinalityInvoker>(), val signedByA = aliceNode.signDummyContract(alice.ref(1), bob.ref(1))
startFlow<ContractUpgradeFlow.Initiate<*, *>>(), val stx = bobNode.addSignatureTo(signedByA)
startFlow<ContractUpgradeFlow.Authorise>(), assert.that(rpcA.finalise(stx, bob), rpcSucceeds())
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) } 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)
val rejectedFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Initiate(stateAndRef, upgrade) }, // Cannot upgrade contract without prior authorisation from counterparty
atx!!.tx.outRef<DummyContract.State>(0), assert.that(
DummyContractV2::class.java).returnValue rpcA.initiateDummyContractUpgrade(atx),
rpcFails<CordaRuntimeException>())
mockNet.runNetwork() // Party B authorises the contract state upgrade, and immediately deauthorises the same.
assertFailsWith(CordaRuntimeException::class) { rejectedFuture.getOrThrow() } assert.that(rpcB.authoriseDummyContractUpgrade(btx), rpcSucceeds())
assert.that(rpcB.deauthoriseContractUpgrade(btx), rpcSucceeds())
// Party B authorise the contract state upgrade, and immediately deauthorise the same. // Cannot upgrade contract if counterparty has deauthorised a previously-given authority
rpcB.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Authorise(stateAndRef, upgrade) }, assert.that(
btx!!.tx.outRef<ContractState>(0), rpcA.initiateDummyContractUpgrade(atx),
DummyContractV2::class.java).returnValue rpcFails<CordaRuntimeException>())
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. // Party B authorise the contract state upgrade.
val deauthorisedFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Initiate(stateAndRef, upgrade) }, assert.that(rpcB.authoriseDummyContractUpgrade(btx), rpcSucceeds())
atx.tx.outRef<DummyContract.State>(0),
DummyContractV2::class.java).returnValue
mockNet.runNetwork() // Party A initiates contract upgrade flow, expected to succeed this time.
assertFailsWith(CordaRuntimeException::class) { deauthorisedFuture.getOrThrow() } assert.that(
rpcA.initiateDummyContractUpgrade(atx),
// Party B authorise the contract state upgrade. rpcSucceedsWith(
rpcB.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Authorise(stateAndRef, upgrade) }, aliceNode.hasDummyContractUpgradeTransaction()
btx.tx.outRef<ContractState>(0), and bobNode.hasDummyContractUpgradeTransaction()))
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 StartedNode<*>.issueCash(amount: Amount<Currency> = Amount(1000, USD)) =
services.startFlow(CashIssueFlow(amount, OpaqueBytes.of(1), notary))
.andRunNetwork()
.resultFuture.getOrThrow()
private fun StartedNode<*>.getBaseStateFromVault() = getStateFromVault(ContractState::class)
private fun StartedNode<*>.getCashStateFromVault() = getStateFromVault(CashV2.State::class)
private fun hasIssuedAmount(expected: Amount<Issued<Currency>>) =
hasContractState(has(CashV2.State::amount, equalTo(expected)))
private fun belongsTo(vararg recipients: AbstractParty) =
hasContractState(has(CashV2.State::owners, equalTo(recipients.toList())))
private fun <T : ContractState> hasContractState(expectation: Matcher<T>) =
has<StateAndRef<T>, T>(
"contract state",
{ it.state.data },
expectation)
@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), succeeds())
// 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), succeeds())
assert.that(aliceNode.getCashStateFromVault(), hasContractState(equalTo(movedState)))
} }
class CashV2 : UpgradedContractWithLegacyConstraint<Cash.State, CashV2.State> { class CashV2 : UpgradedContractWithLegacyConstraint<Cash.State, CashV2.State> {
@ -260,4 +214,63 @@ class ContractUpgradeFlowTest {
@Suspendable @Suspendable
override fun call(): SignedTransaction = subFlow(FinalityFlow(transaction, extraRecipients)) override fun call(): SignedTransaction = subFlow(FinalityFlow(transaction, extraRecipients))
} }
//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<FinalityInvoker>(),
startFlow<ContractUpgradeFlow.Initiate<*, *>>(),
startFlow<ContractUpgradeFlow.Authorise>(),
startFlow<ContractUpgradeFlow.Deauthorise>()
))
//endregion
//region Operations
private fun StartedNode<*>.initiateDummyContractUpgrade(tx: SignedTransaction) =
initiateContractUpgrade(tx, DummyContractV2::class)
private fun StartedNode<*>.authoriseDummyContractUpgrade(tx: SignedTransaction) =
authoriseContractUpgrade(tx, DummyContractV2::class)
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>) = database.transaction {
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.fails
import net.corda.core.flows.matchers.succeedsWith
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)) succeedsWith(
mockNet.runNetwork() requiredSignatures(1)
val notarisedTx = flow.getOrThrow() and transactionVisibleTo(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)) fails<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.succeedsWith
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].startFlow(initiatingFlow).andRunNetwork(),
val receivedAnswer = flow.resultFuture.getOrThrow() succeedsWith(isA(equalTo(answer))))
assertThat(receivedAnswer).isEqualTo(answer)
} }
@Test @Test
@ -61,10 +69,11 @@ 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].startFlow(ParallelAlgorithmMap(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity()))
assertThat(result).isEqualTo(doubleValue * stringValue.length) .andRunNetwork(),
succeedsWith(equalTo(doubleValue * stringValue.length)))
} }
@Test @Test
@ -73,12 +82,11 @@ 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].startFlow(ParallelAlgorithmList(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity()))
assertThat(data[0]).isEqualTo(value1) .andRunNetwork(),
assertThat(data[1]).isEqualTo(value2) succeedsWith(equalTo(listOf(value1, 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

@ -1,4 +1,4 @@
package net.corda.core.matchers package net.corda.core.flows.matchers
import com.natpryce.hamkrest.MatchResult import com.natpryce.hamkrest.MatchResult
import com.natpryce.hamkrest.Matcher import com.natpryce.hamkrest.Matcher
@ -8,11 +8,24 @@ import net.corda.core.utilities.getOrThrow
/** /**
* Matches a Flow that succeeds with a result matched by the given matcher * Matches a Flow that succeeds with a result matched by the given matcher
*/ */
fun <T> succeedsWith(successMatcher: Matcher<T>) = object : Matcher<FlowStateMachine<T>> { fun <T> succeeds() = object : Matcher<FlowStateMachine<T>> {
override val description: String override val description: String = "is a flow that succeeds"
get() = "A flow that succeeds with ${successMatcher.description}"
override fun invoke(actual: FlowStateMachine<T>): MatchResult = try { override fun invoke(actual: FlowStateMachine<T>): MatchResult = try {
actual.resultFuture.getOrThrow()
MatchResult.Match
} catch (e: Exception) {
MatchResult.Mismatch("Failed with $e")
}
}
/**
* Matches a Flow that succeeds with a result matched by the given matcher
*/
fun <T> succeedsWith(successMatcher: Matcher<T>) = object : Matcher<FlowStateMachine<out T>> {
override val description: String = "is a flow that succeeds with a value that ${successMatcher.description}"
override fun invoke(actual: FlowStateMachine<out T>): MatchResult = try {
successMatcher(actual.resultFuture.getOrThrow()) successMatcher(actual.resultFuture.getOrThrow())
} catch (e: Exception) { } catch (e: Exception) {
MatchResult.Mismatch("Failed with $e") MatchResult.Mismatch("Failed with $e")
@ -22,9 +35,9 @@ fun <T> succeedsWith(successMatcher: Matcher<T>) = object : Matcher<FlowStateMac
/** /**
* Matches a Flow that fails, with an exception matched by the given matcher. * 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<*>> { inline fun <reified E: Exception> fails(failureMatcher: Matcher<E>) = object : Matcher<FlowStateMachine<*>> {
override val description: String override val description: String
get() = "A flow that fails with a ${E::class.java.simpleName} that ${failureMatcher.description}" get() = "is a flow that fails with a ${E::class.java.simpleName} that ${failureMatcher.description}"
override fun invoke(actual: FlowStateMachine<*>): MatchResult = try { override fun invoke(actual: FlowStateMachine<*>): MatchResult = try {
actual.resultFuture.getOrThrow() actual.resultFuture.getOrThrow()
@ -40,9 +53,9 @@ inline fun <reified E: Exception> failsWith(failureMatcher: Matcher<E>) = object
/** /**
* Matches a Flow that fails, with an exception of the specified type. * Matches a Flow that fails, with an exception of the specified type.
*/ */
inline fun <reified E: Exception> failsWith() = object : Matcher<FlowStateMachine<*>> { inline fun <reified E: Exception> fails() = object : Matcher<FlowStateMachine<*>> {
override val description: String override val description: String
get() = "A flow that fails with a ${E::class.java}" get() = "is a flow that fails with a ${E::class.java}"
override fun invoke(actual: FlowStateMachine<*>): MatchResult = try { override fun invoke(actual: FlowStateMachine<*>): MatchResult = try {
actual.resultFuture.getOrThrow() actual.resultFuture.getOrThrow()

View File

@ -0,0 +1,69 @@
package net.corda.core.flows.matchers
import com.natpryce.hamkrest.MatchResult
import com.natpryce.hamkrest.Matcher
import net.corda.core.messaging.FlowHandle
import net.corda.core.utilities.getOrThrow
/**
* Matches an RPC flow handle that succeeds with a result matched by the given matcher
*/
fun <T> rpcSucceeds() = object : Matcher<FlowHandle<T>> {
override val description: String = "is an RPC flow handle that succeeds"
override fun invoke(actual: FlowHandle<T>): MatchResult = try {
actual.returnValue.getOrThrow()
MatchResult.Match
} catch (e: Exception) {
MatchResult.Mismatch("Failed with $e")
}
}
/**
* Matches an RPC flow handle that succeeds with a result matched by the given matcher
*/
fun <T> rpcSucceedsWith(successMatcher: Matcher<T>) = object : Matcher<FlowHandle<T>> {
override val description: String = "is an RPC flow handle that succeeds with a value that ${successMatcher.description}"
override fun invoke(actual: FlowHandle<T>): MatchResult = try {
successMatcher(actual.returnValue.getOrThrow())
} catch (e: Exception) {
MatchResult.Mismatch("Failed with $e")
}
}
/**
* Matches an RPC Flow handle that fails, with an exception matched by the given matcher.
*/
inline fun <reified E: Exception> rpcFails(failureMatcher: Matcher<E>) = object : Matcher<FlowHandle<*>> {
override val description: String
get() = "is an RPC flow handle that fails with a ${E::class.java.simpleName} that ${failureMatcher.description}"
override fun invoke(actual: FlowHandle<*>): MatchResult = try {
actual.returnValue.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> rpcFails() = object : Matcher<FlowHandle<*>> {
override val description: String
get() = "is an RPC flow handle that fails with a ${E::class.java}"
override fun invoke(actual: FlowHandle<*>): MatchResult = try {
actual.returnValue.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,97 @@
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.identity.Party
import net.corda.core.identity.PartyAndCertificate
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 {
val magicNumber: Int
//region Generators
fun createDummyContract(owner: PartyAndReference, vararg others: PartyAndReference) =
DummyContract.generateInitial(
magicNumber,
mockNet.defaultNotaryIdentity,
owner,
*others)
//region
//region Operations
fun StartedNode<*>.createConfidentialIdentity(party: Party) = database.transaction {
services.keyManagementService.freshKeyAndCert(
services.myInfo.legalIdentitiesAndCerts.single { it.name == party.name },
false)
}
fun StartedNode<*>.verifyAndRegister(identity: PartyAndCertificate) = database.transaction {
services.identityService.verifyAndRegisterIdentity(identity)
}
fun StartedNode<*>.signDummyContract(owner: PartyAndReference, vararg others: PartyAndReference) =
services.signDummyContract(owner, *others).andRunNetwork()
fun ServiceHub.signDummyContract(owner: PartyAndReference, vararg others: PartyAndReference) =
signInitialTransaction(createDummyContract(owner, *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,36 @@
package net.corda.core.flows.mixins
import com.natpryce.hamkrest.Matcher
import com.natpryce.hamkrest.equalTo
import net.corda.core.flows.ContractUpgradeFlowTest
import net.corda.core.flows.FinalityFlow
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) = database.transaction {
services.validatedTransactions.getTransaction(stx.id)!!
}
fun CordaRPCOps.finalise(stx: SignedTransaction, vararg parties: Party) =
startFlow(ContractUpgradeFlowTest::FinalityInvoker, stx, parties.toSet())
.andRunNetwork()
//endregion
//region Matchers
fun transactionVisibleTo(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
}

View File

@ -0,0 +1,89 @@
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.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 {
mockNet.runNetwork()
return this
}
//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>) = database.transaction {
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()
//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

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