CORDA-4054: combine different identities of the same notary after its key rotation (#6734)

This commit is contained in:
Denis Rekalov 2020-10-16 15:53:04 +03:00 committed by GitHub
parent d0cfeb1fbe
commit 551b3f0811
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 207 additions and 28 deletions

View File

@ -26,6 +26,7 @@ import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.AttachmentStorage
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.NetworkParametersService import net.corda.core.node.services.NetworkParametersService
import net.corda.core.node.services.TransactionStorage import net.corda.core.node.services.TransactionStorage
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
@ -242,6 +243,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
val attachmentStorage = rigorousMock<AttachmentStorage>() val attachmentStorage = rigorousMock<AttachmentStorage>()
doReturn(attachmentStorage).whenever(services).attachments doReturn(attachmentStorage).whenever(services).attachments
doReturn(mock<TransactionStorage>()).whenever(services).validatedTransactions doReturn(mock<TransactionStorage>()).whenever(services).validatedTransactions
doReturn(mock<IdentityService>()).whenever(services).identityService
val attachment = rigorousMock<ContractAttachment>() val attachment = rigorousMock<ContractAttachment>()
doReturn(attachment).whenever(attachmentStorage).openAttachment(attachmentId) doReturn(attachment).whenever(attachmentStorage).openAttachment(attachmentId)
doReturn(attachmentId).whenever(attachment).id doReturn(attachmentId).whenever(attachment).id

View File

@ -20,7 +20,6 @@ import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.identity.excludeHostNode import net.corda.core.identity.excludeHostNode
import net.corda.core.identity.groupAbstractPartyByWellKnownParty import net.corda.core.identity.groupAbstractPartyByWellKnownParty
import net.corda.core.node.services.IdentityService
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
@ -32,7 +31,6 @@ import net.corda.testing.core.TestIdentity
import net.corda.testing.core.singleIdentity import net.corda.testing.core.singleIdentity
import net.corda.coretesting.internal.matchers.flow.willReturn import net.corda.coretesting.internal.matchers.flow.willReturn
import net.corda.coretesting.internal.matchers.flow.willThrow import net.corda.coretesting.internal.matchers.flow.willThrow
import net.corda.coretesting.internal.rigorousMock
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.internal.DUMMY_CONTRACTS_CORDAPP import net.corda.testing.node.internal.DUMMY_CONTRACTS_CORDAPP
import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNetwork
@ -47,7 +45,7 @@ import java.security.PublicKey
class CollectSignaturesFlowTests : WithContracts { class CollectSignaturesFlowTests : WithContracts {
companion object { companion object {
private val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")) private val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
private val miniCorpServices = MockServices(listOf("net.corda.testing.contracts"), miniCorp, rigorousMock<IdentityService>()) private val miniCorpServices = MockServices(listOf("net.corda.testing.contracts"), miniCorp)
private val classMockNet = InternalMockNetwork(cordappsForAllNodes = listOf(DUMMY_CONTRACTS_CORDAPP, enclosedCordapp())) private val classMockNet = InternalMockNetwork(cordappsForAllNodes = listOf(DUMMY_CONTRACTS_CORDAPP, enclosedCordapp()))
private const val MAGIC_NUMBER = 1337 private const val MAGIC_NUMBER = 1337

View File

@ -80,7 +80,7 @@ class TransactionSerializationTests {
//override mock implementation with a real one //override mock implementation with a real one
override fun loadContractAttachment(stateRef: StateRef): Attachment = servicesForResolution.loadContractAttachment(stateRef) override fun loadContractAttachment(stateRef: StateRef): Attachment = servicesForResolution.loadContractAttachment(stateRef)
} }
val notaryServices = MockServices(listOf("net.corda.coretests.serialization"), DUMMY_NOTARY.name, rigorousMock(), DUMMY_NOTARY_KEY) val notaryServices = MockServices(listOf("net.corda.coretests.serialization"), DUMMY_NOTARY.name, key = DUMMY_NOTARY_KEY)
lateinit var tx: TransactionBuilder lateinit var tx: TransactionBuilder
@Before @Before

View File

@ -13,6 +13,7 @@ import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.node.ZoneVersionTooLowException import net.corda.core.node.ZoneVersionTooLowException
import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.AttachmentStorage
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.NetworkParametersService import net.corda.core.node.services.NetworkParametersService
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
@ -51,6 +52,7 @@ class TransactionBuilderTest {
doReturn(cordappProvider).whenever(services).cordappProvider doReturn(cordappProvider).whenever(services).cordappProvider
doReturn(contractAttachmentId).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID) doReturn(contractAttachmentId).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID)
doReturn(networkParameters).whenever(services).networkParameters doReturn(networkParameters).whenever(services).networkParameters
doReturn(mock<IdentityService>()).whenever(services).identityService
val attachmentStorage = rigorousMock<AttachmentStorage>() val attachmentStorage = rigorousMock<AttachmentStorage>()
doReturn(attachmentStorage).whenever(services).attachments doReturn(attachmentStorage).whenever(services).attachments

View File

@ -82,9 +82,13 @@ class NotaryFlow {
*/ */
protected fun checkTransaction(): Party { protected fun checkTransaction(): Party {
val notaryParty = stx.notary ?: throw IllegalStateException("Transaction does not specify a Notary") val notaryParty = stx.notary ?: throw IllegalStateException("Transaction does not specify a Notary")
check(serviceHub.networkMapCache.isNotary(notaryParty)) { "$notaryParty is not a notary on the network" } val notaries = setOf(notaryParty) + serviceHub.loadStates(stx.inputs.toSet() + stx.references.toSet()).map { it.state.notary }
check(serviceHub.loadStates(stx.inputs.toSet() + stx.references.toSet()).all { it.state.notary == notaryParty }) { notaries.forEach {
"Input states and reference input states must have the same Notary" check(serviceHub.networkMapCache.isNotary(it)) { "${it.description()} is not a notary on the network" }
// Transaction can combine different identities of the same notary after key rotation.
check(it.name == notaryParty.name) {
"Input states and reference input states must have the same Notary as the transaction Notary"
}
} }
if (!skipVerification) { if (!skipVerification) {
@ -117,10 +121,10 @@ class NotaryFlow {
and can't be used for regular transactions. and can't be used for regular transactions.
*/ */
check(stx.coreTransaction is NotaryChangeWireTransaction) { check(stx.coreTransaction is NotaryChangeWireTransaction) {
"Notary $notaryParty is not on the network parameter whitelist. A non-whitelisted notary can only be used for notary change transactions" "Notary ${notaryParty.description()} is not on the network parameter whitelist. A non-whitelisted notary can only be used for notary change transactions"
} }
val historicNotary = (serviceHub.networkParametersService as NetworkParametersStorage).getHistoricNotary(notaryParty) val historicNotary = (serviceHub.networkParametersService as NetworkParametersStorage).getHistoricNotary(notaryParty)
?: throw IllegalStateException("The notary party $notaryParty specified by transaction ${stx.id}, is not recognised as a current or historic notary.") ?: throw IllegalStateException("The notary party ${notaryParty.description()} specified by transaction ${stx.id}, is not recognised as a current or historic notary.")
historicNotary.validating historicNotary.validating
} else serviceHub.networkMapCache.isValidatingNotary(notaryParty) } else serviceHub.networkMapCache.isValidatingNotary(notaryParty)
} }

View File

@ -5,6 +5,7 @@ import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.toStringShort
import net.corda.core.flows.* import net.corda.core.flows.*
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.IdempotentFlow import net.corda.core.internal.IdempotentFlow
@ -108,7 +109,8 @@ abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service:
@Suspendable @Suspendable
private fun checkNotary(notary: Party?) { private fun checkNotary(notary: Party?) {
require(notary?.owningKey == service.notaryIdentityKey) { require(notary?.owningKey == service.notaryIdentityKey) {
"The notary specified on the transaction: [$notary] does not match the notary service's identity: [${service.notaryIdentityKey}] " "The notary specified on the transaction: [${notary?.description()}] does not match the notary service's identity:" +
" [${service.notaryIdentityKey.toStringShort()}] "
} }
} }

View File

@ -40,7 +40,9 @@ fun NotarisationRequest.verifySignature(requestSignature: NotarisationRequestSig
*/ */
fun NotarisationResponse.validateSignatures(txId: SecureHash, notary: Party) { fun NotarisationResponse.validateSignatures(txId: SecureHash, notary: Party) {
val signingKeys = signatures.map { it.by } val signingKeys = signatures.map { it.by }
require(notary.owningKey.isFulfilledBy(signingKeys)) { "Insufficient signatures to fulfill the notary signing requirement for $notary" } require(notary.owningKey.isFulfilledBy(signingKeys)) {
"Insufficient signatures to fulfill the notary signing requirement for ${notary.description()}"
}
signatures.forEach { it.verify(txId) } signatures.forEach { it.verify(txId) }
} }

View File

@ -120,7 +120,7 @@ interface NetworkMapCacheBase {
// DOCEND 2 // DOCEND 2
/** Returns true if and only if the given [Party] is a notary, which is defined by the network parameters. */ /** Returns true if and only if the given [Party] is a notary, which is defined by the network parameters. */
fun isNotary(party: Party): Boolean = party in notaryIdentities fun isNotary(party: Party): Boolean
/** /**
* Returns true if and only if the given [Party] is validating notary. For every party that is a validating notary, * Returns true if and only if the given [Party] is validating notary. For every party that is a validating notary,

View File

@ -42,9 +42,12 @@ abstract class FullTransaction : BaseTransaction() {
private fun checkInputsAndReferencesHaveSameNotary() { private fun checkInputsAndReferencesHaveSameNotary() {
if (inputs.isEmpty() && references.isEmpty()) return if (inputs.isEmpty() && references.isEmpty()) return
val notaries = (inputs + references).map { it.state.notary }.toHashSet() // Transaction can combine different identities of the same notary after key rotation.
val notaries = (inputs + references).map { it.state.notary.name }.toHashSet()
check(notaries.size == 1) { "All inputs and reference inputs must point to the same notary" } check(notaries.size == 1) { "All inputs and reference inputs must point to the same notary" }
check(notaries.single() == notary) { "The specified notary must be the one specified by all inputs and input references" } check(notaries.single() == notary?.name) {
"The specified transaction notary must be the one specified by all inputs and input references"
}
} }
/** Make sure the assigned notary is part of the network parameter whitelist. */ /** Make sure the assigned notary is part of the network parameter whitelist. */
@ -53,8 +56,14 @@ abstract class FullTransaction : BaseTransaction() {
// Network parameters will never be null if the transaction is resolved from a CoreTransaction rather than constructed directly. // Network parameters will never be null if the transaction is resolved from a CoreTransaction rather than constructed directly.
networkParameters?.let { parameters -> networkParameters?.let { parameters ->
val notaryWhitelist = parameters.notaries.map { it.identity } val notaryWhitelist = parameters.notaries.map { it.identity }
check(notaryParty in notaryWhitelist) { // Transaction can combine different identities of the same notary after key rotation.
"Notary ($notaryParty) specified by the transaction is not on the network parameter whitelist: [${notaryWhitelist.joinToString()}]" // Each of these identities should be whitelisted.
val notaries = setOf(notaryParty) + (inputs + references).map { it.state.notary }
notaries.forEach {
check(it in notaryWhitelist) {
"Notary [${it.description()}] specified by the transaction is not on the network parameter whitelist: " +
" [${notaryWhitelist.joinToString { party -> party.description() }}]"
}
} }
} }
} }

View File

@ -156,7 +156,7 @@ private constructor(
networkParameters?.let { parameters -> networkParameters?.let { parameters ->
val notaryWhitelist = parameters.notaries.map { it.identity } val notaryWhitelist = parameters.notaries.map { it.identity }
check(newNotary in notaryWhitelist) { check(newNotary in notaryWhitelist) {
"The output notary $newNotary is not whitelisted in the attached network parameters." "The output notary ${newNotary.description()} is not whitelisted in the attached network parameters."
} }
} }
} }

View File

@ -154,6 +154,7 @@ open class TransactionBuilder(
if (referenceStates.isNotEmpty()) { if (referenceStates.isNotEmpty()) {
services.ensureMinimumPlatformVersion(4, "Reference states") services.ensureMinimumPlatformVersion(4, "Reference states")
} }
resolveNotary(services)
val (allContractAttachments: Collection<AttachmentId>, resolvedOutputs: List<TransactionState<ContractState>>) val (allContractAttachments: Collection<AttachmentId>, resolvedOutputs: List<TransactionState<ContractState>>)
= selectContractAttachmentsAndOutputStateConstraints(services, serializationContext) = selectContractAttachmentsAndOutputStateConstraints(services, serializationContext)
@ -635,8 +636,10 @@ open class TransactionBuilder(
private fun checkNotary(stateAndRef: StateAndRef<*>) { private fun checkNotary(stateAndRef: StateAndRef<*>) {
val notary = stateAndRef.state.notary val notary = stateAndRef.state.notary
require(notary == this.notary) { // Transaction can combine different identities of the same notary after key rotation.
"Input state requires notary \"$notary\" which does not match the transaction notary \"${this.notary}\"." require(notary.name == this.notary?.name) {
"Input state requires notary \"${notary.description()}\" which does not match" +
" the transaction notary \"${this.notary?.description()}\"."
} }
} }
@ -648,7 +651,25 @@ open class TransactionBuilder(
} }
} }
private fun checkReferencesUseSameNotary() = referencesWithTransactionState.map { it.notary }.toSet().size == 1 // Transaction can combine different identities of the same notary after key rotation.
private fun checkReferencesUseSameNotary() = referencesWithTransactionState.map { it.notary.name }.toSet().size == 1
// Automatically correct notary after its key rotation
private fun resolveNotary(services: ServicesForResolution) {
notary?.let {
val activeNotary = services.identityService.wellKnownPartyFromX500Name(it.name)
if (activeNotary != null && activeNotary != it) {
log.warn("Replacing notary on the transaction '${it.description()}' with '${activeNotary.description()}'.")
notary = activeNotary
}
outputs.forEachIndexed { index, transactionState ->
if (activeNotary != null && activeNotary != transactionState.notary) {
log.warn("Replacing notary on the transaction output '${it.description()}' with '${activeNotary.description()}'.")
outputs[index] = transactionState.copy(notary = activeNotary)
}
}
}
}
/** /**
* If any inputs or outputs added to the [TransactionBuilder] contain [StatePointer]s, then this method is used * If any inputs or outputs added to the [TransactionBuilder] contain [StatePointer]s, then this method is used

View File

@ -11,6 +11,7 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.AttachmentStorage
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.NetworkParametersService import net.corda.core.node.services.NetworkParametersService
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
@ -91,6 +92,7 @@ class AttachmentsClassLoaderStaticContractTests {
val contractAttachmentId = SecureHash.randomSHA256() val contractAttachmentId = SecureHash.randomSHA256()
doReturn(listOf(contractAttachmentId)).whenever(attachmentStorage) doReturn(listOf(contractAttachmentId)).whenever(attachmentStorage)
.getLatestContractAttachments(AttachmentDummyContract.ATTACHMENT_PROGRAM_ID) .getLatestContractAttachments(AttachmentDummyContract.ATTACHMENT_PROGRAM_ID)
doReturn(mock<IdentityService>()).whenever(it).identityService
} }
@Test(timeout=300_000) @Test(timeout=300_000)

View File

@ -0,0 +1,129 @@
package net.corda.node.services.identity
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.internal.createDirectories
import net.corda.core.utilities.OpaqueBytes
import net.corda.finance.DOLLARS
import net.corda.finance.USD
import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.finance.workflows.getCashBalance
import net.corda.node.services.config.NotaryConfig
import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.createDevNetworkMapCa
import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.testing.common.internal.addNotary
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.node.MockNetworkNotarySpec
import net.corda.testing.node.internal.FINANCE_CORDAPPS
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNodeParameters
import net.corda.testing.node.internal.TestStartedNode
import net.corda.testing.node.internal.startFlow
import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import kotlin.test.assertEquals
@RunWith(Parameterized::class)
class NotaryCertificateRotationTest(private val validating: Boolean) {
private val ref = OpaqueBytes.of(0x01)
private val TestStartedNode.party get() = info.legalIdentities.first()
private lateinit var mockNet: InternalMockNetwork
companion object {
@JvmStatic
@Parameterized.Parameters(name = "validating = {0}")
fun data() = listOf(false, true)
}
@After
fun tearDown() {
mockNet.stopNodes()
}
@Test(timeout = 300_000)
fun `rotate notary identity`() {
mockNet = InternalMockNetwork(
cordappsForAllNodes = FINANCE_CORDAPPS,
notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME, validating))
)
val alice = mockNet.createPartyNode(ALICE_NAME)
val bob = mockNet.createPartyNode(BOB_NAME)
// Issue states and notarize them with initial notary identity.
alice.services.startFlow(CashIssueFlow(1000.DOLLARS, ref, mockNet.defaultNotaryIdentity))
alice.services.startFlow(CashIssueAndPaymentFlow(2000.DOLLARS, ref, alice.party, false, mockNet.defaultNotaryIdentity))
alice.services.startFlow(CashIssueAndPaymentFlow(4000.DOLLARS, ref, bob.party, false, mockNet.defaultNotaryIdentity))
mockNet.runNetwork()
// Rotate notary identity and update network parameters.
val newNotaryIdentity = DevIdentityGenerator.installKeyStoreWithNodeIdentity(
mockNet.baseDirectory(mockNet.nextNodeId),
DUMMY_NOTARY_NAME
)
val newNetworkParameters = testNetworkParameters(epoch = 2)
.addNotary(mockNet.defaultNotaryIdentity, validating)
.addNotary(newNotaryIdentity, validating)
val ca = createDevNetworkMapCa()
NetworkParametersCopier(newNetworkParameters, ca, overwriteFile = true).apply {
install(mockNet.baseDirectory(alice))
install(mockNet.baseDirectory(bob))
install(mockNet.baseDirectory(mockNet.nextNodeId))
install(mockNet.baseDirectory(mockNet.nextNodeId + 1).apply { createDirectories() })
}
// Start notary with new identity and restart nodes.
val notary2 = mockNet.createNode(InternalMockNodeParameters(
legalName = DUMMY_NOTARY_NAME,
configOverrides = { doReturn(NotaryConfig(validating)).whenever(it).notary }
))
val alice2 = mockNet.restartNode(alice)
val bob2 = mockNet.restartNode(bob)
val charlie = mockNet.createPartyNode(CHARLIE_NAME)
// Save previous network parameters for subsequent backchain verification.
mockNet.nodes.forEach { it.services.networkParametersService.saveParameters(ca.sign(mockNet.networkParameters)) }
// Verify that notary identity has been changed.
assertEquals(listOf(newNotaryIdentity), alice2.services.networkMapCache.notaryIdentities)
assertEquals(listOf(newNotaryIdentity), bob2.services.networkMapCache.notaryIdentities)
assertEquals(listOf(newNotaryIdentity), charlie.services.networkMapCache.notaryIdentities)
assertEquals(newNotaryIdentity, alice2.services.identityService.wellKnownPartyFromX500Name(DUMMY_NOTARY_NAME))
assertEquals(newNotaryIdentity, bob2.services.identityService.wellKnownPartyFromX500Name(DUMMY_NOTARY_NAME))
assertEquals(newNotaryIdentity, charlie.services.identityService.wellKnownPartyFromX500Name(DUMMY_NOTARY_NAME))
assertEquals(newNotaryIdentity, alice2.services.identityService.wellKnownPartyFromAnonymous(mockNet.defaultNotaryIdentity))
assertEquals(newNotaryIdentity, bob2.services.identityService.wellKnownPartyFromAnonymous(mockNet.defaultNotaryIdentity))
assertEquals(newNotaryIdentity, charlie.services.identityService.wellKnownPartyFromAnonymous(mockNet.defaultNotaryIdentity))
// Move states notarized with previous notary identity.
alice2.services.startFlow(CashPaymentFlow(3000.DOLLARS, bob2.party, false))
mockNet.runNetwork()
bob2.services.startFlow(CashPaymentFlow(7000.DOLLARS, charlie.party, false))
mockNet.runNetwork()
charlie.services.startFlow(CashPaymentFlow(7000.DOLLARS, alice2.party, false))
mockNet.runNetwork()
// Combine states notarized with previous and present notary identities.
bob2.services.startFlow(CashIssueAndPaymentFlow(300.DOLLARS, ref, alice2.party, false, notary2.party))
mockNet.runNetwork()
alice2.services.startFlow(CashPaymentFlow(7300.DOLLARS, charlie.party, false))
mockNet.runNetwork()
// Verify that the ledger contains expected states.
assertEquals(0.DOLLARS, alice2.services.getCashBalance(USD))
assertEquals(0.DOLLARS, bob2.services.getCashBalance(USD))
assertEquals(7300.DOLLARS, charlie.services.getCashBalance(USD))
}
}

View File

@ -58,7 +58,13 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
@Volatile @Volatile
private lateinit var notaries: List<NotaryInfo> private lateinit var notaries: List<NotaryInfo>
@Volatile
private lateinit var rotatedNotaries: Set<CordaX500Name>
// Notary whitelist may contain multiple identities with the same X.500 name after certificate rotation.
// Exclude duplicated entries, which are not present in the network map.
override val notaryIdentities: List<Party> get() = notaries.map { it.identity } override val notaryIdentities: List<Party> get() = notaries.map { it.identity }
.filterNot { it.name in rotatedNotaries && it != getPeerCertificateByLegalName(it.name)?.party }
override val allNodeHashes: List<SecureHash> override val allNodeHashes: List<SecureHash>
get() { get() {
@ -74,7 +80,7 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
} }
fun start(notaries: List<NotaryInfo>) { fun start(notaries: List<NotaryInfo>) {
this.notaries = notaries onNewNotaryList(notaries)
} }
override fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? { override fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? {
@ -98,6 +104,8 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
} }
} }
override fun isNotary(party: Party): Boolean = notaries.any { it.identity == party }
override fun isValidatingNotary(party: Party): Boolean = notaries.any { it.validating && it.identity == party } override fun isValidatingNotary(party: Party): Boolean = notaries.any { it.validating && it.identity == party }
override fun getPartyInfo(party: Party): PartyInfo? { override fun getPartyInfo(party: Party): PartyInfo? {
@ -426,5 +434,6 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
override fun onNewNotaryList(notaries: List<NotaryInfo>) { override fun onNewNotaryList(notaries: List<NotaryInfo>) {
this.notaries = notaries this.notaries = notaries
this.rotatedNotaries = notaries.groupBy { it.identity.name }.filter { it.value.size > 1 }.keys
} }
} }

View File

@ -91,7 +91,8 @@ class NonValidatingNotaryFlow(otherSideSession: FlowSession, service: SinglePart
val notaryWhitelist = networkParameters.notaries.map { it.identity } val notaryWhitelist = networkParameters.notaries.map { it.identity }
check(notary in notaryWhitelist) { check(notary in notaryWhitelist) {
"Notary specified by the transaction ($notary) is not on the network parameter whitelist: ${notaryWhitelist.joinToString()}" "Notary [${notary.description()}] specified by the transaction is not on the network parameter whitelist: " +
" [${notaryWhitelist.joinToString { party -> party.description() }}]"
} }
} }

View File

@ -14,10 +14,10 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.internal.configureDatabase import net.corda.testing.internal.configureDatabase
import net.corda.coretesting.internal.rigorousMock
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import net.corda.testing.node.createMockCordaService import net.corda.testing.node.createMockCordaService
import net.corda.testing.node.makeTestIdentityService
import org.junit.After import org.junit.After
import org.junit.Assert.* import org.junit.Assert.*
import org.junit.Before import org.junit.Before
@ -50,7 +50,7 @@ class NodeInterestRatesTest {
EURIBOR 2016-03-15 2M = 0.111 EURIBOR 2016-03-15 2M = 0.111
""".trimIndent()) """.trimIndent())
private val dummyCashIssuer = TestIdentity(CordaX500Name("Cash issuer", "London", "GB")) private val dummyCashIssuer = TestIdentity(CordaX500Name("Cash issuer", "London", "GB"))
private val services = MockServices(listOf("net.corda.finance.contracts.asset"), dummyCashIssuer, rigorousMock(), MEGA_CORP_KEY) private val services = MockServices(listOf("net.corda.finance.contracts.asset"), dummyCashIssuer, makeTestIdentityService(), MEGA_CORP_KEY)
// This is safe because MockServices only ever have a single identity // This is safe because MockServices only ever have a single identity
private val identity = services.myInfo.singleIdentity() private val identity = services.myInfo.singleIdentity()

View File

@ -3,7 +3,6 @@ package net.corda.traderdemo.flow
import net.corda.core.contracts.CommandData import net.corda.core.contracts.CommandData
import net.corda.core.crypto.newSecureRandom import net.corda.core.crypto.newSecureRandom
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.node.services.IdentityService
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
@ -13,7 +12,6 @@ import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
import net.corda.testing.core.dummyCommand import net.corda.testing.core.dummyCommand
import net.corda.coretesting.internal.rigorousMock
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.internal.MockTransactionStorage import net.corda.testing.node.internal.MockTransactionStorage
import org.junit.Rule import org.junit.Rule
@ -47,8 +45,8 @@ class TransactionGraphSearchTests {
* @param signer signer for the two transactions and their commands. * @param signer signer for the two transactions and their commands.
*/ */
fun buildTransactions(command: CommandData): GraphTransactionStorage { fun buildTransactions(command: CommandData): GraphTransactionStorage {
val megaCorpServices = MockServices(listOf("net.corda.testing.contracts"), megaCorp, rigorousMock<IdentityService>()) val megaCorpServices = MockServices(listOf("net.corda.testing.contracts"), megaCorp)
val notaryServices = MockServices(listOf("net.corda.testing.contracts"), dummyNotary, rigorousMock<IdentityService>()) val notaryServices = MockServices(listOf("net.corda.testing.contracts"), dummyNotary)
val originBuilder = TransactionBuilder(dummyNotary.party) val originBuilder = TransactionBuilder(dummyNotary.party)
.addOutputState(DummyState(random31BitValue()), DummyContract.PROGRAM_ID) .addOutputState(DummyState(random31BitValue()), DummyContract.PROGRAM_ID)
.addCommand(command, megaCorp.publicKey) .addCommand(command, megaCorp.publicKey)