mirror of
https://github.com/corda/corda.git
synced 2024-12-24 07:06:44 +00:00
Merge pull request #7868 from corda/adel/ENT-12373
ENT-12373: Can now cope with diff input states from diff rotated CorDapps.
This commit is contained in:
commit
7c591de607
@ -78,6 +78,8 @@ data class RotatedKeys(val rotatedSigningKeys: List<List<SecureHash>> = emptyLis
|
||||
}
|
||||
}
|
||||
|
||||
fun rotateToHash(key: PublicKey) = rotate(key.hash.sha256())
|
||||
|
||||
private fun rotate(key: SecureHash): SecureHash {
|
||||
return rotateMap[key] ?: key
|
||||
}
|
||||
|
@ -659,7 +659,9 @@ open class TransactionBuilder(
|
||||
constraints.any { it is HashAttachmentConstraint } -> constraints.find { it is HashAttachmentConstraint }!!
|
||||
|
||||
// TODO, we don't currently support mixing signature constraints with different signers. This will change once we introduce third party signers.
|
||||
constraints.count { it is SignatureAttachmentConstraint } > 1 ->
|
||||
(constraints.count { it is SignatureAttachmentConstraint } > 1) &&
|
||||
(constraints.filterIsInstance<SignatureAttachmentConstraint>().map { serviceHub?.toVerifyingServiceHub()?.rotatedKeys?.rotateToHash(it.key) ?: it.key}.toSet().size > 1)
|
||||
->
|
||||
throw IllegalArgumentException("Cannot mix SignatureAttachmentConstraints signed by different parties in the same transaction.")
|
||||
|
||||
// This ensures a smooth migration from a Whitelist Constraint to a Signature Constraint
|
||||
@ -675,6 +677,10 @@ open class TransactionBuilder(
|
||||
// When all input states have the same constraint.
|
||||
constraints.size == 1 -> constraints.single()
|
||||
|
||||
// if we are here then the multiple SignatureAttachmentConstraint keys must be rotations of each other due to above check
|
||||
(constraints.count { it is SignatureAttachmentConstraint } > 1)
|
||||
-> constraints.filterIsInstance<SignatureAttachmentConstraint>().first { it == makeSignatureAttachmentConstraint(attachmentToUse.signerKeys) }
|
||||
|
||||
else -> throw IllegalArgumentException("Unexpected constraints $constraints.")
|
||||
}
|
||||
|
||||
|
@ -2,14 +2,18 @@ package net.corda.node
|
||||
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.internal.hash
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.GBP
|
||||
import net.corda.finance.POUNDS
|
||||
import net.corda.finance.USD
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.finance.flows.CashIssueAndPaymentFlow
|
||||
import net.corda.finance.flows.CashIssueFlow
|
||||
import net.corda.finance.flows.CashPaymentFlow
|
||||
import net.corda.finance.`issued by`
|
||||
import net.corda.finance.workflows.getCashBalance
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.config.RotatedCorDappSignerKeyConfiguration
|
||||
@ -34,6 +38,7 @@ import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.whenever
|
||||
import kotlin.io.path.div
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class ContractWithRotatedKeyTest {
|
||||
private val ref = OpaqueBytes.of(0x01)
|
||||
@ -132,4 +137,99 @@ class ContractWithRotatedKeyTest {
|
||||
keyStoreDir1.close()
|
||||
keyStoreDir2.close()
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `transaction can be created with multiple contract input states from rotated CorDapps`() {
|
||||
val keyStoreDir1 = SelfCleaningDir()
|
||||
val keyStoreDir2 = SelfCleaningDir()
|
||||
|
||||
val packageOwnerKey1 = keyStoreDir1.path.generateKey(alias="1-testcordapp-rsa")
|
||||
val packageOwnerKey2 = keyStoreDir2.path.generateKey(alias="1-testcordapp-rsa")
|
||||
|
||||
val unsignedFinanceCorDapp1 = cordappWithPackages("net.corda.finance", "migration", "META-INF.services")
|
||||
val unsignedFinanceCorDapp2 = cordappWithPackages("net.corda.finance", "migration", "META-INF.services").copy(versionId = 2)
|
||||
|
||||
val signedFinanceCorDapp1 = unsignedFinanceCorDapp1.signed( keyStoreDir1.path )
|
||||
val signedFinanceCorDapp2 = unsignedFinanceCorDapp2.signed( keyStoreDir2.path )
|
||||
|
||||
val configOverrides = { conf: NodeConfiguration ->
|
||||
val rotatedKeys = listOf(RotatedCorDappSignerKeyConfiguration(listOf(packageOwnerKey1.hash.sha256().toString(), packageOwnerKey2.hash.sha256().toString())))
|
||||
doReturn(rotatedKeys).whenever(conf).rotatedCordappSignerKeys
|
||||
}
|
||||
|
||||
val alice = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, additionalCordapps = listOf(signedFinanceCorDapp1), configOverrides = configOverrides))
|
||||
|
||||
val flow1 = alice.services.startFlow(CashIssueAndPaymentFlow(300.DOLLARS, ref, alice.party, false, mockNet.defaultNotaryIdentity))
|
||||
val flow2 = alice.services.startFlow(CashIssueAndPaymentFlow(1000.POUNDS, ref, alice.party, false, mockNet.defaultNotaryIdentity))
|
||||
mockNet.runNetwork()
|
||||
flow1.resultFuture.getOrThrow()
|
||||
flow2.resultFuture.getOrThrow()
|
||||
|
||||
val alice2 = restartNodeAndDeleteOldCorDapps(mockNet, alice, parameters = InternalMockNodeParameters(additionalCordapps = listOf(signedFinanceCorDapp2), configOverrides = configOverrides))
|
||||
|
||||
val flow3 = alice2.services.startFlow(CashIssueFlow(700.DOLLARS, ref, mockNet.defaultNotaryIdentity))
|
||||
mockNet.runNetwork()
|
||||
flow3.resultFuture.getOrThrow()
|
||||
|
||||
val vaultStates = alice2.services.vaultService.queryBy(Cash.State::class.java).states
|
||||
assertEquals(3, vaultStates.size)
|
||||
|
||||
val outputState = Cash.State(1000.POUNDS `issued by` alice2.party.ref(1), alice2.party)
|
||||
val tx = TransactionBuilder(notary = mockNet.defaultNotaryIdentity, serviceHub = alice2.services).apply {
|
||||
addInputState(vaultStates[0])
|
||||
addInputState(vaultStates[1])
|
||||
addInputState(vaultStates[2])
|
||||
addOutputState(outputState)
|
||||
addCommand(Cash.Commands.Move(), listOf(alice2.party.owningKey))
|
||||
}
|
||||
tx.toWireTransaction(alice2.services)
|
||||
|
||||
keyStoreDir1.close()
|
||||
keyStoreDir2.close()
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `transaction creation fails with multiple contract input states from different CorDapps`() {
|
||||
val keyStoreDir1 = SelfCleaningDir()
|
||||
val keyStoreDir2 = SelfCleaningDir()
|
||||
|
||||
val unsignedFinanceCorDapp1 = cordappWithPackages("net.corda.finance", "migration", "META-INF.services")
|
||||
val unsignedFinanceCorDapp2 = cordappWithPackages("net.corda.finance", "migration", "META-INF.services").copy(versionId = 2)
|
||||
|
||||
val signedFinanceCorDapp1 = unsignedFinanceCorDapp1.signed( keyStoreDir1.path )
|
||||
val signedFinanceCorDapp2 = unsignedFinanceCorDapp2.signed( keyStoreDir2.path )
|
||||
|
||||
val alice = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, additionalCordapps = listOf(signedFinanceCorDapp1)))
|
||||
|
||||
val flow1 = alice.services.startFlow(CashIssueAndPaymentFlow(300.DOLLARS, ref, alice.party, false, mockNet.defaultNotaryIdentity))
|
||||
val flow2 = alice.services.startFlow(CashIssueAndPaymentFlow(1000.POUNDS, ref, alice.party, false, mockNet.defaultNotaryIdentity))
|
||||
mockNet.runNetwork()
|
||||
flow1.resultFuture.getOrThrow()
|
||||
flow2.resultFuture.getOrThrow()
|
||||
|
||||
val alice2 = restartNodeAndDeleteOldCorDapps(mockNet, alice, parameters = InternalMockNodeParameters(additionalCordapps = listOf(signedFinanceCorDapp2)))
|
||||
|
||||
val flow3 = alice2.services.startFlow(CashIssueFlow(700.DOLLARS, ref, mockNet.defaultNotaryIdentity))
|
||||
mockNet.runNetwork()
|
||||
flow3.resultFuture.getOrThrow()
|
||||
|
||||
val vaultStates = alice2.services.vaultService.queryBy(Cash.State::class.java).states
|
||||
assertEquals(3, vaultStates.size)
|
||||
|
||||
val outputState = Cash.State(1000.POUNDS `issued by` alice2.party.ref(1), alice2.party)
|
||||
val tx = TransactionBuilder(notary = mockNet.defaultNotaryIdentity, serviceHub = alice2.services).apply {
|
||||
addInputState(vaultStates[0])
|
||||
addInputState(vaultStates[1])
|
||||
addInputState(vaultStates[2])
|
||||
addOutputState(outputState)
|
||||
addCommand(Cash.Commands.Move(), listOf(alice2.party.owningKey))
|
||||
}
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
tx.toWireTransaction(alice2.services)
|
||||
}.also {
|
||||
assertEquals("Cannot mix SignatureAttachmentConstraints signed by different parties in the same transaction.", it.message)
|
||||
}
|
||||
keyStoreDir1.close()
|
||||
keyStoreDir2.close()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user