From 1f982933775be6d7954517ae165e81854542bee1 Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Fri, 17 Nov 2017 18:13:10 +0000 Subject: [PATCH 1/3] Stabilise NodeStatePersistenceTests (#2079) Pass notary identity into flow in `NodeStatePersistenceTests` rather than resolving it from the network map cache, which avoids a race condition between the flow starting and the notary registration being sent to the cache. --- .../test/node/NodeStatePersistenceTests.kt | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt index c2355f7723..7c7e09073f 100644 --- a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt @@ -40,26 +40,30 @@ class NodeStatePersistenceTests { val user = User("mark", "dadada", setOf(startFlow(), invokeRpc("vaultQuery"))) val message = Message("Hello world!") - driver(isDebug = true, startNodesInProcess = isQuasarAgentSpecified()) { + val stateAndRef: StateAndRef? = driver(isDebug = true, startNodesInProcess = isQuasarAgentSpecified()) { val nodeName = { val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow() val nodeName = nodeHandle.nodeInfo.chooseIdentity().name + // Ensure the notary node has finished starting up, before starting a flow that needs a notary + defaultNotaryNode.getOrThrow() nodeHandle.rpcClientToNode().start(user.username, user.password).use { - it.proxy.startFlow(::SendMessageFlow, message).returnValue.getOrThrow() + it.proxy.startFlow(::SendMessageFlow, message, defaultNotaryIdentity).returnValue.getOrThrow() } nodeHandle.stop() nodeName }() val nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user)).getOrThrow() - nodeHandle.rpcClientToNode().start(user.username, user.password).use { + val result = nodeHandle.rpcClientToNode().start(user.username, user.password).use { val page = it.proxy.vaultQuery(MessageState::class.java) - val stateAndRef = page.states.singleOrNull() - assertNotNull(stateAndRef) - val retrievedMessage = stateAndRef!!.state.data.message - assertEquals(message, retrievedMessage) + page.states.singleOrNull() } + nodeHandle.stop() + result } + assertNotNull(stateAndRef) + val retrievedMessage = stateAndRef!!.state.data.message + assertEquals(message, retrievedMessage) } } @@ -126,7 +130,7 @@ open class MessageContract : Contract { } @StartableByRPC -class SendMessageFlow(private val message: Message) : FlowLogic() { +class SendMessageFlow(private val message: Message, private val notary: Party) : FlowLogic() { companion object { object GENERATING_TRANSACTION : ProgressTracker.Step("Generating transaction based on the message.") object VERIFYING_TRANSACTION : ProgressTracker.Step("Verifying contract constraints.") @@ -142,8 +146,6 @@ class SendMessageFlow(private val message: Message) : FlowLogic Date: Fri, 17 Nov 2017 18:13:35 +0000 Subject: [PATCH 2/3] CORDA-759: Enforce key checks on identity de-anonymisation (#1993) Previously when de-anonymising a Party instance, the name of the Party was used rather than the key, meaning a Party could be constructed with a random nonsense key and any name, and be treated as corresponding to the well known identity. This is not a security hole in itself as in any real scenario a party shouldn't be trusted without having been registered, it creates a significant risk of a security hole depending on how trusted the anonymous identity is, and the returned identity is considered. --- docs/source/changelog.rst | 3 + .../finance/contracts/asset/CashTests.kt | 63 ++++++----- .../identity/InMemoryIdentityService.kt | 12 +- .../identity/PersistentIdentityService.kt | 12 +- ...bstractPartyToX500NameAsStringConverter.kt | 2 +- .../services/vault/VaultQueryJavaTests.java | 70 ++++++++---- .../identity/InMemoryIdentityServiceTests.kt | 18 ++- .../PersistentIdentityServiceTests.kt | 20 +++- .../persistence/HibernateConfigurationTest.kt | 104 ++++++++++-------- .../services/vault/NodeVaultServiceTest.kt | 16 ++- .../node/services/vault/VaultQueryTests.kt | 46 +++++--- .../node/services/vault/VaultWithCashTest.kt | 4 +- .../net/corda/testing/node/MockServices.kt | 3 +- .../kotlin/net/corda/testing/CoreTestUtils.kt | 3 - .../corda/testing/contracts/VaultFiller.kt | 26 ++++- 15 files changed, 250 insertions(+), 152 deletions(-) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index b8a7353ef3..11b2632071 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -11,6 +11,9 @@ UNRELEASED * ``CordaRPCOps`` implementation now checks permissions for any function invocation, rather than just when starting flows. +* ``wellKnownPartyFromAnonymous()`` now always resolve the key to a ``Party``, then the party to the well known party. + Previously if it was passed a ``Party`` it would use its name as-is without verifying the key matched that name. + * ``OpaqueBytes.bytes`` now returns a clone of its underlying ``ByteArray``, and has been redeclared as ``final``. This is a minor change to the public API, but is required to ensure that classes like ``SecureHash`` are immutable. diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt index 6884abbacf..db35907d50 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashTests.kt @@ -54,8 +54,7 @@ class CashTests { lateinit var database: CordaPersistence private lateinit var vaultStatesUnconsumed: List> - private lateinit var OUR_IDENTITY_1: AbstractParty - private lateinit var OUR_IDENTITY_AND_CERT: PartyAndCertificate + private lateinit var ourIdentity: AbstractParty private lateinit var miniCorpAnonymised: AnonymousParty private val CHARLIE_ANONYMISED = CHARLIE_IDENTITY.party.anonymise() @@ -65,28 +64,33 @@ class CashTests { fun setUp() { LogHelper.setLevel(NodeVaultService::class) megaCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), MEGA_CORP.name, MEGA_CORP_KEY) + miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), MINI_CORP.name, MINI_CORP_KEY) + val notaryServices = MockServices(listOf("net.corda.finance.contracts.asset"), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) val databaseAndServices = makeTestDatabaseAndMockServices( cordappPackages = listOf("net.corda.finance.contracts.asset"), initialIdentityName = CordaX500Name(organisation = "Me", locality = "London", country = "GB"), keys = listOf(generateKeyPair())) database = databaseAndServices.first - miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), MINI_CORP.name, MINI_CORP_KEY) ourServices = databaseAndServices.second - OUR_IDENTITY_AND_CERT = ourServices.myInfo.singleIdentityAndCert() - OUR_IDENTITY_1 = ourServices.myInfo.singleIdentity() + + // Set up and register identities + ourIdentity = ourServices.myInfo.singleIdentity() + miniCorpAnonymised = miniCorpServices.myInfo.singleIdentityAndCert().party.anonymise() + (miniCorpServices.myInfo.legalIdentitiesAndCerts + megaCorpServices.myInfo.legalIdentitiesAndCerts + notaryServices.myInfo.legalIdentitiesAndCerts).forEach { identity -> + ourServices.identityService.verifyAndRegisterIdentity(identity) + } // Create some cash. Any attempt to spend >$500 will require multiple issuers to be involved. - database.transaction { + database.transaction { ourServices.fillWithSomeTestCash(howMuch = 100.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1, - ownedBy = OUR_IDENTITY_1, issuedBy = MEGA_CORP.ref(1), issuerServices = megaCorpServices) + owner = ourIdentity, issuedBy = MEGA_CORP.ref(1), issuerServices = megaCorpServices) ourServices.fillWithSomeTestCash(howMuch = 400.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1, - ownedBy = OUR_IDENTITY_1, issuedBy = MEGA_CORP.ref(1), issuerServices = megaCorpServices) + owner = ourIdentity, issuedBy = MEGA_CORP.ref(1), issuerServices = megaCorpServices) ourServices.fillWithSomeTestCash(howMuch = 80.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1, - ownedBy = OUR_IDENTITY_1, issuedBy = MINI_CORP.ref(1), issuerServices = miniCorpServices) + owner = ourIdentity, issuedBy = MINI_CORP.ref(1), issuerServices = miniCorpServices) ourServices.fillWithSomeTestCash(howMuch = 80.SWISS_FRANCS, atLeastThisManyStates = 1, atMostThisManyStates = 1, - ownedBy = OUR_IDENTITY_1, issuedBy = MINI_CORP.ref(1), issuerServices = miniCorpServices) + owner = ourIdentity, issuedBy = MINI_CORP.ref(1), issuerServices = miniCorpServices) } - miniCorpAnonymised = miniCorpServices.myInfo.singleIdentityAndCert().party.anonymise() database.transaction { vaultStatesUnconsumed = ourServices.vaultService.queryBy().states } @@ -495,7 +499,7 @@ class CashTests { private fun makeCash(amount: Amount, issuer: AbstractParty, depositRef: Byte = 1) = StateAndRef( - TransactionState(Cash.State(amount `issued by` issuer.ref(depositRef), OUR_IDENTITY_1), Cash.PROGRAM_ID, DUMMY_NOTARY), + TransactionState(Cash.State(amount `issued by` issuer.ref(depositRef), ourIdentity), Cash.PROGRAM_ID, DUMMY_NOTARY), StateRef(SecureHash.randomSHA256(), Random().nextInt(32)) ) @@ -509,12 +513,14 @@ class CashTests { return tx.toWireTransaction(serviceHub) } - private fun makeSpend(amount: Amount, dest: AbstractParty): WireTransaction { + private fun makeSpend(services: ServiceHub, amount: Amount, dest: AbstractParty): WireTransaction { + val ourIdentity = services.myInfo.singleIdentityAndCert() + val changeIdentity = services.keyManagementService.freshKeyAndCert(ourIdentity, false) val tx = TransactionBuilder(DUMMY_NOTARY) database.transaction { - Cash.generateSpend(ourServices, tx, amount, OUR_IDENTITY_AND_CERT, dest) + Cash.generateSpend(services, tx, amount, changeIdentity, dest) } - return tx.toWireTransaction(miniCorpServices) + return tx.toWireTransaction(services) } /** @@ -588,7 +594,7 @@ class CashTests { fun generateExitWithEmptyVault() { assertFailsWith { val tx = TransactionBuilder(DUMMY_NOTARY) - Cash().generateExit(tx, Amount(100, Issued(CHARLIE.ref(1), GBP)), emptyList(), OUR_IDENTITY_1) + Cash().generateExit(tx, Amount(100, Issued(CHARLIE.ref(1), GBP)), emptyList(), ourIdentity) } } @@ -596,22 +602,23 @@ class CashTests { fun generateSimpleDirectSpend() { val wtx = database.transaction { - makeSpend(100.DOLLARS, miniCorpAnonymised) + makeSpend(ourServices, 100.DOLLARS, miniCorpAnonymised) } database.transaction { val vaultState = vaultStatesUnconsumed.elementAt(0) assertEquals(vaultState.ref, wtx.inputs[0]) assertEquals(vaultState.state.data.copy(owner = miniCorpAnonymised), wtx.getOutput(0)) - assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) + assertEquals(ourIdentity.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) } } @Test fun generateSimpleSpendWithParties() { + val changeIdentity = ourServices.keyManagementService.freshKeyAndCert(ourServices.myInfo.singleIdentityAndCert(), false) database.transaction { val tx = TransactionBuilder(DUMMY_NOTARY) - Cash.generateSpend(ourServices, tx, 80.DOLLARS, OUR_IDENTITY_AND_CERT, ALICE, setOf(MINI_CORP)) + Cash.generateSpend(ourServices, tx, 80.DOLLARS, changeIdentity, ALICE, setOf(MINI_CORP)) assertEquals(vaultStatesUnconsumed.elementAt(2).ref, tx.inputStates()[0]) } @@ -621,7 +628,7 @@ class CashTests { fun generateSimpleSpendWithChange() { val wtx = database.transaction { - makeSpend(10.DOLLARS, miniCorpAnonymised) + makeSpend(ourServices, 10.DOLLARS, miniCorpAnonymised) } database.transaction { val vaultState = vaultStatesUnconsumed.elementAt(0) @@ -638,7 +645,7 @@ class CashTests { assertEquals(vaultState.ref, wtx.inputs[0]) assertEquals(vaultState.state.data.copy(owner = miniCorpAnonymised, amount = 10.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data) assertEquals(vaultState.state.data.copy(amount = changeAmount, owner = changeOwner), wtx.outputs[1].data) - assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) + assertEquals(ourIdentity.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) } } @@ -646,7 +653,7 @@ class CashTests { fun generateSpendWithTwoInputs() { val wtx = database.transaction { - makeSpend(500.DOLLARS, miniCorpAnonymised) + makeSpend(ourServices, 500.DOLLARS, miniCorpAnonymised) } database.transaction { val vaultState0 = vaultStatesUnconsumed.elementAt(0) @@ -654,7 +661,7 @@ class CashTests { assertEquals(vaultState0.ref, wtx.inputs[0]) assertEquals(vaultState1.ref, wtx.inputs[1]) assertEquals(vaultState0.state.data.copy(owner = miniCorpAnonymised, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.getOutput(0)) - assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) + assertEquals(ourIdentity.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) } } @@ -662,7 +669,7 @@ class CashTests { fun generateSpendMixedDeposits() { val wtx = database.transaction { - val wtx = makeSpend(580.DOLLARS, miniCorpAnonymised) + val wtx = makeSpend(ourServices, 580.DOLLARS, miniCorpAnonymised) assertEquals(3, wtx.inputs.size) wtx } @@ -675,7 +682,7 @@ class CashTests { assertEquals(vaultState2.ref, wtx.inputs[2]) assertEquals(vaultState0.state.data.copy(owner = miniCorpAnonymised, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[1].data) assertEquals(vaultState2.state.data.copy(owner = miniCorpAnonymised), wtx.outputs[0].data) - assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) + assertEquals(ourIdentity.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) } } @@ -684,12 +691,12 @@ class CashTests { database.transaction { val e: InsufficientBalanceException = assertFailsWith("balance") { - makeSpend(1000.DOLLARS, miniCorpAnonymised) + makeSpend(ourServices, 1000.DOLLARS, miniCorpAnonymised) } assertEquals((1000 - 580).DOLLARS, e.amountMissing) assertFailsWith(InsufficientBalanceException::class) { - makeSpend(81.SWISS_FRANCS, miniCorpAnonymised) + makeSpend(ourServices, 81.SWISS_FRANCS, miniCorpAnonymised) } } } @@ -821,7 +828,7 @@ class CashTests { fun multiSpend() { val tx = TransactionBuilder(DUMMY_NOTARY) database.transaction { - val changeIdentity = ourServices.keyManagementService.freshKeyAndCert(OUR_IDENTITY_AND_CERT, false) + val changeIdentity = ourServices.keyManagementService.freshKeyAndCert(ourServices.myInfo.singleIdentityAndCert(), false) val payments = listOf( PartyAndAmount(miniCorpAnonymised, 400.DOLLARS), PartyAndAmount(CHARLIE_ANONYMISED, 150.DOLLARS) diff --git a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt index dcbe701f4b..679e5251c4 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt @@ -99,14 +99,14 @@ class InMemoryIdentityService(identities: Iterable = emptyS override fun partyFromKey(key: PublicKey): Party? = keyToParties[key]?.party override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = principalToParties[name]?.party override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? { - // Expand the anonymous party to a full party (i.e. has a name) if possible - val candidate = party as? Party ?: keyToParties[party.owningKey]?.party + // The original version of this would return the party as-is if it was a Party (rather than AnonymousParty), + // however that means that we don't verify that we know who owns the key. As such as now enforce turning the key + // into a party, and from there figure out the well known party. + val candidate = partyFromKey(party.owningKey) // TODO: This should be done via the network map cache, which is the authoritative source of well known identities - // Look up the well known identity for that name return if (candidate != null) { - // If we have a well known identity by that name, use it in preference to the candidate. Otherwise default - // back to the candidate. - principalToParties[candidate.name]?.party ?: candidate + require(party.nameOrNull() == null || party.nameOrNull() == candidate.name) { "Candidate party ${candidate} does not match expected ${party}" } + wellKnownPartyFromX500Name(candidate.name) } else { null } diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt index 2309560d65..dba40da607 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt @@ -164,15 +164,13 @@ class PersistentIdentityService(identities: Iterable = empt override fun partyFromKey(key: PublicKey): Party? = certificateFromKey(key)?.party override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = certificateFromCordaX500Name(name)?.party override fun wellKnownPartyFromAnonymous(party: AbstractParty): Party? { - // Expand the anonymous party to a full party (i.e. has a name) if possible - val candidate = party as? Party ?: partyFromKey(party.owningKey) + // The original version of this would return the party as-is if it was a Party (rather than AnonymousParty), + // however that means that we don't verify that we know who owns the key. As such as now enforce turning the key + // into a party, and from there figure out the well known party. + val candidate = partyFromKey(party.owningKey) // TODO: This should be done via the network map cache, which is the authoritative source of well known identities - // Look up the well known identity for that name return if (candidate != null) { - // If we have a well known identity by that name, use it in preference to the candidate. Otherwise default - // back to the candidate. - val res = wellKnownPartyFromX500Name(candidate.name) ?: candidate - res + wellKnownPartyFromX500Name(candidate.name) } else { null } diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyToX500NameAsStringConverter.kt b/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyToX500NameAsStringConverter.kt index 8dc9c857bd..81dfe1c7ec 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyToX500NameAsStringConverter.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/AbstractPartyToX500NameAsStringConverter.kt @@ -23,7 +23,7 @@ class AbstractPartyToX500NameAsStringConverter(identitySvc: () -> IdentityServic if (party != null) { val partyName = identityService.wellKnownPartyFromAnonymous(party)?.toString() if (partyName != null) return partyName - log.warn("Identity service unable to resolve AbstractParty: $party") + log.warn("Identity service unable to resolve AbstractParty: $party") } return null // non resolvable anonymous parties } diff --git a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java index bf532ae053..350c6a4a3e 100644 --- a/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java +++ b/node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java @@ -1,37 +1,57 @@ package net.corda.node.services.vault; -import com.google.common.collect.*; -import kotlin.*; +import com.google.common.collect.ImmutableSet; +import kotlin.Pair; +import kotlin.Triple; import net.corda.core.contracts.*; -import net.corda.core.identity.*; -import net.corda.core.messaging.*; -import net.corda.core.node.services.*; +import net.corda.core.identity.AbstractParty; +import net.corda.core.messaging.DataFeed; +import net.corda.core.node.services.IdentityService; +import net.corda.core.node.services.Vault; +import net.corda.core.node.services.VaultQueryException; +import net.corda.core.node.services.VaultService; import net.corda.core.node.services.vault.*; -import net.corda.core.node.services.vault.QueryCriteria.*; -import net.corda.core.utilities.*; -import net.corda.finance.contracts.*; -import net.corda.finance.contracts.asset.*; -import net.corda.finance.schemas.*; -import net.corda.node.utilities.*; -import net.corda.testing.*; -import net.corda.testing.contracts.*; -import net.corda.testing.node.*; -import org.junit.*; +import net.corda.core.node.services.vault.QueryCriteria.LinearStateQueryCriteria; +import net.corda.core.node.services.vault.QueryCriteria.VaultCustomQueryCriteria; +import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria; +import net.corda.core.utilities.EncodingUtils; +import net.corda.core.utilities.OpaqueBytes; +import net.corda.finance.contracts.DealState; +import net.corda.finance.contracts.asset.Cash; +import net.corda.finance.contracts.asset.CashUtilities; +import net.corda.finance.schemas.CashSchemaV1; +import net.corda.node.utilities.CordaPersistence; +import net.corda.node.utilities.DatabaseTransaction; +import net.corda.testing.SerializationEnvironmentRule; +import net.corda.testing.TestConstants; +import net.corda.testing.contracts.DummyLinearContract; +import net.corda.testing.contracts.VaultFiller; +import net.corda.testing.node.MockServices; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; import rx.Observable; -import java.io.*; -import java.lang.reflect.*; -import java.security.*; +import java.io.IOException; +import java.lang.reflect.Field; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPair; +import java.security.cert.CertificateException; import java.util.*; -import java.util.stream.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; -import static net.corda.core.node.services.vault.QueryCriteriaUtils.*; -import static net.corda.core.utilities.ByteArrays.*; +import static net.corda.core.node.services.vault.QueryCriteriaUtils.DEFAULT_PAGE_NUM; +import static net.corda.core.node.services.vault.QueryCriteriaUtils.MAX_PAGE_SIZE; +import static net.corda.core.utilities.ByteArrays.toHexString; import static net.corda.finance.contracts.asset.CashUtilities.*; import static net.corda.testing.CoreTestUtils.*; import static net.corda.testing.TestConstants.*; -import static net.corda.testing.node.MockServices.*; -import static org.assertj.core.api.Assertions.*; +import static net.corda.testing.node.MockServices.makeTestDatabaseAndMockServices; +import static net.corda.testing.node.MockServices.makeTestIdentityService; +import static org.assertj.core.api.Assertions.assertThat; public class VaultQueryJavaTests { @Rule @@ -42,7 +62,7 @@ public class VaultQueryJavaTests { private CordaPersistence database; @Before - public void setUp() { + public void setUp() throws CertificateException, InvalidAlgorithmParameterException { List cordappPackages = Arrays.asList("net.corda.testing.contracts", "net.corda.finance.contracts.asset", CashSchemaV1.class.getPackage().getName()); ArrayList keys = new ArrayList<>(); keys.add(getMEGA_CORP_KEY()); @@ -54,6 +74,8 @@ public class VaultQueryJavaTests { database = databaseAndServices.getFirst(); services = databaseAndServices.getSecond(); vaultService = services.getVaultService(); + services.getIdentityService().verifyAndRegisterIdentity(getDUMMY_CASH_ISSUER_IDENTITY()); + services.getIdentityService().verifyAndRegisterIdentity(getDUMMY_NOTARY_IDENTITY()); } @After diff --git a/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt index c69cf8e4c1..63d6a80e30 100644 --- a/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt @@ -169,9 +169,23 @@ class InMemoryIdentityServiceTests { * Ensure if we feed in a full identity, we get the same identity back. */ @Test - fun `deanonymising a well known identity`() { + fun `deanonymising a well known identity should return the identity`() { + val service = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT) val expected = ALICE - val actual = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT).wellKnownPartyFromAnonymous(expected) + service.verifyAndRegisterIdentity(ALICE_IDENTITY) + val actual = service.wellKnownPartyFromAnonymous(expected) assertEquals(expected, actual) } + + /** + * Ensure we don't blindly trust what an anonymous identity claims to be. + */ + @Test + fun `deanonymising a false well known identity should return null`() { + val service = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT) + val notAlice = Party(ALICE.name, generateKeyPair().public) + service.verifyAndRegisterIdentity(ALICE_IDENTITY) + val actual = service.wellKnownPartyFromAnonymous(notAlice) + assertNull(actual) + } } diff --git a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt index c01cd2be76..30c1753a43 100644 --- a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt @@ -269,11 +269,23 @@ class PersistentIdentityServiceTests { * Ensure if we feed in a full identity, we get the same identity back. */ @Test - fun `deanonymising a well known identity`() { + fun `deanonymising a well known identity should return the identity`() { + val service = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT) val expected = ALICE - val actual = database.transaction { - identityService.wellKnownPartyFromAnonymous(expected) - } + service.verifyAndRegisterIdentity(ALICE_IDENTITY) + val actual = service.wellKnownPartyFromAnonymous(expected) assertEquals(expected, actual) } + + /** + * Ensure we don't blindly trust what an anonymous identity claims to be. + */ + @Test + fun `deanonymising a false well known identity should return null`() { + val service = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT) + val notAlice = Party(ALICE.name, generateKeyPair().public) + service.verifyAndRegisterIdentity(ALICE_IDENTITY) + val actual = service.wellKnownPartyFromAnonymous(notAlice) + assertNull(actual) + } } diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt index a63846573e..a7851fc154 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt @@ -1,24 +1,26 @@ package net.corda.node.services.persistence -import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef import net.corda.core.contracts.TransactionState import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.generateKeyPair +import net.corda.core.identity.Party import net.corda.core.node.StatesToRecord -import net.corda.core.utilities.toBase58String import net.corda.core.node.services.Vault import net.corda.core.node.services.VaultService import net.corda.core.schemas.CommonSchemaV1 import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentStateRef -import net.corda.core.serialization.deserialize -import net.corda.core.serialization.SerializationDefaults import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.toBase58String import net.corda.finance.DOLLARS import net.corda.finance.POUNDS import net.corda.finance.SWISS_FRANCS -import net.corda.finance.contracts.asset.* +import net.corda.finance.contracts.asset.Cash +import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_KEY +import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER_NAME +import net.corda.finance.contracts.asset.DummyFungibleContract import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.schemas.SampleCashSchemaV2 import net.corda.finance.schemas.SampleCashSchemaV3 @@ -54,7 +56,9 @@ class HibernateConfigurationTest { @JvmField val testSerialization = SerializationEnvironmentRule() lateinit var services: MockServices + lateinit var bankServices: MockServices lateinit var issuerServices: MockServices + lateinit var notaryServices: MockServices lateinit var database: CordaPersistence val vault: VaultService get() = services.vaultService @@ -65,19 +69,27 @@ class HibernateConfigurationTest { lateinit var entityManager: EntityManager lateinit var criteriaBuilder: CriteriaBuilder + // Identities used + private lateinit var identity: Party + private lateinit var issuer: Party + private lateinit var notary: Party + // test States lateinit var cashStates: List> @Before fun setUp() { val cordappPackages = listOf("net.corda.testing.contracts", "net.corda.finance.contracts.asset") - issuerServices = MockServices(cordappPackages, DUMMY_CASH_ISSUER_NAME, DUMMY_CASH_ISSUER_KEY, BOB_KEY, BOC_KEY) + bankServices = MockServices(cordappPackages, BOC.name, BOC_KEY) + issuerServices = MockServices(cordappPackages, DUMMY_CASH_ISSUER_NAME, DUMMY_CASH_ISSUER_KEY) + notaryServices = MockServices(cordappPackages, DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) val dataSourceProps = makeTestDataSourceProperties() val defaultDatabaseProperties = makeTestDatabaseProperties() database = configureDatabase(dataSourceProps, defaultDatabaseProperties, ::makeTestIdentityService) database.transaction { hibernateConfig = database.hibernateConfig - services = object : MockServices(cordappPackages, BOB_NAME, BOB_KEY, BOC_KEY, DUMMY_NOTARY_KEY) { + // `consumeCash` expects we can self-notarise transactions + services = object : MockServices(cordappPackages, BOB_NAME, generateKeyPair(), DUMMY_NOTARY_KEY) { override val vaultService = makeVaultService(database.hibernateConfig) override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { for (stx in txs) { @@ -91,7 +103,17 @@ class HibernateConfigurationTest { } hibernatePersister = services.hibernatePersister } - setUpDb() + + identity = services.myInfo.singleIdentity() + issuer = issuerServices.myInfo.singleIdentity() + notary = notaryServices.myInfo.singleIdentity() + + database.transaction { + val numStates = 10 + cashStates = services.fillWithSomeTestCash(100.DOLLARS, issuerServices, notary, numStates, numStates, Random(0L), issuedBy = issuer.ref(1)) + .states.toList() + } + sessionFactory = sessionFactoryForSchemas(VaultSchemaV1, CashSchemaV1, SampleCashSchemaV2, SampleCashSchemaV3) entityManager = sessionFactory.createEntityManager() criteriaBuilder = sessionFactory.criteriaBuilder @@ -104,12 +126,6 @@ class HibernateConfigurationTest { database.close() } - private fun setUpDb() { - database.transaction { - cashStates = services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 10, 10, Random(0L)).states.toList() - } - } - @Test fun `count rows`() { // structure query @@ -125,7 +141,7 @@ class HibernateConfigurationTest { @Test fun `consumed states`() { database.transaction { - services.consumeCash(50.DOLLARS, notary = DUMMY_NOTARY) + services.consumeCash(50.DOLLARS, notary = notary) } // structure query @@ -206,11 +222,11 @@ class HibernateConfigurationTest { fun `with sorting by state ref desc and asc`() { // generate additional state ref indexes database.transaction { - services.consumeCash(1.DOLLARS, notary = DUMMY_NOTARY) - services.consumeCash(2.DOLLARS, notary = DUMMY_NOTARY) - services.consumeCash(3.DOLLARS, notary = DUMMY_NOTARY) - services.consumeCash(4.DOLLARS, notary = DUMMY_NOTARY) - services.consumeCash(5.DOLLARS, notary = DUMMY_NOTARY) + services.consumeCash(1.DOLLARS, notary = notary) + services.consumeCash(2.DOLLARS, notary = notary) + services.consumeCash(3.DOLLARS, notary = notary) + services.consumeCash(4.DOLLARS, notary = notary) + services.consumeCash(5.DOLLARS, notary = notary) } // structure query @@ -236,11 +252,11 @@ class HibernateConfigurationTest { fun `with sorting by state ref index and txId desc and asc`() { // generate additional state ref indexes database.transaction { - services.consumeCash(1.DOLLARS, notary = DUMMY_NOTARY) - services.consumeCash(2.DOLLARS, notary = DUMMY_NOTARY) - services.consumeCash(3.DOLLARS, notary = DUMMY_NOTARY) - services.consumeCash(4.DOLLARS, notary = DUMMY_NOTARY) - services.consumeCash(5.DOLLARS, notary = DUMMY_NOTARY) + services.consumeCash(1.DOLLARS, notary = notary) + services.consumeCash(2.DOLLARS, notary = notary) + services.consumeCash(3.DOLLARS, notary = notary) + services.consumeCash(4.DOLLARS, notary = notary) + services.consumeCash(5.DOLLARS, notary = notary) } // structure query @@ -267,7 +283,7 @@ class HibernateConfigurationTest { fun `with pagination`() { // add 100 additional cash entries database.transaction { - services.fillWithSomeTestCash(1000.POUNDS, issuerServices, DUMMY_NOTARY, 100, 100, Random(0L), issuedBy = DUMMY_CASH_ISSUER) + services.fillWithSomeTestCash(1000.POUNDS, issuerServices, notary, 100, 100, Random(0L), issuedBy = issuer.ref(1)) } // structure query @@ -369,11 +385,11 @@ class HibernateConfigurationTest { fun `calculate cash balances`() { database.transaction { - services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 10, 10, Random(0L)) // +$100 = $200 - services.fillWithSomeTestCash(50.POUNDS, issuerServices, DUMMY_NOTARY, 5, 5, Random(0L)) // £50 = £50 - services.fillWithSomeTestCash(25.POUNDS, issuerServices, DUMMY_NOTARY, 5, 5, Random(0L)) // +£25 = £175 - services.fillWithSomeTestCash(500.SWISS_FRANCS, issuerServices, DUMMY_NOTARY, 10, 10, Random(0L)) // CHF500 = CHF500 - services.fillWithSomeTestCash(250.SWISS_FRANCS, issuerServices, DUMMY_NOTARY, 5, 5, Random(0L)) // +CHF250 = CHF750 + services.fillWithSomeTestCash(100.DOLLARS, issuerServices, notary, 10, issuer.ref(1)) // +$100 = $200 + services.fillWithSomeTestCash(50.POUNDS, issuerServices, notary, 5, issuer.ref(1)) // £50 = £50 + services.fillWithSomeTestCash(25.POUNDS, issuerServices, notary, 5, issuer.ref(1)) // +£25 = £175 + services.fillWithSomeTestCash(500.SWISS_FRANCS, issuerServices, notary, 10, issuer.ref(1)) // CHF500 = CHF500 + services.fillWithSomeTestCash(250.SWISS_FRANCS, issuerServices, notary, 5, issuer.ref(1)) // +CHF250 = CHF750 } // structure query @@ -402,8 +418,8 @@ class HibernateConfigurationTest { @Test fun `calculate cash balance for single currency`() { database.transaction { - services.fillWithSomeTestCash(50.POUNDS, issuerServices, DUMMY_NOTARY, 5, 5, Random(0L)) // £50 = £50 - services.fillWithSomeTestCash(25.POUNDS, issuerServices, DUMMY_NOTARY, 5, 5, Random(0L)) // +£25 = £175 + services.fillWithSomeTestCash(50.POUNDS, issuerServices, notary, 5, issuer.ref(1)) // £50 = £50 + services.fillWithSomeTestCash(25.POUNDS, issuerServices, notary, 5, issuer.ref(1)) // +£25 = £175 } // structure query @@ -432,10 +448,10 @@ class HibernateConfigurationTest { @Test fun `calculate and order by cash balance for owner and currency`() { database.transaction { - - services.fillWithSomeTestCash(200.DOLLARS, issuerServices, DUMMY_NOTARY, 2, 2, Random(0L), issuedBy = BOC.ref(1)) - services.fillWithSomeTestCash(300.POUNDS, issuerServices, DUMMY_NOTARY, 3, 3, Random(0L), issuedBy = DUMMY_CASH_ISSUER) - services.fillWithSomeTestCash(400.POUNDS, issuerServices, DUMMY_NOTARY, 4, 4, Random(0L), issuedBy = BOC.ref(2)) + val bank = bankServices.myInfo.legalIdentities.single() + services.fillWithSomeTestCash(200.DOLLARS, bankServices, notary, 2, bank.ref(1)) + services.fillWithSomeTestCash(300.POUNDS, issuerServices, notary, 3, issuer.ref(1)) + services.fillWithSomeTestCash(400.POUNDS, bankServices, notary, 4, bank.ref(2)) } // structure query @@ -622,9 +638,9 @@ class HibernateConfigurationTest { hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3) } - services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 2, 2, Random(0L), ownedBy = ALICE) - val cashStates = services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 2, 2, Random(0L), - issuedBy = BOB.ref(0), ownedBy = (BOB)).states + services.fillWithSomeTestCash(100.DOLLARS, issuerServices, notary, 2, 2, Random(0L), + issuedBy = issuer.ref(1), owner = ALICE) + val cashStates = services.fillWithSomeTestCash(100.DOLLARS, services, notary, 2, identity.ref(0)).states // persist additional cash states explicitly with V3 schema cashStates.forEach { val cashState = it.state.data @@ -646,7 +662,7 @@ class HibernateConfigurationTest { // search predicate val cashStatesSchema = criteriaQuery.from(SampleCashSchemaV3.PersistentCashState::class.java) - val queryOwner = BOB.name.toString() + val queryOwner = identity.name.toString() criteriaQuery.where(criteriaBuilder.equal(cashStatesSchema.get("owner"), queryOwner)) val joinVaultStatesToCash = criteriaBuilder.equal(vaultStates.get("stateRef"), cashStatesSchema.get("stateRef")) @@ -701,8 +717,8 @@ class HibernateConfigurationTest { hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3) } - val moreCash = services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 2, 2, Random(0L), - issuedBy = BOB.ref(0), ownedBy = BOB).states + val moreCash = services.fillWithSomeTestCash(100.DOLLARS, services, notary, 2, 2, Random(0L), + issuedBy = identity.ref(0), owner = identity).states // persist additional cash states explicitly with V3 schema moreCash.forEach { val cashState = it.state.data @@ -710,7 +726,7 @@ class HibernateConfigurationTest { hibernatePersister.persistStateWithSchema(dummyFungibleState, it.ref, SampleCashSchemaV3) } - val cashStates = services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 2, 2, Random(0L), ownedBy = (ALICE)).states + val cashStates = services.fillWithSomeTestCash(100.DOLLARS, issuerServices, notary, 2, 2, Random(0L), owner = ALICE, issuedBy = issuer.ref(1)).states // persist additional cash states explicitly with V3 schema cashStates.forEach { val cashState = it.state.data diff --git a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt index 12689c3b30..123335a1de 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt @@ -5,7 +5,8 @@ import net.corda.core.contracts.Amount import net.corda.core.contracts.Issued import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef -import net.corda.core.crypto.* +import net.corda.core.crypto.NullKeys +import net.corda.core.crypto.generateKeyPair import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party @@ -77,7 +78,9 @@ class NodeVaultServiceTest { identity = services.myInfo.singleIdentityAndCert() issuerServices = MockServices(cordappPackages, DUMMY_CASH_ISSUER_NAME, DUMMY_CASH_ISSUER_KEY) bocServices = MockServices(cordappPackages, BOC_NAME, BOC_KEY) + services.identityService.verifyAndRegisterIdentity(DUMMY_CASH_ISSUER_IDENTITY) + services.identityService.verifyAndRegisterIdentity(BOC_IDENTITY) } @After @@ -511,10 +514,9 @@ class NodeVaultServiceTest { @Test fun `correct updates are generated for general transactions`() { - val service = vaultService val notary = identity.party val vaultSubscriber = TestSubscriber>().apply { - service.updates.subscribe(this) + vaultService.updates.subscribe(this) } val identity = services.myInfo.singleIdentityAndCert() @@ -533,15 +535,16 @@ class NodeVaultServiceTest { val signedIssuedTx = services.signInitialTransaction(issueBuilder) services.validatedTransactions.addTransaction(signedIssuedTx) - database.transaction { service.notify(StatesToRecord.ONLY_RELEVANT, issueTx) } + database.transaction { vaultService.notify(StatesToRecord.ONLY_RELEVANT, issueTx) } val expectedIssueUpdate = Vault.Update(emptySet(), setOf(cashState), null) database.transaction { val moveBuilder = TransactionBuilder(notary).apply { - Cash.generateSpend(services, this, Amount(1000, GBP), thirdPartyIdentity) + val changeIdentity = services.keyManagementService.freshKeyAndCert(identity, false) + Cash.generateSpend(services, this, Amount(1000, GBP), changeIdentity, thirdPartyIdentity) } val moveTx = moveBuilder.toWireTransaction(services) - service.notify(StatesToRecord.ONLY_RELEVANT, moveTx) + vaultService.notify(StatesToRecord.ONLY_RELEVANT, moveTx) } val expectedMoveUpdate = Vault.Update(setOf(cashState), emptySet(), null) @@ -580,6 +583,7 @@ class NodeVaultServiceTest { val initialCashState = StateAndRef(issueStx.tx.outputs.single(), StateRef(issueStx.id, 0)) // Change notary + services.identityService.verifyAndRegisterIdentity(DUMMY_NOTARY_IDENTITY) val newNotary = DUMMY_NOTARY val changeNotaryTx = NotaryChangeWireTransaction(listOf(initialCashState.ref), issueStx.notary!!, newNotary) val cashStateWithNewNotary = StateAndRef(initialCashState.state.copy(notary = newNotary), StateRef(changeNotaryTx.id, 0)) diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt index 002e8ba00a..cd2d87fd0e 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt @@ -12,7 +12,10 @@ import net.corda.core.internal.packageName import net.corda.core.node.services.* import net.corda.core.node.services.vault.* import net.corda.core.node.services.vault.QueryCriteria.* -import net.corda.core.utilities.* +import net.corda.core.utilities.NonEmptySet +import net.corda.core.utilities.days +import net.corda.core.utilities.seconds +import net.corda.core.utilities.toHexString import net.corda.finance.* import net.corda.finance.contracts.CommercialPaper import net.corda.finance.contracts.Commodity @@ -31,7 +34,6 @@ import net.corda.testing.contracts.* import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties -import net.corda.testing.node.MockServices.Companion.makeTestIdentityService import net.corda.testing.schemas.DummyLinearStateSchemaV1 import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions.assertThat @@ -57,7 +59,7 @@ class VaultQueryTests { private lateinit var services: MockServices private lateinit var notaryServices: MockServices private val vaultService: VaultService get() = services.vaultService - private val identitySvc: IdentityService = makeTestIdentityService() + private lateinit var identitySvc: IdentityService private lateinit var database: CordaPersistence // test cash notary @@ -68,14 +70,16 @@ class VaultQueryTests { @Before fun setUp() { // register additional identities - identitySvc.verifyAndRegisterIdentity(CASH_NOTARY_IDENTITY) - identitySvc.verifyAndRegisterIdentity(BOC_IDENTITY) val databaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(MEGA_CORP_KEY, DUMMY_NOTARY_KEY), - createIdentityService = { identitySvc }, cordappPackages = cordappPackages) database = databaseAndServices.first services = databaseAndServices.second notaryServices = MockServices(cordappPackages, DUMMY_NOTARY.name, DUMMY_NOTARY_KEY, DUMMY_CASH_ISSUER_KEY, BOC_KEY, MEGA_CORP_KEY) + identitySvc = services.identityService + // Register all of the identities we're going to use + (notaryServices.myInfo.legalIdentitiesAndCerts + BOC_IDENTITY + CASH_NOTARY_IDENTITY + MINI_CORP_IDENTITY + MEGA_CORP_IDENTITY).forEach { identity -> + services.identityService.verifyAndRegisterIdentity(identity) + } } @After @@ -1388,20 +1392,24 @@ class VaultQueryTests { // GBP issuer val gbpCashIssuerName = CordaX500Name(organisation = "British Pounds Cash Issuer", locality = "London", country = "GB") val gbpCashIssuerServices = MockServices(cordappPackages, gbpCashIssuerName, generateKeyPair()) - val gbpCashIssuer = gbpCashIssuerServices.myInfo.singleIdentity().ref(1) + val gbpCashIssuer = gbpCashIssuerServices.myInfo.singleIdentityAndCert() // USD issuer val usdCashIssuerName = CordaX500Name(organisation = "US Dollars Cash Issuer", locality = "New York", country = "US") val usdCashIssuerServices = MockServices(cordappPackages, usdCashIssuerName, generateKeyPair()) - val usdCashIssuer = usdCashIssuerServices.myInfo.singleIdentity().ref(1) + val usdCashIssuer = usdCashIssuerServices.myInfo.singleIdentityAndCert() // CHF issuer val chfCashIssuerName = CordaX500Name(organisation = "Swiss Francs Cash Issuer", locality = "Zurich", country = "CH") val chfCashIssuerServices = MockServices(cordappPackages, chfCashIssuerName, generateKeyPair()) - val chfCashIssuer = chfCashIssuerServices.myInfo.singleIdentity().ref(1) - + val chfCashIssuer = chfCashIssuerServices.myInfo.singleIdentityAndCert() + listOf(gbpCashIssuer, usdCashIssuer, chfCashIssuer).forEach { identity -> + services.identityService.verifyAndRegisterIdentity(identity) + } + database.transaction { + services.fillWithSomeTestCash(100.POUNDS, gbpCashIssuerServices, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = gbpCashIssuer.party.ref(1)) + services.fillWithSomeTestCash(100.DOLLARS, usdCashIssuerServices, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = usdCashIssuer.party.ref(1)) + services.fillWithSomeTestCash(100.SWISS_FRANCS, chfCashIssuerServices, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = chfCashIssuer.party.ref(1)) + } database.transaction { - services.fillWithSomeTestCash(100.POUNDS, gbpCashIssuerServices, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = gbpCashIssuer) - services.fillWithSomeTestCash(100.DOLLARS, usdCashIssuerServices, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = usdCashIssuer) - services.fillWithSomeTestCash(100.SWISS_FRANCS, chfCashIssuerServices, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = chfCashIssuer) val criteria = FungibleAssetQueryCriteria(issuer = listOf(gbpCashIssuer.party, usdCashIssuer.party)) val results = vaultService.queryBy>(criteria) assertThat(results.states).hasSize(2) @@ -1413,8 +1421,9 @@ class VaultQueryTests { database.transaction { services.fillWithSomeTestCash(100.DOLLARS, notaryServices, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = BOC.ref(1)) services.fillWithSomeTestCash(100.DOLLARS, notaryServices, DUMMY_NOTARY, 1, 1, Random(0L), - issuedBy = MEGA_CORP.ref(0), ownedBy = (MINI_CORP)) - + issuedBy = MEGA_CORP.ref(0), owner = (MINI_CORP)) + } + database.transaction { val criteria = FungibleAssetQueryCriteria(owner = listOf(MEGA_CORP)) val results = vaultService.queryBy>(criteria) assertThat(results.states).hasSize(1) // can only be 1 owner of a node (MEGA_CORP in this MockServices setup) @@ -1426,10 +1435,11 @@ class VaultQueryTests { database.transaction { services.fillWithSomeTestCash(100.DOLLARS, notaryServices, CASH_NOTARY, 1, 1, Random(0L)) services.fillWithSomeTestCash(100.DOLLARS, notaryServices, DUMMY_NOTARY, 1, 1, Random(0L), - issuedBy = MEGA_CORP.ref(0), ownedBy = (MEGA_CORP)) + issuedBy = MEGA_CORP.ref(0), owner = (MEGA_CORP)) services.fillWithSomeTestCash(100.DOLLARS, notaryServices, DUMMY_NOTARY, 1, 1, Random(0L), - issuedBy = BOC.ref(0), ownedBy = (MINI_CORP)) // irrelevant to this vault - + issuedBy = BOC.ref(0), owner = MINI_CORP) // irrelevant to this vault + } + database.transaction { // DOCSTART VaultQueryExample5.2 val criteria = FungibleAssetQueryCriteria(owner = listOf(MEGA_CORP, BOC)) val results = vaultService.queryBy(criteria) diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt index aef17cd726..34b5672736 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultWithCashTest.kt @@ -148,7 +148,7 @@ class VaultWithCashTest { database.transaction { // A tx that sends us money. - services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 10, 10, Random(0L), ownedBy = AnonymousParty(freshKey), + services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 10, 10, Random(0L), owner = AnonymousParty(freshKey), issuedBy = MEGA_CORP.ref(1)) println("Cash balance: ${services.getCashBalance(USD)}") } @@ -298,7 +298,7 @@ class VaultWithCashTest { val freshKey = services.keyManagementService.freshKey() database.transaction { - services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 3, 3, Random(0L), ownedBy = AnonymousParty(freshKey)) + services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 3, 3, Random(0L), owner = AnonymousParty(freshKey)) services.fillWithSomeTestCash(100.SWISS_FRANCS, issuerServices, DUMMY_NOTARY, 2, 2, Random(0L)) services.fillWithSomeTestCash(100.POUNDS, issuerServices, DUMMY_NOTARY, 1, 1, Random(0L)) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index d3dde4f44d..4ed9b5ef94 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -56,6 +56,7 @@ open class MockServices( vararg val keys: KeyPair ) : ServiceHub, StateLoader by stateLoader { companion object { + private val MOCK_IDENTITIES = listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY) @JvmStatic val MOCK_VERSION_INFO = VersionInfo(1, "Mock release", "Mock revision", "Mock Vendor") @@ -101,7 +102,7 @@ open class MockServices( /** * Makes database and mock services appropriate for unit tests. * @param keys a list of [KeyPair] instances to be used by [MockServices]. Defaults to [MEGA_CORP_KEY] - * @param createIdentityService a lambda function returning an instance of [IdentityService]. Defauts to [InMemoryIdentityService]. + * @param createIdentityService a lambda function returning an instance of [IdentityService]. Defaults to [InMemoryIdentityService]. * * @return a pair where the first element is the instance of [CordaPersistence] and the second is [MockServices]. */ diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt index d4743c39a5..1f95993b37 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt @@ -87,9 +87,6 @@ val ALL_TEST_KEYS: List get() = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, AL val DUMMY_CASH_ISSUER_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(DUMMY_CASH_ISSUER.party as Party) -val MOCK_IDENTITIES = listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY) -val MOCK_IDENTITY_SERVICE: IdentityService get() = InMemoryIdentityService(MOCK_IDENTITIES, emptySet(), DEV_CA.certificate.cert) - val MOCK_HOST_AND_PORT = NetworkHostAndPort("mockHost", 30000) fun generateStateRef() = StateRef(SecureHash.randomSHA256(), 0) diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/VaultFiller.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/VaultFiller.kt index cad028aa20..5cdc3f1bfd 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/VaultFiller.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/contracts/VaultFiller.kt @@ -96,9 +96,26 @@ fun ServiceHub.fillWithSomeTestLinearStates(numberToCreate: Int, return Vault(states) } +/** + * Creates a random set of cash states that add up to the given amount and adds them to the vault. This is intended for + * unit tests. The cash is owned by the legal identity key from the storage service. + * + * The service hub needs to provide at least a key management service and a storage service. + * + * @param issuerServices service hub of the issuer node, which will be used to sign the transaction. + * @param outputNotary the notary to use for output states. The transaction is NOT signed by this notary. + * @return a vault object that represents the generated states (it will NOT be the full vault from the service hub!). + */ +fun ServiceHub.fillWithSomeTestCash(howMuch: Amount, + issuerServices: ServiceHub, + outputNotary: Party, + states: Int, + issuedBy: PartyAndReference): Vault + = fillWithSomeTestCash(howMuch, issuerServices, outputNotary, states, states, issuedBy = issuedBy) + /** * Creates a random set of between (by default) 3 and 10 cash states that add up to the given amount and adds them - * to the vault. This is intended for unit tests. The cash is issued by [DUMMY_CASH_ISSUER] and owned by the legal + * to the vault. This is intended for unit tests. By default the cash is issued by [DUMMY_CASH_ISSUER] and owned by the legal * identity key from the storage service. * * The service hub needs to provide at least a key management service and a storage service. @@ -113,18 +130,15 @@ fun ServiceHub.fillWithSomeTestCash(howMuch: Amount, atLeastThisManyStates: Int = 3, atMostThisManyStates: Int = 10, rng: Random = Random(), - ownedBy: AbstractParty? = null, + owner: AbstractParty? = null, issuedBy: PartyAndReference = DUMMY_CASH_ISSUER): Vault { val amounts = calculateRandomlySizedAmounts(howMuch, atLeastThisManyStates, atMostThisManyStates, rng) - val myKey = ownedBy?.owningKey ?: myInfo.chooseIdentity().owningKey - val anonParty = AnonymousParty(myKey) - // We will allocate one state to one transaction, for simplicities sake. val cash = Cash() val transactions: List = amounts.map { pennies -> val issuance = TransactionBuilder(null as Party?) - cash.generateIssue(issuance, Amount(pennies, Issued(issuedBy, howMuch.token)), anonParty, outputNotary) + cash.generateIssue(issuance, Amount(pennies, Issued(issuedBy, howMuch.token)),owner ?: myInfo.singleIdentity(), outputNotary) return@map issuerServices.signInitialTransaction(issuance, issuedBy.party.owningKey) } From 332915f08bb201596e20d4aecde04cd6c7001dff Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Fri, 17 Nov 2017 14:15:49 +0000 Subject: [PATCH 3/3] Bug fix for cash selection on H2 where the accumulated pennies amount is larger than max int --- .../cash/selection/CashSelectionH2Impl.kt | 4 +--- .../selection/CashSelectionH2ImplTest.kt} | 22 +++++++++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) rename finance/src/test/kotlin/net/corda/finance/contracts/asset/{CashSelectionH2Test.kt => cash/selection/CashSelectionH2ImplTest.kt} (59%) diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt index d8be0af698..08fa686c07 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt @@ -13,7 +13,6 @@ import java.sql.ResultSet import java.util.* class CashSelectionH2Impl : AbstractCashSelection() { - companion object { const val JDBC_DRIVER_NAME = "H2 JDBC Driver" val log = loggerFor() @@ -25,7 +24,6 @@ class CashSelectionH2Impl : AbstractCashSelection() { override fun toString() = "${this::class.java} for $JDBC_DRIVER_NAME" - // We are using an H2 specific means of selecting a minimum set of rows that match a request amount of coins: // 1) There is no standard SQL mechanism of calculating a cumulative total on a field and restricting row selection on the // running total of such an accumulator @@ -34,7 +32,7 @@ class CashSelectionH2Impl : AbstractCashSelection() { // 3) H2 does not support JOIN's in FOR UPDATE (hence we are forced to execute 2 queries) override fun executeQuery(connection: Connection, amount: Amount, lockId: UUID, notary: Party?, onlyFromIssuerParties: Set, withIssuerRefs: Set) : ResultSet { - connection.createStatement().execute("CALL SET(@t, 0);") + connection.createStatement().execute("CALL SET(@t, CAST(0 AS BIGINT));") val selectJoin = """ SELECT vs.transaction_id, vs.output_index, ccs.pennies, SET(@t, ifnull(@t,0)+ccs.pennies) total_pennies, vs.lock_id diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2ImplTest.kt similarity index 59% rename from finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt rename to finance/src/test/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2ImplTest.kt index 4f4a39abec..c044387f00 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2ImplTest.kt @@ -1,8 +1,12 @@ -package net.corda.finance.contracts.asset +package net.corda.finance.contracts.asset.cash.selection +import net.corda.core.internal.concurrent.transpose +import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.getOrThrow import net.corda.finance.DOLLARS +import net.corda.finance.POUNDS import net.corda.finance.flows.CashException +import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNodeParameters @@ -10,8 +14,9 @@ import net.corda.testing.startFlow import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.After import org.junit.Test +import java.util.Collections.nCopies -class CashSelectionH2Test { +class CashSelectionH2ImplTest { private val mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance")) @After @@ -19,6 +24,19 @@ class CashSelectionH2Test { mockNet.stopNodes() } + @Test + fun `selecting pennies amount larger than max int, which is split across multiple cash states`() { + val node = mockNet.createNode() + // The amount has to split across at least two states, probably to trigger the H2 accumulator variable during the + // spend operation below. + // Issuing Integer.MAX_VALUE will not cause an exception since PersistentCashState.pennies is a long + nCopies(2, Integer.MAX_VALUE).map { issueAmount -> + node.services.startFlow(CashIssueFlow(issueAmount.POUNDS, OpaqueBytes.of(1), mockNet.defaultNotaryIdentity)).resultFuture + }.transpose().getOrThrow() + // The spend must be more than the size of a single cash state to force the accumulator onto the second state. + node.services.startFlow(CashPaymentFlow((Integer.MAX_VALUE + 1L).POUNDS, node.info.legalIdentities[0])).resultFuture.getOrThrow() + } + @Test fun `check does not hold connection over retries`() { val bankA = mockNet.createNode(MockNodeParameters(configOverrides = {