diff --git a/core/src/main/kotlin/net/corda/core/crypto/Party.kt b/core/src/main/kotlin/net/corda/core/crypto/Party.kt index c02983313e..a8f0dd7378 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/Party.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/Party.kt @@ -18,12 +18,17 @@ import java.security.PublicKey * cluster of Corda nodes. A [Party] representing a distributed service will use a composite key containing all * individual cluster nodes' public keys. Each of the nodes in the cluster will advertise the same group [Party]. * + * Note that equality is based solely on the owning key. + * * @see CompositeKey */ -data class Party(val name: String, val owningKey: CompositeKey) { +class Party(val name: String, val owningKey: CompositeKey) { /** A helper constructor that converts the given [PublicKey] in to a [CompositeKey] with a single node */ constructor(name: String, owningKey: PublicKey) : this(name, owningKey.composite) + /** Anonymised parties do not include any detail apart from owning key, so equality is dependent solely on the key */ + override fun equals(other: Any?): Boolean = other is Party && this.owningKey == other.owningKey + override fun hashCode(): Int = owningKey.hashCode() override fun toString() = name fun ref(bytes: OpaqueBytes) = PartyAndReference(this, bytes) diff --git a/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt b/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt index c81f002e3e..468a8244b3 100644 --- a/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt +++ b/core/src/main/kotlin/net/corda/core/node/NodeInfo.kt @@ -18,6 +18,10 @@ data class NodeInfo(val address: SingleMessageRecipient, val legalIdentity: Party, var advertisedServices: List = emptyList(), val physicalLocation: PhysicalLocation? = null) { + init { + require(advertisedServices.none { it.identity == legalIdentity }) { "Service identities must be different from node legal identity" } + } + val notaryIdentity: Party get() = advertisedServices.single { it.info.type.isNotary() }.identity fun serviceIdentities(type: ServiceType): List = advertisedServices.filter { it.info.type.isSubTypeOf(type) }.map { it.identity } } diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt index 71f3af45c1..9a1cba3b93 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt @@ -1,5 +1,6 @@ package net.corda.docs +import net.corda.core.crypto.Party import net.corda.core.contracts.* import net.corda.core.getOrThrow import net.corda.core.node.services.ServiceInfo @@ -9,6 +10,7 @@ import net.corda.core.utilities.DUMMY_NOTARY import net.corda.core.utilities.DUMMY_NOTARY_KEY import net.corda.flows.CashCommand import net.corda.flows.CashFlow +import net.corda.core.node.ServiceEntry import net.corda.node.services.network.NetworkMapService import net.corda.node.services.transactions.ValidatingNotaryService import net.corda.node.utilities.databaseTransaction @@ -27,10 +29,11 @@ class FxTransactionBuildTutorialTest { @Before fun setup() { net = MockNetwork(threadPerNode = true) + val notaryService = ServiceInfo(ValidatingNotaryService.type) notaryNode = net.createNode( legalName = DUMMY_NOTARY.name, - keyPair = DUMMY_NOTARY_KEY, - advertisedServices = *arrayOf(ServiceInfo(NetworkMapService.type), ServiceInfo(ValidatingNotaryService.type))) + overrideServices = mapOf(Pair(notaryService, DUMMY_NOTARY_KEY)), + advertisedServices = *arrayOf(ServiceInfo(NetworkMapService.type), notaryService)) nodeA = net.createPartyNode(notaryNode.info.address) nodeB = net.createPartyNode(notaryNode.info.address) FxTransactionDemoTutorial.registerFxProtocols(nodeA.services) diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt index 8e553f4f75..70558a35a6 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt @@ -4,6 +4,7 @@ import net.corda.core.contracts.LinearState import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef import net.corda.core.getOrThrow +import net.corda.core.node.ServiceEntry import net.corda.core.node.ServiceHub import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.linearHeadsOfType @@ -35,10 +36,11 @@ class WorkflowTransactionBuildTutorialTest { @Before fun setup() { net = MockNetwork(threadPerNode = true) + val notaryService = ServiceInfo(ValidatingNotaryService.type) notaryNode = net.createNode( legalName = DUMMY_NOTARY.name, - keyPair = DUMMY_NOTARY_KEY, - advertisedServices = *arrayOf(ServiceInfo(NetworkMapService.type), ServiceInfo(ValidatingNotaryService.type))) + overrideServices = mapOf(Pair(notaryService, DUMMY_NOTARY_KEY)), + advertisedServices = *arrayOf(ServiceInfo(NetworkMapService.type), notaryService)) nodeA = net.createPartyNode(notaryNode.info.address) nodeB = net.createPartyNode(notaryNode.info.address) FxTransactionDemoTutorial.registerFxProtocols(nodeA.services) diff --git a/docs/source/release-notes.rst b/docs/source/release-notes.rst index 245b0afdef..5d2d25e9e7 100644 --- a/docs/source/release-notes.rst +++ b/docs/source/release-notes.rst @@ -3,6 +3,14 @@ Release notes Here are brief summaries of what's changed between each snapshot release. +Milestone 8 +----------- + +* API: + + * ``Party`` equality is now based on the owning key, rather than the owning key and name. This is important for + party anonymisation to work, as each key must identify exactly one party. + Milestone 7 ----------- diff --git a/finance/src/test/kotlin/net/corda/flows/IssuerFlowTest.kt b/finance/src/test/kotlin/net/corda/flows/IssuerFlowTest.kt index 92efac55b3..07e20b5bce 100644 --- a/finance/src/test/kotlin/net/corda/flows/IssuerFlowTest.kt +++ b/finance/src/test/kotlin/net/corda/flows/IssuerFlowTest.kt @@ -29,12 +29,12 @@ class IssuerFlowTest { fun `test issuer flow`() { net = MockNetwork(false, true) ledger { - notaryNode = net.createNotaryNode(null, DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) - bankOfCordaNode = net.createPartyNode(notaryNode.info.address, BOC.name, BOC_KEY) - bankClientNode = net.createPartyNode(notaryNode.info.address, MEGA_CORP.name, MEGA_CORP_KEY) + notaryNode = net.createNotaryNode(null, DUMMY_NOTARY.name) + bankOfCordaNode = net.createPartyNode(notaryNode.info.address, BOC.name) + bankClientNode = net.createPartyNode(notaryNode.info.address, MEGA_CORP.name) // using default IssueTo Party Reference - val issueToPartyAndRef = MEGA_CORP.ref(OpaqueBytes.Companion.of(123)) + val issueToPartyAndRef = bankClientNode.info.legalIdentity.ref(OpaqueBytes.Companion.of(123)) val (issuer, issuerResult) = runIssuerAndIssueRequester(1000000.DOLLARS, issueToPartyAndRef) assertEquals(issuerResult.get(), issuer.get().resultFuture.get()) diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index cdba196d02..f37cd1248e 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -275,16 +275,16 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, } private fun makeInfo(): NodeInfo { - val services = makeServiceEntries() + val advertisedServiceEntries = makeServiceEntries() val legalIdentity = obtainLegalIdentity() - return NodeInfo(net.myAddress, legalIdentity, services, findMyLocation()) + return NodeInfo(net.myAddress, legalIdentity, advertisedServiceEntries, findMyLocation()) } /** * A service entry contains the advertised [ServiceInfo] along with the service identity. The identity *name* is * taken from the configuration or, if non specified, generated by combining the node's legal name and the service id. */ - protected fun makeServiceEntries(): List { + protected open fun makeServiceEntries(): List { return advertisedServices.map { val serviceId = it.type.id val serviceName = it.name ?: "$serviceId|${configuration.myLegalName}" diff --git a/node/src/test/kotlin/net/corda/node/messaging/AttachmentTests.kt b/node/src/test/kotlin/net/corda/node/messaging/AttachmentTests.kt index 94c4856486..8ace2bbc3f 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/AttachmentTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/AttachmentTests.kt @@ -14,11 +14,14 @@ import net.corda.node.services.network.NetworkMapService import net.corda.node.services.persistence.NodeAttachmentService import net.corda.node.services.transactions.SimpleNotaryService import net.corda.testing.node.MockNetwork +import net.i2p.crypto.eddsa.KeyPairGenerator import org.junit.Before import org.junit.Test import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream +import java.math.BigInteger import java.security.KeyPair +import java.security.KeyPairGeneratorSpi import java.util.jar.JarOutputStream import java.util.zip.ZipEntry import kotlin.test.assertEquals @@ -84,8 +87,10 @@ class AttachmentTests { // Make a node that doesn't do sanity checking at load time. val n0 = network.createNode(null, -1, object : MockNetwork.Factory { override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - advertisedServices: Set, id: Int, keyPair: KeyPair?): MockNetwork.MockNode { - return object : MockNetwork.MockNode(config, network, networkMapAddr, advertisedServices, id, keyPair) { + advertisedServices: Set, id: Int, + overrideServices: Map?, + entropyRoot: BigInteger): MockNetwork.MockNode { + return object : MockNetwork.MockNode(config, network, networkMapAddr, advertisedServices, id, overrideServices, entropyRoot) { override fun start(): MockNetwork.MockNode { super.start() (storage.attachments as NodeAttachmentService).checkAttachmentsOnLoad = false diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index 06370a7449..b469f33c79 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -20,7 +20,6 @@ import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.DUMMY_NOTARY -import net.corda.core.utilities.DUMMY_NOTARY_KEY import net.corda.core.utilities.LogHelper import net.corda.core.utilities.TEST_TX_TIME import net.corda.flows.TwoPartyTradeFlow.Buyer @@ -43,6 +42,7 @@ import org.junit.Test import rx.Observable import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream +import java.math.BigInteger import java.security.KeyPair import java.util.* import java.util.concurrent.Future @@ -60,9 +60,6 @@ import kotlin.test.assertTrue */ class TwoPartyTradeFlowTests { lateinit var net: MockNetwork - lateinit var notaryNode: MockNetwork.MockNode - lateinit var aliceNode: MockNetwork.MockNode - lateinit var bobNode: MockNetwork.MockNode @Before fun before() { @@ -84,9 +81,9 @@ class TwoPartyTradeFlowTests { net = MockNetwork(false, true) ledger { - notaryNode = net.createNotaryNode(null, DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) - aliceNode = net.createPartyNode(notaryNode.info.address, ALICE.name, ALICE_KEY) - bobNode = net.createPartyNode(notaryNode.info.address, BOB.name, BOB_KEY) + val notaryNode = net.createNotaryNode(null, DUMMY_NOTARY.name) + val aliceNode = net.createPartyNode(notaryNode.info.address, ALICE.name) + val bobNode = net.createPartyNode(notaryNode.info.address, BOB.name) val aliceKey = aliceNode.services.legalIdentityKey val notaryKey = notaryNode.services.notaryIdentityKey @@ -99,9 +96,10 @@ class TwoPartyTradeFlowTests { val alicesFakePaper = fillUpForSeller(false, aliceNode.info.legalIdentity.owningKey, 1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, null, notaryNode.info.notaryIdentity).second - insertFakeTransactions(alicesFakePaper, aliceNode, aliceKey, notaryKey) + insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, aliceKey, notaryKey) - val (bobStateMachine, aliceResult) = runBuyerAndSeller("alice's paper".outputStateAndRef()) + val (bobStateMachine, aliceResult) = runBuyerAndSeller(notaryNode, aliceNode, bobNode, + "alice's paper".outputStateAndRef()) // TODO: Verify that the result was inserted into the transaction database. // assertEquals(bobResult.get(), aliceNode.storage.validatedTransactions[aliceResult.get().id]) @@ -124,9 +122,9 @@ class TwoPartyTradeFlowTests { @Test fun `shutdown and restore`() { ledger { - notaryNode = net.createNotaryNode(null, DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) - aliceNode = net.createPartyNode(notaryNode.info.address, ALICE.name, ALICE_KEY) - bobNode = net.createPartyNode(notaryNode.info.address, BOB.name, BOB_KEY) + val notaryNode = net.createNotaryNode(null, DUMMY_NOTARY.name) + val aliceNode = net.createPartyNode(notaryNode.info.address, ALICE.name) + var bobNode = net.createPartyNode(notaryNode.info.address, BOB.name) aliceNode.disableDBCloseOnStop() bobNode.disableDBCloseOnStop() val aliceKey = aliceNode.services.legalIdentityKey @@ -142,8 +140,8 @@ class TwoPartyTradeFlowTests { } val alicesFakePaper = fillUpForSeller(false, aliceNode.info.legalIdentity.owningKey, 1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, null, notaryNode.info.notaryIdentity).second - insertFakeTransactions(alicesFakePaper, aliceNode, aliceKey, notaryKey) - val aliceFuture = runBuyerAndSeller("alice's paper".outputStateAndRef()).sellerResult + insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, aliceKey, notaryKey) + val aliceFuture = runBuyerAndSeller(notaryNode, aliceNode, bobNode, "alice's paper".outputStateAndRef()).sellerResult // Everything is on this thread so we can now step through the flow one step at a time. // Seller Alice already sent a message to Buyer Bob. Pump once: @@ -179,10 +177,11 @@ class TwoPartyTradeFlowTests { // that Bob was waiting on before the reboot occurred. bobNode = net.createNode(networkMapAddr, bobAddr.id, object : MockNetwork.Factory { override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - advertisedServices: Set, id: Int, keyPair: KeyPair?): MockNetwork.MockNode { - return MockNetwork.MockNode(config, network, networkMapAddr, advertisedServices, bobAddr.id, BOB_KEY) + advertisedServices: Set, id: Int, overrideServices: Map?, + entropyRoot: BigInteger): MockNetwork.MockNode { + return MockNetwork.MockNode(config, network, networkMapAddr, advertisedServices, bobAddr.id, overrideServices, entropyRoot) } - }, true, BOB.name, BOB_KEY) + }, true, BOB.name) // Find the future representing the result of this state machine again. val bobFuture = bobNode.smm.findStateMachines(Buyer::class.java).single().second @@ -213,12 +212,16 @@ class TwoPartyTradeFlowTests { // Creates a mock node with an overridden storage service that uses a RecordingMap, that lets us test the order // of gets and puts. - private fun makeNodeWithTracking(networkMapAddr: SingleMessageRecipient?, name: String, keyPair: KeyPair): MockNetwork.MockNode { + private fun makeNodeWithTracking(networkMapAddr: SingleMessageRecipient?, name: String, overrideServices: Map? = null): MockNetwork.MockNode { // Create a node in the mock network ... return net.createNode(networkMapAddr, -1, object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - advertisedServices: Set, id: Int, keyPair: KeyPair?): MockNetwork.MockNode { - return object : MockNetwork.MockNode(config, network, networkMapAddr, advertisedServices, id, keyPair) { + override fun create(config: NodeConfiguration, + network: MockNetwork, + networkMapAddr: SingleMessageRecipient?, + advertisedServices: Set, id: Int, + overrideServices: Map?, + entropyRoot: BigInteger): MockNetwork.MockNode { + return object : MockNetwork.MockNode(config, network, networkMapAddr, advertisedServices, id, overrideServices, entropyRoot) { // That constructs the storage service object in a customised way ... override fun constructStorageService( attachments: NodeAttachmentService, @@ -229,14 +232,14 @@ class TwoPartyTradeFlowTests { } } } - }, true, name, keyPair) + }, true, name, overrideServices) } @Test fun `check dependencies of sale asset are resolved`() { - notaryNode = net.createNotaryNode(null, DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) - aliceNode = makeNodeWithTracking(notaryNode.info.address, ALICE.name, ALICE_KEY) - bobNode = makeNodeWithTracking(notaryNode.info.address, BOB.name, BOB_KEY) + val notaryNode = net.createNotaryNode(null, DUMMY_NOTARY.name) + val aliceNode = makeNodeWithTracking(notaryNode.info.address, ALICE.name) + val bobNode = makeNodeWithTracking(notaryNode.info.address, BOB.name) val aliceKey = aliceNode.services.legalIdentityKey ledger(aliceNode.services) { @@ -250,15 +253,18 @@ class TwoPartyTradeFlowTests { } val attachmentID = attachment(ByteArrayInputStream(stream.toByteArray())) - val bobsFakeCash = fillUpForBuyer(false, bobNode.keyManagement.freshKey().public.composite, notaryNode.info.notaryIdentity).second - val bobsSignedTxns = insertFakeTransactions(bobsFakeCash, bobNode) + val extraKey = bobNode.keyManagement.freshKey() + val bobsFakeCash = fillUpForBuyer(false, extraKey.public.composite, + DUMMY_CASH_ISSUER.party, + notaryNode.info.notaryIdentity).second + val bobsSignedTxns = insertFakeTransactions(bobsFakeCash, bobNode, notaryNode, bobNode.services.legalIdentityKey, extraKey) val alicesFakePaper = fillUpForSeller(false, aliceNode.info.legalIdentity.owningKey, 1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, attachmentID, notaryNode.info.notaryIdentity).second - val alicesSignedTxns = insertFakeTransactions(alicesFakePaper, aliceNode, aliceKey) + val alicesSignedTxns = insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, aliceKey) net.runNetwork() // Clear network map registration messages - runBuyerAndSeller("alice's paper".outputStateAndRef()) + runBuyerAndSeller(notaryNode, aliceNode, bobNode, "alice's paper".outputStateAndRef()) net.runNetwork() @@ -326,9 +332,9 @@ class TwoPartyTradeFlowTests { @Test fun `track() works`() { - notaryNode = net.createNotaryNode(null, DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) - aliceNode = makeNodeWithTracking(notaryNode.info.address, ALICE.name, ALICE_KEY) - bobNode = makeNodeWithTracking(notaryNode.info.address, BOB.name, BOB_KEY) + val notaryNode = net.createNotaryNode(null, DUMMY_NOTARY.name) + val aliceNode = makeNodeWithTracking(notaryNode.info.address, ALICE.name) + val bobNode = makeNodeWithTracking(notaryNode.info.address, BOB.name) val aliceKey = aliceNode.services.legalIdentityKey ledger(aliceNode.services) { @@ -342,11 +348,13 @@ class TwoPartyTradeFlowTests { } val attachmentID = attachment(ByteArrayInputStream(stream.toByteArray())) - val bobsFakeCash = fillUpForBuyer(false, bobNode.keyManagement.freshKey().public.composite, notaryNode.info.notaryIdentity).second - insertFakeTransactions(bobsFakeCash, bobNode) + val bobsFakeCash = fillUpForBuyer(false, bobNode.keyManagement.freshKey().public.composite, + DUMMY_CASH_ISSUER.party, + notaryNode.info.notaryIdentity).second + insertFakeTransactions(bobsFakeCash, bobNode, notaryNode) val alicesFakePaper = fillUpForSeller(false, aliceNode.info.legalIdentity.owningKey, 1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, attachmentID, notaryNode.info.notaryIdentity).second - insertFakeTransactions(alicesFakePaper, aliceNode, aliceKey) + insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, aliceKey) net.runNetwork() // Clear network map registration messages @@ -356,7 +364,8 @@ class TwoPartyTradeFlowTests { val aliceTxMappings = databaseTransaction(aliceNode.database) { aliceMappingsStorage.track().second } - val aliceSmId = runBuyerAndSeller("alice's paper".outputStateAndRef()).sellerId + val aliceSmId = runBuyerAndSeller(notaryNode, aliceNode, bobNode, + "alice's paper".outputStateAndRef()).sellerId net.runNetwork() @@ -412,12 +421,15 @@ class TwoPartyTradeFlowTests { val sellerId: StateMachineRunId ) - private fun runBuyerAndSeller(assetToSell: StateAndRef): RunResult { - val buyerFuture = bobNode.initiateSingleShotFlow(Seller::class) { otherParty -> + private fun runBuyerAndSeller(notaryNode: MockNetwork.MockNode, + sellerNode: MockNetwork.MockNode, + buyerNode: MockNetwork.MockNode, + assetToSell: StateAndRef): RunResult { + val buyerFuture = buyerNode.initiateSingleShotFlow(Seller::class) { otherParty -> Buyer(otherParty, notaryNode.info.notaryIdentity, 1000.DOLLARS, CommercialPaper.State::class.java) }.map { it.stateMachine } - val seller = Seller(bobNode.info.legalIdentity, notaryNode.info, assetToSell, 1000.DOLLARS, ALICE_KEY) - val sellerResultFuture = aliceNode.services.startFlow(seller).resultFuture + val seller = Seller(buyerNode.info.legalIdentity, notaryNode.info, assetToSell, 1000.DOLLARS, sellerNode.services.legalIdentityKey) + val sellerResultFuture = sellerNode.services.startFlow(seller).resultFuture return RunResult(buyerFuture, sellerResultFuture, seller.stateMachine.id) } @@ -426,23 +438,24 @@ class TwoPartyTradeFlowTests { aliceError: Boolean, expectedMessageSubstring: String ) { - notaryNode = net.createNotaryNode(null, DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) - aliceNode = net.createPartyNode(notaryNode.info.address, ALICE.name, ALICE_KEY) - bobNode = net.createPartyNode(notaryNode.info.address, BOB.name, BOB_KEY) + val notaryNode = net.createNotaryNode(null, DUMMY_NOTARY.name) + val aliceNode = net.createPartyNode(notaryNode.info.address, ALICE.name) + val bobNode = net.createPartyNode(notaryNode.info.address, BOB.name) val aliceKey = aliceNode.services.legalIdentityKey val bobKey = bobNode.services.legalIdentityKey val issuer = MEGA_CORP.ref(1, 2, 3) - val bobsBadCash = fillUpForBuyer(bobError, bobKey.public.composite, notaryNode.info.notaryIdentity).second + val bobsBadCash = fillUpForBuyer(bobError, bobKey.public.composite, DUMMY_CASH_ISSUER.party, + notaryNode.info.notaryIdentity).second val alicesFakePaper = fillUpForSeller(aliceError, aliceNode.info.legalIdentity.owningKey, 1200.DOLLARS `issued by` issuer, null, notaryNode.info.notaryIdentity).second - insertFakeTransactions(bobsBadCash, bobNode, bobKey) - insertFakeTransactions(alicesFakePaper, aliceNode, aliceKey) + insertFakeTransactions(bobsBadCash, bobNode, notaryNode, bobKey) + insertFakeTransactions(alicesFakePaper, aliceNode, notaryNode, aliceKey) net.runNetwork() // Clear network map registration messages - val (bobStateMachine, aliceResult) = runBuyerAndSeller("alice's paper".outputStateAndRef()) + val (bobStateMachine, aliceResult) = runBuyerAndSeller(notaryNode, aliceNode, bobNode, "alice's paper".outputStateAndRef()) net.runNetwork() @@ -461,9 +474,10 @@ class TwoPartyTradeFlowTests { private fun insertFakeTransactions( wtxToSign: List, node: AbstractNode, + notaryNode: MockNetwork.MockNode, vararg extraKeys: KeyPair): Map { + val signed: List = signAll(wtxToSign, extraKeys.toList() + notaryNode.services.notaryIdentityKey + DUMMY_CASH_ISSUER_KEY) return databaseTransaction(node.database) { - val signed: List = signAll(wtxToSign, extraKeys.toList() + DUMMY_CASH_ISSUER_KEY) node.services.recordTransactions(signed) val validatedTransactions = node.services.storageService.validatedTransactions if (validatedTransactions is RecordingTransactionStorage) { @@ -475,20 +489,21 @@ class TwoPartyTradeFlowTests { private fun LedgerDSL.fillUpForBuyer( withError: Boolean, - owner: CompositeKey = BOB_PUBKEY, + owner: CompositeKey, + issuer: Party, notary: Party): Pair> { - val issuer = DUMMY_CASH_ISSUER + val interimOwnerKey = MEGA_CORP_PUBKEY // Bob (Buyer) has some cash he got from the Bank of Elbonia, Alice (Seller) has some commercial paper she // wants to sell to Bob. val eb1 = transaction(transactionBuilder = TransactionBuilder(notary = notary)) { // Issued money to itself. - output("elbonian money 1", notary = notary) { 800.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY } - output("elbonian money 2", notary = notary) { 1000.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY } + output("elbonian money 1", notary = notary) { 800.DOLLARS.CASH `issued by` issuer `owned by` interimOwnerKey } + output("elbonian money 2", notary = notary) { 1000.DOLLARS.CASH `issued by` issuer `owned by` interimOwnerKey } if (!withError) { - command(DUMMY_CASH_ISSUER_KEY.public.composite) { Cash.Commands.Issue() } + command(issuer.owningKey) { Cash.Commands.Issue() } } else { // Put a broken command on so at least a signature is created - command(DUMMY_CASH_ISSUER_KEY.public.composite) { Cash.Commands.Move() } + command(issuer.owningKey) { Cash.Commands.Move() } } timestamp(TEST_TX_TIME) if (withError) { @@ -502,15 +517,15 @@ class TwoPartyTradeFlowTests { val bc1 = transaction(transactionBuilder = TransactionBuilder(notary = notary)) { input("elbonian money 1") output("bob cash 1", notary = notary) { 800.DOLLARS.CASH `issued by` issuer `owned by` owner } - command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() } + command(interimOwnerKey) { Cash.Commands.Move() } this.verifies() } val bc2 = transaction(transactionBuilder = TransactionBuilder(notary = notary)) { input("elbonian money 2") output("bob cash 2", notary = notary) { 300.DOLLARS.CASH `issued by` issuer `owned by` owner } - output(notary = notary) { 700.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY } // Change output. - command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() } + output(notary = notary) { 700.DOLLARS.CASH `issued by` issuer `owned by` interimOwnerKey } // Change output. + command(interimOwnerKey) { Cash.Commands.Move() } this.verifies() } diff --git a/node/src/test/kotlin/net/corda/node/services/InMemoryNetworkMapCacheTest.kt b/node/src/test/kotlin/net/corda/node/services/InMemoryNetworkMapCacheTest.kt index 86e0cc0ba1..bee9b114f2 100644 --- a/node/src/test/kotlin/net/corda/node/services/InMemoryNetworkMapCacheTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/InMemoryNetworkMapCacheTest.kt @@ -1,7 +1,5 @@ package net.corda.node.services -import net.corda.core.crypto.composite -import net.corda.core.crypto.generateKeyPair import net.corda.core.getOrThrow import net.corda.core.node.services.ServiceInfo import net.corda.node.services.network.NetworkMapService @@ -9,6 +7,7 @@ import net.corda.node.utilities.databaseTransaction import net.corda.testing.expect import net.corda.testing.node.MockNetwork import org.junit.Test +import java.math.BigInteger import kotlin.test.assertEquals class InMemoryNetworkMapCacheTest { @@ -24,19 +23,23 @@ class InMemoryNetworkMapCacheTest { @Test fun `key collision`() { - val keyPair = generateKeyPair() - val nodeA = network.createNode(null, -1, MockNetwork.DefaultFactory, true, "Node A", keyPair, ServiceInfo(NetworkMapService.type)) - val nodeB = network.createNode(null, -1, MockNetwork.DefaultFactory, true, "Node B", keyPair, ServiceInfo(NetworkMapService.type)) + val entropy = BigInteger.valueOf(24012017L) + val nodeA = network.createNode(null, -1, MockNetwork.DefaultFactory, true, "Node A", null, entropy, ServiceInfo(NetworkMapService.type)) + val nodeB = network.createNode(null, -1, MockNetwork.DefaultFactory, true, "Node B", null, entropy, ServiceInfo(NetworkMapService.type)) + assertEquals(nodeA.info.legalIdentity, nodeB.info.legalIdentity) // Node A currently knows only about itself, so this returns node A - assertEquals(nodeA.netMapCache.getNodeByLegalIdentityKey(keyPair.public.composite), nodeA.info) + assertEquals(nodeA.netMapCache.getNodeByLegalIdentityKey(nodeA.info.legalIdentity.owningKey), nodeA.info) databaseTransaction(nodeA.database) { nodeA.netMapCache.addNode(nodeB.info) } // Now both nodes match, so it throws an error expect { - nodeA.netMapCache.getNodeByLegalIdentityKey(keyPair.public.composite) + nodeA.netMapCache.getNodeByLegalIdentityKey(nodeA.info.legalIdentity.owningKey) + } + expect { + nodeA.netMapCache.getNodeByLegalIdentityKey(nodeB.info.legalIdentity.owningKey) } } } diff --git a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt index d6e67f452c..5c3798dbe6 100644 --- a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt @@ -37,7 +37,6 @@ class NotaryChangeTests { net = MockNetwork() oldNotaryNode = net.createNode( legalName = DUMMY_NOTARY.name, - keyPair = DUMMY_NOTARY_KEY, advertisedServices = *arrayOf(ServiceInfo(NetworkMapService.type), ServiceInfo(SimpleNotaryService.type))) clientNodeA = net.createNode(networkMapAddress = oldNotaryNode.info.address) clientNodeB = net.createNode(networkMapAddress = oldNotaryNode.info.address) diff --git a/node/src/test/kotlin/net/corda/node/services/NotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/NotaryServiceTests.kt index 077e56fa74..619a71e0c1 100644 --- a/node/src/test/kotlin/net/corda/node/services/NotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/NotaryServiceTests.kt @@ -23,6 +23,7 @@ import net.corda.testing.node.MockNetwork import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test +import java.security.KeyPair import java.time.Instant import java.util.* import kotlin.test.assertEquals @@ -32,14 +33,15 @@ class NotaryServiceTests { lateinit var net: MockNetwork lateinit var notaryNode: MockNetwork.MockNode lateinit var clientNode: MockNetwork.MockNode + lateinit var clientKeyPair: KeyPair @Before fun setup() { net = MockNetwork() notaryNode = net.createNode( legalName = DUMMY_NOTARY.name, - keyPair = DUMMY_NOTARY_KEY, advertisedServices = *arrayOf(ServiceInfo(NetworkMapService.type), ServiceInfo(SimpleNotaryService.type))) - clientNode = net.createNode(networkMapAddress = notaryNode.info.address, keyPair = MINI_CORP_KEY) + clientNode = net.createNode(networkMapAddress = notaryNode.info.address) + clientKeyPair = clientNode.keyManagement.toKeyPair(clientNode.info.legalIdentity.owningKey.keys.single()) net.runNetwork() // Clear network map registration messages } @@ -48,7 +50,7 @@ class NotaryServiceTests { val inputState = issueState(clientNode) val tx = TransactionType.General.Builder(notaryNode.info.notaryIdentity).withItems(inputState) tx.setTime(Instant.now(), 30.seconds) - tx.signWith(clientNode.keyPair!!) + tx.signWith(clientKeyPair) tx.toSignedTransaction(false) } @@ -61,7 +63,7 @@ class NotaryServiceTests { val stx = run { val inputState = issueState(clientNode) val tx = TransactionType.General.Builder(notaryNode.info.notaryIdentity).withItems(inputState) - tx.signWith(clientNode.keyPair!!) + tx.signWith(clientKeyPair) tx.toSignedTransaction(false) } @@ -75,7 +77,7 @@ class NotaryServiceTests { val inputState = issueState(clientNode) val tx = TransactionType.General.Builder(notaryNode.info.notaryIdentity).withItems(inputState) tx.setTime(Instant.now().plusSeconds(3600), 30.seconds) - tx.signWith(clientNode.keyPair!!) + tx.signWith(clientKeyPair) tx.toSignedTransaction(false) } @@ -89,7 +91,7 @@ class NotaryServiceTests { val stx = run { val inputState = issueState(clientNode) val tx = TransactionType.General.Builder(notaryNode.info.notaryIdentity).withItems(inputState) - tx.signWith(clientNode.keyPair!!) + tx.signWith(clientKeyPair) tx.toSignedTransaction(false) } @@ -107,13 +109,13 @@ class NotaryServiceTests { val inputState = issueState(clientNode) val stx = run { val tx = TransactionType.General.Builder(notaryNode.info.notaryIdentity).withItems(inputState) - tx.signWith(clientNode.keyPair!!) + tx.signWith(clientKeyPair) tx.toSignedTransaction(false) } val stx2 = run { val tx = TransactionType.General.Builder(notaryNode.info.notaryIdentity).withItems(inputState) tx.addInputState(issueState(clientNode)) - tx.signWith(clientNode.keyPair!!) + tx.signWith(clientKeyPair) tx.toSignedTransaction(false) } diff --git a/node/src/test/kotlin/net/corda/node/services/PersistentNetworkMapServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/PersistentNetworkMapServiceTest.kt index 0f06eab263..aa3f73a90e 100644 --- a/node/src/test/kotlin/net/corda/node/services/PersistentNetworkMapServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/PersistentNetworkMapServiceTest.kt @@ -14,7 +14,9 @@ import net.corda.testing.node.MockNetwork import org.junit.After import org.junit.Before import org.junit.Test +import java.math.BigInteger import java.security.KeyPair +import java.security.KeyPairGeneratorSpi /** * This class mirrors [InMemoryNetworkMapServiceTest] but switches in a [PersistentNetworkMapService] and @@ -55,8 +57,10 @@ class PersistentNetworkMapServiceTest : AbstractNetworkMapServiceTest() { private object NodeFactory : MockNetwork.Factory { override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - advertisedServices: Set, id: Int, keyPair: KeyPair?): MockNetwork.MockNode { - return object : MockNetwork.MockNode(config, network, networkMapAddr, advertisedServices, id, keyPair) { + advertisedServices: Set, id: Int, + overrideServices: Map?, + entropyRoot: BigInteger): MockNetwork.MockNode { + return object : MockNetwork.MockNode(config, network, networkMapAddr, advertisedServices, id, overrideServices, entropyRoot) { override fun makeNetworkMapService() { inNodeNetworkMapService = SwizzleNetworkMapService(services) diff --git a/node/src/test/kotlin/net/corda/node/services/ScheduledFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/ScheduledFlowTests.kt index dd1f47b1ee..b3545fccff 100644 --- a/node/src/test/kotlin/net/corda/node/services/ScheduledFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/ScheduledFlowTests.kt @@ -98,7 +98,6 @@ class ScheduledFlowTests { net = MockNetwork(threadPerNode = true) notaryNode = net.createNode( legalName = DUMMY_NOTARY.name, - keyPair = DUMMY_NOTARY_KEY, advertisedServices = *arrayOf(ServiceInfo(NetworkMapService.type), ServiceInfo(ValidatingNotaryService.type))) nodeA = net.createNode(notaryNode.info.address, start = false) nodeB = net.createNode(notaryNode.info.address, start = false) diff --git a/node/src/test/kotlin/net/corda/node/services/ValidatingNotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/ValidatingNotaryServiceTests.kt index ee2c1f5c3b..2228443f9f 100644 --- a/node/src/test/kotlin/net/corda/node/services/ValidatingNotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/ValidatingNotaryServiceTests.kt @@ -34,10 +34,9 @@ class ValidatingNotaryServiceTests { net = MockNetwork() notaryNode = net.createNode( legalName = DUMMY_NOTARY.name, - keyPair = DUMMY_NOTARY_KEY, advertisedServices = *arrayOf(ServiceInfo(NetworkMapService.type), ServiceInfo(ValidatingNotaryService.type)) ) - clientNode = net.createNode(networkMapAddress = notaryNode.info.address, keyPair = MINI_CORP_KEY) + clientNode = net.createNode(networkMapAddress = notaryNode.info.address) net.runNetwork() // Clear network map registration messages } @@ -45,7 +44,8 @@ class ValidatingNotaryServiceTests { val stx = run { val inputState = issueInvalidState(clientNode, notaryNode.info.notaryIdentity) val tx = TransactionType.General.Builder(notaryNode.info.notaryIdentity).withItems(inputState) - tx.signWith(clientNode.keyPair!!) + val keyPair = clientNode.services.keyManagementService.toKeyPair(clientNode.info.legalIdentity.owningKey.keys.single()) + tx.signWith(keyPair) tx.toSignedTransaction(false) } @@ -62,7 +62,8 @@ class ValidatingNotaryServiceTests { val command = Command(DummyContract.Commands.Move(), expectedMissingKey) val tx = TransactionType.General.Builder(notaryNode.info.notaryIdentity).withItems(inputState, command) - tx.signWith(clientNode.keyPair!!) + val keyPair = clientNode.services.keyManagementService.toKeyPair(clientNode.info.legalIdentity.owningKey.keys.single()) + tx.signWith(keyPair) tx.toSignedTransaction(false) } diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/StateMachineManagerTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/StateMachineManagerTests.kt index b37ccc5232..3800dee863 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/StateMachineManagerTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/StateMachineManagerTests.kt @@ -13,6 +13,8 @@ import net.corda.core.flows.FlowLogic import net.corda.core.getOrThrow import net.corda.core.map import net.corda.core.messaging.MessageRecipients +import net.corda.core.node.services.PartyInfo +import net.corda.core.node.services.ServiceInfo import net.corda.core.random63BitValue import net.corda.core.rootCause import net.corda.core.serialization.OpaqueBytes @@ -21,6 +23,7 @@ import net.corda.flows.CashCommand import net.corda.flows.CashFlow import net.corda.flows.NotaryFlow import net.corda.node.services.persistence.checkpoints +import net.corda.node.services.transactions.ValidatingNotaryService import net.corda.node.utilities.databaseTransaction import net.corda.testing.expect import net.corda.testing.expectEvents @@ -55,10 +58,12 @@ class StateMachineManagerTests { node1 = nodes.first node2 = nodes.second val notaryKeyPair = generateKeyPair() + val notaryService = ServiceInfo(ValidatingNotaryService.type, "notary-service-2000") + val overrideServices = mapOf(Pair(notaryService, notaryKeyPair)) // Note that these notaries don't operate correctly as they don't share their state. They are only used for testing // service addressing. - notary1 = net.createNotaryNode(networkMapAddr = node1.services.myInfo.address, keyPair = notaryKeyPair, serviceName = "notary-service-2000") - notary2 = net.createNotaryNode(networkMapAddr = node1.services.myInfo.address, keyPair = notaryKeyPair, serviceName = "notary-service-2000") + notary1 = net.createNotaryNode(networkMapAddr = node1.services.myInfo.address, overrideServices = overrideServices, serviceName = "notary-service-2000") + notary2 = net.createNotaryNode(networkMapAddr = node1.services.myInfo.address, overrideServices = overrideServices, serviceName = "notary-service-2000") net.messagingNetwork.receivedMessages.toSessionTransfers().forEach { sessionTransfers += it } net.runNetwork() @@ -321,6 +326,8 @@ class StateMachineManagerTests { net.runNetwork() } val endpoint = net.messagingNetwork.endpoint(notary1.net.myAddress as InMemoryMessagingNetwork.PeerHandle)!! + val party1Info = notary1.services.networkMapCache.getPartyInfo(notary1.info.notaryIdentity)!! + assert(party1Info is PartyInfo.Service) val notary1Address: MessageRecipients = endpoint.getAddressOfParty(notary1.services.networkMapCache.getPartyInfo(notary1.info.notaryIdentity)!!) assert(notary1Address is InMemoryMessagingNetwork.ServiceHandle) assertEquals(notary1Address, endpoint.getAddressOfParty(notary2.services.networkMapCache.getPartyInfo(notary2.info.notaryIdentity)!!)) diff --git a/samples/irs-demo/src/main/kotlin/net/corda/simulation/IRSSimulation.kt b/samples/irs-demo/src/main/kotlin/net/corda/simulation/IRSSimulation.kt index 4b67d341eb..a48c0711c5 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/simulation/IRSSimulation.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/simulation/IRSSimulation.kt @@ -22,6 +22,8 @@ import net.corda.node.utilities.databaseTransaction import net.corda.testing.initiateSingleShotFlow import net.corda.testing.node.InMemoryMessagingNetwork import net.corda.testing.node.MockIdentityService +import net.i2p.crypto.eddsa.KeyPairGenerator +import java.security.SecureRandom import java.time.LocalDate import java.util.* @@ -126,7 +128,7 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten showProgressFor(listOf(node1, node2)) showConsensusFor(listOf(node1, node2, regulators[0])) - val instigator = Instigator(node2.info.legalIdentity, AutoOffer(notary.info.notaryIdentity, irs), node1.keyPair!!) + val instigator = Instigator(node2.info.legalIdentity, AutoOffer(notary.info.notaryIdentity, irs), node1.services.legalIdentityKey) val instigatorTx: ListenableFuture = node1.services.startFlow(instigator).resultFuture return Futures.allAsList(instigatorTx, acceptorTx).flatMap { instigatorTx } diff --git a/samples/irs-demo/src/main/kotlin/net/corda/simulation/Simulation.kt b/samples/irs-demo/src/main/kotlin/net/corda/simulation/Simulation.kt index ab5575a761..bfb5dd8dab 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/simulation/Simulation.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/simulation/Simulation.kt @@ -2,7 +2,6 @@ package net.corda.simulation import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture -import net.corda.core.crypto.generateKeyPair import net.corda.core.flatMap import net.corda.core.flows.FlowLogic import net.corda.core.messaging.SingleMessageRecipient @@ -25,6 +24,7 @@ import net.corda.testing.node.TestClock import net.corda.testing.node.setTo import rx.Observable import rx.subjects.PublishSubject +import java.math.BigInteger import java.security.KeyPair import java.time.LocalDate import java.time.LocalDateTime @@ -50,8 +50,9 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, // This puts together a mock network of SimulatedNodes. open class SimulatedNode(config: NodeConfiguration, mockNet: MockNetwork, networkMapAddress: SingleMessageRecipient?, - advertisedServices: Set, id: Int, keyPair: KeyPair?) - : MockNetwork.MockNode(config, mockNet, networkMapAddress, advertisedServices, id, keyPair) { + advertisedServices: Set, id: Int, overrideServices: Map?, + entropyRoot: BigInteger) + : MockNetwork.MockNode(config, mockNet, networkMapAddress, advertisedServices, id, overrideServices, entropyRoot) { override fun findMyLocation(): PhysicalLocation? = CityDatabase[configuration.nearestCity] } @@ -59,7 +60,8 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, var counter = 0 override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - advertisedServices: Set, id: Int, keyPair: KeyPair?): MockNetwork.MockNode { + advertisedServices: Set, id: Int, overrideServices: Map?, + entropyRoot: BigInteger): MockNetwork.MockNode { val letter = 'A' + counter val city = bankLocations[counter++ % bankLocations.size] @@ -69,12 +71,12 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, myLegalName = "Bank $letter", nearestCity = city, networkMapService = null) - return SimulatedNode(cfg, network, networkMapAddr, advertisedServices, id, keyPair) + return SimulatedNode(cfg, network, networkMapAddr, advertisedServices, id, overrideServices, entropyRoot) } fun createAll(): List { return bankLocations.map { - network.createNode(networkMap.info.address, start = false, nodeFactory = this, keyPair = generateKeyPair()) as SimulatedNode + network.createNode(networkMap.info.address, start = false, nodeFactory = this) as SimulatedNode } } } @@ -82,41 +84,44 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, val bankFactory = BankFactory() object NetworkMapNodeFactory : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, - networkMapAddr: SingleMessageRecipient?, advertisedServices: Set, id: Int, keyPair: KeyPair?): MockNetwork.MockNode { + override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, + advertisedServices: Set, id: Int, overrideServices: Map?, + entropyRoot: BigInteger): MockNetwork.MockNode { require(advertisedServices.containsType(NetworkMapService.type)) val cfg = TestNodeConfiguration( baseDirectory = config.baseDirectory, myLegalName = "Network coordination center", nearestCity = "Amsterdam", networkMapService = null) - return object : SimulatedNode(cfg, network, networkMapAddr, advertisedServices, id, keyPair) {} + return object : SimulatedNode(cfg, network, networkMapAddr, advertisedServices, id, overrideServices, entropyRoot) {} } } object NotaryNodeFactory : MockNetwork.Factory { override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - advertisedServices: Set, id: Int, keyPair: KeyPair?): MockNetwork.MockNode { + advertisedServices: Set, id: Int, overrideServices: Map?, + entropyRoot: BigInteger): MockNetwork.MockNode { require(advertisedServices.containsType(SimpleNotaryService.type)) val cfg = TestNodeConfiguration( baseDirectory = config.baseDirectory, myLegalName = "Notary Service", nearestCity = "Zurich", networkMapService = null) - return SimulatedNode(cfg, network, networkMapAddr, advertisedServices, id, keyPair) + return SimulatedNode(cfg, network, networkMapAddr, advertisedServices, id, overrideServices, entropyRoot) } } object RatesOracleFactory : MockNetwork.Factory { override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - advertisedServices: Set, id: Int, keyPair: KeyPair?): MockNetwork.MockNode { + advertisedServices: Set, id: Int, overrideServices: Map?, + entropyRoot: BigInteger): MockNetwork.MockNode { require(advertisedServices.containsType(NodeInterestRates.type)) val cfg = TestNodeConfiguration( baseDirectory = config.baseDirectory, myLegalName = "Rates Service Provider", nearestCity = "Madrid", networkMapService = null) - return object : SimulatedNode(cfg, network, networkMapAddr, advertisedServices, id, keyPair) { + return object : SimulatedNode(cfg, network, networkMapAddr, advertisedServices, id, overrideServices, entropyRoot) { override fun start(): MockNetwork.MockNode { super.start() javaClass.classLoader.getResourceAsStream("example.rates.txt").use { @@ -132,13 +137,14 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, object RegulatorFactory : MockNetwork.Factory { override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - advertisedServices: Set, id: Int, keyPair: KeyPair?): MockNetwork.MockNode { + advertisedServices: Set, id: Int, overrideServices: Map?, + entropyRoot: BigInteger): MockNetwork.MockNode { val cfg = TestNodeConfiguration( baseDirectory = config.baseDirectory, myLegalName = "Regulator A", nearestCity = "Paris", networkMapService = null) - return object : SimulatedNode(cfg, network, networkMapAddr, advertisedServices, id, keyPair) { + return object : SimulatedNode(cfg, network, networkMapAddr, advertisedServices, id, overrideServices, entropyRoot) { // TODO: Regulatory nodes don't actually exist properly, this is a last minute demo request. // So we just fire a message at a node that doesn't know how to handle it, and it'll ignore it. // But that's fine for visualisation purposes. diff --git a/test-utils/src/main/kotlin/net/corda/testing/node/MockNode.kt b/test-utils/src/main/kotlin/net/corda/testing/node/MockNode.kt index ea5da50ab9..89d05e3626 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -6,10 +6,12 @@ import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture import net.corda.core.* import net.corda.core.crypto.Party +import net.corda.core.crypto.entropyToKeyPair import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.CordaPluginRegistry import net.corda.core.node.PhysicalLocation +import net.corda.core.node.ServiceEntry import net.corda.core.node.services.* import net.corda.core.utilities.DUMMY_NOTARY_KEY import net.corda.core.utilities.loggerFor @@ -28,6 +30,7 @@ import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.testing.TestNodeConfiguration import org.apache.activemq.artemis.utils.ReusableLatch import org.slf4j.Logger +import java.math.BigInteger import java.nio.file.FileSystem import java.security.KeyPair import java.util.* @@ -72,14 +75,22 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, /** Allows customisation of how nodes are created. */ interface Factory { + /** + * @param overrideServices a set of service entries to use in place of the node's default service entries, + * for example where a node's service is part of a cluster. + * @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, + * but can be overriden to cause nodes to have stable or colliding identity/service keys. + */ fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - advertisedServices: Set, id: Int, keyPair: KeyPair?): MockNode + advertisedServices: Set, id: Int, overrideServices: Map?, + entropyRoot: BigInteger): MockNode } object DefaultFactory : Factory { override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - advertisedServices: Set, id: Int, keyPair: KeyPair?): MockNode { - return MockNode(config, network, networkMapAddr, advertisedServices, id, keyPair) + advertisedServices: Set, id: Int, overrideServices: Map?, + entropyRoot: BigInteger): MockNode { + return MockNode(config, network, networkMapAddr, advertisedServices, id, overrideServices, entropyRoot) } } @@ -108,12 +119,20 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, } } + /** + * @param overrideServices a set of service entries to use in place of the node's default service entries, + * for example where a node's service is part of a cluster. + * @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, + * but can be overriden to cause nodes to have stable or colliding identity/service keys. + */ open class MockNode(config: NodeConfiguration, val mockNet: MockNetwork, override val networkMapAddress: SingleMessageRecipient?, advertisedServices: Set, val id: Int, - val keyPair: KeyPair?) : AbstractNode(config, advertisedServices, TestClock(), mockNet.busyLatch) { + val overrideServices: Map?, + val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue())) : AbstractNode(config, advertisedServices, TestClock(), mockNet.busyLatch) { + var counter = entropyRoot override val log: Logger = loggerFor() override val serverThread: AffinityExecutor = if (mockNet.threadPerNode) @@ -134,7 +153,9 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, override fun makeVaultService(): VaultService = NodeVaultService(services) - override fun makeKeyManagementService(): KeyManagementService = E2ETestKeyManagementService(partyKeys) + override fun makeKeyManagementService(): KeyManagementService { + return E2ETestKeyManagementService(partyKeys + (overrideServices?.values ?: emptySet())) + } override fun startMessagingService(rpcOps: RPCOps) { // Nothing to do @@ -144,7 +165,28 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, inNodeNetworkMapService = InMemoryNetworkMapService(services) } - override fun generateKeyPair(): KeyPair = keyPair ?: super.generateKeyPair() + override fun makeServiceEntries(): List { + val defaultEntries = super.makeServiceEntries() + return if (overrideServices == null) { + defaultEntries + } else { + defaultEntries.map { + val override = overrideServices[it.info] + if (override != null) { + // TODO: Store the key + ServiceEntry(it.info, Party(it.identity.name, override.public)) + } else { + it + } + } + } + } + + // This is not thread safe, but node construction is done on a single thread, so that should always be fine + override fun generateKeyPair(): KeyPair { + counter = counter.add(BigInteger.ONE) + return entropyToKeyPair(counter) + } // It's OK to not have a network map service in the mock network. override fun noNetworkMapConfigured(): ListenableFuture = Futures.immediateFuture(Unit) @@ -189,9 +231,28 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, override fun acceptableLiveFiberCountOnStop(): Int = acceptableLiveFiberCountOnStop } - /** Returns a node, optionally created by the passed factory method. */ + /** + * Returns a node, optionally created by the passed factory method. + * @param overrideServices a set of service entries to use in place of the node's default service entries, + * for example where a node's service is part of a cluster. + * @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, + * but can be overriden to cause nodes to have stable or colliding identity/service keys. + */ fun createNode(networkMapAddress: SingleMessageRecipient? = null, forcedID: Int = -1, nodeFactory: Factory = defaultFactory, - start: Boolean = true, legalName: String? = null, keyPair: KeyPair? = null, + start: Boolean = true, legalName: String? = null, overrideServices: Map? = null, + vararg advertisedServices: ServiceInfo): MockNode + = createNode(networkMapAddress, forcedID, nodeFactory, start, legalName, overrideServices, BigInteger.valueOf(random63BitValue()), *advertisedServices) + + /** + * Returns a node, optionally created by the passed factory method. + * @param overrideServices a set of service entries to use in place of the node's default service entries, + * for example where a node's service is part of a cluster. + * @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, + * but can be overriden to cause nodes to have stable or colliding identity/service keys. + */ + fun createNode(networkMapAddress: SingleMessageRecipient? = null, forcedID: Int = -1, nodeFactory: Factory = defaultFactory, + start: Boolean = true, legalName: String? = null, overrideServices: Map? = null, + entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), vararg advertisedServices: ServiceInfo): MockNode { val newNode = forcedID == -1 val id = if (newNode) nextNodeId++ else forcedID @@ -205,7 +266,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, myLegalName = legalName ?: "Mock Company $id", networkMapService = null, dataSourceProperties = makeTestDataSourceProperties("node_${id}_net_$networkId")) - val node = nodeFactory.create(config, this, networkMapAddress, advertisedServices.toSet(), id, keyPair) + val node = nodeFactory.create(config, this, networkMapAddress, advertisedServices.toSet(), id, overrideServices, entropyRoot) if (start) { node.setup().start() if (threadPerNode && networkMapAddress != null) @@ -242,8 +303,13 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, */ fun createTwoNodes(nodeFactory: Factory = defaultFactory, notaryKeyPair: KeyPair? = null): Pair { require(nodes.isEmpty()) + val notaryServiceInfo = ServiceInfo(SimpleNotaryService.type) + val notaryOverride = if (notaryKeyPair != null) + mapOf(Pair(notaryServiceInfo, notaryKeyPair)) + else + null return Pair( - createNode(null, -1, nodeFactory, true, null, notaryKeyPair, ServiceInfo(NetworkMapService.type), ServiceInfo(SimpleNotaryService.type)), + createNode(null, -1, nodeFactory, true, null, notaryOverride, BigInteger.valueOf(random63BitValue()), ServiceInfo(NetworkMapService.type), notaryServiceInfo), createNode(nodes[0].info.address, -1, nodeFactory, true, null) ) } @@ -260,9 +326,14 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, */ fun createSomeNodes(numPartyNodes: Int = 2, nodeFactory: Factory = defaultFactory, notaryKeyPair: KeyPair? = DUMMY_NOTARY_KEY): BasketOfNodes { require(nodes.isEmpty()) + val notaryServiceInfo = ServiceInfo(SimpleNotaryService.type) + val notaryOverride = if (notaryKeyPair != null) + mapOf(Pair(notaryServiceInfo, notaryKeyPair)) + else + null val mapNode = createNode(null, nodeFactory = nodeFactory, advertisedServices = ServiceInfo(NetworkMapService.type)) - val notaryNode = createNode(mapNode.info.address, nodeFactory = nodeFactory, keyPair = notaryKeyPair, - advertisedServices = ServiceInfo(SimpleNotaryService.type)) + val notaryNode = createNode(mapNode.info.address, nodeFactory = nodeFactory, overrideServices = notaryOverride, + advertisedServices = notaryServiceInfo) val nodes = ArrayList() repeat(numPartyNodes) { nodes += createPartyNode(mapNode.info.address) @@ -270,12 +341,18 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, return BasketOfNodes(nodes, notaryNode, mapNode) } - fun createNotaryNode(networkMapAddr: SingleMessageRecipient? = null, legalName: String? = null, keyPair: KeyPair? = null, serviceName: String? = null): MockNode { - return createNode(networkMapAddr, -1, defaultFactory, true, legalName, keyPair, ServiceInfo(NetworkMapService.type), ServiceInfo(ValidatingNotaryService.type, serviceName)) + fun createNotaryNode(networkMapAddr: SingleMessageRecipient? = null, + legalName: String? = null, + overrideServices: Map? = null, + serviceName: String? = null): MockNode { + return createNode(networkMapAddr, -1, defaultFactory, true, legalName, overrideServices, BigInteger.valueOf(random63BitValue()), + ServiceInfo(NetworkMapService.type), ServiceInfo(ValidatingNotaryService.type, serviceName)) } - fun createPartyNode(networkMapAddr: SingleMessageRecipient, legalName: String? = null, keyPair: KeyPair? = null): MockNode { - return createNode(networkMapAddr, -1, defaultFactory, true, legalName, keyPair) + fun createPartyNode(networkMapAddr: SingleMessageRecipient, + legalName: String? = null, + overrideServices: Map? = null): MockNode { + return createNode(networkMapAddr, -1, defaultFactory, true, legalName, overrideServices) } @Suppress("unused") // This is used from the network visualiser tool.