ENT-12366: External verifier now sets appclassloader to legacy contra… (#7855)

* ENT-12366: External verifier now sets appclassloader to legacy contracts directory instead of the cordapps directory.
* ENT-12366: Now check legacy-contracts exists before start external verifier.
This commit is contained in:
Adel El-Beik 2024-10-28 15:28:50 +00:00 committed by GitHub
parent 7852754ad9
commit 33cf48e04b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 44 additions and 12 deletions

View File

@ -4,6 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.TransactionState import net.corda.core.contracts.TransactionState
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party
import net.corda.core.internal.hash import net.corda.core.internal.hash
import net.corda.core.internal.mapToSet import net.corda.core.internal.mapToSet
import net.corda.core.internal.toPath import net.corda.core.internal.toPath
@ -22,9 +23,13 @@ import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.finance.issuedBy import net.corda.finance.issuedBy
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.internal.JarSignatureTestUtils.unsignJar import net.corda.testing.core.internal.JarSignatureTestUtils.unsignJar
import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.NodeParameters import net.corda.testing.driver.NodeParameters
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.TestCordapp import net.corda.testing.node.TestCordapp
import net.corda.testing.node.internal.DriverDSLImpl import net.corda.testing.node.internal.DriverDSLImpl
import net.corda.testing.node.internal.FINANCE_WORKFLOWS_CORDAPP import net.corda.testing.node.internal.FINANCE_WORKFLOWS_CORDAPP
@ -81,7 +86,8 @@ class TransactionBuilderDriverTest {
internalDriver( internalDriver(
cordappsForAllNodes = listOf(FINANCE_WORKFLOWS_CORDAPP), cordappsForAllNodes = listOf(FINANCE_WORKFLOWS_CORDAPP),
startNodesInProcess = false, startNodesInProcess = false,
networkParameters = testNetworkParameters(minimumPlatformVersion = 4) networkParameters = testNetworkParameters(minimumPlatformVersion = 4),
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = false))
) { ) {
val (legacyContracts, legacyDependency) = splitFinanceContractCordapp(legacyFinanceContractsJar) val (legacyContracts, legacyDependency) = splitFinanceContractCordapp(legacyFinanceContractsJar)
val currentContracts = TestCordapp.of(currentFinanceContractsJar.toUri()).asSigned() as TestCordappInternal val currentContracts = TestCordapp.of(currentFinanceContractsJar.toUri()).asSigned() as TestCordappInternal
@ -95,15 +101,23 @@ class TransactionBuilderDriverTest {
legacyContracts = listOf(legacyContracts) legacyContracts = listOf(legacyContracts)
)).getOrThrow() )).getOrThrow()
val nodeBob = startNode(NodeParameters(
BOB_NAME,
additionalCordapps = listOf(currentContracts),
legacyContracts = listOf(legacyContracts,legacyDependency)
)).getOrThrow()
val bobParty = nodeBob.nodeInfo.singleIdentity()
// First make sure the missing dependency causes an issue // First make sure the missing dependency causes an issue
assertThatThrownBy { assertThatThrownBy {
createTransaction(node) createTransaction(node, bobParty)
}.hasMessageContaining("Transaction being built has a missing legacy attachment for class net/corda/finance/contracts/asset/") }.hasMessageContaining("Transaction being built has a missing legacy attachment for class net/corda/finance/contracts/asset/")
// Upload the missing dependency // Upload the missing dependency
legacyDependency.jarFile.inputStream().use(node.rpc::uploadAttachment) legacyDependency.jarFile.inputStream().use(node.rpc::uploadAttachment)
val stx = createTransaction(node) val stx = createTransaction(node, bobParty)
assertThat(stx.tx.legacyAttachments).contains(legacyContracts.jarFile.hash, legacyDependency.jarFile.hash) assertThat(stx.tx.legacyAttachments).contains(legacyContracts.jarFile.hash, legacyDependency.jarFile.hash)
} }
} }
@ -167,12 +181,12 @@ class TransactionBuilderDriverTest {
) )
} }
private fun DriverDSLImpl.createTransaction(node: NodeHandle): SignedTransaction { private fun DriverDSLImpl.createTransaction(node: NodeHandle, destination: Party = defaultNotaryIdentity): SignedTransaction {
return node.rpc.startFlow( return node.rpc.startFlow(
::CashIssueAndPaymentFlow, ::CashIssueAndPaymentFlow,
1.DOLLARS, 1.DOLLARS,
OpaqueBytes.of(0x00), OpaqueBytes.of(0x00),
defaultNotaryIdentity, destination,
false, false,
defaultNotaryIdentity defaultNotaryIdentity
).returnValue.getOrThrow().stx ).returnValue.getOrThrow().stx

View File

@ -193,17 +193,18 @@ class ExternalVerificationUnsignedCordappsTest {
fun startNodes() { fun startNodes() {
// The 4.11 finance CorDapp jars // The 4.11 finance CorDapp jars
val legacyCordapps = listOf(unsignedResourceJar("corda-finance-contracts-4.11.jar"), smokeTestResource("corda-finance-workflows-4.11.jar")) val legacyCordapps = listOf(unsignedResourceJar("corda-finance-contracts-4.11.jar"), smokeTestResource("corda-finance-workflows-4.11.jar"))
val legacyCordappsWithoutContracts = listOf(smokeTestResource("corda-finance-workflows-4.11.jar"))
// The current version finance CorDapp jars // The current version finance CorDapp jars
val currentCordapps = listOf(unsignedResourceJar("corda-finance-contracts.jar"), smokeTestResource("corda-finance-workflows.jar")) val currentCordapps = listOf(unsignedResourceJar("corda-finance-contracts.jar"), smokeTestResource("corda-finance-workflows.jar"))
notary = factory.createNotaries(nodeParams(DUMMY_NOTARY_NAME, currentCordapps))[0] notary = factory.createNotaries(nodeParams(DUMMY_NOTARY_NAME, currentCordapps, legacyCordapps))[0]
oldNode = factory.createNode(nodeParams( oldNode = factory.createNode(nodeParams(
CordaX500Name("Old", "Delhi", "IN"), CordaX500Name("Old", "Delhi", "IN"),
legacyCordapps, legacyCordapps,
clientRpcConfig = CordaRPCClientConfiguration(minimumServerProtocolVersion = 13), clientRpcConfig = CordaRPCClientConfiguration(minimumServerProtocolVersion = 13),
version = "4.11" version = "4.11"
)) ))
newNode = factory.createNode(nodeParams(CordaX500Name("New", "York", "US"), currentCordapps)) newNode = factory.createNode(nodeParams(CordaX500Name("New", "York", "US"), currentCordapps, legacyCordappsWithoutContracts))
} }
@AfterClass @AfterClass

View File

@ -24,8 +24,10 @@ import net.corda.coretesting.internal.matchers.rpc.willThrow
import net.corda.testing.node.User import net.corda.testing.node.User
import net.corda.testing.node.internal.* import net.corda.testing.node.internal.*
import org.junit.AfterClass import org.junit.AfterClass
import org.junit.Ignore
import org.junit.Test import org.junit.Test
@Ignore("Explicit contract upgrade not supported in 4.12")
class ContractUpgradeFlowRPCTest : WithContracts, WithFinality { class ContractUpgradeFlowRPCTest : WithContracts, WithFinality {
companion object { companion object {
private val classMockNet = InternalMockNetwork(cordappsForAllNodes = listOf(DUMMY_CONTRACTS_CORDAPP, enclosedCordapp())) private val classMockNet = InternalMockNetwork(cordappsForAllNodes = listOf(DUMMY_CONTRACTS_CORDAPP, enclosedCordapp()))

View File

@ -47,9 +47,11 @@ import net.corda.testing.node.internal.TestStartedNode
import net.corda.testing.node.internal.enclosedCordapp import net.corda.testing.node.internal.enclosedCordapp
import net.corda.testing.node.internal.startFlow import net.corda.testing.node.internal.startFlow
import org.junit.AfterClass import org.junit.AfterClass
import org.junit.Ignore
import org.junit.Test import org.junit.Test
import java.util.Currency import java.util.Currency
@Ignore("Explicit contract upgrade not supported in 4.12")
class ContractUpgradeFlowTest : WithContracts, WithFinality { class ContractUpgradeFlowTest : WithContracts, WithFinality {
companion object { companion object {

View File

@ -240,6 +240,7 @@ class ResolveTransactionsFlowTest {
} }
@Test(timeout=300_000) @Test(timeout=300_000)
@Ignore("Need to pass legacy contracts to internal mock network & need to create a legacy contract for test below")
fun `can resolve a chain of transactions containing a contract upgrade transaction`() { fun `can resolve a chain of transactions containing a contract upgrade transaction`() {
val tx = contractUpgradeChain() val tx = contractUpgradeChain()
var numUpdates = 0 var numUpdates = 0

View File

@ -60,6 +60,9 @@ data class RotatedKeys(val rotatedSigningKeys: List<List<SecureHash>> = emptyLis
} }
fun canBeTransitioned(inputKeys: List<PublicKey>, outputKeys: List<PublicKey>): Boolean { fun canBeTransitioned(inputKeys: List<PublicKey>, outputKeys: List<PublicKey>): Boolean {
if (inputKeys.isEmpty() && outputKeys.isEmpty()) {
return true
}
return canBeTransitioned(CompositeKey.Builder().addKeys(inputKeys).build(), CompositeKey.Builder().addKeys(outputKeys).build()) return canBeTransitioned(CompositeKey.Builder().addKeys(inputKeys).build(), CompositeKey.Builder().addKeys(outputKeys).build())
} }

View File

@ -220,9 +220,11 @@ class JarScanningCordappLoader(private val cordappJars: Set<Path>,
private fun checkSignersMatch(legacyCordapp: CordappImpl, nonLegacyCordapp: CordappImpl) { private fun checkSignersMatch(legacyCordapp: CordappImpl, nonLegacyCordapp: CordappImpl) {
val legacySigners = legacyCordapp.jarPath.openStream().let(::JarInputStream).use(JarSignatureCollector::collectSigners) val legacySigners = legacyCordapp.jarPath.openStream().let(::JarInputStream).use(JarSignatureCollector::collectSigners)
val nonLegacySigners = nonLegacyCordapp.jarPath.openStream().let(::JarInputStream).use(JarSignatureCollector::collectSigners) val nonLegacySigners = nonLegacyCordapp.jarPath.openStream().let(::JarInputStream).use(JarSignatureCollector::collectSigners)
check(rotatedKeys.canBeTransitioned(legacySigners, nonLegacySigners)) { if (legacySigners.isNotEmpty() || nonLegacySigners.isNotEmpty()) {
"Newer contract CorDapp '${nonLegacyCordapp.jarFile}' signers do not match legacy contract CorDapp " + check(rotatedKeys.canBeTransitioned(legacySigners, nonLegacySigners)) {
"'${legacyCordapp.jarFile}' signers." "Newer contract CorDapp '${nonLegacyCordapp.jarFile}' signers do not match legacy contract CorDapp " +
"'${legacyCordapp.jarFile}' signers."
}
} }
} }

View File

@ -58,6 +58,7 @@ import kotlin.io.path.div
import kotlin.io.path.fileAttributesViewOrNull import kotlin.io.path.fileAttributesViewOrNull
import kotlin.io.path.isExecutable import kotlin.io.path.isExecutable
import kotlin.io.path.isWritable import kotlin.io.path.isWritable
import kotlin.io.path.notExists
/** /**
* Handle to the node's external verifier. The verifier process is started lazily on the first verification request. * Handle to the node's external verifier. The verifier process is started lazily on the first verification request.
@ -116,6 +117,12 @@ class ExternalVerifierHandleImpl(
} }
private fun startServer() { private fun startServer() {
val legacyContractsPath = (baseDirectory / "legacy-contracts")
if (legacyContractsPath.notExists()) {
log.error("Failed to start external verifier because $legacyContractsPath does not exist. Please create a legacy-contracts " +
"directory under $baseDirectory and place your legacy contracts into this directory. See the documentation for details.")
throw IOException("Cannot start external verifier because $legacyContractsPath does not exist.")
}
if (::socketFile.isInitialized) return if (::socketFile.isInitialized) return
// Try to create the UNIX domain file in /tmp to keep the full path under the 100 char limit. If we don't have access to it then // Try to create the UNIX domain file in /tmp to keep the full path under the 100 char limit. If we don't have access to it then
// fallback to the temp dir specified by the JVM and hope it's short enough. // fallback to the temp dir specified by the JVM and hope it's short enough.

View File

@ -52,7 +52,7 @@ sealed class ExternalVerifierInbound {
val ctx: CoreTransaction, val ctx: CoreTransaction,
val ctxInputsAndReferences: Map<StateRef, SerializedTransactionState> val ctxInputsAndReferences: Map<StateRef, SerializedTransactionState>
) : ExternalVerifierInbound() { ) : ExternalVerifierInbound() {
override fun toString(): String = "VerificationRequest(ctx=$ctx)" override fun toString(): String = "VerificationRequest(transaction id=${ctx.id})"
} }
data class PartiesResult(val parties: List<Party?>) : ExternalVerifierInbound() data class PartiesResult(val parties: List<Party?>) : ExternalVerifierInbound()

View File

@ -124,7 +124,7 @@ class ExternalVerifier(private val baseDirectory: Path, private val channel: Soc
} }
private fun createAppClassLoader(): ClassLoader { private fun createAppClassLoader(): ClassLoader {
val cordappJarUrls = (baseDirectory / "cordapps").listDirectoryEntries("*.jar") val cordappJarUrls = (baseDirectory / "legacy-contracts").listDirectoryEntries("*.jar")
.stream() .stream()
.map { it.toUri().toURL() } .map { it.toUri().toURL() }
.toTypedArray() .toTypedArray()