Skip to content

Commit f30ba33

Browse files
committed
ENT-11255: Scan attachments to determine if they are Kotlin 1.2 or later
The node now sends a transaction to the verifier if any of its attachments were compiled with Kotlin 1.2 (the net.corda.node.verification.external system property has been removed). It uses kotlinx-metadata to read the Kotlin metadata in the attachment to determine this. For now this scanning is done each time the attachment is loaded from the database. The existing external verification integration tests were converted into smoke tests so that 4.11 nodes could be involved. This required various improvements to NodeProcess.Factory. A new JAVA_8_HOME environment variable, pointing to JDK 8, is required to run these tests. There is still some follow-up work that needs to be done: Sending transactions from a 4.11 node to a 4.12 node works, but not the other way round. A new WireTransaction component group needs to be introduced for storing 4.12 attachments so that they can be safely ignored by 4.11 nodes, and the 4.12 node needs to be able to load both 4.11 and 4.12 versions of the same contracts CorDapp so that they can be both attached to the transaction. Even though attachments are cached when retrieved from the database, the Kotlin metadata version should be stored in the attachments db table, rather than being scanned each time. Finally, VerificationService was refactored into NodeVerificationSupport and can be passed into SignedTransaction.verifyInternal, instead of needing the much heavier VerifyingServiceHub. This makes it easier for internal tools to verify transactions and spawn the verifier if necessary.
1 parent 1ff853b commit f30ba33

File tree

37 files changed

+829
-696
lines changed

37 files changed

+829
-696
lines changed

.ci/dev/nightly-regression/Jenkinsfile

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ pipeline {
4646
CORDA_ARTIFACTORY_USERNAME = "${env.ARTIFACTORY_CREDENTIALS_USR}"
4747
CORDA_USE_CACHE = "corda-remotes"
4848
JAVA_HOME = "/usr/lib/jvm/java-17-amazon-corretto"
49+
JAVA_8_HOME = "/usr/lib/jvm/java-1.8.0-amazon-corretto"
4950
}
5051

5152
stages {

.ci/dev/regression/Jenkinsfile

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ pipeline {
7171
SNYK_TOKEN = credentials('c4-os-snyk-api-token-secret') //Jenkins credential type: Secret text
7272
C4_OS_SNYK_ORG_ID = credentials('corda4-os-snyk-org-id')
7373
JAVA_HOME = "/usr/lib/jvm/java-17-amazon-corretto"
74+
JAVA_8_HOME = "/usr/lib/jvm/java-1.8.0-amazon-corretto"
7475
}
7576

7677
stages {

Jenkinsfile

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ pipeline {
5454
CORDA_GRADLE_SCAN_KEY = credentials('gradle-build-scans-key')
5555
CORDA_USE_CACHE = "corda-remotes"
5656
JAVA_HOME="/usr/lib/jvm/java-17-amazon-corretto"
57+
JAVA_8_HOME = "/usr/lib/jvm/java-1.8.0-amazon-corretto"
5758
}
5859

5960
stages {

client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java

+17-58
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package net.corda.java.rpc;
22

3-
import net.corda.client.rpc.CordaRPCConnection;
43
import net.corda.core.contracts.Amount;
54
import net.corda.core.identity.CordaX500Name;
65
import net.corda.core.identity.Party;
@@ -10,88 +9,48 @@
109
import net.corda.finance.flows.AbstractCashFlow;
1110
import net.corda.finance.flows.CashIssueFlow;
1211
import net.corda.nodeapi.internal.config.User;
13-
import net.corda.smoketesting.NodeConfig;
12+
import net.corda.smoketesting.NodeParams;
1413
import net.corda.smoketesting.NodeProcess;
1514
import org.junit.After;
1615
import org.junit.Before;
1716
import org.junit.Test;
1817

19-
import java.io.IOException;
20-
import java.nio.file.Files;
21-
import java.nio.file.Path;
22-
import java.nio.file.Paths;
23-
import java.util.*;
18+
import java.util.Currency;
19+
import java.util.HashSet;
2420
import java.util.concurrent.ExecutionException;
2521
import java.util.concurrent.atomic.AtomicInteger;
26-
import java.util.stream.Stream;
2722

2823
import static java.util.Collections.singletonList;
2924
import static kotlin.test.AssertionsKt.assertEquals;
30-
import static kotlin.test.AssertionsKt.fail;
3125
import static net.corda.finance.workflows.GetBalances.getCashBalance;
26+
import static net.corda.kotlin.rpc.StandaloneCordaRPClientTest.gatherCordapps;
3227

3328
public class StandaloneCordaRPCJavaClientTest {
29+
private final User superUser = new User("superUser", "test", new HashSet<>(singletonList("ALL")));
3430

35-
public static void copyCordapps(NodeProcess.Factory factory, NodeConfig notaryConfig) {
36-
Path cordappsDir = (factory.baseDirectory(notaryConfig).resolve(NodeProcess.CORDAPPS_DIR_NAME));
37-
try {
38-
Files.createDirectories(cordappsDir);
39-
} catch (IOException ex) {
40-
fail("Failed to create directories");
41-
}
42-
try (Stream<Path> paths = Files.walk(Paths.get("build", "resources", "smokeTest"))) {
43-
paths.filter(path -> path.toFile().getName().startsWith("cordapp")).forEach(file -> {
44-
try {
45-
Files.copy(file, cordappsDir.resolve(file.getFileName()));
46-
} catch (IOException ex) {
47-
fail("Failed to copy cordapp jar");
48-
}
49-
});
50-
} catch (IOException e) {
51-
fail("Failed to walk files");
52-
}
53-
}
54-
55-
private List<String> perms = singletonList("ALL");
56-
private Set<String> permSet = new HashSet<>(perms);
57-
private User superUser = new User("superUser", "test", permSet);
31+
private final AtomicInteger port = new AtomicInteger(15000);
32+
private final NodeProcess.Factory factory = new NodeProcess.Factory();
5833

59-
private AtomicInteger port = new AtomicInteger(15000);
60-
61-
private NodeProcess notary;
6234
private CordaRPCOps rpcProxy;
63-
private CordaRPCConnection connection;
6435
private Party notaryNodeIdentity;
6536

66-
private NodeConfig notaryConfig = new NodeConfig(
67-
new CordaX500Name("Notary Service", "Zurich", "CH"),
68-
port.getAndIncrement(),
69-
port.getAndIncrement(),
70-
port.getAndIncrement(),
71-
true,
72-
singletonList(superUser),
73-
true
74-
);
75-
7637
@Before
7738
public void setUp() {
78-
NodeProcess.Factory factory = new NodeProcess.Factory();
79-
copyCordapps(factory, notaryConfig);
80-
notary = factory.create(notaryConfig);
81-
connection = notary.connect(superUser);
82-
rpcProxy = connection.getProxy();
39+
NodeProcess notary = factory.createNotaries(new NodeParams(
40+
new CordaX500Name("Notary Service", "Zurich", "CH"),
41+
port.getAndIncrement(),
42+
port.getAndIncrement(),
43+
port.getAndIncrement(),
44+
singletonList(superUser),
45+
gatherCordapps()
46+
)).get(0);
47+
rpcProxy = notary.connect(superUser).getProxy();
8348
notaryNodeIdentity = rpcProxy.nodeInfo().getLegalIdentities().get(0);
8449
}
8550

8651
@After
8752
public void done() {
88-
try {
89-
connection.close();
90-
} finally {
91-
if (notary != null) {
92-
notary.close();
93-
}
94-
}
53+
factory.close();
9554
}
9655

9756
@Test

client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt

+56-40
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import net.corda.core.utilities.getOrThrow
2727
import net.corda.core.utilities.minutes
2828
import net.corda.core.utilities.seconds
2929
import net.corda.finance.DOLLARS
30+
import net.corda.finance.GBP
3031
import net.corda.finance.POUNDS
3132
import net.corda.finance.SWISS_FRANCS
3233
import net.corda.finance.USD
@@ -35,32 +36,35 @@ import net.corda.finance.flows.CashIssueFlow
3536
import net.corda.finance.flows.CashPaymentFlow
3637
import net.corda.finance.workflows.getCashBalance
3738
import net.corda.finance.workflows.getCashBalances
38-
import net.corda.java.rpc.StandaloneCordaRPCJavaClientTest
3939
import net.corda.nodeapi.internal.config.User
4040
import net.corda.sleeping.SleepingFlow
41-
import net.corda.smoketesting.NodeConfig
41+
import net.corda.smoketesting.NodeParams
4242
import net.corda.smoketesting.NodeProcess
4343
import org.hamcrest.text.MatchesPattern
4444
import org.junit.After
45+
import org.junit.AfterClass
4546
import org.junit.Before
47+
import org.junit.BeforeClass
4648
import org.junit.Ignore
4749
import org.junit.Rule
4850
import org.junit.Test
4951
import org.junit.rules.ExpectedException
5052
import java.io.FilterInputStream
5153
import java.io.InputStream
5254
import java.io.OutputStream.nullOutputStream
53-
import java.util.Currency
55+
import java.nio.file.Path
5456
import java.util.concurrent.CountDownLatch
5557
import java.util.concurrent.atomic.AtomicInteger
5658
import java.util.regex.Pattern
59+
import kotlin.io.path.Path
60+
import kotlin.io.path.listDirectoryEntries
5761
import kotlin.test.assertEquals
5862
import kotlin.test.assertFalse
5963
import kotlin.test.assertNotEquals
6064
import kotlin.test.assertTrue
6165

6266
class StandaloneCordaRPClientTest {
63-
private companion object {
67+
companion object {
6468
private val log = contextLogger()
6569
val superUser = User("superUser", "test", permissions = setOf("ALL"))
6670
val nonUser = User("nonUser", "test", permissions = emptySet())
@@ -69,46 +73,57 @@ class StandaloneCordaRPClientTest {
6973
val port = AtomicInteger(15200)
7074
const val ATTACHMENT_SIZE = 2116
7175
val timeout = 60.seconds
76+
77+
private val factory = NodeProcess.Factory()
78+
79+
private lateinit var notary: NodeProcess
80+
81+
private val notaryConfig = NodeParams(
82+
legalName = CordaX500Name(organisation = "Notary Service", locality = "Zurich", country = "CH"),
83+
p2pPort = port.andIncrement,
84+
rpcPort = port.andIncrement,
85+
rpcAdminPort = port.andIncrement,
86+
users = listOf(superUser, nonUser, rpcUser, flowUser),
87+
cordappJars = gatherCordapps()
88+
)
89+
90+
@BeforeClass
91+
@JvmStatic
92+
fun startNotary() {
93+
notary = factory.createNotaries(notaryConfig)[0]
94+
}
95+
96+
@AfterClass
97+
@JvmStatic
98+
fun close() {
99+
factory.close()
100+
}
101+
102+
@JvmStatic
103+
fun gatherCordapps(): List<Path> {
104+
return Path("build", "resources", "smokeTest").listDirectoryEntries("cordapp*.jar")
105+
}
72106
}
73107

74-
private lateinit var factory: NodeProcess.Factory
75-
private lateinit var notary: NodeProcess
76-
private lateinit var rpcProxy: CordaRPCOps
77108
private lateinit var connection: CordaRPCConnection
78-
private lateinit var notaryNode: NodeInfo
109+
private lateinit var rpcProxy: CordaRPCOps
79110
private lateinit var notaryNodeIdentity: Party
80111

81-
private val notaryConfig = NodeConfig(
82-
legalName = CordaX500Name(organisation = "Notary Service", locality = "Zurich", country = "CH"),
83-
p2pPort = port.andIncrement,
84-
rpcPort = port.andIncrement,
85-
rpcAdminPort = port.andIncrement,
86-
isNotary = true,
87-
users = listOf(superUser, nonUser, rpcUser, flowUser)
88-
)
89-
90112
@get:Rule
91113
val exception: ExpectedException = ExpectedException.none()
92114

93115
@Before
94116
fun setUp() {
95-
factory = NodeProcess.Factory()
96-
StandaloneCordaRPCJavaClientTest.copyCordapps(factory, notaryConfig)
97-
notary = factory.create(notaryConfig)
98117
connection = notary.connect(superUser)
99118
rpcProxy = connection.proxy
100-
notaryNode = fetchNotaryIdentity()
101119
notaryNodeIdentity = rpcProxy.nodeInfo().legalIdentitiesAndCerts.first().party
102120
}
103121

104122
@After
105-
fun done() {
106-
connection.use {
107-
notary.close()
108-
}
123+
fun closeConnection() {
124+
connection.close()
109125
}
110126

111-
112127
@Test(timeout=300_000)
113128
fun `test attachments`() {
114129
val attachment = InputStreamAndHash.createInMemoryTestZip(ATTACHMENT_SIZE, 1)
@@ -168,8 +183,7 @@ class StandaloneCordaRPClientTest {
168183

169184
@Test(timeout=300_000)
170185
fun `test state machines`() {
171-
val (stateMachines, updates) = rpcProxy.stateMachinesFeed()
172-
assertEquals(0, stateMachines.size)
186+
val (_, updates) = rpcProxy.stateMachinesFeed()
173187

174188
val updateLatch = CountDownLatch(1)
175189
val updateCount = AtomicInteger(0)
@@ -190,8 +204,9 @@ class StandaloneCordaRPClientTest {
190204

191205
@Test(timeout=300_000)
192206
fun `test vault track by`() {
193-
val (vault, vaultUpdates) = rpcProxy.vaultTrackBy<Cash.State>(paging = PageSpecification(DEFAULT_PAGE_NUM))
194-
assertEquals(0, vault.totalStatesAvailable)
207+
val initialGbpBalance = rpcProxy.getCashBalance(GBP)
208+
209+
val (_, vaultUpdates) = rpcProxy.vaultTrackBy<Cash.State>(paging = PageSpecification(DEFAULT_PAGE_NUM))
195210

196211
val updateLatch = CountDownLatch(1)
197212
vaultUpdates.subscribe { update ->
@@ -207,34 +222,35 @@ class StandaloneCordaRPClientTest {
207222
// Check that this cash exists in the vault
208223
val cashBalance = rpcProxy.getCashBalances()
209224
log.info("Cash Balances: $cashBalance")
210-
assertEquals(1, cashBalance.size)
211-
assertEquals(629.POUNDS, cashBalance[Currency.getInstance("GBP")])
225+
assertEquals(629.POUNDS, cashBalance[GBP]!! - initialGbpBalance)
212226
}
213227

214228
@Test(timeout=300_000)
215229
fun `test vault query by`() {
216-
// Now issue some cash
217-
rpcProxy.startFlow(::CashIssueFlow, 629.POUNDS, OpaqueBytes.of(0), notaryNodeIdentity)
218-
.returnValue.getOrThrow(timeout)
219-
220230
val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL)
221231
val paging = PageSpecification(DEFAULT_PAGE_NUM, 10)
222232
val sorting = Sort(setOf(Sort.SortColumn(SortAttribute.Standard(Sort.VaultStateAttribute.RECORDED_TIME), Sort.Direction.DESC)))
223233

234+
val initialStateCount = rpcProxy.vaultQueryBy<Cash.State>(criteria, paging, sorting).totalStatesAvailable
235+
val initialGbpBalance = rpcProxy.getCashBalance(GBP)
236+
237+
// Now issue some cash
238+
rpcProxy.startFlow(::CashIssueFlow, 629.POUNDS, OpaqueBytes.of(0), notaryNodeIdentity)
239+
.returnValue.getOrThrow(timeout)
240+
224241
val queryResults = rpcProxy.vaultQueryBy<Cash.State>(criteria, paging, sorting)
225-
assertEquals(1, queryResults.totalStatesAvailable)
242+
assertEquals(1, queryResults.totalStatesAvailable - initialStateCount)
226243
assertEquals(queryResults.states.first().state.data.amount.quantity, 629.POUNDS.quantity)
227244

228245
rpcProxy.startFlow(::CashPaymentFlow, 100.POUNDS, notaryNodeIdentity, true, notaryNodeIdentity).returnValue.getOrThrow()
229246

230247
val moreResults = rpcProxy.vaultQueryBy<Cash.State>(criteria, paging, sorting)
231-
assertEquals(3, moreResults.totalStatesAvailable) // 629 - 100 + 100
248+
assertEquals(3, moreResults.totalStatesAvailable - initialStateCount) // 629 - 100 + 100
232249

233250
// Check that this cash exists in the vault
234251
val cashBalances = rpcProxy.getCashBalances()
235252
log.info("Cash Balances: $cashBalances")
236-
assertEquals(1, cashBalances.size)
237-
assertEquals(629.POUNDS, cashBalances[Currency.getInstance("GBP")])
253+
assertEquals(629.POUNDS, cashBalances[GBP]!! - initialGbpBalance)
238254
}
239255

240256
@Test(timeout=300_000)

0 commit comments

Comments
 (0)