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
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.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.Party
import net.corda.core.internal.FetchAttachmentsFlow
@ -20,21 +24,20 @@ 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)
@ -69,7 +72,7 @@ class AttachmentTests {
// Get node one to fetch a non-existent attachment.
assert.that(
bobNode.startAttachmentFlow(hash, alice),
failsWith<FetchDataFlow.HashNotFound>(
fails<FetchDataFlow.HashNotFound>(
has("requested hash", { it.requested }, equalTo(hash))))
}
@ -93,7 +96,7 @@ class AttachmentTests {
// Get n1 to fetch the attachment. Should receive corrupted bytes.
assert.that(
bobNode.startAttachmentFlow(id, badAlice),
failsWith<FetchDataFlow.DownloadedVsRequestedDataMismatch>()
fails<FetchDataFlow.DownloadedVsRequestedDataMismatch>()
)
}
@ -113,22 +116,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 ->
@ -157,11 +158,6 @@ class AttachmentTests {
private fun StartedNode<*>.getAttachmentWithId(id: SecureHash) = database.transaction {
attachments.openAttachment(id)!!
}
private fun <T : Any> T.andRunNetwork(): T {
mockNet.runNetwork()
return this
}
//endregion
//region Matchers

View File

@ -1,45 +1,43 @@
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.Command
import net.corda.core.contracts.StateAndContract
import net.corda.core.contracts.requireThat
import net.corda.core.identity.*
import net.corda.core.matchers.succeedsWith
import net.corda.core.flows.matchers.fails
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.TransactionBuilder
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.startFlow
import org.junit.AfterClass
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 {
private val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
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
@JvmStatic
@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 bobNode = makeNode(BOB_NAME)
private val charlieNode = makeNode(CHARLIE_NAME)
@ -75,7 +73,7 @@ class CollectSignaturesFlowTests {
assert.that(
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
@ -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
// 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
@ -133,71 +140,4 @@ class CollectSignaturesFlowTests {
}
}
//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
import co.paralleluniverse.fibers.Suspendable
import com.natpryce.hamkrest.*
import com.natpryce.hamkrest.assertion.assert
import net.corda.core.CordaRuntimeException
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.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
@ -28,208 +31,159 @@ 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 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
override val magicNumber = 0
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), 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),
fails<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), succeeds())
assert.that(bobNode.deauthoriseContractUpgrade(btx), succeeds())
// 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),
fails<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), succeeds())
// 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)
}
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()
assert.that(
aliceNode.initiateDummyContractUpgrade(atx),
succeedsWith(
aliceNode.hasDummyContractUpgradeTransaction()
and bobNode.hasDummyContractUpgradeTransaction()))
}
@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)
fun `2 parties contract upgrade using RPC`() = rpcDriver {
val testUser = createTestUser()
val rpcA = startProxy(aliceNode, testUser)
val rpcB = startProxy(bobNode, testUser)
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()
// Create, sign and finalise dummy contract.
val signedByA = aliceNode.signDummyContract(alice.ref(1), bob.ref(1))
val stx = bobNode.addSignatureTo(signedByA)
assert.that(rpcA.finalise(stx, bob), rpcSucceeds())
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)
val rejectedFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Initiate(stateAndRef, upgrade) },
atx!!.tx.outRef<DummyContract.State>(0),
DummyContractV2::class.java).returnValue
// Cannot upgrade contract without prior authorisation from counterparty
assert.that(
rpcA.initiateDummyContractUpgrade(atx),
rpcFails<CordaRuntimeException>())
mockNet.runNetwork()
assertFailsWith(CordaRuntimeException::class) { rejectedFuture.getOrThrow() }
// Party B authorises the contract state upgrade, and immediately deauthorises the same.
assert.that(rpcB.authoriseDummyContractUpgrade(btx), rpcSucceeds())
assert.that(rpcB.deauthoriseContractUpgrade(btx), rpcSucceeds())
// 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
// Cannot upgrade contract if counterparty has deauthorised a previously-given authority
assert.that(
rpcA.initiateDummyContractUpgrade(atx),
rpcFails<CordaRuntimeException>())
// 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
// Party B authorise the contract state upgrade.
assert.that(rpcB.authoriseDummyContractUpgrade(btx), rpcSucceeds())
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)
}
}
// Party A initiates contract upgrade flow, expected to succeed this time.
assert.that(
rpcA.initiateDummyContractUpgrade(atx),
rpcSucceedsWith(
aliceNode.hasDummyContractUpgradeTransaction()
and bobNode.hasDummyContractUpgradeTransaction()))
}
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
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), 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> {
@ -260,4 +214,63 @@ class ContractUpgradeFlowTest {
@Suspendable
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 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.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.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),
succeedsWith(
requiredSignatures(1)
and transactionVisibleTo(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),
fails<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.succeedsWith
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].startFlow(initiatingFlow).andRunNetwork(),
succeedsWith(isA(equalTo(answer))))
}
@Test
@ -61,10 +69,11 @@ 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].startFlow(ParallelAlgorithmMap(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity()))
.andRunNetwork(),
succeedsWith(equalTo(doubleValue * stringValue.length)))
}
@Test
@ -73,12 +82,11 @@ 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].startFlow(ParallelAlgorithmList(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity()))
.andRunNetwork(),
succeedsWith(equalTo(listOf(value1, value2))))
}
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.Matcher
@ -8,11 +8,24 @@ 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}"
fun <T> succeeds() = object : Matcher<FlowStateMachine<T>> {
override val description: String = "is a flow that succeeds"
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())
} catch (e: Exception) {
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.
*/
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
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 {
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.
*/
inline fun <reified E: Exception> failsWith() = object : Matcher<FlowStateMachine<*>> {
inline fun <reified E: Exception> fails() = object : Matcher<FlowStateMachine<*>> {
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 {
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.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 {