diff --git a/core-tests/build.gradle b/core-tests/build.gradle index 8c82496a2a..26ebe42ae1 100644 --- a/core-tests/build.gradle +++ b/core-tests/build.gradle @@ -54,7 +54,15 @@ processSmokeTestResources { rename 'corda-finance-contracts-.*.jar', 'corda-finance-contracts.jar' } from(tasks.getByPath(":testing:cordapps:4.11-workflows:jar")) - from(configurations.corda4_11) + from(configurations.corda4_11) { + rename 'jackson-core-.*.jar', 'jackson-core.jar' + } + from(tasks.getByPath(":legacy411:jar")) { + rename 'legacy411-.*.jar', 'legacy411.jar' + } + from(tasks.getByPath(":legacy412:jar")) { + rename 'legacy412-.*.jar', 'legacy412.jar' + } } processIntegrationTestResources { @@ -123,6 +131,8 @@ dependencies { smokeTestImplementation project(":finance:workflows") smokeTestImplementation project(":testing:cordapps:4.11-workflows") smokeTestImplementation project(":finance:contracts") + smokeTestImplementation project(":legacy411") + smokeTestImplementation project(":legacy412") smokeTestImplementation "org.assertj:assertj-core:${assertj_version}" smokeTestImplementation "org.bouncycastle:bcprov-lts8on:${bouncycastle_version}" smokeTestImplementation "co.paralleluniverse:quasar-core:$quasar_version" @@ -137,6 +147,7 @@ dependencies { corda4_11 "net.corda:corda-finance-contracts:4.11" corda4_11 "net.corda:corda-finance-workflows:4.11" corda4_11 "net.corda:corda:4.11" + corda4_11 "com.fasterxml.jackson.core:jackson-core:2.17.2" } tasks.withType(Test).configureEach { diff --git a/core-tests/src/smoke-test/kotlin/net/corda/coretests/verification/ExternalVerificationTests.kt b/core-tests/src/smoke-test/kotlin/net/corda/coretests/verification/ExternalVerificationTests.kt index fd742d58f2..8e0c9aef13 100644 --- a/core-tests/src/smoke-test/kotlin/net/corda/coretests/verification/ExternalVerificationTests.kt +++ b/core-tests/src/smoke-test/kotlin/net/corda/coretests/verification/ExternalVerificationTests.kt @@ -3,6 +3,7 @@ package net.corda.coretests.verification import net.corda.client.rpc.CordaRPCClientConfiguration import net.corda.client.rpc.notUsed import net.corda.core.contracts.Amount +import net.corda.core.contracts.StateRef import net.corda.core.crypto.SecureHash import net.corda.core.flows.UnexpectedFlowEndException import net.corda.core.identity.CordaX500Name @@ -24,6 +25,7 @@ import net.corda.finance.flows.AbstractCashFlow import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow import net.corda.finance.workflows.getCashBalance +import net.corda.legacy.workflows.LegacyIssuanceFlow import net.corda.nodeapi.internal.config.User import net.corda.smoketesting.NodeParams import net.corda.smoketesting.NodeProcess @@ -63,6 +65,8 @@ class ExternalVerificationSignedCordappsTest { val (legacyContractsCordapp, legacyWorkflowsCordapp) = listOf("contracts", "workflows").map { smokeTestResource("corda-finance-$it-4.11.jar") } // The current version finance CorDapp jars val currentCordapps = listOf("contracts", "workflows").map { smokeTestResource("corda-finance-$it.jar") } + val legacyJacksonCordapp411 = smokeTestResource("legacy411.jar") + val legacyJacksonCordapp412 = smokeTestResource("legacy412.jar") notaries = factory.createNotaries( nodeParams(DUMMY_NOTARY_NAME, cordappJars = currentCordapps, legacyContractJars = listOf(legacyContractsCordapp)), @@ -76,9 +80,15 @@ class ExternalVerificationSignedCordappsTest { )) currentNode = factory.createNode(nodeParams( CordaX500Name("New", "York", "US"), - currentCordapps, - listOf(legacyContractsCordapp) + currentCordapps + listOf(legacyJacksonCordapp412), + listOf(legacyContractsCordapp, legacyJacksonCordapp411) )) + val legacyJars = currentNode.nodeDir / "legacy-jars" + legacyJars.toFile().mkdir() + + val jacksonDestination = legacyJars / "jackson-core.jar" + val jacksonSource = smokeTestResource("jackson-core.jar") + jacksonSource.copyTo(jacksonDestination) } @AfterClass @@ -123,6 +133,18 @@ class ExternalVerificationSignedCordappsTest { oldRpc.startFlow(::IssueAndChangeNotaryFlow, notaryIdentities[0], notaryIdentities[1]).returnValue.getOrThrow() } + @Test(timeout = 300_000) + fun `transaction containing 4_11 and 4_12 contract referencing Jackson dependency issued on new node`() { + val issuanceStateRef = legacyJackonIssuance(currentNode) + currentNode.assertTransactionsWereVerified(BOTH, issuanceStateRef.txhash) + } + + private fun legacyJackonIssuance(issuer: NodeProcess): StateRef { + val issuerRpc = issuer.connect(superUser).proxy + val issuanceStateRef = issuerRpc.startFlowDynamic(LegacyIssuanceFlow::class.java, 2).returnValue.getOrThrow() as StateRef + return issuanceStateRef + } + private fun cashIssuanceAndPayment(issuer: NodeProcess, recipient: NodeProcess): Pair { val issuerRpc = issuer.connect(superUser).proxy val recipientRpc = recipient.connect(superUser).proxy diff --git a/legacy411/build.gradle b/legacy411/build.gradle new file mode 100644 index 0000000000..cf53a0921f --- /dev/null +++ b/legacy411/build.gradle @@ -0,0 +1,39 @@ +apply plugin: 'org.jetbrains.kotlin.jvm' +apply plugin: 'net.corda.plugins.quasar-utils' +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'java' + +compileJava { + sourceCompatibility = '1.8' + targetCompatibility = '1.8' +} + +description 'Legacy CorDapp for testing' + +dependencies { + cordaProvided("net.corda:corda-core:4.11") { + exclude group: quasar_group, module: 'quasar-core' + } + cordaProvided configurations['quasar'] + cordaProvided "com.fasterxml.jackson.core:jackson-databind:$jackson_version" +} + +cordapp { + targetPlatformVersion 1 + minimumPlatformVersion 1 + sealing { + enabled false // This needs to be disabled for AttachmentsClassLoaderSerializationTests to work + } + contract { + name "Legacy Test CorDapp" + versionId 1 + vendor "R3" + licence "Open Source (Apache 2)" + } + workflow { + name "Legacy Test CorDapp" + versionId 1 + vendor "R3" + licence "Open Source (Apache 2)" + } +} diff --git a/legacy411/src/main/java/net/corda/legacy/contracts/AnotherDummyContract.java b/legacy411/src/main/java/net/corda/legacy/contracts/AnotherDummyContract.java new file mode 100644 index 0000000000..b98418946d --- /dev/null +++ b/legacy411/src/main/java/net/corda/legacy/contracts/AnotherDummyContract.java @@ -0,0 +1,116 @@ +package net.corda.legacy.contracts; + +import com.fasterxml.jackson.core.JsonProcessingException; +import kotlin.collections.CollectionsKt; +import kotlin.jvm.internal.Intrinsics; +import net.corda.core.contracts.Command; +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.contracts.ContractState; +import net.corda.core.contracts.PartyAndReference; +import net.corda.core.contracts.StateAndContract; +import net.corda.core.contracts.TypeOnlyCommandData; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.core.transactions.LedgerTransaction; +import net.corda.core.transactions.TransactionBuilder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public final class AnotherDummyContract implements Contract { + @NotNull + private final String magicString = "helloworld"; + @NotNull + public static final String ANOTHER_DUMMY_PROGRAM_ID = "net.corda.legacy.contracts.AnotherDummyContract"; + + @NotNull + public final String getMagicString() { + return this.magicString; + } + + public void verify(@NotNull LedgerTransaction tx) { + Intrinsics.checkNotNullParameter(tx, "tx"); + } + + public void randomMethod() throws JsonProcessingException { + throw new JsonProcessingException("") { + }; + } + + @NotNull + public final TransactionBuilder generateInitial(@NotNull PartyAndReference owner, int magicNumber, @NotNull Party notary) { + Intrinsics.checkNotNullParameter(owner, "owner"); + Intrinsics.checkNotNullParameter(notary, "notary"); + State state = new State(magicNumber); + TransactionBuilder var10000 = new TransactionBuilder(notary); + Object[] var5 = new Object[]{new StateAndContract((ContractState) state, ANOTHER_DUMMY_PROGRAM_ID), new Command(new Commands.Create(), owner.getParty().getOwningKey())}; + return var10000.withItems(var5); + } + + public final int inspectState(@NotNull ContractState state) { + Intrinsics.checkNotNullParameter(state, "state"); + return ((State) state).getMagicNumber(); + } + + public interface Commands extends CommandData { + public static final class Create extends TypeOnlyCommandData implements Commands { + } + } + + public static final class State implements ContractState { + private final int magicNumber; + + public State(int magicNumber) { + this.magicNumber = magicNumber; + } + + public final int getMagicNumber() { + return this.magicNumber; + } + + @NotNull + public List getParticipants() { + return CollectionsKt.emptyList(); + } + + public final int component1() { + return this.magicNumber; + } + + @NotNull + public final State copy(int magicNumber) { + return new State(magicNumber); + } + + // $FF: synthetic method + public static State copy$default(State var0, int var1, int var2, Object var3) { + if ((var2 & 1) != 0) { + var1 = var0.magicNumber; + } + + return var0.copy(var1); + } + + @NotNull + public String toString() { + return "State(magicNumber=" + this.magicNumber + ')'; + } + + public int hashCode() { + return Integer.hashCode(this.magicNumber); + } + + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } else if (!(other instanceof State)) { + return false; + } else { + State var2 = (State) other; + return this.magicNumber == var2.magicNumber; + } + } + } +} diff --git a/legacy412/build.gradle b/legacy412/build.gradle new file mode 100644 index 0000000000..b8e16404a9 --- /dev/null +++ b/legacy412/build.gradle @@ -0,0 +1,39 @@ +apply plugin: 'org.jetbrains.kotlin.jvm' +apply plugin: 'net.corda.plugins.quasar-utils' +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'java' + +compileJava { + sourceCompatibility = '1.8' + targetCompatibility = '1.8' +} + +description 'Legacy CorDapp for testing' + +dependencies { + cordaProvided("net.corda:corda-core:4.11") { + exclude group: quasar_group, module: 'quasar-core' + } + cordaProvided configurations['quasar'] + cordaProvided "com.fasterxml.jackson.core:jackson-databind:$jackson_version" +} + +cordapp { + targetPlatformVersion 1 + minimumPlatformVersion 1 + sealing { + enabled false // This needs to be disabled for AttachmentsClassLoaderSerializationTests to work + } + contract { + name "Legacy Test CorDapp" + versionId 2 + vendor "R3" + licence "Open Source (Apache 2)" + } + workflow { + name "Legacy Test CorDapp" + versionId 2 + vendor "R3" + licence "Open Source (Apache 2)" + } +} diff --git a/legacy412/src/main/java/net/corda/legacy/contracts/AnotherDummyContract.java b/legacy412/src/main/java/net/corda/legacy/contracts/AnotherDummyContract.java new file mode 100644 index 0000000000..b98418946d --- /dev/null +++ b/legacy412/src/main/java/net/corda/legacy/contracts/AnotherDummyContract.java @@ -0,0 +1,116 @@ +package net.corda.legacy.contracts; + +import com.fasterxml.jackson.core.JsonProcessingException; +import kotlin.collections.CollectionsKt; +import kotlin.jvm.internal.Intrinsics; +import net.corda.core.contracts.Command; +import net.corda.core.contracts.CommandData; +import net.corda.core.contracts.Contract; +import net.corda.core.contracts.ContractState; +import net.corda.core.contracts.PartyAndReference; +import net.corda.core.contracts.StateAndContract; +import net.corda.core.contracts.TypeOnlyCommandData; +import net.corda.core.identity.AbstractParty; +import net.corda.core.identity.Party; +import net.corda.core.transactions.LedgerTransaction; +import net.corda.core.transactions.TransactionBuilder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public final class AnotherDummyContract implements Contract { + @NotNull + private final String magicString = "helloworld"; + @NotNull + public static final String ANOTHER_DUMMY_PROGRAM_ID = "net.corda.legacy.contracts.AnotherDummyContract"; + + @NotNull + public final String getMagicString() { + return this.magicString; + } + + public void verify(@NotNull LedgerTransaction tx) { + Intrinsics.checkNotNullParameter(tx, "tx"); + } + + public void randomMethod() throws JsonProcessingException { + throw new JsonProcessingException("") { + }; + } + + @NotNull + public final TransactionBuilder generateInitial(@NotNull PartyAndReference owner, int magicNumber, @NotNull Party notary) { + Intrinsics.checkNotNullParameter(owner, "owner"); + Intrinsics.checkNotNullParameter(notary, "notary"); + State state = new State(magicNumber); + TransactionBuilder var10000 = new TransactionBuilder(notary); + Object[] var5 = new Object[]{new StateAndContract((ContractState) state, ANOTHER_DUMMY_PROGRAM_ID), new Command(new Commands.Create(), owner.getParty().getOwningKey())}; + return var10000.withItems(var5); + } + + public final int inspectState(@NotNull ContractState state) { + Intrinsics.checkNotNullParameter(state, "state"); + return ((State) state).getMagicNumber(); + } + + public interface Commands extends CommandData { + public static final class Create extends TypeOnlyCommandData implements Commands { + } + } + + public static final class State implements ContractState { + private final int magicNumber; + + public State(int magicNumber) { + this.magicNumber = magicNumber; + } + + public final int getMagicNumber() { + return this.magicNumber; + } + + @NotNull + public List getParticipants() { + return CollectionsKt.emptyList(); + } + + public final int component1() { + return this.magicNumber; + } + + @NotNull + public final State copy(int magicNumber) { + return new State(magicNumber); + } + + // $FF: synthetic method + public static State copy$default(State var0, int var1, int var2, Object var3) { + if ((var2 & 1) != 0) { + var1 = var0.magicNumber; + } + + return var0.copy(var1); + } + + @NotNull + public String toString() { + return "State(magicNumber=" + this.magicNumber + ')'; + } + + public int hashCode() { + return Integer.hashCode(this.magicNumber); + } + + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } else if (!(other instanceof State)) { + return false; + } else { + State var2 = (State) other; + return this.magicNumber == var2.magicNumber; + } + } + } +} diff --git a/legacy412/src/main/java/net/corda/legacy/workflows/LegacyIssuanceFlow.java b/legacy412/src/main/java/net/corda/legacy/workflows/LegacyIssuanceFlow.java new file mode 100644 index 0000000000..2673643e5f --- /dev/null +++ b/legacy412/src/main/java/net/corda/legacy/workflows/LegacyIssuanceFlow.java @@ -0,0 +1,54 @@ +package net.corda.legacy.workflows; + +import co.paralleluniverse.fibers.Suspendable; +import kotlin.collections.CollectionsKt; +import kotlin.jvm.internal.Intrinsics; +import net.corda.core.contracts.AttachmentResolutionException; +import net.corda.core.contracts.StateRef; +import net.corda.core.contracts.TransactionResolutionException; +import net.corda.core.contracts.TransactionVerificationException; +import net.corda.core.flows.FlowLogic; +import net.corda.core.flows.StartableByRPC; +import net.corda.core.identity.Party; +import net.corda.core.node.ServiceHub; +import net.corda.core.transactions.SignedTransaction; +import net.corda.core.transactions.TransactionBuilder; +import net.corda.legacy.contracts.AnotherDummyContract; +import org.jetbrains.annotations.NotNull; + +import java.security.SignatureException; + +@StartableByRPC +public final class LegacyIssuanceFlow extends FlowLogic { + private final int magicNumber; + + public LegacyIssuanceFlow(int magicNumber) { + this.magicNumber = magicNumber; + } + + @Suspendable + @NotNull + public StateRef call() { + ServiceHub var10000 = this.getServiceHub(); + AnotherDummyContract var10001 = new AnotherDummyContract(); + Party var10002 = this.getOurIdentity(); + byte[] var3 = new byte[]{0}; + TransactionBuilder var2 = var10001.generateInitial(var10002.ref(var3), this.magicNumber, (Party) CollectionsKt.first(this.getServiceHub().getNetworkMapCache().getNotaryIdentities())); + Intrinsics.checkNotNullExpressionValue(var2, "generateInitial(...)"); + SignedTransaction stx = var10000.signInitialTransaction(var2); + try { + stx.verify(this.getServiceHub(), false); + } catch (SignatureException e) { + throw new RuntimeException(e); + } catch (AttachmentResolutionException e) { + throw new RuntimeException(e); + } catch (TransactionResolutionException e) { + throw new RuntimeException(e); + } catch (TransactionVerificationException e) { + throw new RuntimeException(e); + } + //SignedTransaction.verify$default(stx, this.getServiceHub(), false, 2, (Object)null); + this.getServiceHub().recordTransactions(stx, new SignedTransaction[0]); + return stx.getTx().outRef(0).getRef(); + } +} diff --git a/node/src/main/kotlin/net/corda/node/verification/ExternalVerifierHandleImpl.kt b/node/src/main/kotlin/net/corda/node/verification/ExternalVerifierHandleImpl.kt index 211eda6d2d..f66ff00ca1 100644 --- a/node/src/main/kotlin/net/corda/node/verification/ExternalVerifierHandleImpl.kt +++ b/node/src/main/kotlin/net/corda/node/verification/ExternalVerifierHandleImpl.kt @@ -36,6 +36,7 @@ import net.corda.serialization.internal.verifier.ExternalVerifierOutbound.Verifi import net.corda.serialization.internal.verifier.ExternalVerifierOutbound.VerifierRequest.GetTrustedClassAttachments import net.corda.serialization.internal.verifier.readCordaSerializable import net.corda.serialization.internal.verifier.writeCordaSerializable +import java.io.File import java.io.IOException import java.lang.Character.MAX_RADIX import java.lang.ProcessBuilder.Redirect @@ -206,9 +207,18 @@ class ExternalVerifierHandleImpl( val command = ArrayList() command += "${Path(System.getProperty("java.home"), "bin", "java")}" command += inheritedJvmArgs + + // Build list of 3rd party jars + val legacyJarsPath = baseDirectory / "legacy-jars" + val extraClassPath = legacyJarsPath.toFile().listFiles { _, name -> + name.endsWith(".jar") + }?.joinToString(File.pathSeparator, File.pathSeparator) + val classpath = if (extraClassPath == null) "$verifierJar" else "$verifierJar$extraClassPath" + command += listOf( - "-jar", - "$verifierJar", + "-cp", + classpath, + "net.corda.verifier.Main", socketFile.absolutePathString(), log.level.name.lowercase() ) diff --git a/settings.gradle b/settings.gradle index 6193fd6ef2..a3ea3c83a6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -44,6 +44,8 @@ include 'finance:workflows' include 'core' include 'core-1.2' include 'core-tests' +include 'legacy411' +include 'legacy412' include 'docs' include 'node-api' include 'node-api-tests'