diff --git a/.ci/api-current.txt b/.ci/api-current.txt
index a2703c32b2..363b258082 100644
--- a/.ci/api-current.txt
+++ b/.ci/api-current.txt
@@ -4735,8 +4735,6 @@ public interface net.corda.core.node.ServiceHub extends net.corda.core.node.Serv
@NotNull
public abstract net.corda.core.node.services.TelemetryService getTelemetryService()
@NotNull
- public abstract net.corda.core.node.services.TransactionVerifierService getTransactionVerifierService()
- @NotNull
public abstract net.corda.core.node.services.TransactionStorage getValidatedTransactions()
@NotNull
public abstract net.corda.core.node.services.VaultService getVaultService()
@@ -5087,11 +5085,6 @@ public interface net.corda.core.node.services.TransactionStorage
@NotNull
public abstract net.corda.core.concurrent.CordaFuture trackTransaction(net.corda.core.crypto.SecureHash)
##
-@DoNotImplement
-public interface net.corda.core.node.services.TransactionVerifierService
- @NotNull
- public abstract net.corda.core.concurrent.CordaFuture verify(net.corda.core.transactions.LedgerTransaction)
-##
@CordaSerializable
public final class net.corda.core.node.services.UnknownAnonymousPartyException extends net.corda.core.CordaException
public (String)
@@ -7846,8 +7839,6 @@ public class net.corda.core.transactions.TransactionBuilder extends java.lang.Ob
@NotNull
public final net.corda.core.transactions.LedgerTransaction toLedgerTransaction(net.corda.core.node.ServiceHub)
@NotNull
- public final net.corda.core.transactions.LedgerTransaction toLedgerTransactionWithContext(net.corda.core.node.ServicesForResolution, net.corda.core.serialization.SerializationContext)
- @NotNull
public final net.corda.core.transactions.SignedTransaction toSignedTransaction(net.corda.core.node.services.KeyManagementService, java.security.PublicKey, net.corda.core.crypto.SignatureMetadata, net.corda.core.node.ServicesForResolution)
@NotNull
public final net.corda.core.transactions.WireTransaction toWireTransaction(net.corda.core.node.ServicesForResolution)
@@ -9890,8 +9881,6 @@ public class net.corda.testing.node.MockServices extends java.lang.Object implem
@NotNull
public net.corda.core.internal.telemetry.TelemetryServiceImpl getTelemetryService()
@NotNull
- public net.corda.core.node.services.TransactionVerifierService getTransactionVerifierService()
- @NotNull
public net.corda.core.node.services.TransactionStorage getValidatedTransactions()
@NotNull
public net.corda.core.node.services.VaultService getVaultService()
diff --git a/build.gradle b/build.gradle
index 7bdd1871cb..c239bed37a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -215,7 +215,7 @@ plugins {
id 'org.jetbrains.kotlin.jvm' apply false
id 'org.jetbrains.kotlin.plugin.allopen' apply false
id 'org.jetbrains.kotlin.plugin.jpa' apply false
- id 'com.github.johnrengelman.shadow' version '2.0.4' apply false
+ id 'com.github.johnrengelman.shadow' version '7.1.2' apply false
id "org.ajoberstar.grgit" version "4.0.0"
id 'corda.root-publish'
id "org.jetbrains.dokka" version "1.8.20"
diff --git a/client/jackson/build.gradle b/client/jackson/build.gradle
index eb4cca280e..47b9b51ac3 100644
--- a/client/jackson/build.gradle
+++ b/client/jackson/build.gradle
@@ -4,7 +4,8 @@ apply plugin: 'net.corda.plugins.api-scanner'
apply plugin: 'corda.common-publishing'
dependencies {
- implementation project(':core')
+ api project(':core')
+
implementation project(':serialization')
// Jackson and its plugins: parsing to/from JSON and other textual formats.
@@ -27,6 +28,7 @@ dependencies {
testImplementation project(':test-common')
testImplementation project(':core-test-utils')
testImplementation project(':test-utils')
+ testImplementation project(":node-driver")
testImplementation project(path: ':core', configuration: 'testArtifacts')
testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
diff --git a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt
index f753375b5a..fddafc6547 100644
--- a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt
+++ b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt
@@ -8,27 +8,36 @@ import com.fasterxml.jackson.databind.node.ObjectNode
import com.fasterxml.jackson.databind.node.TextNode
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.convertValue
-import org.mockito.kotlin.any
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
-import org.mockito.kotlin.spy
import net.corda.client.jackson.internal.childrenAs
import net.corda.client.jackson.internal.valueAs
-import net.corda.core.contracts.*
+import net.corda.core.contracts.Amount
+import net.corda.core.contracts.Command
+import net.corda.core.contracts.LinearState
+import net.corda.core.contracts.PrivacySalt
+import net.corda.core.contracts.StateRef
+import net.corda.core.contracts.TimeWindow
+import net.corda.core.contracts.TransactionState
+import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.cordapp.CordappProvider
-import net.corda.core.crypto.*
import net.corda.core.crypto.CompositeKey
+import net.corda.core.crypto.Crypto
+import net.corda.core.crypto.DigestService
+import net.corda.core.crypto.DigitalSignature
+import net.corda.core.crypto.PartialMerkleTree
import net.corda.core.crypto.PartialMerkleTree.PartialTree
-import net.corda.core.identity.*
-import net.corda.core.internal.AbstractAttachment
+import net.corda.core.crypto.SecureHash
+import net.corda.core.crypto.SignatureMetadata
+import net.corda.core.crypto.SignatureScheme
+import net.corda.core.crypto.TransactionSignature
+import net.corda.core.crypto.secureRandomBytes
+import net.corda.core.identity.AbstractParty
+import net.corda.core.identity.AnonymousParty
+import net.corda.core.identity.CordaX500Name
+import net.corda.core.identity.Party
+import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub
-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.TransactionStorage
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
@@ -37,14 +46,27 @@ import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction
-import net.corda.core.utilities.*
+import net.corda.core.utilities.ByteSequence
+import net.corda.core.utilities.NetworkHostAndPort
+import net.corda.core.utilities.OpaqueBytes
+import net.corda.core.utilities.days
+import net.corda.core.utilities.hours
+import net.corda.core.utilities.toBase58String
+import net.corda.core.utilities.toBase64
+import net.corda.core.utilities.toHexString
+import net.corda.coretesting.internal.createNodeInfoAndSigned
+import net.corda.coretesting.internal.rigorousMock
import net.corda.finance.USD
import net.corda.nodeapi.internal.crypto.x509Certificates
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract
-import net.corda.testing.core.*
-import net.corda.coretesting.internal.createNodeInfoAndSigned
-import net.corda.coretesting.internal.rigorousMock
+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.DummyCommandData
+import net.corda.testing.core.SerializationEnvironmentRule
+import net.corda.testing.core.TestIdentity
+import net.corda.testing.node.MockServices
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Before
@@ -54,15 +76,22 @@ import org.junit.jupiter.api.TestFactory
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
import java.math.BigInteger
import java.nio.charset.StandardCharsets.UTF_8
import java.security.PublicKey
import java.security.cert.CertPath
import java.security.cert.X509Certificate
import java.time.Instant
-import java.util.*
+import java.util.Currency
+import java.util.Date
+import java.util.UUID
import javax.security.auth.x500.X500Principal
-import kotlin.collections.ArrayList
+import kotlin.collections.component1
+import kotlin.collections.component2
+import kotlin.collections.component3
+import kotlin.collections.component4
@RunWith(Parameterized::class)
class JacksonSupportTest(@Suppress("unused") private val name: String, factory: JsonFactory) {
@@ -90,23 +119,12 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
@Before
fun setup() {
- val unsignedAttachment = object : AbstractAttachment({ byteArrayOf() }, "test") {
- override val id: SecureHash get() = throw UnsupportedOperationException()
- }
-
- val attachments = rigorousMock().also {
- doReturn(unsignedAttachment).whenever(it).openAttachment(any())
- }
- services = rigorousMock()
+ services = MockServices(
+ listOf("net.corda.testing.contracts"),
+ MINI_CORP,
+ testNetworkParameters(minimumPlatformVersion = 4)
+ )
cordappProvider = rigorousMock()
- val networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
- val networkParametersService = rigorousMock().also {
- doReturn(networkParameters.serialize().hash).whenever(it).currentHash
- }
- doReturn(networkParametersService).whenever(services).networkParametersService
- doReturn(cordappProvider).whenever(services).cordappProvider
- doReturn(networkParameters).whenever(services).networkParameters
- doReturn(attachments).whenever(services).attachments
}
@Test(timeout=300_000)
@@ -263,17 +281,6 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
@Test(timeout=300_000)
fun `SignedTransaction (WireTransaction)`() {
val attachmentId = SecureHash.randomSHA256()
- doReturn(attachmentId).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID)
- val attachmentStorage = rigorousMock()
- doReturn(attachmentStorage).whenever(services).attachments
- doReturn(mock()).whenever(services).validatedTransactions
- doReturn(mock()).whenever(services).identityService
- val attachment = rigorousMock()
- doReturn(attachment).whenever(attachmentStorage).openAttachment(attachmentId)
- doReturn(attachmentId).whenever(attachment).id
- doReturn(emptyList()).whenever(attachment).signerKeys
- doReturn(setOf(DummyContract.PROGRAM_ID)).whenever(attachment).allContracts
- doReturn("app").whenever(attachment).uploader
val wtx = TransactionBuilder(
notary = DUMMY_NOTARY,
diff --git a/core-tests/src/test/kotlin/net/corda/coretests/flows/ContractUpgradeFlowRPCTest.kt b/core-tests/src/test/kotlin/net/corda/coretests/flows/ContractUpgradeFlowRPCTest.kt
index 01e9e81df7..70e0996a9d 100644
--- a/core-tests/src/test/kotlin/net/corda/coretests/flows/ContractUpgradeFlowRPCTest.kt
+++ b/core-tests/src/test/kotlin/net/corda/coretests/flows/ContractUpgradeFlowRPCTest.kt
@@ -9,6 +9,7 @@ import net.corda.core.CordaRuntimeException
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
import net.corda.core.flows.ContractUpgradeFlow
+import net.corda.core.internal.getRequiredTransaction
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.transactions.ContractUpgradeLedgerTransaction
import net.corda.core.transactions.SignedTransaction
@@ -120,8 +121,7 @@ class ContractUpgradeFlowRPCTest : WithContracts, WithFinality {
isUpgrade())
private fun TestStartedNode.getContractUpgradeTransaction(state: StateAndRef) =
- services.validatedTransactions.getTransaction(state.ref.txhash)!!
- .resolveContractUpgradeTransaction(services)
+ services.getRequiredTransaction(state.ref.txhash).resolveContractUpgradeTransaction(services)
private inline fun isUpgrade() =
isUpgradeFrom() and isUpgradeTo()
diff --git a/core-tests/src/test/kotlin/net/corda/coretests/flows/ContractUpgradeFlowTest.kt b/core-tests/src/test/kotlin/net/corda/coretests/flows/ContractUpgradeFlowTest.kt
index 1ea3dceb50..0f4dbb7214 100644
--- a/core-tests/src/test/kotlin/net/corda/coretests/flows/ContractUpgradeFlowTest.kt
+++ b/core-tests/src/test/kotlin/net/corda/coretests/flows/ContractUpgradeFlowTest.kt
@@ -1,32 +1,55 @@
package net.corda.coretests.flows
-import com.natpryce.hamkrest.*
+import com.natpryce.hamkrest.Matcher
+import com.natpryce.hamkrest.and
+import com.natpryce.hamkrest.anything
import com.natpryce.hamkrest.assertion.assertThat
-import net.corda.core.contracts.*
+import com.natpryce.hamkrest.equalTo
+import com.natpryce.hamkrest.has
+import com.natpryce.hamkrest.isA
+import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint
+import net.corda.core.contracts.Amount
+import net.corda.core.contracts.AttachmentConstraint
+import net.corda.core.contracts.BelongsToContract
+import net.corda.core.contracts.CommandAndState
+import net.corda.core.contracts.ContractState
+import net.corda.core.contracts.FungibleAsset
+import net.corda.core.contracts.Issued
+import net.corda.core.contracts.StateAndRef
+import net.corda.core.contracts.TypeOnlyCommandData
+import net.corda.core.contracts.UpgradedContractWithLegacyConstraint
import net.corda.core.flows.UnexpectedFlowEndException
import net.corda.core.identity.AbstractParty
import net.corda.core.internal.Emoji
+import net.corda.core.internal.getRequiredTransaction
+import net.corda.core.internal.mapToSet
import net.corda.core.transactions.ContractUpgradeLedgerTransaction
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
+import net.corda.coretesting.internal.matchers.flow.willReturn
+import net.corda.coretesting.internal.matchers.flow.willThrow
import net.corda.finance.USD
-import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.flows.CashIssueFlow
+import net.corda.finance.`issued by`
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyContractV2
import net.corda.testing.contracts.DummyContractV3
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity
-import net.corda.coretesting.internal.matchers.flow.willReturn
-import net.corda.coretesting.internal.matchers.flow.willThrow
-import net.corda.testing.node.internal.*
+import net.corda.testing.node.internal.DUMMY_CONTRACTS_CORDAPP
+import net.corda.testing.node.internal.FINANCE_CONTRACTS_CORDAPP
+import net.corda.testing.node.internal.FINANCE_WORKFLOWS_CORDAPP
+import net.corda.testing.node.internal.InternalMockNetwork
+import net.corda.testing.node.internal.TestStartedNode
+import net.corda.testing.node.internal.enclosedCordapp
+import net.corda.testing.node.internal.startFlow
import org.junit.AfterClass
import org.junit.Ignore
import org.junit.Test
-import java.util.*
+import java.util.Currency
@Ignore("TODO JDK17: class cast exception")
class ContractUpgradeFlowTest : WithContracts, WithFinality {
@@ -161,7 +184,7 @@ class ContractUpgradeFlowTest : WithContracts, WithFinality {
@BelongsToContract(CashV2::class)
data class State(override val amount: Amount>, val owners: List) : FungibleAsset {
override val owner: AbstractParty = owners.first()
- override val exitKeys = (owners + amount.token.issuer.party).map { it.owningKey }.toSet()
+ override val exitKeys = (owners + amount.token.issuer.party).mapToSet { it.owningKey }
override val participants = owners
override fun withNewOwnerAndAmount(newAmount: Amount>, newOwner: AbstractParty) = copy(amount = amount.copy(newAmount.quantity), owners = listOf(newOwner))
@@ -182,8 +205,7 @@ class ContractUpgradeFlowTest : WithContracts, WithFinality {
isUpgrade())
private fun TestStartedNode.getContractUpgradeTransaction(state: StateAndRef) =
- services.validatedTransactions.getTransaction(state.ref.txhash)!!
- .resolveContractUpgradeTransaction(services)
+ services.getRequiredTransaction(state.ref.txhash).resolveContractUpgradeTransaction(services)
private inline fun isUpgrade() =
isUpgradeFrom() and isUpgradeTo()
diff --git a/core-tests/src/test/kotlin/net/corda/coretests/flows/WithFinality.kt b/core-tests/src/test/kotlin/net/corda/coretests/flows/WithFinality.kt
index 714e8b85fa..127cc1ccf9 100644
--- a/core-tests/src/test/kotlin/net/corda/coretests/flows/WithFinality.kt
+++ b/core-tests/src/test/kotlin/net/corda/coretests/flows/WithFinality.kt
@@ -13,6 +13,7 @@ import net.corda.core.flows.ReceiveFinalityFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party
import net.corda.core.internal.FlowStateMachineHandle
+import net.corda.core.internal.getRequiredTransaction
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.FlowHandle
import net.corda.core.messaging.startFlow
@@ -26,9 +27,7 @@ interface WithFinality : WithMockNet {
return startFlowAndRunNetwork(FinalityInvoker(stx, recipients.toSet(), emptySet()))
}
- fun TestStartedNode.getValidatedTransaction(stx: SignedTransaction): SignedTransaction {
- return services.validatedTransactions.getTransaction(stx.id)!!
- }
+ fun TestStartedNode.getValidatedTransaction(stx: SignedTransaction): SignedTransaction = services.getRequiredTransaction(stx.id)
fun CordaRPCOps.finalise(stx: SignedTransaction, vararg recipients: Party): FlowHandle {
return startFlow(WithFinality::FinalityInvoker, stx, recipients.toSet(), emptySet()).andRunNetwork()
diff --git a/core-tests/src/test/kotlin/net/corda/coretests/internal/verification/AttachmentFixupsTest.kt b/core-tests/src/test/kotlin/net/corda/coretests/internal/verification/AttachmentFixupsTest.kt
new file mode 100644
index 0000000000..67dc930fe7
--- /dev/null
+++ b/core-tests/src/test/kotlin/net/corda/coretests/internal/verification/AttachmentFixupsTest.kt
@@ -0,0 +1,137 @@
+package net.corda.coretests.internal.verification
+
+import net.corda.core.internal.verification.AttachmentFixups
+import net.corda.core.node.services.AttachmentId
+import net.corda.node.VersionInfo
+import net.corda.node.internal.cordapp.JarScanningCordappLoader
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+import java.net.URL
+import java.nio.file.Files
+import java.nio.file.Path
+import java.util.jar.JarOutputStream
+import java.util.zip.Deflater
+import java.util.zip.ZipEntry
+import kotlin.io.path.outputStream
+import kotlin.test.assertFailsWith
+
+class AttachmentFixupsTest {
+ companion object {
+ @JvmField
+ val ID1 = AttachmentId.randomSHA256()
+ @JvmField
+ val ID2 = AttachmentId.randomSHA256()
+ @JvmField
+ val ID3 = AttachmentId.randomSHA256()
+ @JvmField
+ val ID4 = AttachmentId.randomSHA256()
+ }
+
+ @Test(timeout=300_000)
+ fun `test fixup rule that adds attachment`() {
+ val fixupJar = Files.createTempFile("fixup", ".jar")
+ .writeFixupRules("$ID1 => $ID2, $ID3")
+ val fixedIDs = with(newFixupService(fixupJar.toUri().toURL())) {
+ fixupAttachmentIds(listOf(ID1))
+ }
+ assertThat(fixedIDs).containsExactly(ID2, ID3)
+ }
+
+ @Test(timeout=300_000)
+ fun `test fixup rule that deletes attachment`() {
+ val fixupJar = Files.createTempFile("fixup", ".jar")
+ .writeFixupRules("$ID1 =>")
+ val fixedIDs = with(newFixupService(fixupJar.toUri().toURL())) {
+ fixupAttachmentIds(listOf(ID1))
+ }
+ assertThat(fixedIDs).isEmpty()
+ }
+
+ @Test(timeout=300_000)
+ fun `test fixup rule with blank LHS`() {
+ val fixupJar = Files.createTempFile("fixup", ".jar")
+ .writeFixupRules(" => $ID2")
+ val ex = assertFailsWith {
+ newFixupService(fixupJar.toUri().toURL())
+ }
+ assertThat(ex).hasMessageContaining(
+ "Forbidden empty list of source attachment IDs in '$fixupJar'"
+ )
+ }
+
+ @Test(timeout=300_000)
+ fun `test fixup rule without arrows`() {
+ val rule = " $ID1 "
+ val fixupJar = Files.createTempFile("fixup", ".jar")
+ .writeFixupRules(rule)
+ val ex = assertFailsWith {
+ newFixupService(fixupJar.toUri().toURL())
+ }
+ assertThat(ex).hasMessageContaining(
+ "Invalid fix-up line '${rule.trim()}' in '$fixupJar'"
+ )
+ }
+
+ @Test(timeout=300_000)
+ fun `test fixup rule with too many arrows`() {
+ val rule = " $ID1 => $ID2 => $ID3 "
+ val fixupJar = Files.createTempFile("fixup", ".jar")
+ .writeFixupRules(rule)
+ val ex = assertFailsWith {
+ newFixupService(fixupJar.toUri().toURL())
+ }
+ assertThat(ex).hasMessageContaining(
+ "Invalid fix-up line '${rule.trim()}' in '$fixupJar'"
+ )
+ }
+
+ @Test(timeout=300_000)
+ fun `test fixup file containing multiple rules and comments`() {
+ val fixupJar = Files.createTempFile("fixup", ".jar").writeFixupRules(
+ "# Whole line comment",
+ "\t$ID1,$ID2 => $ID2,, $ID3 # EOl comment",
+ " # Empty line with comment",
+ "",
+ "$ID3 => $ID4"
+ )
+ val fixedIDs = with(newFixupService(fixupJar.toUri().toURL())) {
+ fixupAttachmentIds(listOf(ID2, ID1))
+ }
+ assertThat(fixedIDs).containsExactlyInAnyOrder(ID2, ID4)
+ }
+
+ private fun Path.writeFixupRules(vararg lines: String): Path {
+ JarOutputStream(outputStream()).use { jar ->
+ jar.setMethod(ZipEntry.DEFLATED)
+ jar.setLevel(Deflater.NO_COMPRESSION)
+ jar.putNextEntry(directoryEntry("META-INF"))
+ jar.putNextEntry(fileEntry("META-INF/Corda-Fixups"))
+ for (line in lines) {
+ jar.write(line.toByteArray())
+ jar.write('\r'.code)
+ jar.write('\n'.code)
+ }
+ }
+ return this
+ }
+
+ private fun directoryEntry(internalName: String): ZipEntry {
+ return ZipEntry("$internalName/").apply {
+ method = ZipEntry.STORED
+ compressedSize = 0
+ size = 0
+ crc = 0
+ }
+ }
+
+ private fun fileEntry(internalName: String): ZipEntry {
+ return ZipEntry(internalName).apply {
+ method = ZipEntry.DEFLATED
+ }
+ }
+
+ private fun newFixupService(vararg urls: URL): AttachmentFixups {
+ val loader = JarScanningCordappLoader.fromJarUrls(urls.toList(), VersionInfo.UNKNOWN)
+ return AttachmentFixups().apply { load(loader.appClassLoader) }
+ }
+}
diff --git a/core-tests/src/test/kotlin/net/corda/coretests/transactions/TransactionBuilderTest.kt b/core-tests/src/test/kotlin/net/corda/coretests/transactions/TransactionBuilderTest.kt
index b48199ec47..f1887ed00c 100644
--- a/core-tests/src/test/kotlin/net/corda/coretests/transactions/TransactionBuilderTest.kt
+++ b/core-tests/src/test/kotlin/net/corda/coretests/transactions/TransactionBuilderTest.kt
@@ -1,7 +1,6 @@
package net.corda.coretests.transactions
import net.corda.core.contracts.Command
-import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.HashAttachmentConstraint
import net.corda.core.contracts.PrivacySalt
import net.corda.core.contracts.SignatureAttachmentConstraint
@@ -10,45 +9,33 @@ import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.contracts.TransactionState
import net.corda.core.contracts.TransactionVerificationException.UnsupportedHashTypeException
-import net.corda.core.cordapp.CordappProvider
-import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash
-import net.corda.core.identity.Party
-import net.corda.core.internal.AbstractAttachment
import net.corda.core.internal.HashAgility
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.digestService
-import net.corda.core.node.ServicesForResolution
import net.corda.core.node.ZoneVersionTooLowException
-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.serialization.serialize
+import net.corda.core.serialization.internal._driverSerializationEnv
import net.corda.core.transactions.TransactionBuilder
-import net.corda.coretesting.internal.rigorousMock
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyState
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.DummyCommandData
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
+import net.corda.testing.node.MockNetwork
+import net.corda.testing.node.MockNetworkParameters
+import net.corda.testing.node.MockServices
+import net.corda.testing.node.internal.cordappWithPackages
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.assertj.core.api.Assertions.assertThatThrownBy
-import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
-import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
-import java.security.PublicKey
import java.time.Instant
import kotlin.test.assertFailsWith
@@ -58,33 +45,12 @@ class TransactionBuilderTest {
val testSerialization = SerializationEnvironmentRule()
private val notary = TestIdentity(DUMMY_NOTARY_NAME).party
- private val services = rigorousMock()
- private val contractAttachmentId = SecureHash.randomSHA256()
- private val attachments = rigorousMock()
- private val networkParametersService = mock()
-
- @Before
- fun setup() {
- val cordappProvider = rigorousMock()
- val networkParameters = testNetworkParameters(minimumPlatformVersion = PLATFORM_VERSION)
- doReturn(networkParametersService).whenever(services).networkParametersService
- doReturn(networkParameters.serialize().hash).whenever(networkParametersService).currentHash
- doReturn(cordappProvider).whenever(services).cordappProvider
- doReturn(contractAttachmentId).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID)
- doReturn(networkParameters).whenever(services).networkParameters
- doReturn(mock()).whenever(services).identityService
-
- val attachmentStorage = rigorousMock()
- doReturn(attachmentStorage).whenever(services).attachments
- val attachment = rigorousMock()
- doReturn(attachment).whenever(attachmentStorage).openAttachment(contractAttachmentId)
- doReturn(contractAttachmentId).whenever(attachment).id
- doReturn(setOf(DummyContract.PROGRAM_ID)).whenever(attachment).allContracts
- doReturn("app").whenever(attachment).uploader
- doReturn(emptyList()).whenever(attachment).signerKeys
- doReturn(listOf(contractAttachmentId)).whenever(attachmentStorage)
- .getLatestContractAttachments("net.corda.testing.contracts.DummyContract")
- }
+ private val services = MockServices(
+ listOf("net.corda.testing.contracts"),
+ TestIdentity(ALICE_NAME),
+ testNetworkParameters(minimumPlatformVersion = PLATFORM_VERSION)
+ )
+ private val contractAttachmentId = services.attachments.getLatestContractAttachments(DummyContract.PROGRAM_ID)[0]
@Test(timeout=300_000)
fun `bare minimum issuance tx`() {
@@ -100,13 +66,11 @@ class TransactionBuilderTest {
val wtx = builder.toWireTransaction(services)
assertThat(wtx.outputs).containsOnly(outputState)
assertThat(wtx.commands).containsOnly(Command(DummyCommandData, notary.owningKey))
- assertThat(wtx.networkParametersHash).isEqualTo(networkParametersService.currentHash)
+ assertThat(wtx.networkParametersHash).isEqualTo(services.networkParametersService.currentHash)
}
@Test(timeout=300_000)
fun `automatic hash constraint`() {
- doReturn(unsignedAttachment).whenever(attachments).openAttachment(contractAttachmentId)
-
val outputState = TransactionState(data = DummyState(), contract = DummyContract.PROGRAM_ID, notary = notary)
val builder = TransactionBuilder()
.addOutputState(outputState)
@@ -117,8 +81,6 @@ class TransactionBuilderTest {
@Test(timeout=300_000)
fun `reference states`() {
- doReturn(unsignedAttachment).whenever(attachments).openAttachment(contractAttachmentId)
-
val referenceState = TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary)
val referenceStateRef = StateRef(SecureHash.randomSHA256(), 1)
val builder = TransactionBuilder(notary)
@@ -126,54 +88,55 @@ class TransactionBuilderTest {
.addOutputState(TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary))
.addCommand(DummyCommandData, notary.owningKey)
- doReturn(testNetworkParameters(minimumPlatformVersion = 3)).whenever(services).networkParameters
- assertThatThrownBy { builder.toWireTransaction(services) }
- .isInstanceOf(ZoneVersionTooLowException::class.java)
- .hasMessageContaining("Reference states")
+ with(testNetworkParameters(minimumPlatformVersion = 3)) {
+ val services = MockServices(listOf("net.corda.testing.contracts"), TestIdentity(ALICE_NAME), this)
+ assertThatThrownBy { builder.toWireTransaction(services) }
+ .isInstanceOf(ZoneVersionTooLowException::class.java)
+ .hasMessageContaining("Reference states")
+ }
- doReturn(testNetworkParameters(minimumPlatformVersion = 4)).whenever(services).networkParameters
- doReturn(referenceState).whenever(services).loadState(referenceStateRef)
- val wtx = builder.toWireTransaction(services)
- assertThat(wtx.references).containsOnly(referenceStateRef)
+ with(testNetworkParameters(minimumPlatformVersion = 4)) {
+ val services = MockServices(listOf("net.corda.testing.contracts"), TestIdentity(ALICE_NAME), this)
+ val wtx = builder.toWireTransaction(services)
+ assertThat(wtx.references).containsOnly(referenceStateRef)
+ }
}
@Test(timeout=300_000)
fun `automatic signature constraint`() {
- val aliceParty = TestIdentity(ALICE_NAME).party
- val bobParty = TestIdentity(BOB_NAME).party
- val compositeKey = CompositeKey.Builder().addKeys(aliceParty.owningKey, bobParty.owningKey).build()
- val expectedConstraint = SignatureAttachmentConstraint(compositeKey)
- val signedAttachment = signedAttachment(aliceParty, bobParty)
+ // We need to use a MockNetwork so that we can create a signed attachment. However, SerializationEnvironmentRule and MockNetwork
+ // don't work well together, so we temporarily clear out the driverSerializationEnv for this test.
+ val driverSerializationEnv = _driverSerializationEnv.get()
+ _driverSerializationEnv.set(null)
+ val mockNetwork = MockNetwork(
+ MockNetworkParameters(
+ networkParameters = testNetworkParameters(minimumPlatformVersion = PLATFORM_VERSION),
+ cordappsForAllNodes = listOf(cordappWithPackages("net.corda.testing.contracts").signed())
+ )
+ )
- assertTrue(expectedConstraint.isSatisfiedBy(signedAttachment))
- assertFalse(expectedConstraint.isSatisfiedBy(unsignedAttachment))
+ try {
+ val services = mockNetwork.notaryNodes[0].services
- doReturn(attachments).whenever(services).attachments
- doReturn(signedAttachment).whenever(attachments).openAttachment(contractAttachmentId)
- doReturn(listOf(contractAttachmentId)).whenever(attachments)
- .getLatestContractAttachments("net.corda.testing.contracts.DummyContract")
+ val attachment = services.attachments.openAttachment(services.attachments.getLatestContractAttachments(DummyContract.PROGRAM_ID)[0])
+ val attachmentSigner = attachment!!.signerKeys.single()
- val outputState = TransactionState(data = DummyState(), contract = DummyContract.PROGRAM_ID, notary = notary)
- val builder = TransactionBuilder()
- .addOutputState(outputState)
- .addCommand(DummyCommandData, notary.owningKey)
- val wtx = builder.toWireTransaction(services)
+ val expectedConstraint = SignatureAttachmentConstraint(attachmentSigner)
+ assertTrue(expectedConstraint.isSatisfiedBy(attachment))
- assertThat(wtx.outputs).containsOnly(outputState.copy(constraint = expectedConstraint))
+ val outputState = TransactionState(data = DummyState(), contract = DummyContract.PROGRAM_ID, notary = notary)
+ val builder = TransactionBuilder()
+ .addOutputState(outputState)
+ .addCommand(DummyCommandData, notary.owningKey)
+ val wtx = builder.toWireTransaction(services)
+
+ assertThat(wtx.outputs).containsOnly(outputState.copy(constraint = expectedConstraint))
+ } finally {
+ mockNetwork.stopNodes()
+ _driverSerializationEnv.set(driverSerializationEnv)
+ }
}
- private val unsignedAttachment = ContractAttachment(object : AbstractAttachment({ byteArrayOf() }, "test") {
- override val id: SecureHash get() = throw UnsupportedOperationException()
-
- override val signerKeys: List get() = emptyList()
- }, DummyContract.PROGRAM_ID)
-
- private fun signedAttachment(vararg parties: Party) = ContractAttachment.create(object : AbstractAttachment({ byteArrayOf() }, "test") {
- override val id: SecureHash get() = contractAttachmentId
-
- override val signerKeys: List get() = parties.map { it.owningKey }
- }, DummyContract.PROGRAM_ID, signerKeys = parties.map { it.owningKey })
-
@Test(timeout=300_000)
fun `list accessors are mutable copies`() {
val inputState1 = TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary)
diff --git a/core/build.gradle b/core/build.gradle
index 9916f83039..f2fe5c1ea1 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -40,9 +40,6 @@ dependencies {
// Hamkrest, for fluent, composable matchers
testImplementation "com.natpryce:hamkrest:$hamkrest_version"
- // Thread safety annotations
- implementation "com.google.code.findbugs:jsr305:$jsr305_version"
-
// SLF4J: commons-logging bindings for a SLF4J back end
implementation "org.slf4j:jcl-over-slf4j:$slf4j_version"
implementation "org.slf4j:slf4j-api:$slf4j_version"
@@ -66,10 +63,7 @@ dependencies {
// Bouncy castle support needed for X509 certificate manipulation
implementation "org.bouncycastle:bcprov-jdk18on:${bouncycastle_version}"
- implementation "org.bouncycastle:bcpkix-jdk18on:${bouncycastle_version}"
-
- // JPA 2.2 annotations.
- implementation "javax.persistence:javax.persistence-api:2.2"
+ testImplementation "org.bouncycastle:bcpkix-jdk18on:${bouncycastle_version}"
// required to use @Type annotation
implementation "org.hibernate:hibernate-core:$hibernate_version"
diff --git a/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt b/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt
index 0b56707ee1..24852fad3c 100644
--- a/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt
+++ b/core/src/main/kotlin/net/corda/core/contracts/ContractAttachment.kt
@@ -27,6 +27,7 @@ class ContractAttachment private constructor(
companion object {
@CordaInternal
+ @JvmSynthetic
fun create(attachment: Attachment,
contract: ContractClassName,
additionalContracts: Set = emptySet(),
diff --git a/core/src/main/kotlin/net/corda/core/crypto/internal/DigestAlgorithmFactory.kt b/core/src/main/kotlin/net/corda/core/crypto/internal/DigestAlgorithmFactory.kt
index 892506aa76..a2f2396607 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/internal/DigestAlgorithmFactory.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/internal/DigestAlgorithmFactory.kt
@@ -1,10 +1,10 @@
package net.corda.core.crypto.internal
import net.corda.core.crypto.DigestAlgorithm
-import java.lang.reflect.Constructor
+import net.corda.core.internal.loadClassOfType
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
-import java.util.*
+import java.util.Collections
import java.util.concurrent.ConcurrentHashMap
sealed class DigestAlgorithmFactory {
@@ -28,9 +28,8 @@ sealed class DigestAlgorithmFactory {
}
private class CustomAlgorithmFactory(className: String) : DigestAlgorithmFactory() {
- val constructor: Constructor = Class.forName(className, false, javaClass.classLoader)
- .asSubclass(DigestAlgorithm::class.java)
- .getConstructor()
+ private val constructor = loadClassOfType(className, false, javaClass.classLoader).getConstructor()
+
override val algorithm: String = constructor.newInstance().algorithm
override fun create(): DigestAlgorithm {
diff --git a/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt b/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt
index 173107dd5f..91bc460bce 100644
--- a/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt
+++ b/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt
@@ -11,6 +11,8 @@ import net.corda.core.internal.NetworkParametersStorage
import net.corda.core.internal.PlatformVersionSwitches
import net.corda.core.internal.RetrieveAnyTransactionPayload
import net.corda.core.internal.ServiceHubCoreInternal
+import net.corda.core.internal.getRequiredTransaction
+import net.corda.core.internal.mapToSet
import net.corda.core.internal.readFully
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.StatesToRecord
@@ -169,13 +171,10 @@ open class DataVendingFlow(val otherSessions: Set, val payload: Any
is SignedTransaction -> TransactionAuthorisationFilter().addAuthorised(getInputTransactions(payload))
is RetrieveAnyTransactionPayload -> TransactionAuthorisationFilter(acceptAll = true)
is List<*> -> TransactionAuthorisationFilter().addAuthorised(payload.flatMap { someObject ->
- if (someObject is StateAndRef<*>) {
- getInputTransactions(serviceHub.validatedTransactions.getTransaction(someObject.ref.txhash)!!) + someObject.ref.txhash
- }
- else if (someObject is NamedByHash) {
- setOf(someObject.id)
- } else {
- throw Exception("Unknown payload type: ${someObject!!::class.java} ?")
+ when (someObject) {
+ is StateAndRef<*> -> getInputTransactions(serviceHub.getRequiredTransaction(someObject.ref.txhash)) + someObject.ref.txhash
+ is NamedByHash -> setOf(someObject.id)
+ else -> throw Exception("Unknown payload type: ${someObject!!::class.java} ?")
}
}.toSet())
else -> throw Exception("Unknown payload type: ${payload::class.java} ?")
@@ -308,7 +307,7 @@ open class DataVendingFlow(val otherSessions: Set, val payload: Any
@Suspendable
private fun getInputTransactions(tx: SignedTransaction): Set {
- return tx.inputs.map { it.txhash }.toSet() + tx.references.map { it.txhash }.toSet()
+ return tx.inputs.mapToSet { it.txhash } + tx.references.mapToSet { it.txhash }
}
private class TransactionAuthorisationFilter(private val authorisedTransactions: MutableSet = mutableSetOf(), val acceptAll: Boolean = false) {
diff --git a/core/src/main/kotlin/net/corda/core/internal/ClassLoadingUtils.kt b/core/src/main/kotlin/net/corda/core/internal/ClassLoadingUtils.kt
index 2e80d05eb0..4b1fe0b291 100644
--- a/core/src/main/kotlin/net/corda/core/internal/ClassLoadingUtils.kt
+++ b/core/src/main/kotlin/net/corda/core/internal/ClassLoadingUtils.kt
@@ -21,8 +21,7 @@ import net.corda.core.serialization.internal.AttachmentURLStreamHandlerFactory.a
fun createInstancesOfClassesImplementing(classloader: ClassLoader, clazz: Class,
classVersionRange: IntRange? = null): Set {
return getNamesOfClassesImplementing(classloader, clazz, classVersionRange)
- .map { Class.forName(it, false, classloader).asSubclass(clazz) }
- .mapTo(LinkedHashSet()) { it.kotlin.objectOrNewInstance() }
+ .mapToSet { loadClassOfType(clazz, it, false, classloader).kotlin.objectOrNewInstance() }
}
/**
@@ -56,10 +55,23 @@ fun getNamesOfClassesImplementing(classloader: ClassLoader, clazz: Clas
}
result.getClassesImplementing(clazz.name)
.filterNot(ClassInfo::isAbstract)
- .mapTo(LinkedHashSet(), ClassInfo::getName)
+ .mapToSet(ClassInfo::getName)
}
}
+/**
+ * @throws ClassNotFoundException
+ * @throws ClassCastException
+ * @see Class.forName
+ */
+inline fun loadClassOfType(className: String, initialize: Boolean = true, classLoader: ClassLoader? = null): Class {
+ return loadClassOfType(T::class.java, className, initialize, classLoader)
+}
+
+fun loadClassOfType(type: Class, className: String, initialize: Boolean = true, classLoader: ClassLoader? = null): Class {
+ return Class.forName(className, initialize, classLoader).asSubclass(type)
+}
+
fun executeWithThreadContextClassLoader(classloader: ClassLoader, fn: () -> T): T {
val threadClassLoader = Thread.currentThread().contextClassLoader
try {
@@ -68,5 +80,4 @@ fun executeWithThreadContextClassLoader(classloader: ClassLoader, fn:
} finally {
Thread.currentThread().contextClassLoader = threadClassLoader
}
-
}
diff --git a/core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt b/core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt
index 9f315a778d..9531bd512e 100644
--- a/core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt
+++ b/core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt
@@ -1,29 +1,22 @@
@file:Suppress("TooManyFunctions")
package net.corda.core.internal
-import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractClassName
-import net.corda.core.cordapp.CordappProvider
+import net.corda.core.contracts.TransactionResolutionException
+import net.corda.core.crypto.SecureHash
import net.corda.core.flows.DataVendingFlow
import net.corda.core.flows.FlowLogic
import net.corda.core.node.NetworkParameters
+import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.ZoneVersionTooLowException
-import net.corda.core.node.services.AttachmentId
-import net.corda.core.node.services.AttachmentStorage
-import net.corda.core.node.services.vault.AttachmentQueryCriteria
-import net.corda.core.node.services.vault.AttachmentSort
-import net.corda.core.node.services.vault.Builder
-import net.corda.core.node.services.vault.Sort
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializationContext
-import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction
import org.slf4j.MDC
import java.security.PublicKey
-import java.util.jar.JarInputStream
// *Internal* Corda-specific utilities.
@@ -68,11 +61,6 @@ fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serial
return toWireTransactionWithContext(services, serializationContext)
}
-/** Provide access to internal method for AttachmentClassLoaderTests. */
-fun TransactionBuilder.toLedgerTransaction(services: ServicesForResolution, serializationContext: SerializationContext): LedgerTransaction {
- return toLedgerTransactionWithContext(services, serializationContext)
-}
-
/** Checks if this flow is an idempotent flow. */
fun Class>.isIdempotentFlow(): Boolean {
return IdempotentFlow::class.java.isAssignableFrom(this)
@@ -125,40 +113,6 @@ fun noPackageOverlap(packages: Collection): Boolean {
return packages.all { outer -> packages.none { inner -> inner != outer && inner.startsWith("$outer.") } }
}
-/**
- * @return The set of [AttachmentId]s after the node's fix-up rules have been applied to [attachmentIds].
- */
-fun CordappProvider.internalFixupAttachmentIds(attachmentIds: Collection): Set {
- return (this as CordappFixupInternal).fixupAttachmentIds(attachmentIds)
-}
-
-/**
- * Scans trusted (installed locally) attachments to find all that contain the [className].
- * This is required as a workaround until explicit cordapp dependencies are implemented.
- * DO NOT USE IN CLIENT code.
- *
- * @return the attachments with the highest version.
- *
- * TODO: Should throw when the class is found in multiple contract attachments (not different versions).
- */
-fun AttachmentStorage.internalFindTrustedAttachmentForClass(className: String): Attachment? {
- val allTrusted = queryAttachments(
- AttachmentQueryCriteria.AttachmentsQueryCriteria().withUploader(Builder.`in`(TRUSTED_UPLOADERS)),
- AttachmentSort(listOf(AttachmentSort.AttachmentSortColumn(AttachmentSort.AttachmentSortAttribute.VERSION, Sort.Direction.DESC))))
-
- // TODO - add caching if performance is affected.
- for (attId in allTrusted) {
- val attch = openAttachment(attId)!!
- if (attch.openAsJAR().use { hasFile(it, "$className.class") }) return attch
- }
- return null
-}
-
-private fun hasFile(jarStream: JarInputStream, className: String): Boolean {
- while (true) {
- val e = jarStream.nextJarEntry ?: return false
- if (e.name == className) {
- return true
- }
- }
+fun ServiceHub.getRequiredTransaction(txhash: SecureHash): SignedTransaction {
+ return validatedTransactions.getTransaction(txhash) ?: throw TransactionResolutionException(txhash)
}
diff --git a/core/src/main/kotlin/net/corda/core/internal/CordappFixupInternal.kt b/core/src/main/kotlin/net/corda/core/internal/CordappFixupInternal.kt
deleted file mode 100644
index e1ac4e22c6..0000000000
--- a/core/src/main/kotlin/net/corda/core/internal/CordappFixupInternal.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package net.corda.core.internal
-
-import net.corda.core.node.services.AttachmentId
-
-interface CordappFixupInternal {
- fun fixupAttachmentIds(attachmentIds: Collection): Set
-}
diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt
index 7ad05fe75c..9f227123ae 100644
--- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt
+++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt
@@ -23,7 +23,6 @@ import rx.subjects.UnicastSubject
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream
-import java.io.OutputStream
import java.lang.reflect.Field
import java.lang.reflect.Member
import java.lang.reflect.Modifier
@@ -138,10 +137,34 @@ fun List.randomOrNull(): T? {
/** Returns the index of the given item or throws [IllegalArgumentException] if not found. */
fun List.indexOfOrThrow(item: T): Int {
val i = indexOf(item)
- require(i != -1){"No such element"}
+ require(i != -1) { "No such element" }
return i
}
+/**
+ * Similar to [Iterable.map] except it maps to a [Set] which preserves the iteration order.
+ */
+inline fun Iterable.mapToSet(transform: (T) -> R): Set {
+ if (this is Collection) {
+ when (size) {
+ 0 -> return emptySet()
+ 1 -> return setOf(transform(first()))
+ }
+ }
+ return mapTo(LinkedHashSet(), transform)
+}
+
+/**
+ * Similar to [Iterable.flatMap] except it maps to a [Set] which preserves the iteration order.
+ */
+inline fun Iterable.flatMapToSet(transform: (T) -> Iterable): Set {
+ return if (this is Collection && isEmpty()) {
+ emptySet()
+ } else {
+ flatMapTo(LinkedHashSet(), transform)
+ }
+}
+
fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.copy(this, target, *options)
/** Same as [InputStream.readBytes] but also closes the stream. */
@@ -165,12 +188,6 @@ fun InputStream.hash(): SecureHash {
inline fun InputStream.readObject(): T = readFully().deserialize()
-object NullOutputStream : OutputStream() {
- override fun write(b: Int) = Unit
- override fun write(b: ByteArray) = Unit
- override fun write(b: ByteArray, off: Int, len: Int) = Unit
-}
-
fun String.abbreviate(maxWidth: Int): String = if (length <= maxWidth) this else take(maxWidth - 1) + "…"
/** Return the sum of an Iterable of [BigDecimal]s. */
@@ -532,11 +549,6 @@ fun ByteBuffer.copyBytes(): ByteArray = ByteArray(remaining()).also { get(it) }
val PublicKey.hash: SecureHash get() = Crypto.encodePublicKey(this).sha256()
-/**
- * Extension method for providing a sumBy method that processes and returns a Long
- */
-fun Iterable.sumByLong(selector: (T) -> Long): Long = this.map { selector(it) }.sum()
-
fun SerializedBytes.checkPayloadIs(type: Class): UntrustworthyData {
val payloadData: T = try {
val serializer = SerializationDefaults.SERIALIZATION_FACTORY
@@ -563,6 +575,10 @@ fun MutableMap.toSynchronised(): MutableMap = Collections.syn
/** @see Collections.synchronizedSet */
fun MutableSet.toSynchronised(): MutableSet = Collections.synchronizedSet(this)
+fun Collection<*>.equivalent(other: Collection<*>): Boolean {
+ return this.size == other.size && this.containsAll(other) && other.containsAll(this)
+}
+
/**
* List implementation that applies the expensive [transform] function only when the element is accessed and caches calculated values.
* Size is very cheap as it doesn't call [transform].
diff --git a/core/src/main/kotlin/net/corda/core/internal/NamedCache.kt b/core/src/main/kotlin/net/corda/core/internal/NamedCache.kt
index 82c1c35b66..d192557345 100644
--- a/core/src/main/kotlin/net/corda/core/internal/NamedCache.kt
+++ b/core/src/main/kotlin/net/corda/core/internal/NamedCache.kt
@@ -9,18 +9,24 @@ import com.github.benmanes.caffeine.cache.LoadingCache
* Allow extra functionality to be injected to our caches.
*/
interface NamedCacheFactory {
+ companion object {
+ private val allowedChars = Regex("""^[0-9A-Za-z_.]*$""")
+ }
+
/**
* Restrict the allowed characters of a cache name - this ensures that each cache has a name, and that
* the name can be used to create a file name or a metric name.
*/
fun checkCacheName(name: String) {
- require(!name.isBlank()){"Name must not be empty or only whitespace"}
- require(allowedChars.matches(name)){"Invalid characters in cache name"}
+ require(name.isNotBlank()) { "Name must not be empty or only whitespace" }
+ require(allowedChars.matches(name)) { "Invalid characters in cache name" }
}
+ fun buildNamed(name: String): Cache = buildNamed(Caffeine.newBuilder(), name)
+
fun buildNamed(caffeine: Caffeine, name: String): Cache
+ fun buildNamed(name: String, loader: CacheLoader): LoadingCache = buildNamed(Caffeine.newBuilder(), name, loader)
+
fun buildNamed(caffeine: Caffeine, name: String, loader: CacheLoader): LoadingCache
}
-
-private val allowedChars = Regex("^[0-9A-Za-z_.]*\$")
diff --git a/core/src/main/kotlin/net/corda/core/internal/ServiceHubCoreInternal.kt b/core/src/main/kotlin/net/corda/core/internal/ServiceHubCoreInternal.kt
index bd7c1142ac..84ba452deb 100644
--- a/core/src/main/kotlin/net/corda/core/internal/ServiceHubCoreInternal.kt
+++ b/core/src/main/kotlin/net/corda/core/internal/ServiceHubCoreInternal.kt
@@ -6,19 +6,15 @@ import net.corda.core.crypto.TransactionSignature
import net.corda.core.flows.TransactionMetadata
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.notary.NotaryService
-import net.corda.core.node.ServiceHub
+import net.corda.core.internal.verification.VerifyingServiceHub
import net.corda.core.node.StatesToRecord
-import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
import net.corda.core.transactions.SignedTransaction
import java.util.concurrent.ExecutorService
// TODO: This should really be called ServiceHubInternal but that name is already taken by net.corda.node.services.api.ServiceHubInternal.
-interface ServiceHubCoreInternal : ServiceHub {
-
+interface ServiceHubCoreInternal : VerifyingServiceHub {
val externalOperationExecutor: ExecutorService
- val attachmentTrustCalculator: AttachmentTrustCalculator
-
/**
* Optional `NotaryService` which will be `null` for all non-Notary nodes.
*/
@@ -26,8 +22,6 @@ interface ServiceHubCoreInternal : ServiceHub {
fun createTransactionsResolver(flow: ResolveTransactionsFlow): TransactionsResolver
- val attachmentsClassLoaderCache: AttachmentsClassLoaderCache
-
/**
* Stores [SignedTransaction] and participant signatures without the notary signature in the local transaction storage,
* inclusive of flow recovery metadata.
diff --git a/core/src/main/kotlin/net/corda/core/internal/TransactionUtils.kt b/core/src/main/kotlin/net/corda/core/internal/TransactionUtils.kt
index 41e94a236a..a6aa9c2ac4 100644
--- a/core/src/main/kotlin/net/corda/core/internal/TransactionUtils.kt
+++ b/core/src/main/kotlin/net/corda/core/internal/TransactionUtils.kt
@@ -172,11 +172,13 @@ fun createComponentGroups(inputs: List,
return componentGroupMap
}
+typealias SerializedTransactionState = SerializedBytes>
+
/**
* A SerializedStateAndRef is a pair (BinaryStateRepresentation, StateRef).
* The [serializedState] is the actual component from the original wire transaction.
*/
-data class SerializedStateAndRef(val serializedState: SerializedBytes>, val ref: StateRef) {
+data class SerializedStateAndRef(val serializedState: SerializedTransactionState, val ref: StateRef) {
fun toStateAndRef(factory: SerializationFactory, context: SerializationContext) = StateAndRef(serializedState.deserialize(factory, context), ref)
fun toStateAndRef(): StateAndRef {
val factory = SerializationFactory.defaultFactory
diff --git a/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappProviderInternal.kt b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappProviderInternal.kt
new file mode 100644
index 0000000000..5b94f2571a
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/internal/cordapp/CordappProviderInternal.kt
@@ -0,0 +1,13 @@
+package net.corda.core.internal.cordapp
+
+import net.corda.core.cordapp.Cordapp
+import net.corda.core.cordapp.CordappProvider
+import net.corda.core.flows.FlowLogic
+import net.corda.core.node.services.AttachmentId
+
+interface CordappProviderInternal : CordappProvider {
+ val appClassLoader: ClassLoader
+ val cordapps: List
+ fun getCordappForFlow(flowLogic: FlowLogic<*>): Cordapp?
+ fun fixupAttachmentIds(attachmentIds: Collection): Set
+}
diff --git a/core/src/main/kotlin/net/corda/core/internal/verification/AttachmentFixups.kt b/core/src/main/kotlin/net/corda/core/internal/verification/AttachmentFixups.kt
new file mode 100644
index 0000000000..4e20a46d41
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/internal/verification/AttachmentFixups.kt
@@ -0,0 +1,79 @@
+package net.corda.core.internal.verification
+
+import net.corda.core.crypto.SecureHash
+import net.corda.core.internal.mapNotNull
+import net.corda.core.node.services.AttachmentFixup
+import net.corda.core.node.services.AttachmentId
+import net.corda.core.node.services.AttachmentStorage
+import net.corda.core.transactions.TransactionBuilder
+import net.corda.core.utilities.loggerFor
+import java.net.JarURLConnection
+import java.net.URL
+
+class AttachmentFixups {
+ private val fixupRules = ArrayList()
+
+ /**
+ * Loads the "fixup" rules from all META-INF/Corda-Fixups files.
+ * These files have the following format:
+ * ,...=>,,...
+ * where each is the SHA256 of a CorDapp JAR that [TransactionBuilder] will expect to find inside [AttachmentStorage].
+ *
+ * These rules are for repairing broken CorDapps. A correctly written CorDapp should not require them.
+ */
+ fun load(appClassLoader: ClassLoader) {
+ for (url in appClassLoader.resources("META-INF/Corda-Fixups")) {
+ val connection = toValidFixupResource(url) ?: continue
+ connection.inputStream.bufferedReader().lines().use { lines ->
+ lines.mapNotNull(::cleanLine).forEach { line ->
+ val tokens = line.split("=>")
+ require(tokens.size == 2) {
+ "Invalid fix-up line '$line' in '${connection.jarFile.name}'"
+ }
+ val sourceIds = parseIds(tokens[0])
+ require(sourceIds.isNotEmpty()) {
+ "Forbidden empty list of source attachment IDs in '${connection.jarFile.name}'"
+ }
+ val targetIds = parseIds(tokens[1])
+ fixupRules += AttachmentFixup(sourceIds, targetIds)
+ }
+ }
+ }
+ }
+
+ private fun toValidFixupResource(url: URL): JarURLConnection? {
+ val connection = url.openConnection() as? JarURLConnection ?: return null
+ val isValid = connection.jarFile.stream().allMatch { it.name.startsWith("META-INF/") }
+ if (!isValid) {
+ loggerFor().warn("FixUp '{}' contains files outside META-INF/ - IGNORING!", connection.jarFile.name)
+ return null
+ }
+ return connection
+ }
+
+ private fun cleanLine(line: String): String? = line.substringBefore('#').trim().takeIf(String::isNotEmpty)
+
+ private fun parseIds(ids: String): Set {
+ return ids.splitToSequence(",")
+ .map(String::trim)
+ .filterNot(String::isEmpty)
+ .mapTo(LinkedHashSet(), SecureHash.Companion::create)
+ }
+
+ /**
+ * Apply this node's attachment fix-up rules to the given attachment IDs.
+ *
+ * @param attachmentIds A collection of [AttachmentId]s, e.g. as provided by a transaction.
+ * @return The [attachmentIds] with the fix-up rules applied.
+ */
+ fun fixupAttachmentIds(attachmentIds: Collection): Set {
+ val replacementIds = LinkedHashSet(attachmentIds)
+ for ((sourceIds, targetIds) in fixupRules) {
+ if (replacementIds.containsAll(sourceIds)) {
+ replacementIds.removeAll(sourceIds)
+ replacementIds.addAll(targetIds)
+ }
+ }
+ return replacementIds
+ }
+}
diff --git a/core/src/main/kotlin/net/corda/core/internal/verification/VerificationSupport.kt b/core/src/main/kotlin/net/corda/core/internal/verification/VerificationSupport.kt
new file mode 100644
index 0000000000..5d1ea265bb
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/internal/verification/VerificationSupport.kt
@@ -0,0 +1,50 @@
+package net.corda.core.internal.verification
+
+import net.corda.core.contracts.Attachment
+import net.corda.core.contracts.StateAndRef
+import net.corda.core.contracts.StateRef
+import net.corda.core.crypto.SecureHash
+import net.corda.core.identity.Party
+import net.corda.core.internal.SerializedTransactionState
+import net.corda.core.node.NetworkParameters
+import net.corda.core.serialization.SerializationContext
+import net.corda.core.serialization.deserialize
+import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
+import net.corda.core.transactions.LedgerTransaction
+import net.corda.core.transactions.defaultVerifier
+import java.security.PublicKey
+
+/**
+ * Represents the operations required to resolve and verify a transaction.
+ */
+interface VerificationSupport {
+ val isResolutionLazy: Boolean get() = true
+
+ val appClassLoader: ClassLoader
+
+ val attachmentsClassLoaderCache: AttachmentsClassLoaderCache? get() = null
+
+ // TODO Use SequencedCollection if upgraded to Java 21
+ fun getParties(keys: Collection): List
+
+ fun getAttachment(id: SecureHash): Attachment?
+
+ // TODO Use SequencedCollection if upgraded to Java 21
+ fun getAttachments(ids: Collection): List = ids.map(::getAttachment)
+
+ fun isAttachmentTrusted(attachment: Attachment): Boolean
+
+ fun getTrustedClassAttachment(className: String): Attachment?
+
+ fun getNetworkParameters(id: SecureHash?): NetworkParameters?
+
+ fun getSerializedState(stateRef: StateRef): SerializedTransactionState
+
+ fun getStateAndRef(stateRef: StateRef): StateAndRef<*> = StateAndRef(getSerializedState(stateRef).deserialize(), stateRef)
+
+ fun fixupAttachmentIds(attachmentIds: Collection): Set
+
+ fun createVerifier(ltx: LedgerTransaction, serializationContext: SerializationContext): Verifier {
+ return defaultVerifier(ltx, serializationContext)
+ }
+}
diff --git a/core/src/main/kotlin/net/corda/core/internal/TransactionVerifierServiceInternal.kt b/core/src/main/kotlin/net/corda/core/internal/verification/Verifier.kt
similarity index 95%
rename from core/src/main/kotlin/net/corda/core/internal/TransactionVerifierServiceInternal.kt
rename to core/src/main/kotlin/net/corda/core/internal/verification/Verifier.kt
index 840163238f..f1e55acc80 100644
--- a/core/src/main/kotlin/net/corda/core/internal/TransactionVerifierServiceInternal.kt
+++ b/core/src/main/kotlin/net/corda/core/internal/verification/Verifier.kt
@@ -1,7 +1,5 @@
-package net.corda.core.internal
+package net.corda.core.internal.verification
-import net.corda.core.concurrent.CordaFuture
-import net.corda.core.contracts.Attachment
import net.corda.core.contracts.Contract
import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.ContractClassName
@@ -30,21 +28,26 @@ import net.corda.core.contracts.TransactionVerificationException.TransactionNota
import net.corda.core.contracts.TransactionVerificationException.TransactionRequiredContractUnspecifiedException
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.SecureHash
+import net.corda.core.internal.AttachmentWithContext
+import net.corda.core.internal.MAX_NUMBER_OF_KEYS_IN_SIGNATURE_CONSTRAINT
+import net.corda.core.internal.PlatformVersionSwitches
+import net.corda.core.internal.canBeTransitionedFrom
+import net.corda.core.internal.checkConstraintValidity
+import net.corda.core.internal.checkMinimumPlatformVersion
+import net.corda.core.internal.checkNotaryWhitelisted
+import net.corda.core.internal.checkSupportedHashType
+import net.corda.core.internal.contractHasAutomaticConstraintPropagation
+import net.corda.core.internal.loadClassOfType
+import net.corda.core.internal.mapToSet
+import net.corda.core.internal.requiredContractClassName
import net.corda.core.internal.rules.StateContractValidationEnforcementRule
+import net.corda.core.internal.warnContractWithoutConstraintPropagation
+import net.corda.core.internal.warnOnce
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.loggerFor
import java.util.function.Function
import java.util.function.Supplier
-interface TransactionVerifierServiceInternal {
- fun reverifyWithFixups(transaction: LedgerTransaction, missingClass: String?): CordaFuture<*>
-}
-
-/**
- * Defined here for visibility reasons.
- */
-fun LedgerTransaction.prepareVerify(attachments: List) = internalPrepareVerify(attachments)
-
interface Verifier {
/**
@@ -142,10 +145,12 @@ private class Validator(private val ltx: LedgerTransaction, private val transact
*/
@Suppress("ThrowsCount")
private fun getUniqueContractAttachmentsByContract(): Map {
- val contractClasses = allStates.mapTo(LinkedHashSet(), TransactionState<*>::contract)
+ val contractClasses = allStates.mapToSet { it.contract }
// Check that there are no duplicate attachments added.
- if (ltx.attachments.size != ltx.attachments.toSet().size) throw DuplicateAttachmentsRejection(ltx.id, ltx.attachments.groupBy { it }.filterValues { it.size > 1 }.keys.first())
+ if (ltx.attachments.size != ltx.attachments.toSet().size) {
+ throw DuplicateAttachmentsRejection(ltx.id, ltx.attachments.groupBy { it }.filterValues { it.size > 1 }.keys.first())
+ }
// For each attachment this finds all the relevant state contracts that it provides.
// And then maps them to the attachment.
@@ -393,7 +398,7 @@ private class Validator(private val ltx: LedgerTransaction, private val transact
@Suppress("NestedBlockDepth", "MagicNumber")
private fun verifyConstraints(contractAttachmentsByContract: Map) {
// For each contract/constraint pair check that the relevant attachment is valid.
- allStates.mapTo(LinkedHashSet()) { it.contract to it.constraint }.forEach { (contract, constraint) ->
+ allStates.mapToSet { it.contract to it.constraint }.forEach { (contract, constraint) ->
if (constraint is SignatureAttachmentConstraint) {
/**
* Support for signature constraints has been added on
@@ -440,7 +445,7 @@ class TransactionVerifier(private val transactionClassLoader: ClassLoader) : Fun
// Loads the contract class from the transactionClassLoader.
private fun createContractClass(id: SecureHash, contractClassName: ContractClassName): Class {
return try {
- Class.forName(contractClassName, false, transactionClassLoader).asSubclass(Contract::class.java)
+ loadClassOfType(contractClassName, false, transactionClassLoader)
} catch (e: Exception) {
throw ContractCreationError(id, contractClassName, e)
}
@@ -448,7 +453,7 @@ class TransactionVerifier(private val transactionClassLoader: ClassLoader) : Fun
private fun generateContracts(ltx: LedgerTransaction): List {
return (ltx.inputs.map(StateAndRef::state) + ltx.outputs)
- .mapTo(LinkedHashSet(), TransactionState<*>::contract)
+ .mapToSet { it.contract }
.map { contractClassName ->
createContractClass(ltx.id, contractClassName)
}.map { contractClass ->
diff --git a/core/src/main/kotlin/net/corda/core/internal/verification/VerifyingServiceHub.kt b/core/src/main/kotlin/net/corda/core/internal/verification/VerifyingServiceHub.kt
new file mode 100644
index 0000000000..eba81ca2dc
--- /dev/null
+++ b/core/src/main/kotlin/net/corda/core/internal/verification/VerifyingServiceHub.kt
@@ -0,0 +1,221 @@
+package net.corda.core.internal.verification
+
+import net.corda.core.contracts.Attachment
+import net.corda.core.contracts.AttachmentResolutionException
+import net.corda.core.contracts.ComponentGroupEnum
+import net.corda.core.contracts.ContractAttachment
+import net.corda.core.contracts.ContractState
+import net.corda.core.contracts.StateAndRef
+import net.corda.core.contracts.StateRef
+import net.corda.core.contracts.TransactionResolutionException
+import net.corda.core.contracts.TransactionState
+import net.corda.core.crypto.SecureHash
+import net.corda.core.identity.Party
+import net.corda.core.internal.AttachmentTrustCalculator
+import net.corda.core.internal.SerializedTransactionState
+import net.corda.core.internal.TRUSTED_UPLOADERS
+import net.corda.core.internal.cordapp.CordappProviderInternal
+import net.corda.core.internal.getRequiredTransaction
+import net.corda.core.node.NetworkParameters
+import net.corda.core.node.ServiceHub
+import net.corda.core.node.ServicesForResolution
+import net.corda.core.node.services.vault.AttachmentQueryCriteria
+import net.corda.core.node.services.vault.AttachmentSort
+import net.corda.core.node.services.vault.AttachmentSort.AttachmentSortAttribute
+import net.corda.core.node.services.vault.Builder
+import net.corda.core.node.services.vault.Sort
+import net.corda.core.serialization.deserialize
+import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
+import net.corda.core.serialization.serialize
+import net.corda.core.transactions.ContractUpgradeLedgerTransaction.Companion.loadUpgradedContract
+import net.corda.core.transactions.ContractUpgradeWireTransaction
+import net.corda.core.transactions.ContractUpgradeWireTransaction.Companion.calculateUpgradedState
+import net.corda.core.transactions.MissingContractAttachments
+import net.corda.core.transactions.NotaryChangeLedgerTransaction
+import net.corda.core.transactions.NotaryChangeWireTransaction
+import net.corda.core.transactions.SignedTransaction
+import net.corda.core.transactions.WireTransaction
+import java.security.PublicKey
+import java.util.jar.JarInputStream
+
+@Suppress("TooManyFunctions", "ThrowsCount")
+interface VerifyingServiceHub : ServiceHub, VerificationSupport {
+ override val cordappProvider: CordappProviderInternal
+
+ val attachmentTrustCalculator: AttachmentTrustCalculator
+
+ override val appClassLoader: ClassLoader get() = cordappProvider.appClassLoader
+
+ override fun loadContractAttachment(stateRef: StateRef): Attachment {
+ // We may need to recursively chase transactions if there are notary changes.
+ return loadContractAttachment(stateRef, null)
+ }
+
+ private fun loadContractAttachment(stateRef: StateRef, forContractClassName: String?): Attachment {
+ val stx = getRequiredTransaction(stateRef.txhash)
+ return when (val ctx = stx.coreTransaction) {
+ is WireTransaction -> {
+ val contractClassName = forContractClassName ?: ctx.outRef(stateRef.index).state.contract
+ ctx.attachments
+ .asSequence()
+ .mapNotNull { id -> loadAttachmentContainingContract(id, contractClassName) }
+ .firstOrNull() ?: throw AttachmentResolutionException(stateRef.txhash)
+ }
+ is ContractUpgradeWireTransaction -> {
+ attachments.openAttachment(ctx.upgradedContractAttachmentId) ?: throw AttachmentResolutionException(stateRef.txhash)
+ }
+ is NotaryChangeWireTransaction -> {
+ val transactionState = getSerializedState(stateRef).deserialize()
+ val input = ctx.inputs.firstOrNull() ?: throw AttachmentResolutionException(stateRef.txhash)
+ loadContractAttachment(input, transactionState.contract)
+ }
+ else -> throw UnsupportedOperationException("Attempting to resolve attachment for index ${stateRef.index} of a " +
+ "${ctx.javaClass} transaction. This is not supported.")
+ }
+ }
+
+ private fun loadAttachmentContainingContract(id: SecureHash, contractClassName: String): Attachment? {
+ return attachments.openAttachment(id)?.takeIf { it is ContractAttachment && contractClassName in it.allContracts }
+ }
+
+ override fun loadState(stateRef: StateRef): TransactionState<*> = getSerializedState(stateRef).deserialize()
+
+ override fun loadStates(stateRefs: Set): Set> = loadStatesInternal(stateRefs, LinkedHashSet())
+
+ fun >> loadStatesInternal(input: Iterable, output: C): C {
+ return input.mapTo(output, ::toStateAndRef)
+ }
+
+ // TODO Bulk party lookup?
+ override fun getParties(keys: Collection): List = keys.map(identityService::partyFromKey)
+
+ override fun getAttachment(id: SecureHash): Attachment? = attachments.openAttachment(id)
+
+ override fun getNetworkParameters(id: SecureHash?): NetworkParameters? {
+ return networkParametersService.lookup(id ?: networkParametersService.defaultHash)
+ }
+
+ /**
+ * This is the main logic that knows how to retrieve the binary representation of [StateRef]s.
+ *
+ * For [ContractUpgradeWireTransaction] or [NotaryChangeWireTransaction] it knows how to recreate the output state in the
+ * correct classloader independent of the node's classpath.
+ */
+ override fun getSerializedState(stateRef: StateRef): SerializedTransactionState {
+ val coreTransaction = getRequiredTransaction(stateRef.txhash).coreTransaction
+ return when (coreTransaction) {
+ is WireTransaction -> getRegularOutput(coreTransaction, stateRef.index)
+ is ContractUpgradeWireTransaction -> getContractUpdateOutput(coreTransaction, stateRef.index)
+ is NotaryChangeWireTransaction -> getNotaryChangeOutput(coreTransaction, stateRef.index)
+ else -> throw UnsupportedOperationException("Attempting to resolve input ${stateRef.index} of a ${coreTransaction.javaClass} " +
+ "transaction. This is not supported.")
+ }
+ }
+
+ private fun getRegularOutput(coreTransaction: WireTransaction, outputIndex: Int): SerializedTransactionState {
+ @Suppress("UNCHECKED_CAST")
+ return coreTransaction.componentGroups
+ .first { it.groupIndex == ComponentGroupEnum.OUTPUTS_GROUP.ordinal }
+ .components[outputIndex] as SerializedTransactionState
+ }
+
+ /**
+ * Creates a binary serialized component for a virtual output state serialised and executed with the attachments from the transaction.
+ */
+ private fun getContractUpdateOutput(wtx: ContractUpgradeWireTransaction, outputIndex: Int): SerializedTransactionState {
+ val binaryInput = getSerializedState(wtx.inputs[outputIndex])
+ val legacyContractAttachment = getAttachment(wtx.legacyContractAttachmentId) ?: throw MissingContractAttachments(emptyList())
+ val upgradedContractAttachment = getAttachment(wtx.upgradedContractAttachmentId) ?: throw MissingContractAttachments(emptyList())
+ val networkParameters = getNetworkParameters(wtx.networkParametersHash) ?: throw TransactionResolutionException(wtx.id)
+
+ return AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(
+ listOf(legacyContractAttachment, upgradedContractAttachment),
+ networkParameters,
+ wtx.id,
+ ::isAttachmentTrusted,
+ attachmentsClassLoaderCache = attachmentsClassLoaderCache
+ ) { serializationContext ->
+ val upgradedContract = loadUpgradedContract(wtx.upgradedContractClassName, wtx.id, serializationContext.deserializationClassLoader)
+ val outputState = calculateUpgradedState(binaryInput.deserialize(), upgradedContract, upgradedContractAttachment)
+ outputState.serialize()
+ }
+ }
+
+ /**
+ * This should return a serialized virtual output state, that will be used to verify spending transactions.
+ * The binary output should not depend on the classpath of the node that is verifying the transaction.
+ *
+ * Ideally the serialization engine would support partial deserialization so that only the Notary ( and the encumbrance can be replaced
+ * from the binary input state)
+ */
+ // TODO - currently this uses the main classloader.
+ private fun getNotaryChangeOutput(wtx: NotaryChangeWireTransaction, outputIndex: Int): SerializedTransactionState {
+ val input = getStateAndRef(wtx.inputs[outputIndex])
+ val output = NotaryChangeLedgerTransaction.computeOutput(input, wtx.newNotary) { wtx.inputs }
+ return output.serialize()
+ }
+
+ /**
+ * Scans trusted (installed locally) attachments to find all that contain the [className].
+ * This is required as a workaround until explicit cordapp dependencies are implemented.
+ *
+ * @return the attachments with the highest version.
+ */
+ // TODO Should throw when the class is found in multiple contract attachments (not different versions).
+ override fun getTrustedClassAttachment(className: String): Attachment? {
+ val allTrusted = attachments.queryAttachments(
+ AttachmentQueryCriteria.AttachmentsQueryCriteria().withUploader(Builder.`in`(TRUSTED_UPLOADERS)),
+ AttachmentSort(listOf(AttachmentSort.AttachmentSortColumn(AttachmentSortAttribute.VERSION, Sort.Direction.DESC)))
+ )
+
+ // TODO - add caching if performance is affected.
+ for (attId in allTrusted) {
+ val attch = attachments.openAttachment(attId)!!
+ if (attch.openAsJAR().use { hasFile(it, "$className.class") }) return attch
+ }
+ return null
+ }
+
+ private fun hasFile(jarStream: JarInputStream, className: String): Boolean {
+ while (true) {
+ val e = jarStream.nextJarEntry ?: return false
+ if (e.name == className) {
+ return true
+ }
+ }
+ }
+
+ override fun isAttachmentTrusted(attachment: Attachment): Boolean = attachmentTrustCalculator.calculate(attachment)
+
+ override fun fixupAttachmentIds(attachmentIds: Collection): Set {
+ return cordappProvider.fixupAttachmentIds(attachmentIds)
+ }
+
+ /**
+ * Try to verify the given transaction on the external verifier, assuming it is available. It is not required to verify externally even
+ * if the verifier is available.
+ *
+ * The default implementation is to only do internal verification.
+ *
+ * @return true if the transaction should (also) be verified internally, regardless of whether it was verified externally.
+ */
+ fun tryExternalVerification(stx: SignedTransaction, checkSufficientSignatures: Boolean): Boolean {
+ return true
+ }
+}
+
+fun ServicesForResolution.toVerifyingServiceHub(): VerifyingServiceHub {
+ if (this is VerifyingServiceHub) {
+ return this
+ }
+ // All ServicesForResolution instances should also implement VerifyingServiceHub, which is something we can enforce with the
+ // @DoNotImplement annotation. The only exception however is MockServices, which does not since it's public API and VerifyingServiceHub
+ // is internal. Instead, MockServices has a private VerifyingServiceHub "view" which we get at via reflection.
+ var clazz: Class<*> = javaClass
+ while (true) {
+ if (clazz.name == "net.corda.testing.node.MockServices") {
+ return clazz.getDeclaredMethod("getVerifyingView").apply { isAccessible = true }.invoke(this) as VerifyingServiceHub
+ }
+ clazz = clazz.superclass ?: throw ClassCastException("${javaClass.name} is not a VerifyingServiceHub")
+ }
+}
diff --git a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt
index 2f8c097804..2bf4bcdfcd 100644
--- a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt
+++ b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt
@@ -11,6 +11,7 @@ import net.corda.core.crypto.TransactionSignature
import net.corda.core.flows.ContractUpgradeFlow
import net.corda.core.internal.PlatformVersionSwitches.TWO_PHASE_FINALITY
import net.corda.core.internal.telemetry.TelemetryComponent
+import net.corda.core.internal.uncheckedCast
import net.corda.core.node.services.*
import net.corda.core.node.services.diagnostics.DiagnosticsService
import net.corda.core.serialization.CordaSerializable
@@ -170,12 +171,6 @@ interface ServiceHub : ServicesForResolution {
*/
val telemetryService: TelemetryService
- /**
- * INTERNAL. DO NOT USE.
- * @suppress
- */
- val transactionVerifierService: TransactionVerifierService
-
/**
* A [Clock] representing the node's current time. This should be used in preference to directly accessing the
* clock so the current time can be controlled during unit testing.
@@ -283,8 +278,7 @@ interface ServiceHub : ServicesForResolution {
*/
@Throws(TransactionResolutionException::class)
fun toStateAndRef(stateRef: StateRef): StateAndRef {
- val stx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
- return stx.resolveBaseTransaction(this).outRef(stateRef.index)
+ return StateAndRef(uncheckedCast(loadState(stateRef)), stateRef)
}
private val legalIdentityKey: PublicKey get() = this.myInfo.legalIdentitiesAndCerts.first().owningKey
@@ -424,8 +418,8 @@ interface ServiceHub : ServicesForResolution {
* When used within a flow, this session automatically forms part of the enclosing flow transaction boundary,
* and thus queryable data will include everything committed as of the last checkpoint.
*
- * We want to make sure users have a restricted access to administrative functions, this function will return a [RestrictedConnection] instance.
- * The following methods are blocked:
+ * We want to make sure users have a restricted access to administrative functions, this function will return a [Connection] instance
+ * with the following methods blocked:
* - abort(executor: Executor?)
* - clearWarnings()
* - close()
diff --git a/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt b/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt
deleted file mode 100644
index d72eec72f2..0000000000
--- a/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package net.corda.core.node.services
-
-import net.corda.core.DoNotImplement
-import net.corda.core.concurrent.CordaFuture
-import net.corda.core.transactions.LedgerTransaction
-
-/**
- * Provides verification service. The implementation may be a simple in-memory verify() call or perhaps an IPC/RPC.
- * @suppress
- */
-@DoNotImplement
-interface TransactionVerifierService {
- /**
- * @param transaction The transaction to be verified.
- * @return A future that completes successfully if the transaction verified, or sets an exception the verifier threw.
- */
- fun verify(transaction: LedgerTransaction): CordaFuture<*>
-}
\ No newline at end of file
diff --git a/core/src/main/kotlin/net/corda/core/transactions/ContractUpgradeTransactions.kt b/core/src/main/kotlin/net/corda/core/transactions/ContractUpgradeTransactions.kt
index 145da6a07c..f26ccd00ad 100644
--- a/core/src/main/kotlin/net/corda/core/transactions/ContractUpgradeTransactions.kt
+++ b/core/src/main/kotlin/net/corda/core/transactions/ContractUpgradeTransactions.kt
@@ -1,28 +1,44 @@
package net.corda.core.transactions
import net.corda.core.CordaInternal
-import net.corda.core.contracts.*
+import net.corda.core.contracts.Attachment
+import net.corda.core.contracts.AttachmentResolutionException
+import net.corda.core.contracts.ContractAttachment
+import net.corda.core.contracts.ContractClassName
+import net.corda.core.contracts.ContractState
+import net.corda.core.contracts.HashAttachmentConstraint
+import net.corda.core.contracts.PrivacySalt
+import net.corda.core.contracts.StateAndRef
+import net.corda.core.contracts.StateRef
+import net.corda.core.contracts.TransactionResolutionException
+import net.corda.core.contracts.TransactionState
+import net.corda.core.contracts.TransactionVerificationException
+import net.corda.core.contracts.UpgradedContract
+import net.corda.core.contracts.UpgradedContractWithLegacyConstraint
+import net.corda.core.contracts.WhitelistedByZoneAttachmentConstraint
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.Party
import net.corda.core.internal.AttachmentWithContext
-import net.corda.core.internal.ServiceHubCoreInternal
import net.corda.core.internal.combinedHash
+import net.corda.core.internal.loadClassOfType
+import net.corda.core.internal.mapToSet
+import net.corda.core.internal.verification.VerificationSupport
+import net.corda.core.internal.verification.toVerifyingServiceHub
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
-import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
-import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
-import net.corda.core.serialization.serialize
import net.corda.core.transactions.ContractUpgradeFilteredTransaction.FilteredComponent
-import net.corda.core.transactions.ContractUpgradeLedgerTransaction.Companion.loadUpgradedContract
-import net.corda.core.transactions.ContractUpgradeLedgerTransaction.Companion.retrieveAppClassLoader
import net.corda.core.transactions.ContractUpgradeWireTransaction.Companion.calculateUpgradedState
-import net.corda.core.transactions.ContractUpgradeWireTransaction.Component.*
-import net.corda.core.transactions.WireTransaction.Companion.resolveStateRefBinaryComponent
+import net.corda.core.transactions.ContractUpgradeWireTransaction.Component.INPUTS
+import net.corda.core.transactions.ContractUpgradeWireTransaction.Component.LEGACY_ATTACHMENT
+import net.corda.core.transactions.ContractUpgradeWireTransaction.Component.NOTARY
+import net.corda.core.transactions.ContractUpgradeWireTransaction.Component.PARAMETERS_HASH
+import net.corda.core.transactions.ContractUpgradeWireTransaction.Component.UPGRADED_ATTACHMENT
+import net.corda.core.transactions.ContractUpgradeWireTransaction.Component.UPGRADED_CONTRACT
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.toBase58String
import java.security.PublicKey
@@ -52,7 +68,10 @@ data class ContractUpgradeWireTransaction(
* Runs the explicit upgrade logic.
*/
@CordaInternal
- internal fun calculateUpgradedState(state: TransactionState, upgradedContract: UpgradedContract, upgradedContractAttachment: Attachment): TransactionState {
+ @JvmSynthetic
+ internal fun calculateUpgradedState(state: TransactionState,
+ upgradedContract: UpgradedContract,
+ upgradedContractAttachment: Attachment): TransactionState {
// TODO: if there are encumbrance states in the inputs, just copy them across without modifying
val upgradedState: S = upgradedContract.upgrade(state.data)
val inputConstraint = state.constraint
@@ -121,60 +140,12 @@ data class ContractUpgradeWireTransaction(
/** Resolves input states and contract attachments, and builds a ContractUpgradeLedgerTransaction. */
fun resolve(services: ServicesForResolution, sigs: List): ContractUpgradeLedgerTransaction {
- val resolvedInputs = services.loadStates(inputs.toSet()).toList()
- val legacyContractAttachment = services.attachments.openAttachment(legacyContractAttachmentId)
- ?: throw AttachmentResolutionException(legacyContractAttachmentId)
- val upgradedContractAttachment = services.attachments.openAttachment(upgradedContractAttachmentId)
- ?: throw AttachmentResolutionException(upgradedContractAttachmentId)
- val hashToResolve = networkParametersHash ?: services.networkParametersService.defaultHash
- val resolvedNetworkParameters = services.networkParametersService.lookup(hashToResolve) ?: throw TransactionResolutionException(id)
- return ContractUpgradeLedgerTransaction.create(
- resolvedInputs,
- notary,
- legacyContractAttachment,
- upgradedContractAttachment,
- id,
- privacySalt,
- sigs,
- resolvedNetworkParameters,
- loadUpgradedContract(upgradedContractClassName, retrieveAppClassLoader(services))
- )
- }
-
- private fun upgradedContract(className: ContractClassName, classLoader: ClassLoader): UpgradedContract = try {
- @Suppress("UNCHECKED_CAST")
- Class.forName(className, false, classLoader).asSubclass(UpgradedContract::class.java).getDeclaredConstructor().newInstance() as UpgradedContract
- } catch (e: Exception) {
- throw TransactionVerificationException.ContractCreationError(id, className, e)
- }
-
- /**
- * Creates a binary serialized component for a virtual output state serialised and executed with the attachments from the transaction.
- */
- @CordaInternal
- internal fun resolveOutputComponent(services: ServicesForResolution, stateRef: StateRef, params: NetworkParameters): SerializedBytes> {
- val binaryInput: SerializedBytes> = resolveStateRefBinaryComponent(inputs[stateRef.index], services)!!
- val legacyAttachment = services.attachments.openAttachment(legacyContractAttachmentId)
- ?: throw MissingContractAttachments(emptyList())
- val upgradedAttachment = services.attachments.openAttachment(upgradedContractAttachmentId)
- ?: throw MissingContractAttachments(emptyList())
-
- return AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(
- listOf(legacyAttachment, upgradedAttachment),
- params,
- id,
- { (services as ServiceHubCoreInternal).attachmentTrustCalculator.calculate(it) },
- attachmentsClassLoaderCache = (services as ServiceHubCoreInternal).attachmentsClassLoaderCache) { serializationContext ->
- val resolvedInput = binaryInput.deserialize()
- val upgradedContract = upgradedContract(upgradedContractClassName, serializationContext.deserializationClassLoader)
- val outputState = calculateUpgradedState(resolvedInput, upgradedContract, upgradedAttachment)
- outputState.serialize()
- }
+ return ContractUpgradeLedgerTransaction.resolve(services.toVerifyingServiceHub(), this, sigs)
}
/** Constructs a filtered transaction: the inputs, the notary party and network parameters hash are always visible, while the rest are hidden. */
fun buildFilteredTransaction(): ContractUpgradeFilteredTransaction {
- val totalComponents = (0 until serializedComponents.size).toSet()
+ val totalComponents = serializedComponents.indices.toSet()
val visibleComponents = mapOf(
INPUTS.ordinal to FilteredComponent(serializedComponents[INPUTS.ordinal], nonces[INPUTS.ordinal]),
NOTARY.ordinal to FilteredComponent(serializedComponents[NOTARY.ordinal], nonces[NOTARY.ordinal]),
@@ -287,39 +258,47 @@ private constructor(
get() = upgradedContract::class.java.name
companion object {
-
@CordaInternal
- internal fun create(
- inputs: List>,
- notary: Party,
- legacyContractAttachment: Attachment,
- upgradedContractAttachment: Attachment,
- id: SecureHash,
- privacySalt: PrivacySalt,
- sigs: List,
- networkParameters: NetworkParameters,
- upgradedContract: UpgradedContract
- ): ContractUpgradeLedgerTransaction {
- return ContractUpgradeLedgerTransaction(inputs, notary, legacyContractAttachment, upgradedContractAttachment, id, privacySalt, sigs, networkParameters, upgradedContract)
+ @JvmSynthetic
+ @Suppress("ThrowsCount")
+ internal fun resolve(verificationSupport: VerificationSupport,
+ wtx: ContractUpgradeWireTransaction,
+ sigs: List): ContractUpgradeLedgerTransaction {
+ val inputs = wtx.inputs.map(verificationSupport::getStateAndRef)
+ val (legacyContractAttachment, upgradedContractAttachment) = verificationSupport.getAttachments(listOf(
+ wtx.legacyContractAttachmentId,
+ wtx.upgradedContractAttachmentId
+ ))
+ val networkParameters = verificationSupport.getNetworkParameters(wtx.networkParametersHash)
+ ?: throw TransactionResolutionException(wtx.id)
+ val upgradedContract = loadUpgradedContract(wtx.upgradedContractClassName, wtx.id, verificationSupport.appClassLoader)
+ return ContractUpgradeLedgerTransaction(
+ inputs,
+ wtx.notary,
+ legacyContractAttachment ?: throw AttachmentResolutionException(wtx.legacyContractAttachmentId),
+ upgradedContractAttachment ?: throw AttachmentResolutionException(wtx.upgradedContractAttachmentId),
+ wtx.id,
+ wtx.privacySalt,
+ sigs,
+ networkParameters,
+ upgradedContract
+ )
}
- // TODO - this has to use a classloader created from the upgraded attachment.
+ // TODO There is an inconsistency with the class loader used with this method. Transaction resolution uses the app class loader,
+ // whilst TransactionStorageVerification.getContractUpdateOutput uses an attachments class loder comprised of the the legacy and
+ // upgraded attachments
@CordaInternal
- internal fun loadUpgradedContract(upgradedContractClassName: ContractClassName, classLoader: ClassLoader): UpgradedContract {
- @Suppress("UNCHECKED_CAST")
- return Class.forName(upgradedContractClassName, false, classLoader)
- .asSubclass(Contract::class.java)
- .getConstructor()
- .newInstance() as UpgradedContract
- }
-
- // This is a "hack" to retrieve the CordappsClassloader from the services without having access to all classes.
- @CordaInternal
- internal fun retrieveAppClassLoader(services: ServicesForResolution): ClassLoader {
- val cordappLoader = services.cordappProvider::class.java.getMethod("getCordappLoader").invoke(services.cordappProvider)
-
- @Suppress("UNCHECKED_CAST")
- return cordappLoader::class.java.getMethod("getAppClassLoader").invoke(cordappLoader) as ClassLoader
+ @JvmSynthetic
+ @Suppress("TooGenericExceptionCaught")
+ internal fun loadUpgradedContract(className: ContractClassName, id: SecureHash, classLoader: ClassLoader): UpgradedContract {
+ return try {
+ loadClassOfType>(className, false, classLoader)
+ .getDeclaredConstructor()
+ .newInstance()
+ } catch (e: Exception) {
+ throw TransactionVerificationException.ContractCreationError(id, className, e)
+ }
}
}
@@ -366,7 +345,7 @@ private constructor(
/** The required signers are the set of all input states' participants. */
override val requiredSigningKeys: Set
- get() = inputs.flatMap { it.state.data.participants }.map { it.owningKey }.toSet() + notary.owningKey
+ get() = inputs.flatMap { it.state.data.participants }.mapToSet { it.owningKey } + notary.owningKey
override fun getKeyDescriptions(keys: Set): List {
return keys.map { it.toBase58String() }
@@ -401,7 +380,7 @@ private constructor(
privacySalt: PrivacySalt,
sigs: List,
networkParameters: NetworkParameters
- ) : this(inputs, notary, legacyContractAttachment, upgradedContractAttachment, id, privacySalt, sigs, networkParameters, loadUpgradedContract(upgradedContractClassName, ContractUpgradeLedgerTransaction::class.java.classLoader))
+ ) : this(inputs, notary, legacyContractAttachment, upgradedContractAttachment, id, privacySalt, sigs, networkParameters, loadUpgradedContract(upgradedContractClassName, id, ContractUpgradeLedgerTransaction::class.java.classLoader))
@Deprecated("ContractUpgradeLedgerTransaction should not be created directly, use ContractUpgradeWireTransaction.resolve instead.")
fun copy(
diff --git a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt
index 846799d0b3..8037668b68 100644
--- a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt
+++ b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt
@@ -16,21 +16,21 @@ import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.Party
-import net.corda.core.internal.AbstractVerifier
import net.corda.core.internal.SerializedStateAndRef
-import net.corda.core.internal.Verifier
import net.corda.core.internal.castIfPossible
import net.corda.core.internal.deserialiseCommands
import net.corda.core.internal.deserialiseComponentGroup
import net.corda.core.internal.eagerDeserialise
import net.corda.core.internal.isUploaderTrusted
import net.corda.core.internal.uncheckedCast
+import net.corda.core.internal.verification.AbstractVerifier
+import net.corda.core.internal.verification.Verifier
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationFactory
-import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
+import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
import net.corda.core.utilities.contextLogger
import java.util.Collections.unmodifiableList
import java.util.function.Predicate
@@ -153,34 +153,35 @@ private constructor(
serializedInputs: List? = null,
serializedReferences: List? = null,
isAttachmentTrusted: (Attachment) -> Boolean,
+ verifierFactory: (LedgerTransaction, SerializationContext) -> Verifier,
attachmentsClassLoaderCache: AttachmentsClassLoaderCache?,
digestService: DigestService
): LedgerTransaction {
return LedgerTransaction(
- inputs = inputs,
- outputs = outputs,
- commands = commands,
- attachments = attachments,
- id = id,
- notary = notary,
- timeWindow = timeWindow,
- privacySalt = privacySalt,
- networkParameters = networkParameters,
- references = references,
- componentGroups = protectOrNull(componentGroups),
- serializedInputs = protectOrNull(serializedInputs),
- serializedReferences = protectOrNull(serializedReferences),
- isAttachmentTrusted = isAttachmentTrusted,
- verifierFactory = ::BasicVerifier,
- attachmentsClassLoaderCache = attachmentsClassLoaderCache,
- digestService = digestService
+ inputs = inputs,
+ outputs = outputs,
+ commands = commands,
+ attachments = attachments,
+ id = id,
+ notary = notary,
+ timeWindow = timeWindow,
+ privacySalt = privacySalt,
+ networkParameters = networkParameters,
+ references = references,
+ componentGroups = protectOrNull(componentGroups),
+ serializedInputs = protectOrNull(serializedInputs),
+ serializedReferences = protectOrNull(serializedReferences),
+ isAttachmentTrusted = isAttachmentTrusted,
+ verifierFactory = verifierFactory,
+ attachmentsClassLoaderCache = attachmentsClassLoaderCache,
+ digestService = digestService
)
}
/**
* This factory function will create an instance of [LedgerTransaction]
* that will be used for contract verification.
- * @see BasicVerifier
+ * @see DefaultVerifier
*/
@CordaInternal
fun createForContractVerify(
@@ -243,26 +244,26 @@ private constructor(
*/
@Throws(TransactionVerificationException::class)
fun verify() {
- internalPrepareVerify(attachments).verify()
+ verifyInternal()
}
/**
* This method has to be called in a context where it has access to the database.
*/
@CordaInternal
- internal fun internalPrepareVerify(txAttachments: List): Verifier {
+ @JvmSynthetic
+ internal fun verifyInternal(txAttachments: List = this.attachments) {
// Switch thread local deserialization context to using a cached attachments classloader. This classloader enforces various rules
// like no-overlap, package namespace ownership and (in future) deterministic Java.
- return AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(
+ val verifier = AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(
txAttachments,
getParamsWithGoo(),
id,
- isAttachmentTrusted = isAttachmentTrusted,
- attachmentsClassLoaderCache = attachmentsClassLoaderCache) { serializationContext ->
-
+ isAttachmentTrusted,
+ attachmentsClassLoaderCache = attachmentsClassLoaderCache
+ ) { serializationContext ->
// Legacy check - warns if the LedgerTransaction was created incorrectly.
checkLtxForVerification()
-
// Create a copy of the outer LedgerTransaction which deserializes all fields using
// the serialization context (or its deserializationClassloader).
// Only the copy will be used for verification, and the outer shell will be discarded.
@@ -270,6 +271,7 @@ private constructor(
// NOTE: The Verifier creates the copies of the LedgerTransaction object now.
verifierFactory(this, serializationContext)
}
+ verifier.verify()
}
/**
@@ -463,7 +465,7 @@ private constructor(
}
inline fun filterInputs(crossinline predicate: (T) -> Boolean): List {
- return filterInputs(T::class.java, Predicate { predicate(it) })
+ return filterInputs(T::class.java) { predicate(it) }
}
/**
@@ -479,7 +481,7 @@ private constructor(
}
inline fun filterReferenceInputs(crossinline predicate: (T) -> Boolean): List {
- return filterReferenceInputs(T::class.java, Predicate { predicate(it) })
+ return filterReferenceInputs(T::class.java) { predicate(it) }
}
/**
@@ -495,7 +497,7 @@ private constructor(
}
inline fun filterInRefs(crossinline predicate: (T) -> Boolean): List> {
- return filterInRefs(T::class.java, Predicate { predicate(it) })
+ return filterInRefs(T::class.java) { predicate(it) }
}
/**
@@ -511,7 +513,7 @@ private constructor(
}
inline fun filterReferenceInputRefs(crossinline predicate: (T) -> Boolean): List> {
- return filterReferenceInputRefs(T::class.java, Predicate { predicate(it) })
+ return filterReferenceInputRefs(T::class.java) { predicate(it) }
}
/**
@@ -528,7 +530,7 @@ private constructor(
}
inline fun findInput(crossinline predicate: (T) -> Boolean): T {
- return findInput(T::class.java, Predicate { predicate(it) })
+ return findInput(T::class.java) { predicate(it) }
}
/**
@@ -541,11 +543,11 @@ private constructor(
* @throws IllegalArgumentException if no item, or multiple items are found matching the requirements.
*/
fun findReference(clazz: Class, predicate: Predicate): T {
- return referenceInputsOfType(clazz).single { predicate.test(it) }
+ return referenceInputsOfType(clazz).single(predicate::test)
}
inline fun findReference(crossinline predicate: (T) -> Boolean): T {
- return findReference(T::class.java, Predicate { predicate(it) })
+ return findReference(T::class.java) { predicate(it) }
}
/**
@@ -562,7 +564,7 @@ private constructor(
}
inline fun findInRef(crossinline predicate: (T) -> Boolean): StateAndRef {
- return findInRef(T::class.java, Predicate { predicate(it) })
+ return findInRef(T::class.java) { predicate(it) }
}
/**
@@ -579,7 +581,7 @@ private constructor(
}
inline fun findReferenceInputRef(crossinline predicate: (T) -> Boolean): StateAndRef {
- return findReferenceInputRef(T::class.java, Predicate { predicate(it) })
+ return findReferenceInputRef(T::class.java) { predicate(it) }
}
/**
@@ -614,7 +616,7 @@ private constructor(
}
inline fun filterCommands(crossinline predicate: (T) -> Boolean): List> {
- return filterCommands(T::class.java, Predicate { predicate(it) })
+ return filterCommands(T::class.java) { predicate(it) }
}
/**
@@ -631,7 +633,7 @@ private constructor(
}
inline fun findCommand(crossinline predicate: (T) -> Boolean): Command {
- return findCommand(T::class.java, Predicate { predicate(it) })
+ return findCommand(T::class.java) { predicate(it) }
}
/**
@@ -706,7 +708,7 @@ private constructor(
serializedInputs = null,
serializedReferences = null,
isAttachmentTrusted = Attachment::isUploaderTrusted,
- verifierFactory = ::BasicVerifier,
+ verifierFactory = ::DefaultVerifier,
attachmentsClassLoaderCache = null
)
@@ -736,7 +738,7 @@ private constructor(
serializedInputs = null,
serializedReferences = null,
isAttachmentTrusted = Attachment::isUploaderTrusted,
- verifierFactory = ::BasicVerifier,
+ verifierFactory = ::DefaultVerifier,
attachmentsClassLoaderCache = null
)
@@ -804,14 +806,19 @@ private constructor(
}
}
+@CordaInternal
+@JvmSynthetic
+fun defaultVerifier(ltx: LedgerTransaction, serializationContext: SerializationContext): Verifier {
+ return DefaultVerifier(ltx, serializationContext)
+}
+
/**
* This is the default [Verifier] that configures Corda
* to execute [Contract.verify(LedgerTransaction)].
*
* THIS CLASS IS NOT PUBLIC API, AND IS DELIBERATELY PRIVATE!
*/
-@CordaInternal
-private class BasicVerifier(
+private class DefaultVerifier(
ltx: LedgerTransaction,
private val serializationContext: SerializationContext
) : AbstractVerifier(ltx, serializationContext.deserializationClassLoader) {
@@ -874,7 +881,6 @@ private class BasicVerifier(
* THIS CLASS IS NOT PUBLIC API, AND IS DELIBERATELY PRIVATE!
*/
@Suppress("unused_parameter")
-@CordaInternal
private class NoOpVerifier(ltx: LedgerTransaction, serializationContext: SerializationContext) : Verifier {
// Invoking LedgerTransaction.verify() from Contract.verify(LedgerTransaction)
// will execute this function. But why would anyone do that?!
diff --git a/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt b/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt
index ab42260f37..cf5db15911 100644
--- a/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt
+++ b/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt
@@ -1,20 +1,31 @@
package net.corda.core.transactions
import net.corda.core.CordaInternal
-import net.corda.core.contracts.*
+import net.corda.core.contracts.ContractState
+import net.corda.core.contracts.StateAndRef
+import net.corda.core.contracts.StateRef
+import net.corda.core.contracts.TransactionResolutionException
+import net.corda.core.contracts.TransactionState
+import net.corda.core.contracts.TransactionVerificationException
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.Party
+import net.corda.core.internal.indexOfOrThrow
+import net.corda.core.internal.mapToSet
+import net.corda.core.internal.verification.VerificationSupport
+import net.corda.core.internal.verification.toVerifyingServiceHub
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
-import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
-import net.corda.core.transactions.NotaryChangeWireTransaction.Component.*
+import net.corda.core.transactions.NotaryChangeWireTransaction.Component.INPUTS
+import net.corda.core.transactions.NotaryChangeWireTransaction.Component.NEW_NOTARY
+import net.corda.core.transactions.NotaryChangeWireTransaction.Component.NOTARY
+import net.corda.core.transactions.NotaryChangeWireTransaction.Component.PARAMETERS_HASH
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.toBase58String
import java.security.PublicKey
@@ -88,32 +99,12 @@ data class NotaryChangeWireTransaction(
/** Resolves input states and network parameters and builds a [NotaryChangeLedgerTransaction]. */
fun resolve(services: ServicesForResolution, sigs: List): NotaryChangeLedgerTransaction {
- val resolvedInputs = services.loadStates(inputs.toSet()).toList()
- val hashToResolve = networkParametersHash ?: services.networkParametersService.defaultHash
- val resolvedNetworkParameters = services.networkParametersService.lookup(hashToResolve)
- ?: throw TransactionResolutionException(id)
- return NotaryChangeLedgerTransaction.create(resolvedInputs, notary, newNotary, id, sigs, resolvedNetworkParameters)
+ return NotaryChangeLedgerTransaction.resolve(services.toVerifyingServiceHub(), this, sigs)
}
/** Resolves input states and builds a [NotaryChangeLedgerTransaction]. */
- fun resolve(services: ServiceHub, sigs: List) = resolve(services as ServicesForResolution, sigs)
-
- /**
- * This should return a serialized virtual output state, that will be used to verify spending transactions.
- * The binary output should not depend on the classpath of the node that is verifying the transaction.
- *
- * Ideally the serialization engine would support partial deserialization so that only the Notary ( and the encumbrance can be replaced from the binary input state)
- *
- *
- * TODO - currently this uses the main classloader.
- */
- @CordaInternal
- internal fun resolveOutputComponent(
- services: ServicesForResolution,
- stateRef: StateRef,
- @Suppress("UNUSED_PARAMETER") params: NetworkParameters
- ): SerializedBytes> {
- return services.loadState(stateRef).serialize()
+ fun resolve(services: ServiceHub, sigs: List): NotaryChangeLedgerTransaction {
+ return resolve(services as ServicesForResolution, sigs)
}
enum class Component {
@@ -140,13 +131,25 @@ private constructor(
) : FullTransaction(), TransactionWithSignatures {
companion object {
@CordaInternal
- internal fun create(inputs: List>,
- notary: Party,
- newNotary: Party,
- id: SecureHash,
- sigs: List,
- networkParameters: NetworkParameters): NotaryChangeLedgerTransaction {
- return NotaryChangeLedgerTransaction(inputs, notary, newNotary, id, sigs, networkParameters)
+ @JvmSynthetic
+ internal fun resolve(verificationSupport: VerificationSupport,
+ wireTx: NotaryChangeWireTransaction,
+ sigs: List): NotaryChangeLedgerTransaction {
+ val inputs = wireTx.inputs.map(verificationSupport::getStateAndRef)
+ val networkParameters = verificationSupport.getNetworkParameters(wireTx.networkParametersHash)
+ ?: throw TransactionResolutionException(wireTx.id)
+ return NotaryChangeLedgerTransaction(inputs, wireTx.notary, wireTx.newNotary, wireTx.id, sigs, networkParameters)
+ }
+
+ @CordaInternal
+ @JvmSynthetic
+ internal inline fun computeOutput(input: StateAndRef<*>, newNotary: Party, inputs: () -> List): TransactionState<*> {
+ val (state, ref) = input
+ val newEncumbrance = state.encumbrance?.let {
+ val encumbranceStateRef = ref.copy(index = state.encumbrance)
+ inputs().indexOfOrThrow(encumbranceStateRef)
+ }
+ return state.copy(notary = newNotary, encumbrance = newEncumbrance)
}
}
@@ -174,22 +177,10 @@ private constructor(
/** We compute the outputs on demand by applying the notary field modification to the inputs. */
override val outputs: List>
- get() = computeOutputs()
-
- private fun computeOutputs(): List> {
- val inputPositionIndex: Map = inputs.mapIndexed { index, stateAndRef -> stateAndRef.ref to index }.toMap()
- return inputs.map { (state, ref) ->
- if (state.encumbrance != null) {
- val encumbranceStateRef = StateRef(ref.txhash, state.encumbrance)
- val encumbrancePosition = inputPositionIndex[encumbranceStateRef]
- ?: throw IllegalStateException("Unable to generate output states – transaction not constructed correctly.")
- state.copy(notary = newNotary, encumbrance = encumbrancePosition)
- } else state.copy(notary = newNotary)
- }
- }
+ get() = inputs.map { computeOutput(it, newNotary) { inputs.map(StateAndRef::ref) } }
override val requiredSigningKeys: Set
- get() = inputs.flatMap { it.state.data.participants }.map { it.owningKey }.toSet() + notary.owningKey
+ get() = inputs.flatMap { it.state.data.participants }.mapToSet { it.owningKey } + notary.owningKey
override fun getKeyDescriptions(keys: Set): List {
return keys.map { it.toBase58String() }
diff --git a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt
index 94e2079967..b1d6f91b6b 100644
--- a/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt
+++ b/core/src/main/kotlin/net/corda/core/transactions/SignedTransaction.kt
@@ -1,27 +1,40 @@
package net.corda.core.transactions
import net.corda.core.CordaException
+import net.corda.core.CordaInternal
import net.corda.core.CordaThrowable
-import net.corda.core.contracts.*
-import net.corda.core.crypto.*
+import net.corda.core.contracts.Attachment
+import net.corda.core.contracts.AttachmentResolutionException
+import net.corda.core.contracts.NamedByHash
+import net.corda.core.contracts.StateRef
+import net.corda.core.contracts.TransactionResolutionException
+import net.corda.core.contracts.TransactionVerificationException
+import net.corda.core.crypto.SecureHash
+import net.corda.core.crypto.SignableData
+import net.corda.core.crypto.SignatureMetadata
+import net.corda.core.crypto.TransactionSignature
+import net.corda.core.crypto.sign
+import net.corda.core.crypto.toStringShort
import net.corda.core.identity.Party
import net.corda.core.internal.TransactionDeserialisationException
-import net.corda.core.internal.TransactionVerifierServiceInternal
import net.corda.core.internal.VisibleForTesting
+import net.corda.core.internal.equivalent
+import net.corda.core.internal.isUploaderTrusted
+import net.corda.core.internal.verification.VerificationSupport
+import net.corda.core.internal.verification.toVerifyingServiceHub
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.CordaSerializable
+import net.corda.core.serialization.MissingAttachmentsException
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.internal.MissingSerializerException
import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger
-import net.corda.core.utilities.getOrThrow
import java.io.NotSerializableException
import java.security.KeyPair
import java.security.PublicKey
import java.security.SignatureException
-import java.util.*
import java.util.function.Predicate
/**
@@ -142,6 +155,12 @@ data class SignedTransaction(val txBits: SerializedBytes,
@JvmOverloads
@Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServiceHub, checkSufficientSignatures: Boolean = true): LedgerTransaction {
+ // We need parameters check here, because finality flow calls stx.toLedgerTransaction() and then verify.
+ resolveAndCheckNetworkParameters(services)
+ return toLedgerTransactionInternal(services.toVerifyingServiceHub(), checkSufficientSignatures)
+ }
+
+ private fun toLedgerTransactionInternal(verificationSupport: VerificationSupport, checkSufficientSignatures: Boolean): LedgerTransaction {
// TODO: We could probably optimise the below by
// a) not throwing if threshold is eventually satisfied, but some of the rest of the signatures are failing.
// b) omit verifying signatures when threshold requirement is met.
@@ -154,9 +173,7 @@ data class SignedTransaction(val txBits: SerializedBytes,
} else {
checkSignaturesAreValid()
}
- // We need parameters check here, because finality flow calls stx.toLedgerTransaction() and then verify.
- resolveAndCheckNetworkParameters(services)
- return tx.toLedgerTransaction(services)
+ return tx.toLedgerTransactionInternal(verificationSupport)
}
/**
@@ -173,10 +190,19 @@ data class SignedTransaction(val txBits: SerializedBytes,
@Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class)
fun verify(services: ServiceHub, checkSufficientSignatures: Boolean = true) {
resolveAndCheckNetworkParameters(services)
+ val verifyingServiceHub = services.toVerifyingServiceHub()
+ if (verifyingServiceHub.tryExternalVerification(this, checkSufficientSignatures)) {
+ verifyInternal(verifyingServiceHub, checkSufficientSignatures)
+ }
+ }
+
+ @CordaInternal
+ @JvmSynthetic
+ fun verifyInternal(verificationSupport: VerificationSupport, checkSufficientSignatures: Boolean) {
when (coreTransaction) {
- is NotaryChangeWireTransaction -> verifyNotaryChangeTransaction(services, checkSufficientSignatures)
- is ContractUpgradeWireTransaction -> verifyContractUpgradeTransaction(services, checkSufficientSignatures)
- else -> verifyRegularTransaction(services, checkSufficientSignatures)
+ is NotaryChangeWireTransaction -> verifyNotaryChangeTransaction(verificationSupport, checkSufficientSignatures)
+ is ContractUpgradeWireTransaction -> verifyContractUpgradeTransaction(verificationSupport, checkSufficientSignatures)
+ else -> verifyRegularTransaction(verificationSupport, checkSufficientSignatures)
}
}
@@ -197,15 +223,15 @@ data class SignedTransaction(val txBits: SerializedBytes,
}
/** No contract code is run when verifying notary change transactions, it is sufficient to check invariants during initialisation. */
- private fun verifyNotaryChangeTransaction(services: ServiceHub, checkSufficientSignatures: Boolean) {
- val ntx = resolveNotaryChangeTransaction(services)
+ private fun verifyNotaryChangeTransaction(verificationSupport: VerificationSupport, checkSufficientSignatures: Boolean) {
+ val ntx = NotaryChangeLedgerTransaction.resolve(verificationSupport, coreTransaction as NotaryChangeWireTransaction, sigs)
if (checkSufficientSignatures) ntx.verifyRequiredSignatures()
else checkSignaturesAreValid()
}
/** No contract code is run when verifying contract upgrade transactions, it is sufficient to check invariants during initialisation. */
- private fun verifyContractUpgradeTransaction(services: ServicesForResolution, checkSufficientSignatures: Boolean) {
- val ctx = resolveContractUpgradeTransaction(services)
+ private fun verifyContractUpgradeTransaction(verificationSupport: VerificationSupport, checkSufficientSignatures: Boolean) {
+ val ctx = ContractUpgradeLedgerTransaction.resolve(verificationSupport, coreTransaction as ContractUpgradeWireTransaction, sigs)
if (checkSufficientSignatures) ctx.verifyRequiredSignatures()
else checkSignaturesAreValid()
}
@@ -213,22 +239,21 @@ data class SignedTransaction(val txBits: SerializedBytes,
// TODO: Verify contract constraints here as well as in LedgerTransaction to ensure that anything being deserialised
// from the attachment is trusted. This will require some partial serialisation work to not load the ContractState
// objects from the TransactionState.
- private fun verifyRegularTransaction(services: ServiceHub, checkSufficientSignatures: Boolean) {
- val ltx = toLedgerTransaction(services, checkSufficientSignatures)
+ private fun verifyRegularTransaction(verificationSupport: VerificationSupport, checkSufficientSignatures: Boolean) {
+ val ltx = toLedgerTransactionInternal(verificationSupport, checkSufficientSignatures)
try {
- // TODO: allow non-blocking verification.
- services.transactionVerifierService.verify(ltx).getOrThrow()
+ ltx.verify()
} catch (e: NoClassDefFoundError) {
checkReverifyAllowed(e)
val missingClass = e.message ?: throw e
log.warn("Transaction {} has missing class: {}", ltx.id, missingClass)
- reverifyWithFixups(ltx, services, missingClass)
+ reverifyWithFixups(ltx, verificationSupport, missingClass)
} catch (e: NotSerializableException) {
checkReverifyAllowed(e)
- retryVerification(e, e, ltx, services)
+ retryVerification(e, e, ltx, verificationSupport)
} catch (e: TransactionDeserialisationException) {
checkReverifyAllowed(e)
- retryVerification(e.cause, e, ltx, services)
+ retryVerification(e.cause, e, ltx, verificationSupport)
}
}
@@ -243,18 +268,18 @@ data class SignedTransaction(val txBits: SerializedBytes,
}
@Suppress("ThrowsCount")
- private fun retryVerification(cause: Throwable?, ex: Throwable, ltx: LedgerTransaction, services: ServiceHub) {
+ private fun retryVerification(cause: Throwable?, ex: Throwable, ltx: LedgerTransaction, verificationSupport: VerificationSupport) {
when (cause) {
is MissingSerializerException -> {
log.warn("Missing serializers: typeDescriptor={}, typeNames={}", cause.typeDescriptor ?: "", cause.typeNames)
- reverifyWithFixups(ltx, services, null)
+ reverifyWithFixups(ltx, verificationSupport, null)
}
is NotSerializableException -> {
val underlying = cause.cause
if (underlying is ClassNotFoundException) {
val missingClass = underlying.message?.replace('.', '/') ?: throw ex
log.warn("Transaction {} has missing class: {}", ltx.id, missingClass)
- reverifyWithFixups(ltx, services, missingClass)
+ reverifyWithFixups(ltx, verificationSupport, missingClass)
} else {
throw ex
}
@@ -266,15 +291,93 @@ data class SignedTransaction(val txBits: SerializedBytes,
// Transactions created before Corda 4 can be missing dependencies on other CorDapps.
// This code has detected a missing custom serializer - probably located inside a workflow CorDapp.
// We need to extract this CorDapp from AttachmentStorage and try verifying this transaction again.
- private fun reverifyWithFixups(ltx: LedgerTransaction, services: ServiceHub, missingClass: String?) {
+ private fun reverifyWithFixups(ltx: LedgerTransaction, verificationSupport: VerificationSupport, missingClass: String?) {
log.warn("""Detected that transaction $id does not contain all cordapp dependencies.
|This may be the result of a bug in a previous version of Corda.
|Attempting to re-verify having applied this node's fix-up rules.
|Please check with the originator that this is a valid transaction.""".trimMargin())
- (services.transactionVerifierService as TransactionVerifierServiceInternal)
- .reverifyWithFixups(ltx, missingClass)
- .getOrThrow()
+ val replacementAttachments = computeReplacementAttachments(ltx, verificationSupport, missingClass)
+ log.warn("Reverifying transaction {} with attachments:{}", ltx.id, replacementAttachments)
+ ltx.verifyInternal(replacementAttachments.toList())
+ }
+
+ private fun computeReplacementAttachments(ltx: LedgerTransaction,
+ verificationSupport: VerificationSupport,
+ missingClass: String?): Collection {
+ val replacements = fixupAttachments(verificationSupport, ltx.attachments)
+ if (!replacements.equivalent(ltx.attachments)) {
+ return replacements
+ }
+
+ // We cannot continue unless we have some idea which class is missing from the attachments.
+ if (missingClass == null) {
+ throw TransactionVerificationException.BrokenTransactionException(
+ txId = ltx.id,
+ message = "No fix-up rules provided for broken attachments: $replacements"
+ )
+ }
+
+ /*
+ * The Node's fix-up rules have not been able to adjust the transaction's attachments,
+ * so resort to the original mechanism of trying to find an attachment that contains
+ * the missing class.
+ */
+ val extraAttachment = requireNotNull(verificationSupport.getTrustedClassAttachment(missingClass)) {
+ """Transaction $ltx is incorrectly formed. Most likely it was created during version 3 of Corda
+ |when the verification logic was more lenient. Attempted to find local dependency for class: $missingClass,
+ |but could not find one.
+ |If you wish to verify this transaction, please contact the originator of the transaction and install the
+ |provided missing JAR.
+ |You can install it using the RPC command: `uploadAttachment` without restarting the node.
+ |""".trimMargin()
+ }
+
+ return replacements.toMutableSet().apply {
+ /*
+ * Check our transaction doesn't already contain this extra attachment.
+ * It seems unlikely that we would, but better safe than sorry!
+ */
+ if (!add(extraAttachment)) {
+ throw TransactionVerificationException.BrokenTransactionException(
+ txId = ltx.id,
+ message = "Unlinkable class $missingClass inside broken attachments: $replacements"
+ )
+ }
+
+ log.warn("""Detected that transaction $ltx does not contain all cordapp dependencies.
+ |This may be the result of a bug in a previous version of Corda.
+ |Attempting to verify using the additional trusted dependency: $extraAttachment for class $missingClass.
+ |Please check with the originator that this is a valid transaction.
+ |YOU ARE ONLY SEEING THIS MESSAGE BECAUSE THE CORDAPPS THAT CREATED THIS TRANSACTION ARE BROKEN!
+ |WE HAVE TRIED TO REPAIR THE TRANSACTION AS BEST WE CAN, BUT CANNOT GUARANTEE WE HAVE SUCCEEDED!
+ |PLEASE FIX THE CORDAPPS AND MIGRATE THESE BROKEN TRANSACTIONS AS SOON AS POSSIBLE!
+ |THIS MESSAGE IS **SUPPOSED** TO BE SCARY!!
+ |""".trimMargin()
+ )
+ }
+ }
+
+ /**
+ * Apply this node's attachment fix-up rules to the given attachments.
+ *
+ * @param attachments A collection of [Attachment] objects, e.g. as provided by a transaction.
+ * @return The [attachments] with the node's fix-up rules applied.
+ */
+ private fun fixupAttachments(verificationSupport: VerificationSupport, attachments: Collection): Collection {
+ val attachmentsById = attachments.associateByTo(LinkedHashMap(), Attachment::id)
+ val replacementIds = verificationSupport.fixupAttachmentIds(attachmentsById.keys)
+ attachmentsById.keys.retainAll(replacementIds)
+ val extraIds = replacementIds - attachmentsById.keys
+ val extraAttachments = verificationSupport.getAttachments(extraIds)
+ for ((index, extraId) in extraIds.withIndex()) {
+ val extraAttachment = extraAttachments[index]
+ if (extraAttachment == null || !extraAttachment.isUploaderTrusted()) {
+ throw MissingAttachmentsException(listOf(extraId))
+ }
+ attachmentsById[extraId] = extraAttachment
+ }
+ return attachmentsById.values
}
/**
@@ -306,7 +409,7 @@ data class SignedTransaction(val txBits: SerializedBytes,
}
/**
- * If [transaction] is a [NotaryChangeWireTransaction], loads the input states and resolves it to a
+ * If [coreTransaction] is a [NotaryChangeWireTransaction], loads the input states and resolves it to a
* [NotaryChangeLedgerTransaction] so the signatures can be verified.
*/
fun resolveNotaryChangeTransaction(services: ServicesForResolution): NotaryChangeLedgerTransaction {
@@ -316,10 +419,12 @@ data class SignedTransaction(val txBits: SerializedBytes,
}
/**
- * If [transaction] is a [NotaryChangeWireTransaction], loads the input states and resolves it to a
+ * If [coreTransaction] is a [NotaryChangeWireTransaction], loads the input states and resolves it to a
* [NotaryChangeLedgerTransaction] so the signatures can be verified.
*/
- fun resolveNotaryChangeTransaction(services: ServiceHub) = resolveNotaryChangeTransaction(services as ServicesForResolution)
+ fun resolveNotaryChangeTransaction(services: ServiceHub): NotaryChangeLedgerTransaction {
+ return resolveNotaryChangeTransaction(services as ServicesForResolution)
+ }
/**
* If [coreTransaction] is a [ContractUpgradeWireTransaction], loads the input states and resolves it to a
diff --git a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt
index 6ff76068bd..32ef6351ca 100644
--- a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt
+++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt
@@ -9,6 +9,8 @@ import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.identity.Party
import net.corda.core.internal.*
+import net.corda.core.internal.verification.VerifyingServiceHub
+import net.corda.core.internal.verification.toVerifyingServiceHub
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution
@@ -28,7 +30,6 @@ import java.time.Duration
import java.time.Instant
import java.util.*
import java.util.regex.Pattern
-import kotlin.collections.ArrayList
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.reflect.KClass
@@ -77,9 +78,6 @@ open class TransactionBuilder(
private const val ID_PATTERN = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"
private val FQCP: Pattern = Pattern.compile("$ID_PATTERN(/$ID_PATTERN)+")
private fun isValidJavaClass(identifier: String) = FQCP.matcher(identifier).matches()
- private fun Collection<*>.deepEquals(other: Collection<*>): Boolean {
- return (size == other.size) && containsAll(other) && other.containsAll(this)
- }
private fun Collection.toPrettyString(): String = sorted().joinToString(
separator = System.lineSeparator(),
prefix = System.lineSeparator()
@@ -178,24 +176,25 @@ open class TransactionBuilder(
}
@CordaInternal
- fun toWireTransactionWithContext(
+ @JvmSynthetic
+ internal fun toWireTransactionWithContext(
services: ServicesForResolution,
serializationContext: SerializationContext?
- ) : WireTransaction = toWireTransactionWithContext(services, serializationContext, 0)
+ ) : WireTransaction = toWireTransactionWithContext(services.toVerifyingServiceHub(), serializationContext, 0)
private tailrec fun toWireTransactionWithContext(
- services: ServicesForResolution,
- serializationContext: SerializationContext?,
- tryCount: Int
+ serviceHub: VerifyingServiceHub,
+ serializationContext: SerializationContext?,
+ tryCount: Int
): WireTransaction {
val referenceStates = referenceStates()
if (referenceStates.isNotEmpty()) {
- services.ensureMinimumPlatformVersion(4, "Reference states")
+ serviceHub.ensureMinimumPlatformVersion(4, "Reference states")
}
- resolveNotary(services)
+ resolveNotary(serviceHub)
val (allContractAttachments: Collection, resolvedOutputs: List>)
- = selectContractAttachmentsAndOutputStateConstraints(services, serializationContext)
+ = selectContractAttachmentsAndOutputStateConstraints(serviceHub, serializationContext)
// Final sanity check that all states have the correct constraints.
for (state in (inputsWithTransactionState.map { it.state } + resolvedOutputs)) {
@@ -213,9 +212,9 @@ open class TransactionBuilder(
notary,
window,
referenceStates,
- services.networkParametersService.currentHash),
+ serviceHub.networkParametersService.currentHash),
privacySalt,
- services.digestService
+ serviceHub.digestService
)
}
@@ -223,10 +222,10 @@ open class TransactionBuilder(
// This is a workaround as the current version of Corda does not support cordapp dependencies.
// It works by running transaction validation and then scan the attachment storage for missing classes.
// TODO - remove once proper support for cordapp dependencies is added.
- val addedDependency = addMissingDependency(services, wireTx, tryCount)
+ val addedDependency = addMissingDependency(serviceHub, wireTx, tryCount)
return if (addedDependency)
- toWireTransactionWithContext(services, serializationContext, tryCount + 1)
+ toWireTransactionWithContext(serviceHub, serializationContext, tryCount + 1)
else
wireTx
}
@@ -241,9 +240,9 @@ open class TransactionBuilder(
/**
* @return true if a new dependency was successfully added.
*/
- private fun addMissingDependency(services: ServicesForResolution, wireTx: WireTransaction, tryCount: Int): Boolean {
+ private fun addMissingDependency(serviceHub: VerifyingServiceHub, wireTx: WireTransaction, tryCount: Int): Boolean {
return try {
- wireTx.toLedgerTransaction(services).verify()
+ wireTx.toLedgerTransactionInternal(serviceHub).verify()
// The transaction verified successfully without adding any extra dependency.
false
} catch (e: Throwable) {
@@ -253,12 +252,12 @@ open class TransactionBuilder(
// Handle various exceptions that can be thrown during verification and drill down the wrappings.
// Note: this is a best effort to preserve backwards compatibility.
rootError is ClassNotFoundException -> {
- ((tryCount == 0) && fixupAttachments(wireTx.attachments, services, e))
- || addMissingAttachment((rootError.message ?: throw e).replace('.', '/'), services, e)
+ ((tryCount == 0) && fixupAttachments(wireTx.attachments, serviceHub, e))
+ || addMissingAttachment((rootError.message ?: throw e).replace('.', '/'), serviceHub, e)
}
rootError is NoClassDefFoundError -> {
- ((tryCount == 0) && fixupAttachments(wireTx.attachments, services, e))
- || addMissingAttachment(rootError.message ?: throw e, services, e)
+ ((tryCount == 0) && fixupAttachments(wireTx.attachments, serviceHub, e))
+ || addMissingAttachment(rootError.message ?: throw e, serviceHub, e)
}
// Ignore these exceptions as they will break unit tests.
@@ -281,18 +280,18 @@ open class TransactionBuilder(
}
private fun fixupAttachments(
- txAttachments: List,
- services: ServicesForResolution,
- originalException: Throwable
+ txAttachments: List,
+ serviceHub: VerifyingServiceHub,
+ originalException: Throwable
): Boolean {
- val replacementAttachments = services.cordappProvider.internalFixupAttachmentIds(txAttachments)
- if (replacementAttachments.deepEquals(txAttachments)) {
+ val replacementAttachments = serviceHub.fixupAttachmentIds(txAttachments)
+ if (replacementAttachments.equivalent(txAttachments)) {
return false
}
val extraAttachments = replacementAttachments - txAttachments
extraAttachments.forEach { id ->
- val attachment = services.attachments.openAttachment(id)
+ val attachment = serviceHub.attachments.openAttachment(id)
if (attachment == null || !attachment.isUploaderTrusted()) {
log.warn("""The node's fix-up rules suggest including attachment {}, which cannot be found either.
|Please contact the developer of the CorDapp for further instructions.
@@ -315,7 +314,7 @@ open class TransactionBuilder(
return true
}
- private fun addMissingAttachment(missingClass: String, services: ServicesForResolution, originalException: Throwable): Boolean {
+ private fun addMissingAttachment(missingClass: String, serviceHub: VerifyingServiceHub, originalException: Throwable): Boolean {
if (!isValidJavaClass(missingClass)) {
log.warn("Could not autodetect a valid attachment for the transaction being built.")
throw originalException
@@ -324,7 +323,7 @@ open class TransactionBuilder(
throw originalException
}
- val attachment = services.attachments.internalFindTrustedAttachmentForClass(missingClass)
+ val attachment = serviceHub.getTrustedClassAttachment(missingClass)
if (attachment == null) {
log.error("""The transaction currently built is missing an attachment for class: $missingClass.
@@ -475,14 +474,14 @@ open class TransactionBuilder(
// Determine if there are any HashConstraints that pin the version of a contract. If there are, check if we trust them.
val hashAttachments = inputsAndOutputs
.filter { it.constraint is HashAttachmentConstraint }
- .map { state ->
+ .mapToSet { state ->
val attachment = services.attachments.openAttachment((state.constraint as HashAttachmentConstraint).attachmentId)
if (attachment == null || attachment !is ContractAttachment || !isUploaderTrusted(attachment.uploader)) {
// This should never happen because these are input states that should have been validated already.
throw MissingContractAttachments(listOf(state))
}
attachment
- }.toSet()
+ }
// Check that states with the HashConstraint don't conflict between themselves or with an explicitly set attachment.
require(hashAttachments.size <= 1) {
@@ -490,7 +489,7 @@ open class TransactionBuilder(
}
if (explicitContractAttachment != null && hashAttachments.singleOrNull() != null) {
- require(explicitContractAttachment == (hashAttachments.single() as ContractAttachment).attachment.id) {
+ require(explicitContractAttachment == hashAttachments.single().attachment.id) {
"An attachment has been explicitly set for contract $contractClassName in the transaction builder which conflicts with the HashConstraint of a state."
}
}
@@ -665,10 +664,6 @@ open class TransactionBuilder(
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServiceHub) = toWireTransaction(services).toLedgerTransaction(services)
- fun toLedgerTransactionWithContext(services: ServicesForResolution, serializationContext: SerializationContext): LedgerTransaction {
- return toWireTransactionWithContext(services, serializationContext).toLedgerTransaction(services)
- }
-
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class)
fun verify(services: ServiceHub) {
toLedgerTransaction(services).verify()
@@ -692,7 +687,7 @@ open class TransactionBuilder(
}
// Transaction can combine different identities of the same notary after key rotation.
- private fun checkReferencesUseSameNotary() = referencesWithTransactionState.map { it.notary.name }.toSet().size == 1
+ private fun checkReferencesUseSameNotary() = referencesWithTransactionState.mapToSet { it.notary.name }.size == 1
// Automatically correct notary after its key rotation
private fun resolveNotary(services: ServicesForResolution) {
@@ -719,8 +714,6 @@ open class TransactionBuilder(
* If this method is called outside the context of a flow, a [ServiceHub] instance must be passed to this method
* for it to be able to resolve [StatePointer]s. Usually for a unit test, this will be an instance of mock services.
*
- * @param serviceHub a [ServiceHub] instance needed for performing vault queries.
- *
* @throws IllegalStateException if no [ServiceHub] is provided and no flow context is available.
*/
private fun resolveStatePointers(transactionState: TransactionState<*>) {
diff --git a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt
index a0fa249240..457b33d246 100644
--- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt
+++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt
@@ -1,21 +1,41 @@
package net.corda.core.transactions
import net.corda.core.CordaInternal
-import net.corda.core.contracts.*
+import net.corda.core.contracts.Attachment
+import net.corda.core.contracts.AttachmentResolutionException
+import net.corda.core.contracts.Command
+import net.corda.core.contracts.CommandWithParties
+import net.corda.core.contracts.ComponentGroupEnum
import net.corda.core.contracts.ComponentGroupEnum.COMMANDS_GROUP
import net.corda.core.contracts.ComponentGroupEnum.OUTPUTS_GROUP
-import net.corda.core.crypto.*
+import net.corda.core.contracts.ContractState
+import net.corda.core.contracts.PrivacySalt
+import net.corda.core.contracts.StateRef
+import net.corda.core.contracts.TimeWindow
+import net.corda.core.contracts.TransactionResolutionException
+import net.corda.core.contracts.TransactionState
+import net.corda.core.crypto.DigestService
+import net.corda.core.crypto.MerkleTree
+import net.corda.core.crypto.SecureHash
+import net.corda.core.crypto.TransactionSignature
+import net.corda.core.crypto.keys
import net.corda.core.identity.Party
-import net.corda.core.internal.*
+import net.corda.core.internal.Emoji
+import net.corda.core.internal.SerializedStateAndRef
+import net.corda.core.internal.SerializedTransactionState
+import net.corda.core.internal.createComponentGroups
+import net.corda.core.internal.flatMapToSet
+import net.corda.core.internal.isUploaderTrusted
+import net.corda.core.internal.lazyMapped
+import net.corda.core.internal.mapToSet
+import net.corda.core.internal.verification.VerificationSupport
+import net.corda.core.internal.verification.toVerifyingServiceHub
import net.corda.core.node.NetworkParameters
-import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializationFactory
-import net.corda.core.serialization.SerializedBytes
-import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
import net.corda.core.serialization.serialize
import net.corda.core.utilities.OpaqueBytes
import java.security.PublicKey
@@ -47,6 +67,7 @@ import java.util.function.Predicate
*
*/
@CordaSerializable
+@Suppress("ThrowsCount")
class WireTransaction(componentGroups: List, val privacySalt: PrivacySalt, digestService: DigestService) : TraversableTransaction(componentGroups, digestService) {
constructor(componentGroups: List) : this(componentGroups, PrivacySalt())
@@ -71,7 +92,7 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr
init {
check(componentGroups.all { it.components.isNotEmpty() }) { "Empty component groups are not allowed" }
- check(componentGroups.map { it.groupIndex }.toSet().size == componentGroups.size) { "Duplicated component groups detected" }
+ check(componentGroups.mapToSet { it.groupIndex }.size == componentGroups.size) { "Duplicated component groups detected" }
checkBaseInvariants()
check(inputs.isNotEmpty() || outputs.isNotEmpty()) { "A transaction must contain at least one input or output state" }
check(commands.isNotEmpty()) { "A transaction must contain at least one command" }
@@ -102,28 +123,7 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr
*/
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServicesForResolution): LedgerTransaction {
- return services.specialise(
- toLedgerTransactionInternal(
- resolveIdentity = { services.identityService.partyFromKey(it) },
- resolveAttachment = { services.attachments.openAttachment(it) },
- resolveStateRefAsSerialized = { resolveStateRefBinaryComponent(it, services) },
- resolveParameters = {
- val hashToResolve = it ?: services.networkParametersService.defaultHash
- services.networkParametersService.lookup(hashToResolve)
- },
- // `as?` is used due to [MockServices] not implementing [ServiceHubCoreInternal]
- isAttachmentTrusted = { (services as? ServiceHubCoreInternal)?.attachmentTrustCalculator?.calculate(it) ?: true },
- attachmentsClassLoaderCache = (services as? ServiceHubCoreInternal)?.attachmentsClassLoaderCache
- )
- )
- }
-
- // Helper for deprecated toLedgerTransaction
- @Suppress("UNUSED") // not sure if this field can be removed safely??
- private val missingAttachment: Attachment by lazy {
- object : AbstractAttachment({ byteArrayOf() }, DEPLOYED_CORDAPP_UPLOADER ) {
- override val id: SecureHash get() = throw UnsupportedOperationException()
- }
+ return toLedgerTransactionInternal(services.toVerifyingServiceHub())
}
/**
@@ -143,29 +143,37 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr
@Suppress("UNUSED_PARAMETER") resolveContractAttachment: (TransactionState) -> AttachmentId?
): LedgerTransaction {
// This reverts to serializing the resolved transaction state.
- return toLedgerTransactionInternal(
- resolveIdentity,
- resolveAttachment,
- { stateRef -> resolveStateRef(stateRef)?.serialize() },
- { null },
- Attachment::isUploaderTrusted,
- null
- )
+ return toLedgerTransactionInternal(object : VerificationSupport {
+ override fun getParties(keys: Collection): List = keys.map(resolveIdentity)
+ override fun getAttachment(id: SecureHash): Attachment? = resolveAttachment(id)
+ override fun getNetworkParameters(id: SecureHash?): NetworkParameters? = null
+ override fun isAttachmentTrusted(attachment: Attachment): Boolean = attachment.isUploaderTrusted()
+ override fun getSerializedState(stateRef: StateRef): SerializedTransactionState {
+ return resolveStateRef(stateRef)?.serialize() ?: throw TransactionResolutionException(stateRef.txhash)
+ }
+ // These are not used
+ override val appClassLoader: ClassLoader get() = throw AbstractMethodError()
+ override fun getTrustedClassAttachment(className: String) = throw AbstractMethodError()
+ override fun fixupAttachmentIds(attachmentIds: Collection) = throw AbstractMethodError()
+ })
}
- @Suppress("LongParameterList", "ThrowsCount")
- private fun toLedgerTransactionInternal(
- resolveIdentity: (PublicKey) -> Party?,
- resolveAttachment: (SecureHash) -> Attachment?,
- resolveStateRefAsSerialized: (StateRef) -> SerializedBytes>?,
- resolveParameters: (SecureHash?) -> NetworkParameters?,
- isAttachmentTrusted: (Attachment) -> Boolean,
- attachmentsClassLoaderCache: AttachmentsClassLoaderCache?
- ): LedgerTransaction {
+ @CordaInternal
+ @JvmSynthetic
+ internal fun toLedgerTransactionInternal(verificationSupport: VerificationSupport): LedgerTransaction {
// Look up public keys to authenticated identities.
- val authenticatedCommands = commands.lazyMapped { cmd, _ ->
- val parties = cmd.signers.mapNotNull(resolveIdentity)
- CommandWithParties(cmd.signers, parties, cmd.value)
+ val authenticatedCommands = if (verificationSupport.isResolutionLazy) {
+ commands.lazyMapped { cmd, _ ->
+ val parties = verificationSupport.getParties(cmd.signers).filterNotNull()
+ CommandWithParties(cmd.signers, parties, cmd.value)
+ }
+ } else {
+ val allSigners = commands.flatMapToSet { it.signers }
+ val allParties = verificationSupport.getParties(allSigners)
+ commands.map { cmd ->
+ val parties = cmd.signers.mapNotNull { allParties[allSigners.indexOf(it)] }
+ CommandWithParties(cmd.signers, parties, cmd.value)
+ }
}
// Ensure that the lazy mappings will use the correct SerializationContext.
@@ -175,19 +183,28 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr
ssar.toStateAndRef(serializationFactory, serializationContext)
}
- val serializedResolvedInputs = inputs.map { ref ->
- SerializedStateAndRef(resolveStateRefAsSerialized(ref) ?: throw TransactionResolutionException(ref.txhash), ref)
+ val serializedResolvedInputs = inputs.map {
+ SerializedStateAndRef(verificationSupport.getSerializedState(it), it)
}
val resolvedInputs = serializedResolvedInputs.lazyMapped(toStateAndRef)
- val serializedResolvedReferences = references.map { ref ->
- SerializedStateAndRef(resolveStateRefAsSerialized(ref) ?: throw TransactionResolutionException(ref.txhash), ref)
+ val serializedResolvedReferences = references.map {
+ SerializedStateAndRef(verificationSupport.getSerializedState(it), it)
}
val resolvedReferences = serializedResolvedReferences.lazyMapped(toStateAndRef)
- val resolvedAttachments = attachments.lazyMapped { att, _ -> resolveAttachment(att) ?: throw AttachmentResolutionException(att) }
+ val resolvedAttachments = if (verificationSupport.isResolutionLazy) {
+ attachments.lazyMapped { id, _ ->
+ verificationSupport.getAttachment(id) ?: throw AttachmentResolutionException(id)
+ }
+ } else {
+ verificationSupport.getAttachments(attachments).mapIndexed { index, attachment ->
+ attachment ?: throw AttachmentResolutionException(attachments[index])
+ }
+ }
- val resolvedNetworkParameters = resolveParameters(networkParametersHash) ?: throw TransactionResolutionException.UnknownParametersException(id, networkParametersHash!!)
+ val resolvedNetworkParameters = verificationSupport.getNetworkParameters(networkParametersHash)
+ ?: throw TransactionResolutionException.UnknownParametersException(id, networkParametersHash!!)
val ltx = LedgerTransaction.create(
resolvedInputs,
@@ -203,8 +220,9 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr
componentGroups,
serializedResolvedInputs,
serializedResolvedReferences,
- isAttachmentTrusted,
- attachmentsClassLoaderCache,
+ verificationSupport::isAttachmentTrusted,
+ verificationSupport::createVerifier,
+ verificationSupport.attachmentsClassLoaderCache,
digestService
)
@@ -230,15 +248,15 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr
// This calculates a value that is slightly lower than the actual re-serialized version. But it is stable and does not depend on the classloader.
fun componentGroupSize(componentGroup: ComponentGroupEnum): Int {
- return this.componentGroups.firstOrNull { it.groupIndex == componentGroup.ordinal }?.let { cg -> cg.components.sumBy { it.size } + 4 } ?: 0
+ return this.componentGroups.firstOrNull { it.groupIndex == componentGroup.ordinal }?.let { cg -> cg.components.sumOf { it.size } + 4 } ?: 0
}
// Check attachments size first as they are most likely to go over the limit. With ContractAttachment instances
// it's likely that the same underlying Attachment CorDapp will occur more than once so we dedup on the attachment id.
ltx.attachments.distinctBy { it.id }.forEach { minus(it.size) }
- minus(resolvedSerializedInputs.sumBy { it.serializedState.size })
- minus(resolvedSerializedReferences.sumBy { it.serializedState.size })
+ minus(resolvedSerializedInputs.sumOf { it.serializedState.size })
+ minus(resolvedSerializedReferences.sumOf { it.serializedState.size })
// For Commands and outputs we can use the component groups as they are already serialized.
minus(componentGroupSize(COMMANDS_GROUP))
@@ -273,7 +291,7 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr
// Even if empty and not used, we should at least send oneHashes for each known
// or received but unknown (thus, bigger than known ordinal) component groups.
val allOnesHash = digestService.allOnesHash
- for (i in 0..componentGroups.map { it.groupIndex }.max()!!) {
+ for (i in 0..componentGroups.maxOf { it.groupIndex }) {
val root = groupsMerkleRoots[i] ?: allOnesHash
listOfLeaves.add(root)
}
@@ -340,37 +358,6 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr
timeWindow: TimeWindow?): List {
return createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList(), null)
}
-
- /**
- * This is the main logic that knows how to retrieve the binary representation of [StateRef]s.
- *
- * For [ContractUpgradeWireTransaction] or [NotaryChangeWireTransaction] it knows how to recreate the output state in the
- * correct classloader independent of the node's classpath.
- */
- @CordaInternal
- fun resolveStateRefBinaryComponent(stateRef: StateRef, services: ServicesForResolution): SerializedBytes>? {
- return if (services is ServiceHub) {
- val coreTransaction = services.validatedTransactions.getTransaction(stateRef.txhash)?.coreTransaction
- ?: throw TransactionResolutionException(stateRef.txhash)
- // Get the network parameters from the tx or whatever the default params are.
- val paramsHash = coreTransaction.networkParametersHash ?: services.networkParametersService.defaultHash
- val params = services.networkParametersService.lookup(paramsHash)
- ?: throw IllegalStateException("Should have been able to fetch parameters by this point: $paramsHash")
- @Suppress("UNCHECKED_CAST")
- when (coreTransaction) {
- is WireTransaction -> coreTransaction.componentGroups
- .firstOrNull { it.groupIndex == OUTPUTS_GROUP.ordinal }
- ?.components
- ?.get(stateRef.index) as SerializedBytes>?
- is ContractUpgradeWireTransaction -> coreTransaction.resolveOutputComponent(services, stateRef, params)
- is NotaryChangeWireTransaction -> coreTransaction.resolveOutputComponent(services, stateRef, params)
- else -> throw UnsupportedOperationException("Attempting to resolve input ${stateRef.index} of a ${coreTransaction.javaClass} transaction. This is not supported.")
- }
- } else {
- // For backwards compatibility revert to using the node classloader.
- services.loadState(stateRef).serialize()
- }
- }
}
override fun toString(): String {
diff --git a/core/src/test/kotlin/net/corda/core/internal/internalAccessTestHelpers.kt b/core/src/test/kotlin/net/corda/core/internal/InternalAccessTestHelpers.kt
similarity index 68%
rename from core/src/test/kotlin/net/corda/core/internal/internalAccessTestHelpers.kt
rename to core/src/test/kotlin/net/corda/core/internal/InternalAccessTestHelpers.kt
index 16a6e6bef8..5f9e48bb2e 100644
--- a/core/src/test/kotlin/net/corda/core/internal/internalAccessTestHelpers.kt
+++ b/core/src/test/kotlin/net/corda/core/internal/InternalAccessTestHelpers.kt
@@ -1,9 +1,19 @@
package net.corda.core.internal
-import net.corda.core.contracts.*
+import net.corda.core.contracts.Attachment
+import net.corda.core.contracts.CommandData
+import net.corda.core.contracts.CommandWithParties
+import net.corda.core.contracts.Contract
+import net.corda.core.contracts.ContractState
+import net.corda.core.contracts.PrivacySalt
+import net.corda.core.contracts.StateAndRef
+import net.corda.core.contracts.TimeWindow
+import net.corda.core.contracts.TransactionState
+import net.corda.core.contracts.TransactionVerificationException
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
+import net.corda.core.internal.verification.AbstractVerifier
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
@@ -40,15 +50,35 @@ fun createLedgerTransaction(
isAttachmentTrusted: (Attachment) -> Boolean,
attachmentsClassLoaderCache: AttachmentsClassLoaderCache,
digestService: DigestService = DigestService.default
-): LedgerTransaction = LedgerTransaction.create(
- inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references, componentGroups, serializedInputs, serializedReferences, isAttachmentTrusted, attachmentsClassLoaderCache, digestService
-).specialise(::PassthroughVerifier)
+): LedgerTransaction {
+ return LedgerTransaction.create(
+ inputs,
+ outputs,
+ commands,
+ attachments,
+ id,
+ notary,
+ timeWindow,
+ privacySalt,
+ networkParameters,
+ references,
+ componentGroups,
+ serializedInputs,
+ serializedReferences,
+ isAttachmentTrusted,
+ ::PassthroughVerifier,
+ attachmentsClassLoaderCache,
+ digestService
+ )
+}
fun createContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable) = TransactionVerificationException.ContractCreationError(txId, contractClass, cause)
fun createContractRejection(txId: SecureHash, contract: Contract, cause: Throwable) = TransactionVerificationException.ContractRejection(txId, contract, cause)
/**
* Verify the [LedgerTransaction] we already have.
+ *
+ * Note, this is not secure!
*/
private class PassthroughVerifier(ltx: LedgerTransaction, context: SerializationContext) : AbstractVerifier(ltx, context.deserializationClassLoader) {
override val transaction: Supplier
diff --git a/finance/contracts/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt b/finance/contracts/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt
index 67b075dee0..ee1e0584e6 100644
--- a/finance/contracts/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt
+++ b/finance/contracts/src/test/kotlin/net/corda/finance/contracts/CommercialPaperTests.kt
@@ -4,6 +4,7 @@ import net.corda.core.contracts.*
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
+import net.corda.core.internal.getRequiredTransaction
import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.Vault
import net.corda.core.transactions.SignedTransaction
@@ -284,8 +285,8 @@ class CommercialPaperTestsGeneric {
}
// Propagate the cash transactions to each side.
- aliceServices.recordTransactions(bigCorpCash.states.map { megaCorpServices.validatedTransactions.getTransaction(it.ref.txhash)!! })
- megaCorpServices.recordTransactions(aliceCash.states.map { aliceServices.validatedTransactions.getTransaction(it.ref.txhash)!! })
+ aliceServices.recordTransactions(bigCorpCash.states.map { megaCorpServices.getRequiredTransaction(it.ref.txhash) })
+ megaCorpServices.recordTransactions(aliceCash.states.map { aliceServices.getRequiredTransaction(it.ref.txhash) })
// MegaCorp™ issues $10,000 of commercial paper, to mature in 30 days, owned initially by itself.
val faceValue = 10000.DOLLARS `issued by` dummyCashIssuer.ref(1)
diff --git a/node-api-tests/src/test/kotlin/net/corda/nodeapitests/internal/AttachmentsClassLoaderStaticContractTests.kt b/node-api-tests/src/test/kotlin/net/corda/nodeapitests/internal/AttachmentsClassLoaderStaticContractTests.kt
index cbc8211c72..d08375d8aa 100644
--- a/node-api-tests/src/test/kotlin/net/corda/nodeapitests/internal/AttachmentsClassLoaderStaticContractTests.kt
+++ b/node-api-tests/src/test/kotlin/net/corda/nodeapitests/internal/AttachmentsClassLoaderStaticContractTests.kt
@@ -1,34 +1,24 @@
package net.corda.nodeapitests.internal
-import org.mockito.kotlin.any
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
-import net.corda.core.contracts.*
-import net.corda.core.crypto.SecureHash
+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.CordaX500Name
import net.corda.core.identity.Party
-import net.corda.core.node.ServicesForResolution
-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.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
-import net.corda.nodeapi.internal.cordapp.CordappLoader
-import net.corda.node.internal.cordapp.CordappProviderImpl
-import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.nodeapitests.internal.AttachmentsClassLoaderStaticContractTests.AttachmentDummyContract.Companion.ATTACHMENT_PROGRAM_ID
-import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
-import net.corda.testing.internal.MockCordappConfigProvider
-import net.corda.coretesting.internal.rigorousMock
-import net.corda.testing.node.internal.cordappWithPackages
-import net.corda.testing.services.MockAttachmentStorage
+import net.corda.testing.node.MockServices
import org.assertj.core.api.Assertions.assertThat
import org.junit.Assert.assertEquals
import org.junit.Rule
@@ -69,31 +59,7 @@ class AttachmentsClassLoaderStaticContractTests {
}
}
- private val networkParameters = testNetworkParameters()
-
- private val networkParametersService get() = mock().also {
- doReturn(networkParameters.serialize().hash).whenever(it).currentHash
- }
-
- private val serviceHub get() = rigorousMock().also {
- val cordappProviderImpl = CordappProviderImpl(cordappLoaderForPackages(listOf("net.corda.nodeapitests.internal")), MockCordappConfigProvider(), MockAttachmentStorage())
- cordappProviderImpl.start()
- doReturn(cordappProviderImpl).whenever(it).cordappProvider
- doReturn(networkParametersService).whenever(it).networkParametersService
- doReturn(networkParameters).whenever(it).networkParameters
- val attachmentStorage = rigorousMock()
- doReturn(attachmentStorage).whenever(it).attachments
- val attachment = rigorousMock()
- doReturn(attachment).whenever(attachmentStorage).openAttachment(any())
- doReturn(it.cordappProvider.getContractAttachmentID(AttachmentDummyContract.ATTACHMENT_PROGRAM_ID)).whenever(attachment).id
- doReturn(setOf(AttachmentDummyContract.ATTACHMENT_PROGRAM_ID)).whenever(attachment).allContracts
- doReturn("app").whenever(attachment).uploader
- doReturn(emptyList()).whenever(attachment).signerKeys
- val contractAttachmentId = SecureHash.randomSHA256()
- doReturn(listOf(contractAttachmentId)).whenever(attachmentStorage)
- .getLatestContractAttachments(AttachmentDummyContract.ATTACHMENT_PROGRAM_ID)
- doReturn(mock()).whenever(it).identityService
- }
+ private val serviceHub = MockServices()
@Test(timeout=300_000)
fun `test serialization of WireTransaction with statically loaded contract`() {
@@ -112,8 +78,4 @@ class AttachmentsClassLoaderStaticContractTests {
val contractClass = Class.forName(ATTACHMENT_PROGRAM_ID)
assertThat(contractClass.getDeclaredConstructor().newInstance()).isInstanceOf(Contract::class.java)
}
-
- private fun cordappLoaderForPackages(packages: Collection): CordappLoader {
- return JarScanningCordappLoader.fromJarUrls(listOf(cordappWithPackages(*packages.toTypedArray()).jarFile.toUri().toURL()))
- }
}
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt
index ee210ca365..598b666c58 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt
+++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt
@@ -1,6 +1,5 @@
package net.corda.nodeapi.internal.persistence
-import com.github.benmanes.caffeine.cache.Caffeine
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.castIfPossible
import net.corda.core.schemas.MappedSchema
@@ -54,7 +53,7 @@ class HibernateConfiguration(
val sessionFactoryFactory = findSessionFactoryFactory(jdbcUrl, customClassLoader)
- private val sessionFactories = cacheFactory.buildNamed, SessionFactory>(Caffeine.newBuilder(), "HibernateConfiguration_sessionFactories")
+ private val sessionFactories = cacheFactory.buildNamed, SessionFactory>("HibernateConfiguration_sessionFactories")
val sessionFactoryForRegisteredSchemas = schemas.let {
logger.info("Init HibernateConfiguration for schemas: $it")
diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CustomSerializationSchemeAdapterTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CustomSerializationSchemeAdapterTests.kt
index 2d4f751ddf..9671c2dfa6 100644
--- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CustomSerializationSchemeAdapterTests.kt
+++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CustomSerializationSchemeAdapterTests.kt
@@ -4,6 +4,7 @@ import net.corda.core.serialization.SerializationSchemeContext
import net.corda.core.serialization.CustomSerializationScheme
import net.corda.core.utilities.ByteSequence
import net.corda.nodeapi.internal.serialization.testutils.serializationContext
+import net.corda.serialization.internal.verifier.CustomSerializationSchemeAdapter
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertTrue
import java.io.NotSerializableException
diff --git a/node/build.gradle b/node/build.gradle
index e3f184e590..81c5f23fec 100644
--- a/node/build.gradle
+++ b/node/build.gradle
@@ -73,6 +73,10 @@ jib.container {
processResources {
from file("$rootDir/config/dev/log4j2.xml")
from file("$rootDir/config/dev/jolokia-access.xml")
+ from(tasks.findByPath(":verifier:shadowJar")) {
+ into("net/corda/node/verification")
+ rename { "external-verifier.jar" }
+ }
}
processTestResources {
diff --git a/node/src/integration-test/kotlin/net/corda/contracts/mutator/MutatorContract.kt b/node/src/integration-test/kotlin/net/corda/contracts/mutator/MutatorContract.kt
index 239525c576..d6a44ff4e6 100644
--- a/node/src/integration-test/kotlin/net/corda/contracts/mutator/MutatorContract.kt
+++ b/node/src/integration-test/kotlin/net/corda/contracts/mutator/MutatorContract.kt
@@ -8,7 +8,7 @@ import net.corda.core.contracts.TransactionState
import net.corda.core.contracts.requireSingleCommand
import net.corda.core.contracts.requireThat
import net.corda.core.identity.AbstractParty
-import net.corda.core.internal.Verifier
+import net.corda.core.internal.verification.Verifier
import net.corda.core.serialization.SerializationContext
import net.corda.core.transactions.LedgerTransaction
diff --git a/node/src/integration-test/kotlin/net/corda/node/CustomSerializationSchemeDriverTest.kt b/node/src/integration-test/kotlin/net/corda/node/CustomSerializationSchemeDriverTest.kt
index 89677dede5..7a30f4840b 100644
--- a/node/src/integration-test/kotlin/net/corda/node/CustomSerializationSchemeDriverTest.kt
+++ b/node/src/integration-test/kotlin/net/corda/node/CustomSerializationSchemeDriverTest.kt
@@ -58,7 +58,7 @@ import org.objenesis.strategy.StdInstantiatorStrategy
import java.io.ByteArrayOutputStream
import java.lang.reflect.Modifier
import java.security.PublicKey
-import java.util.*
+import java.util.Arrays
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@@ -94,7 +94,10 @@ class CustomSerializationSchemeDriverTest {
@Test(timeout = 300_000)
fun `flow can write a wire transaction serialized with custom kryo serializer to the ledger`() {
- driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = listOf(enclosedCordapp()))) {
+ driver(DriverParameters(
+ cordappsForAllNodes = listOf(enclosedCordapp()),
+ systemProperties = mapOf("experimental.corda.customSerializationScheme" to KryoScheme::class.java.name)
+ )) {
val (alice, bob) = listOf(
startNode(NodeParameters(providedName = ALICE_NAME)),
startNode(NodeParameters(providedName = BOB_NAME))
@@ -135,7 +138,7 @@ class CustomSerializationSchemeDriverTest {
@StartableByRPC
@InitiatingFlow
- class WriteTxToLedgerFlow(val counterparty: Party, val notary: Party) : FlowLogic() {
+ class WriteTxToLedgerFlow(private val counterparty: Party, val notary: Party) : FlowLogic() {
@Suspendable
override fun call(): SecureHash {
val wireTx = createWireTx(serviceHub, notary, counterparty.owningKey, KryoScheme.SCHEME_ID)
@@ -146,7 +149,7 @@ class CustomSerializationSchemeDriverTest {
return fullySignedTx.id
}
- fun signWireTx(wireTx: WireTransaction) : SignedTransaction {
+ private fun signWireTx(wireTx: WireTransaction) : SignedTransaction {
val signatureMetadata = SignatureMetadata(
serviceHub.myInfo.platformVersion,
Crypto.findSignatureScheme(serviceHub.myInfo.legalIdentitiesAndCerts.first().owningKey).schemeNumberID
@@ -157,18 +160,18 @@ class CustomSerializationSchemeDriverTest {
}
}
+ @Suppress("unused")
@InitiatedBy(WriteTxToLedgerFlow::class)
class SignWireTxFlow(private val session: FlowSession): FlowLogic() {
@Suspendable
override fun call(): SignedTransaction {
- val signTransactionFlow = object : SignTransactionFlow(session) {
- override fun checkTransaction(stx: SignedTransaction) {
- return
- }
- }
- val txId = subFlow(signTransactionFlow).id
+ val txId = subFlow(NoCheckSignTransactionFlow(session)).id
return subFlow(ReceiveFinalityFlow(session, expectedTxId = txId))
}
+
+ class NoCheckSignTransactionFlow(session: FlowSession) : SignTransactionFlow(session) {
+ override fun checkTransaction(stx: SignedTransaction) = Unit
+ }
}
@StartableByRPC
@@ -226,7 +229,7 @@ class CustomSerializationSchemeDriverTest {
@StartableByRPC
@InitiatingFlow
- class SendFlow(val counterparty: Party) : FlowLogic() {
+ class SendFlow(private val counterparty: Party) : FlowLogic() {
@Suspendable
override fun call(): Boolean {
val wtx = createWireTx(serviceHub, counterparty, counterparty.owningKey, KryoScheme.SCHEME_ID)
@@ -237,13 +240,14 @@ class CustomSerializationSchemeDriverTest {
}
@StartableByRPC
- class CreateWireTxFlow(val counterparty: Party) : FlowLogic() {
+ class CreateWireTxFlow(private val counterparty: Party) : FlowLogic() {
@Suspendable
override fun call(): WireTransaction {
return createWireTx(serviceHub, counterparty, counterparty.owningKey, KryoScheme.SCHEME_ID)
}
}
+ @Suppress("unused")
@InitiatedBy(SendFlow::class)
class ReceiveFlow(private val session: FlowSession): FlowLogic() {
@Suspendable
@@ -301,6 +305,7 @@ class CustomSerializationSchemeDriverTest {
kryo.isRegistrationRequired = false
kryo.instantiatorStrategy = CustomInstantiatorStrategy()
kryo.classLoader = classLoader
+ @Suppress("ReplaceJavaStaticMethodWithKotlinAnalog")
kryo.register(Arrays.asList("").javaClass, ArraysAsListSerializer())
}
diff --git a/node/src/integration-test/kotlin/net/corda/node/verification/ExternalVerificationTest.kt b/node/src/integration-test/kotlin/net/corda/node/verification/ExternalVerificationTest.kt
new file mode 100644
index 0000000000..810b1ada08
--- /dev/null
+++ b/node/src/integration-test/kotlin/net/corda/node/verification/ExternalVerificationTest.kt
@@ -0,0 +1,219 @@
+package net.corda.node.verification
+
+import co.paralleluniverse.fibers.Suspendable
+import com.typesafe.config.ConfigFactory
+import net.corda.core.contracts.CommandData
+import net.corda.core.contracts.Contract
+import net.corda.core.contracts.ContractState
+import net.corda.core.contracts.StateAndRef
+import net.corda.core.contracts.TransactionVerificationException
+import net.corda.core.crypto.SecureHash
+import net.corda.core.flows.FinalityFlow
+import net.corda.core.flows.FlowLogic
+import net.corda.core.flows.FlowSession
+import net.corda.core.flows.InitiatedBy
+import net.corda.core.flows.InitiatingFlow
+import net.corda.core.flows.NotaryChangeFlow
+import net.corda.core.flows.ReceiveFinalityFlow
+import net.corda.core.flows.StartableByRPC
+import net.corda.core.identity.AbstractParty
+import net.corda.core.identity.CordaX500Name
+import net.corda.core.identity.Party
+import net.corda.core.internal.concurrent.map
+import net.corda.core.internal.concurrent.transpose
+import net.corda.core.messaging.startFlow
+import net.corda.core.node.NodeInfo
+import net.corda.core.transactions.LedgerTransaction
+import net.corda.core.transactions.NotaryChangeWireTransaction
+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.contracts.asset.Cash
+import net.corda.finance.flows.CashIssueFlow
+import net.corda.finance.flows.CashPaymentFlow
+import net.corda.node.verification.ExternalVerificationTest.FailExternallyContract.State
+import net.corda.testing.core.ALICE_NAME
+import net.corda.testing.core.BOB_NAME
+import net.corda.testing.core.BOC_NAME
+import net.corda.testing.core.CHARLIE_NAME
+import net.corda.testing.core.DUMMY_NOTARY_NAME
+import net.corda.testing.core.singleIdentity
+import net.corda.testing.driver.NodeHandle
+import net.corda.testing.driver.NodeParameters
+import net.corda.testing.node.NotarySpec
+import net.corda.testing.node.internal.FINANCE_CORDAPPS
+import net.corda.testing.node.internal.cordappWithPackages
+import net.corda.testing.node.internal.enclosedCordapp
+import net.corda.testing.node.internal.internalDriver
+import org.assertj.core.api.Assertions.assertThat
+import org.assertj.core.api.Assertions.assertThatExceptionOfType
+import org.junit.Test
+import java.io.File
+import java.net.InetAddress
+import kotlin.io.path.div
+import kotlin.io.path.listDirectoryEntries
+import kotlin.io.path.name
+import kotlin.io.path.readText
+
+class ExternalVerificationTest {
+ @Test(timeout=300_000)
+ fun `regular transactions are verified in external verifier`() {
+ internalDriver(
+ systemProperties = mapOf("net.corda.node.verification.external" to "true"),
+ cordappsForAllNodes = FINANCE_CORDAPPS,
+ notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true, startInProcess = false))
+ ) {
+ val (notary, alice, bob) = listOf(
+ defaultNotaryNode,
+ startNode(NodeParameters(providedName = ALICE_NAME)),
+ startNode(NodeParameters(providedName = BOB_NAME))
+ ).transpose().getOrThrow()
+
+ val (issuanceTx) = alice.rpc.startFlow(
+ ::CashIssueFlow,
+ 10.DOLLARS,
+ OpaqueBytes.of(0x01),
+ defaultNotaryIdentity
+ ).returnValue.getOrThrow()
+
+ val (paymentTx) = alice.rpc.startFlow(
+ ::CashPaymentFlow,
+ 10.DOLLARS,
+ bob.nodeInfo.singleIdentity(),
+ false,
+ ).returnValue.getOrThrow()
+
+ notary.assertTransactionsWereVerifiedExternally(issuanceTx.id, paymentTx.id)
+ bob.assertTransactionsWereVerifiedExternally(issuanceTx.id, paymentTx.id)
+ }
+ }
+
+ @Test(timeout=300_000)
+ fun `regular transactions can fail verification in external verifier`() {
+ internalDriver(
+ systemProperties = mapOf("net.corda.node.verification.external" to "true"),
+ cordappsForAllNodes = listOf(cordappWithPackages("net.corda.node.verification", "com.typesafe.config"))
+ ) {
+ val (alice, bob, charlie) = listOf(
+ startNode(NodeParameters(providedName = ALICE_NAME)),
+ startNode(NodeParameters(providedName = BOB_NAME)),
+ startNode(NodeParameters(providedName = CHARLIE_NAME))
+ ).transpose().getOrThrow()
+
+ // Create a transaction from Alice to Bob, where Charlie is specified as the contract verification trigger
+ val firstState = alice.rpc.startFlow(::FailExternallyFlow, null, charlie.nodeInfo, bob.nodeInfo).returnValue.getOrThrow()
+ // When the transaction chain tries to moves onto Charlie, it will trigger the failure
+ assertThatExceptionOfType(TransactionVerificationException.ContractRejection::class.java)
+ .isThrownBy { bob.rpc.startFlow(::FailExternallyFlow, firstState, charlie.nodeInfo, charlie.nodeInfo).returnValue.getOrThrow() }
+ .withMessageContaining("Fail in external verifier: ${firstState.ref.txhash}")
+
+ // Make sure Charlie tried to verify the first transaction externally
+ assertThat(charlie.externalVerifierLogs()).contains("Fail in external verifier: ${firstState.ref.txhash}")
+ }
+ }
+
+ @Test(timeout=300_000)
+ fun `notary change transactions are verified in external verifier`() {
+ internalDriver(
+ systemProperties = mapOf("net.corda.node.verification.external" to "true"),
+ cordappsForAllNodes = FINANCE_CORDAPPS + enclosedCordapp(),
+ notarySpecs = listOf(DUMMY_NOTARY_NAME, BOC_NAME).map { NotarySpec(it, validating = true, startInProcess = false) }
+ ) {
+ val (notary1, notary2) = notaryHandles.map { handle -> handle.nodeHandles.map { it[0] } }.transpose().getOrThrow()
+ val alice = startNode(NodeParameters(providedName = ALICE_NAME)).getOrThrow()
+
+ val txId = alice.rpc.startFlow(
+ ::IssueAndChangeNotaryFlow,
+ notary1.nodeInfo.singleIdentity(),
+ notary2.nodeInfo.singleIdentity()
+ ).returnValue.getOrThrow()
+
+ notary1.assertTransactionsWereVerifiedExternally(txId)
+ alice.assertTransactionsWereVerifiedExternally(txId)
+ }
+ }
+
+ private fun NodeHandle.assertTransactionsWereVerifiedExternally(vararg txIds: SecureHash) {
+ val verifierLogContent = externalVerifierLogs()
+ for (txId in txIds) {
+ assertThat(verifierLogContent).contains("SignedTransaction(id=$txId) verified")
+ }
+ }
+
+ private fun NodeHandle.externalVerifierLogs(): String {
+ val verifierLogs = (baseDirectory / "logs")
+ .listDirectoryEntries()
+ .filter { it.name == "verifier-${InetAddress.getLocalHost().hostName}.log" }
+ assertThat(verifierLogs).describedAs("External verifier was not started").hasSize(1)
+ return verifierLogs[0].readText()
+ }
+
+ class FailExternallyContract : Contract {
+ override fun verify(tx: LedgerTransaction) {
+ val command = tx.commandsOfType().single()
+ if (insideExternalVerifier()) {
+ // The current directory for the external verifier is the node's base directory
+ val localName = CordaX500Name.parse(ConfigFactory.parseFile(File("node.conf")).getString("myLegalName"))
+ check(localName != command.value.failForParty.name) { "Fail in external verifier: ${tx.id}" }
+ }
+ }
+
+ private fun insideExternalVerifier(): Boolean {
+ return StackWalker.getInstance().walk { frames ->
+ frames.anyMatch { it.className.startsWith("net.corda.verifier.") }
+ }
+ }
+
+ data class State(val party: Party) : ContractState {
+ override val participants: List get() = listOf(party)
+ }
+
+ data class Command(val failForParty: Party) : CommandData
+ }
+
+ @StartableByRPC
+ @InitiatingFlow
+ class FailExternallyFlow(private val inputState: StateAndRef?,
+ private val failForParty: NodeInfo,
+ private val recipient: NodeInfo) : FlowLogic>() {
+ @Suspendable
+ override fun call(): StateAndRef {
+ val myParty = serviceHub.myInfo.legalIdentities[0]
+ val txBuilder = TransactionBuilder(serviceHub.networkMapCache.notaryIdentities[0])
+ inputState?.let(txBuilder::addInputState)
+ txBuilder.addOutputState(State(myParty), FailExternallyContract::class.java.name)
+ txBuilder.addCommand(FailExternallyContract.Command(failForParty.legalIdentities[0]), myParty.owningKey)
+ val initialTx = serviceHub.signInitialTransaction(txBuilder)
+ val sessions = arrayListOf(initiateFlow(recipient.legalIdentities[0]))
+ inputState?.let { sessions += initiateFlow(it.state.data.party) }
+ val notarisedTx = subFlow(FinalityFlow(initialTx, sessions))
+ return notarisedTx.toLedgerTransaction(serviceHub).outRef(0)
+ }
+ }
+
+ @Suppress("unused")
+ @InitiatedBy(FailExternallyFlow::class)
+ class ReceiverFlow(private val otherSide: FlowSession) : FlowLogic() {
+ @Suspendable
+ override fun call() {
+ subFlow(ReceiveFinalityFlow(otherSide))
+ }
+ }
+
+
+ @StartableByRPC
+ class IssueAndChangeNotaryFlow(private val oldNotary: Party, private val newNotary: Party) : FlowLogic() {
+ @Suspendable
+ override fun call(): SecureHash {
+ subFlow(CashIssueFlow(10.DOLLARS, OpaqueBytes.of(0x01), oldNotary))
+ val oldState = serviceHub.vaultService.queryBy(Cash.State::class.java).states.single()
+ assertThat(oldState.state.notary).isEqualTo(oldNotary)
+ val newState = subFlow(NotaryChangeFlow(oldState, newNotary))
+ assertThat(newState.state.notary).isEqualTo(newNotary)
+ val notaryChangeTx = serviceHub.validatedTransactions.getTransaction(newState.ref.txhash)
+ assertThat(notaryChangeTx?.coreTransaction).isInstanceOf(NotaryChangeWireTransaction::class.java)
+ return notaryChangeTx!!.id
+ }
+ }
+}
diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
index 72c31fc33c..ece28a229f 100644
--- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
@@ -38,6 +38,7 @@ import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.concurrent.flatMap
import net.corda.core.internal.concurrent.map
import net.corda.core.internal.concurrent.openFuture
+import net.corda.core.internal.cordapp.CordappProviderInternal
import net.corda.core.internal.div
import net.corda.core.internal.messaging.AttachmentTrustInfoRPCOps
import net.corda.core.internal.notary.NotaryService
@@ -47,6 +48,7 @@ import net.corda.core.internal.telemetry.SimpleLogTelemetryComponent
import net.corda.core.internal.telemetry.TelemetryComponent
import net.corda.core.internal.telemetry.TelemetryServiceImpl
import net.corda.core.internal.uncheckedCast
+import net.corda.core.internal.verification.VerifyingServiceHub
import net.corda.core.messaging.ClientRpcSslOptions
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.RPCOps
@@ -55,13 +57,11 @@ import net.corda.core.node.AppServiceHub
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub
-import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.ContractUpgradeService
import net.corda.core.node.services.CordaService
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.KeyManagementService
import net.corda.core.node.services.TelemetryService
-import net.corda.core.node.services.TransactionVerifierService
import net.corda.core.node.services.diagnostics.DiagnosticsService
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializationWhitelist
@@ -70,7 +70,7 @@ import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
import net.corda.core.serialization.internal.AttachmentsClassLoaderCacheImpl
import net.corda.core.toFuture
-import net.corda.core.transactions.LedgerTransaction
+import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.days
import net.corda.core.utilities.millis
@@ -82,7 +82,6 @@ import net.corda.node.internal.checkpoints.FlowManagerRPCOpsImpl
import net.corda.node.internal.classloading.requireAnnotation
import net.corda.node.internal.cordapp.CordappConfigFileProvider
import net.corda.node.internal.cordapp.CordappProviderImpl
-import net.corda.node.internal.cordapp.CordappProviderInternal
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.node.internal.cordapp.VirtualCordapp
import net.corda.node.internal.rpc.proxies.AuthenticatedRpcOpsProxy
@@ -122,10 +121,10 @@ import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.network.PersistentPartyInfoCache
import net.corda.node.services.persistence.AbstractPartyDescriptor
import net.corda.node.services.persistence.AbstractPartyToX500NameAsStringConverter
+import net.corda.node.services.persistence.AesDbEncryptionService
import net.corda.node.services.persistence.AttachmentStorageInternal
import net.corda.node.services.persistence.DBCheckpointPerformanceRecorder
import net.corda.node.services.persistence.DBCheckpointStorage
-import net.corda.node.services.persistence.AesDbEncryptionService
import net.corda.node.services.persistence.DBTransactionMappingStorage
import net.corda.node.services.persistence.DBTransactionStorageLedgerRecovery
import net.corda.node.services.persistence.NodeAttachmentService
@@ -141,7 +140,6 @@ import net.corda.node.services.statemachine.FlowOperator
import net.corda.node.services.statemachine.FlowStateMachineImpl
import net.corda.node.services.statemachine.SingleThreadedStateMachineManager
import net.corda.node.services.statemachine.StateMachineManager
-import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
import net.corda.node.services.vault.NodeVaultService
import net.corda.node.utilities.AffinityExecutor
@@ -157,6 +155,7 @@ import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService
import net.corda.nodeapi.internal.lifecycle.NodeLifecycleEvent
import net.corda.nodeapi.internal.lifecycle.NodeLifecycleEventsDistributor
import net.corda.nodeapi.internal.lifecycle.NodeServicesContext
+import net.corda.nodeapi.internal.namedThreadPoolExecutor
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.CordaTransactionSupportImpl
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
@@ -169,7 +168,6 @@ import net.corda.nodeapi.internal.persistence.RestrictedEntityManager
import net.corda.nodeapi.internal.persistence.SchemaMigration
import net.corda.nodeapi.internal.persistence.contextDatabase
import net.corda.nodeapi.internal.persistence.withoutDatabaseAccess
-import net.corda.nodeapi.internal.namedThreadPoolExecutor
import org.apache.activemq.artemis.utils.ReusableLatch
import org.jolokia.jvmagent.JolokiaServer
import org.jolokia.jvmagent.JolokiaServerConfig
@@ -181,7 +179,6 @@ import java.sql.Savepoint
import java.time.Clock
import java.time.Duration
import java.time.format.DateTimeParseException
-import java.util.ArrayList
import java.util.Properties
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
@@ -299,22 +296,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
val pkToIdCache = PublicKeyToOwningIdentityCacheImpl(database, cacheFactory)
@Suppress("LeakingThis")
val keyManagementService = makeKeyManagementService(identityService).tokenize()
- val servicesForResolution = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersStorage, transactionStorage).also {
- attachments.servicesForResolution = it
- }
- @Suppress("LeakingThis")
- val vaultService = makeVaultService(keyManagementService, servicesForResolution, database, cordappLoader).tokenize()
val nodeProperties = NodePropertiesPersistentStore(StubbedNodeUniqueIdProvider::value, database, cacheFactory)
val flowLogicRefFactory = makeFlowLogicRefFactoryImpl()
// TODO Cancelling parameters updates - if we do that, how we ensure that no one uses cancelled parameters in the transactions?
val networkMapUpdater = makeNetworkMapUpdater()
- @Suppress("LeakingThis")
- val transactionVerifierService = InMemoryTransactionVerifierService(
- numberOfWorkers = transactionVerifierWorkerCount,
- cordappProvider = cordappProvider,
- attachments = attachments
- ).tokenize()
private val attachmentsClassLoaderCache: AttachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(cacheFactory).tokenize()
val contractUpgradeService = ContractUpgradeServiceImpl(cacheFactory).tokenize()
val auditService = DummyAuditService().tokenize()
@@ -326,7 +312,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
log.warn("MessagingService subscription error", it)
})
}
- val services = ServiceHubInternalImpl().tokenize()
+ val services = ServiceHubImpl().tokenize()
+ @Suppress("LeakingThis")
+ val vaultService = makeVaultService(keyManagementService, database, cordappLoader).tokenize()
val checkpointStorage = DBCheckpointStorage(DBCheckpointPerformanceRecorder(services.monitoringService.metrics), platformClock)
@Suppress("LeakingThis")
val smm = makeStateMachineManager()
@@ -338,7 +326,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
private val cordappTelemetryComponents = MutableClassToInstanceMap.create()
private val shutdownExecutor = Executors.newSingleThreadExecutor(DefaultThreadFactory("Shutdown"))
- protected abstract val transactionVerifierWorkerCount: Int
/**
* Should be [rx.schedulers.Schedulers.io] for production,
* or [rx.internal.schedulers.CachedThreadScheduler] (with shutdown registered with [runOnStop]) for shared-JVM testing.
@@ -469,8 +456,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
Node.printBasicNodeInfo("Running database schema migration scripts ...")
val props = configuration.dataSourceProperties
if (props.isEmpty) throw DatabaseConfigurationException("There must be a database configured.")
- var pendingAppChanges: Int = 0
- var pendingCoreChanges: Int = 0
+ var pendingAppChanges = 0
+ var pendingCoreChanges = 0
database.startHikariPool(props, metricRegistry) { dataSource, haveCheckpoints ->
val schemaMigration = SchemaMigration(dataSource, cordappLoader, configuration.networkParametersPath, configuration.myLegalName)
if(updateCoreSchemas) {
@@ -505,13 +492,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
val updatedSchemas = listOfNotNull(
("core").takeIf { updateCoreSchemas },
("app").takeIf { updateAppSchemas }
- ).joinToString(separator = " and ");
+ ).joinToString(separator = " and ")
val pendingChanges = listOfNotNull(
("no outstanding").takeIf { pendingAppChanges == 0 && pendingCoreChanges == 0 },
("$pendingCoreChanges outstanding core").takeIf { !updateCoreSchemas && pendingCoreChanges > 0 },
("$pendingAppChanges outstanding app").takeIf { !updateAppSchemas && pendingAppChanges > 0 }
- ).joinToString(prefix = "There are ", postfix = " database changes.");
+ ).joinToString(prefix = "There are ", postfix = " database changes.")
Node.printBasicNodeInfo("Database migration scripts for $updatedSchemas schemas complete. $pendingChanges")
}
@@ -832,7 +819,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
networkMapCache,
NodeInfoWatcher(
configuration.baseDirectory,
- @Suppress("LeakingThis")
rxIoScheduler,
Duration.ofMillis(configuration.additionalNodeInfoPollingFrequencyMsec)
),
@@ -846,7 +832,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
platformClock,
database,
flowStarter,
- servicesForResolution,
+ services,
flowLogicRefFactory,
nodeProperties,
configuration.drainingModePollPeriod,
@@ -1160,12 +1146,19 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
networkParameters: NetworkParameters)
protected open fun makeVaultService(keyManagementService: KeyManagementService,
- services: NodeServicesForResolution,
database: CordaPersistence,
cordappLoader: CordappLoader): VaultServiceInternal {
return NodeVaultService(platformClock, keyManagementService, services, database, schemaService, cordappLoader.appClassLoader)
}
+ /**
+ * Dy default only internal verification is done.
+ * @see VerifyingServiceHub.tryExternalVerification
+ */
+ protected open fun tryExternalVerification(stx: SignedTransaction, checkSufficientSignatures: Boolean): Boolean {
+ return true
+ }
+
// JDK 11: switch to directly instantiating jolokia server (rather than indirectly via dynamically self attaching Java Agents,
// which is no longer supported from JDK 9 onwards (https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8180425).
// No longer need to use https://github.com/electronicarts/ea-agent-loader either (which is also deprecated)
@@ -1178,7 +1171,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
}
}
- inner class ServiceHubInternalImpl : SingletonSerializeAsToken(), ServiceHubInternal, ServicesForResolution by servicesForResolution, NetworkParameterUpdateListener {
+ inner class ServiceHubImpl : SingletonSerializeAsToken(), ServiceHubInternal, NetworkParameterUpdateListener {
override val rpcFlows = ArrayList>>()
override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage(database)
override val identityService: IdentityService get() = this@AbstractNode.identityService
@@ -1191,7 +1184,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
override val nodeProperties: NodePropertiesStore get() = this@AbstractNode.nodeProperties
override val database: CordaPersistence get() = this@AbstractNode.database
override val monitoringService: MonitoringService get() = this@AbstractNode.monitoringService
- override val transactionVerifierService: TransactionVerifierService get() = this@AbstractNode.transactionVerifierService
override val contractUpgradeService: ContractUpgradeService get() = this@AbstractNode.contractUpgradeService
override val auditService: AuditService get() = this@AbstractNode.auditService
override val attachments: AttachmentStorageInternal get() = this@AbstractNode.attachments
@@ -1216,6 +1208,10 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
private lateinit var _networkParameters: NetworkParameters
override val networkParameters: NetworkParameters get() = _networkParameters
+ init {
+ this@AbstractNode.attachments.servicesForResolution = this
+ }
+
fun start(myInfo: NodeInfo, networkParameters: NetworkParameters) {
this._myInfo = myInfo
this._networkParameters = networkParameters
@@ -1300,13 +1296,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
this@AbstractNode.runOnStop += runOnStop
}
- override fun specialise(ltx: LedgerTransaction): LedgerTransaction {
- return servicesForResolution.specialise(ltx)
- }
-
override fun onNewNetworkParameters(networkParameters: NetworkParameters) {
this._networkParameters = networkParameters
}
+
+ override fun tryExternalVerification(stx: SignedTransaction, checkSufficientSignatures: Boolean): Boolean {
+ return this@AbstractNode.tryExternalVerification(stx, checkSufficientSignatures)
+ }
}
}
diff --git a/node/src/main/kotlin/net/corda/node/internal/AppServiceHubImpl.kt b/node/src/main/kotlin/net/corda/node/internal/AppServiceHubImpl.kt
index 5acf706f91..041a1ec756 100644
--- a/node/src/main/kotlin/net/corda/node/internal/AppServiceHubImpl.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/AppServiceHubImpl.kt
@@ -4,13 +4,13 @@ import net.corda.core.context.InvocationContext
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByService
import net.corda.core.internal.FlowStateMachineHandle
+import net.corda.core.internal.ServiceHubCoreInternal
import net.corda.core.internal.concurrent.doneFuture
import net.corda.core.messaging.FlowHandle
import net.corda.core.messaging.FlowHandleImpl
import net.corda.core.messaging.FlowProgressHandle
import net.corda.core.messaging.FlowProgressHandleImpl
import net.corda.core.node.AppServiceHub
-import net.corda.core.node.ServiceHub
import net.corda.core.node.services.ServiceLifecycleEvent
import net.corda.core.node.services.ServiceLifecycleObserver
import net.corda.core.node.services.vault.CordaTransactionSupport
@@ -24,15 +24,16 @@ import net.corda.nodeapi.internal.lifecycle.NodeLifecycleEventsDistributor
import net.corda.nodeapi.internal.lifecycle.NodeLifecycleObserver
import net.corda.nodeapi.internal.lifecycle.NodeLifecycleObserver.Companion.reportSuccess
import rx.Observable
-import java.util.*
+import java.util.Objects
/**
* This customizes the ServiceHub for each [net.corda.core.node.services.CordaService] that is initiating flows.
*/
-internal class AppServiceHubImpl(private val serviceHub: ServiceHub, private val flowStarter: FlowStarter,
+internal class AppServiceHubImpl(private val serviceHub: ServiceHubCoreInternal,
+ private val flowStarter: FlowStarter,
override val database: CordaTransactionSupport,
private val nodeLifecycleEventsDistributor: NodeLifecycleEventsDistributor)
- : AppServiceHub, ServiceHub by serviceHub {
+ : AppServiceHub, ServiceHubCoreInternal by serviceHub {
companion object {
diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt
index 975aa9ea41..ffebec67df 100644
--- a/node/src/main/kotlin/net/corda/node/internal/Node.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt
@@ -7,8 +7,8 @@ import com.github.benmanes.caffeine.cache.Caffeine
import com.palominolabs.metrics.newrelic.AllEnabledMetricAttributeFilter
import com.palominolabs.metrics.newrelic.NewRelicReporter
import io.netty.util.NettyRuntime
-import net.corda.nodeapi.internal.rpc.client.AMQPClientSerializationScheme
import net.corda.cliutils.ShellConstants
+import net.corda.common.logging.errorReporting.NodeDatabaseErrors
import net.corda.core.concurrent.CordaFuture
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.CordaX500Name
@@ -26,6 +26,7 @@ import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.internal.SerializationEnvironment
import net.corda.core.serialization.internal.nodeSerializationEnv
+import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.node.CordaClock
@@ -36,9 +37,6 @@ import net.corda.node.internal.artemis.BrokerAddresses
import net.corda.node.internal.security.RPCSecurityManager
import net.corda.node.internal.security.RPCSecurityManagerImpl
import net.corda.node.internal.security.RPCSecurityManagerWithAdditionalUser
-import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
-import net.corda.nodeapi.internal.serialization.kryo.KRYO_CHECKPOINT_CONTEXT
-import net.corda.nodeapi.internal.serialization.kryo.KryoCheckpointSerializer
import net.corda.node.services.Permissions
import net.corda.node.services.api.FlowStarter
import net.corda.node.services.api.ServiceHubInternal
@@ -64,9 +62,8 @@ import net.corda.node.utilities.BindableNamedCacheFactory
import net.corda.node.utilities.DefaultNamedCacheFactory
import net.corda.node.utilities.DemoClock
import net.corda.node.utilities.errorAndTerminate
+import net.corda.node.verification.ExternalVerifierHandle
import net.corda.nodeapi.internal.ArtemisMessagingClient
-import net.corda.common.logging.errorReporting.NodeDatabaseErrors
-import net.corda.node.internal.classloading.scanForCustomSerializationScheme
import net.corda.nodeapi.internal.ShutdownHook
import net.corda.nodeapi.internal.addShutdownHook
import net.corda.nodeapi.internal.bridging.BridgeControlListener
@@ -74,6 +71,10 @@ import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
import net.corda.nodeapi.internal.protonwrapper.netty.toRevocationConfig
+import net.corda.nodeapi.internal.rpc.client.AMQPClientSerializationScheme
+import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
+import net.corda.nodeapi.internal.serialization.kryo.KRYO_CHECKPOINT_CONTEXT
+import net.corda.nodeapi.internal.serialization.kryo.KryoCheckpointSerializer
import net.corda.serialization.internal.AMQP_P2P_CONTEXT
import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
import net.corda.serialization.internal.AMQP_RPC_SERVER_CONTEXT
@@ -81,6 +82,7 @@ import net.corda.serialization.internal.AMQP_STORAGE_CONTEXT
import net.corda.serialization.internal.SerializationFactoryImpl
import net.corda.serialization.internal.amqp.SerializationFactoryCacheKey
import net.corda.serialization.internal.amqp.SerializerFactory
+import net.corda.serialization.internal.verifier.loadCustomSerializationScheme
import org.apache.commons.lang3.JavaVersion
import org.apache.commons.lang3.SystemUtils
import org.h2.jdbc.JdbcSQLNonTransientConnectionException
@@ -92,7 +94,6 @@ import java.lang.Long.max
import java.lang.Long.min
import java.net.BindException
import java.net.InetAddress
-import java.nio.file.Path
import java.nio.file.Paths
import java.time.Clock
import java.util.concurrent.TimeUnit
@@ -194,13 +195,14 @@ open class Node(configuration: NodeConfiguration,
}
override val log: Logger get() = staticLog
- override val transactionVerifierWorkerCount: Int get() = 4
private var internalRpcMessagingClient: InternalRPCMessagingClient? = null
private var rpcBroker: ArtemisBroker? = null
protected open val journalBufferTimeout : Int? = null
+ private val externalVerifierHandle = ExternalVerifierHandle(services).also { runOnStop += it::close }
+
private var shutdownHook: ShutdownHook? = null
// DISCUSSION
@@ -297,7 +299,7 @@ open class Node(configuration: NodeConfiguration,
printBasicNodeInfo("Advertised P2P messaging addresses", nodeInfo.addresses.joinToString())
val rpcServerConfiguration = RPCServerConfiguration.DEFAULT
- rpcServerAddresses?.let {
+ rpcServerAddresses.let {
internalRpcMessagingClient = InternalRPCMessagingClient(configuration.p2pSslOptions, it.admin, MAX_RPC_MESSAGE_SIZE, CordaX500Name.build(configuration.p2pSslOptions.keyStore.get()[X509Utilities.CORDA_CLIENT_TLS].subjectX500Principal), rpcServerConfiguration)
printBasicNodeInfo("RPC connection address", it.primary.toString())
printBasicNodeInfo("RPC admin connection address", it.admin.toString())
@@ -353,22 +355,18 @@ open class Node(configuration: NodeConfiguration,
)
}
- private fun startLocalRpcBroker(securityManager: RPCSecurityManager): BrokerAddresses? {
- return with(configuration) {
- rpcOptions.address.let {
- val rpcBrokerDirectory: Path = baseDirectory / "brokers" / "rpc"
- with(rpcOptions) {
- rpcBroker = if (useSsl) {
- ArtemisRpcBroker.withSsl(configuration.p2pSslOptions, this.address, adminAddress, sslConfig!!, securityManager, MAX_RPC_MESSAGE_SIZE,
- journalBufferTimeout, jmxMonitoringHttpPort != null, rpcBrokerDirectory, shouldStartLocalShell())
- } else {
- ArtemisRpcBroker.withoutSsl(configuration.p2pSslOptions, this.address, adminAddress, securityManager, MAX_RPC_MESSAGE_SIZE,
- journalBufferTimeout, jmxMonitoringHttpPort != null, rpcBrokerDirectory, shouldStartLocalShell())
- }
- }
- rpcBroker!!.addresses
+ private fun startLocalRpcBroker(securityManager: RPCSecurityManager): BrokerAddresses {
+ val rpcBrokerDirectory = configuration.baseDirectory / "brokers" / "rpc"
+ with(configuration.rpcOptions) {
+ rpcBroker = if (useSsl) {
+ ArtemisRpcBroker.withSsl(configuration.p2pSslOptions, this.address, adminAddress, sslConfig!!, securityManager, MAX_RPC_MESSAGE_SIZE,
+ journalBufferTimeout, configuration.jmxMonitoringHttpPort != null, rpcBrokerDirectory, configuration.shouldStartLocalShell())
+ } else {
+ ArtemisRpcBroker.withoutSsl(configuration.p2pSslOptions, this.address, adminAddress, securityManager, MAX_RPC_MESSAGE_SIZE,
+ journalBufferTimeout, configuration.jmxMonitoringHttpPort != null, rpcBrokerDirectory, configuration.shouldStartLocalShell())
}
}
+ return rpcBroker!!.addresses
}
override fun myAddresses(): List = listOf(getAdvertisedAddress()) + configuration.additionalP2PAddresses
@@ -392,7 +390,7 @@ open class Node(configuration: NodeConfiguration,
* machine's public IP address to be used instead by looking through the network interfaces.
*/
private fun tryDetectIfNotPublicHost(host: String): String? {
- return if (host.toLowerCase() == "localhost") {
+ return if (host.lowercase() == "localhost") {
log.warn("p2pAddress specified as localhost. Trying to autodetect a suitable public address to advertise in network map." +
"To disable autodetect set detectPublicIp = false in the node.conf, or consider using messagingServerAddress and messagingServerExternal")
val foundPublicIP = AddressUtils.tryDetectPublicIP()
@@ -572,7 +570,7 @@ open class Node(configuration: NodeConfiguration,
if (!initialiseSerialization) return
val classloader = cordappLoader.appClassLoader
val customScheme = System.getProperty("experimental.corda.customSerializationScheme")?.let {
- scanForCustomSerializationScheme(it, classloader)
+ loadCustomSerializationScheme(it, classloader)
}
nodeSerializationEnv = SerializationEnvironment.with(
SerializationFactoryImpl().apply {
@@ -590,6 +588,17 @@ open class Node(configuration: NodeConfiguration,
)
}
+ override fun tryExternalVerification(stx: SignedTransaction, checkSufficientSignatures: Boolean): Boolean {
+ // TODO Determine from transaction whether it should be verified externally
+ // TODO If both old and new attachments are present then return true so that internal verification is also done.
+ return if (java.lang.Boolean.getBoolean("net.corda.node.verification.external")) {
+ externalVerifierHandle.verifyTransaction(stx, checkSufficientSignatures)
+ false
+ } else {
+ true
+ }
+ }
+
/** Starts a blocking event loop for message dispatch. */
fun run() {
internalRpcMessagingClient?.start(rpcBroker!!.serverControl)
diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeServicesForResolution.kt b/node/src/main/kotlin/net/corda/node/internal/NodeServicesForResolution.kt
deleted file mode 100644
index 5baa528297..0000000000
--- a/node/src/main/kotlin/net/corda/node/internal/NodeServicesForResolution.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package net.corda.node.internal
-
-import net.corda.core.contracts.ContractState
-import net.corda.core.contracts.StateAndRef
-import net.corda.core.contracts.StateRef
-import net.corda.core.contracts.TransactionResolutionException
-import net.corda.core.node.ServicesForResolution
-import java.util.LinkedHashSet
-
-interface NodeServicesForResolution : ServicesForResolution {
- @Throws(TransactionResolutionException::class)
- override fun loadStates(stateRefs: Set): Set> = loadStates(stateRefs, LinkedHashSet())
-
- fun >> loadStates(input: Iterable, output: C): C
-}
diff --git a/node/src/main/kotlin/net/corda/node/internal/ServicesForResolutionImpl.kt b/node/src/main/kotlin/net/corda/node/internal/ServicesForResolutionImpl.kt
deleted file mode 100644
index ffb21894c1..0000000000
--- a/node/src/main/kotlin/net/corda/node/internal/ServicesForResolutionImpl.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-package net.corda.node.internal
-
-import net.corda.core.contracts.Attachment
-import net.corda.core.contracts.AttachmentResolutionException
-import net.corda.core.contracts.ContractAttachment
-import net.corda.core.contracts.ContractState
-import net.corda.core.contracts.StateAndRef
-import net.corda.core.contracts.StateRef
-import net.corda.core.contracts.TransactionResolutionException
-import net.corda.core.contracts.TransactionState
-import net.corda.core.cordapp.CordappProvider
-import net.corda.core.crypto.SecureHash
-import net.corda.core.internal.SerializedStateAndRef
-import net.corda.core.internal.uncheckedCast
-import net.corda.core.node.NetworkParameters
-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.TransactionStorage
-import net.corda.core.transactions.BaseTransaction
-import net.corda.core.transactions.ContractUpgradeWireTransaction
-import net.corda.core.transactions.NotaryChangeWireTransaction
-import net.corda.core.transactions.SignedTransaction
-import net.corda.core.transactions.WireTransaction
-import net.corda.core.transactions.WireTransaction.Companion.resolveStateRefBinaryComponent
-
-data class ServicesForResolutionImpl(
- override val identityService: IdentityService,
- override val attachments: AttachmentStorage,
- override val cordappProvider: CordappProvider,
- override val networkParametersService: NetworkParametersService,
- private val validatedTransactions: TransactionStorage
-) : NodeServicesForResolution {
- override val networkParameters: NetworkParameters get() = networkParametersService.lookup(networkParametersService.currentHash) ?:
- throw IllegalArgumentException("No current parameters in network parameters storage")
-
- @Throws(TransactionResolutionException::class)
- override fun loadState(stateRef: StateRef): TransactionState<*> {
- return toBaseTransaction(stateRef.txhash).outputs[stateRef.index]
- }
-
- override fun >> loadStates(input: Iterable, output: C): C {
- val baseTxs = HashMap()
- return input.mapTo(output) { stateRef ->
- val baseTx = baseTxs.computeIfAbsent(stateRef.txhash, ::toBaseTransaction)
- StateAndRef(uncheckedCast(baseTx.outputs[stateRef.index]), stateRef)
- }
- }
-
- @Throws(TransactionResolutionException::class, AttachmentResolutionException::class)
- override fun loadContractAttachment(stateRef: StateRef): Attachment {
- // We may need to recursively chase transactions if there are notary changes.
- fun inner(stateRef: StateRef, forContractClassName: String?): Attachment {
- val ctx = getSignedTransaction(stateRef.txhash).coreTransaction
- when (ctx) {
- is WireTransaction -> {
- val transactionState = ctx.outRef(stateRef.index).state
- for (attachmentId in ctx.attachments) {
- val attachment = attachments.openAttachment(attachmentId)
- if (attachment is ContractAttachment && (forContractClassName ?: transactionState.contract) in attachment.allContracts) {
- return attachment
- }
- }
- throw AttachmentResolutionException(stateRef.txhash)
- }
- is ContractUpgradeWireTransaction -> {
- return attachments.openAttachment(ctx.upgradedContractAttachmentId) ?: throw AttachmentResolutionException(stateRef.txhash)
- }
- is NotaryChangeWireTransaction -> {
- val transactionState = SerializedStateAndRef(resolveStateRefBinaryComponent(stateRef, this)!!, stateRef).toStateAndRef().state
- // TODO: check only one (or until one is resolved successfully), max recursive invocations check?
- return ctx.inputs.map { inner(it, transactionState.contract) }.firstOrNull() ?: throw AttachmentResolutionException(stateRef.txhash)
- }
- else -> throw UnsupportedOperationException("Attempting to resolve attachment for index ${stateRef.index} of a ${ctx.javaClass} transaction. This is not supported.")
- }
- }
- return inner(stateRef, null)
- }
-
- private fun toBaseTransaction(txhash: SecureHash): BaseTransaction = getSignedTransaction(txhash).resolveBaseTransaction(this)
-
- private fun getSignedTransaction(txhash: SecureHash): SignedTransaction {
- return validatedTransactions.getTransaction(txhash) ?: throw TransactionResolutionException(txhash)
- }
-}
diff --git a/node/src/main/kotlin/net/corda/node/internal/classloading/Utils.kt b/node/src/main/kotlin/net/corda/node/internal/classloading/Utils.kt
index bb49eeb179..958e879981 100644
--- a/node/src/main/kotlin/net/corda/node/internal/classloading/Utils.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/classloading/Utils.kt
@@ -2,31 +2,6 @@
package net.corda.node.internal.classloading
-import net.corda.core.serialization.CustomSerializationScheme
-import net.corda.node.internal.ConfigurationException
-import net.corda.nodeapi.internal.serialization.CustomSerializationSchemeAdapter
-import net.corda.serialization.internal.SerializationScheme
-import java.lang.reflect.Constructor
-
inline fun Class<*>.requireAnnotation(): A {
return requireNotNull(getDeclaredAnnotation(A::class.java)) { "$name needs to be annotated with ${A::class.java.name}" }
}
-
-fun scanForCustomSerializationScheme(className: String, classLoader: ClassLoader) : SerializationScheme {
- val schemaClass = try {
- Class.forName(className, false, classLoader)
- } catch (exception: ClassNotFoundException) {
- throw ConfigurationException("$className was declared as a custom serialization scheme but could not be found.")
- }
- val constructor = validateScheme(schemaClass, className)
- return CustomSerializationSchemeAdapter(constructor.newInstance() as CustomSerializationScheme)
-}
-
-private fun validateScheme(clazz: Class<*>, className: String): Constructor<*> {
- if (!clazz.interfaces.contains(CustomSerializationScheme::class.java)) {
- throw ConfigurationException("$className was declared as a custom serialization scheme but does not implement" +
- " ${CustomSerializationScheme::class.java.canonicalName}")
- }
- return clazz.constructors.singleOrNull { it.parameters.isEmpty() } ?: throw ConfigurationException("$className was declared as a " +
- "custom serialization scheme but does not have a no argument constructor.")
-}
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt
index 79a657b0ad..3153db4853 100644
--- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt
@@ -1,7 +1,6 @@
package net.corda.node.internal.cordapp
import com.google.common.collect.HashBiMap
-import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractClassName
import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappContext
@@ -9,19 +8,16 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.internal.DEPLOYED_CORDAPP_UPLOADER
import net.corda.core.internal.cordapp.CordappImpl
-import net.corda.core.internal.isUploaderTrusted
-import net.corda.core.node.services.AttachmentFixup
+import net.corda.core.internal.cordapp.CordappProviderInternal
+import net.corda.core.internal.verification.AttachmentFixups
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.AttachmentStorage
-import net.corda.core.serialization.MissingAttachmentsException
import net.corda.core.serialization.SingletonSerializeAsToken
-import net.corda.core.utilities.contextLogger
import net.corda.node.services.persistence.AttachmentStorageInternal
import net.corda.nodeapi.internal.cordapp.CordappLoader
-import java.net.JarURLConnection
import java.net.URL
+import java.nio.file.FileAlreadyExistsException
import java.util.concurrent.ConcurrentHashMap
-import java.util.jar.JarFile
/**
* Cordapp provider and store. For querying CorDapps for their attachment and vice versa.
@@ -29,14 +25,11 @@ import java.util.jar.JarFile
open class CordappProviderImpl(val cordappLoader: CordappLoader,
private val cordappConfigProvider: CordappConfigProvider,
private val attachmentStorage: AttachmentStorage) : SingletonSerializeAsToken(), CordappProviderInternal {
- companion object {
- const val COMMENT_MARKER = '#'
- private val log = contextLogger()
- }
-
private val contextCache = ConcurrentHashMap()
private val cordappAttachments = HashBiMap.create()
- private val attachmentFixups = arrayListOf()
+ private val attachmentFixups = AttachmentFixups()
+
+ override val appClassLoader: ClassLoader get() = cordappLoader.appClassLoader
/**
* Current known CorDapps loaded on this node
@@ -47,7 +40,7 @@ open class CordappProviderImpl(val cordappLoader: CordappLoader,
cordappAttachments.putAll(loadContractsIntoAttachmentStore())
verifyInstalledCordapps()
// Load the fix-ups after uploading any new contracts into attachment storage.
- attachmentFixups.addAll(loadAttachmentFixups())
+ attachmentFixups.load(cordappLoader.appClassLoader)
}
private fun verifyInstalledCordapps() {
@@ -79,116 +72,35 @@ open class CordappProviderImpl(val cordappLoader: CordappLoader,
*/
fun getCordappAttachmentId(cordapp: Cordapp): SecureHash? = cordappAttachments.inverse()[cordapp.jarPath]
- private fun loadContractsIntoAttachmentStore(): Map =
- cordapps.filter { it.contractClassNames.isNotEmpty() }.map { cordapp ->
- cordapp.jarPath.openStream().use { stream ->
- try {
- // This code can be reached by [MockNetwork] tests which uses [MockAttachmentStorage]
- // [MockAttachmentStorage] cannot implement [AttachmentStorageInternal] because
- // doing so results in internal functions being exposed in the public API.
- if (attachmentStorage is AttachmentStorageInternal) {
- attachmentStorage.privilegedImportAttachment(
+ private fun loadContractsIntoAttachmentStore(): Map {
+ return cordapps.filter { it.contractClassNames.isNotEmpty() }.associate { cordapp ->
+ cordapp.jarPath.openStream().use { stream ->
+ try {
+ // This code can be reached by [MockNetwork] tests which uses [MockAttachmentStorage]
+ // [MockAttachmentStorage] cannot implement [AttachmentStorageInternal] because
+ // doing so results in internal functions being exposed in the public API.
+ if (attachmentStorage is AttachmentStorageInternal) {
+ attachmentStorage.privilegedImportAttachment(
stream,
DEPLOYED_CORDAPP_UPLOADER,
cordapp.info.shortName
- )
- } else {
- attachmentStorage.importAttachment(
+ )
+ } else {
+ attachmentStorage.importAttachment(
stream,
DEPLOYED_CORDAPP_UPLOADER,
cordapp.info.shortName
- )
- }
- } catch (faee: java.nio.file.FileAlreadyExistsException) {
- AttachmentId.create(faee.message!!)
+ )
}
- } to cordapp.jarPath
- }.toMap()
-
- /**
- * Loads the "fixup" rules from all META-INF/Corda-Fixups files.
- * These files have the following format:
- * ,...=>,,...
- * where each is the SHA256 of a CorDapp JAR that
- * [net.corda.core.transactions.TransactionBuilder] will expect to find
- * inside [AttachmentStorage].
- *
- * These rules are for repairing broken CorDapps. A correctly written
- * CorDapp should not require them.
- */
- private fun loadAttachmentFixups(): List {
- return cordappLoader.appClassLoader.getResources("META-INF/Corda-Fixups").asSequence()
- .mapNotNull { fixup ->
- fixup.openConnection() as? JarURLConnection
- }.filter { fixupConnection ->
- isValidFixup(fixupConnection.jarFile)
- }.flatMapTo(ArrayList()) { fixupConnection ->
- fixupConnection.inputStream.bufferedReader().useLines { lines ->
- lines.map { it.substringBefore(COMMENT_MARKER) }.map(String::trim).filterNot(String::isEmpty).map { line ->
- val tokens = line.split("=>")
- require(tokens.size == 2) {
- "Invalid fix-up line '$line' in '${fixupConnection.jarFile.name}'"
- }
- val source = parseIds(tokens[0])
- require(source.isNotEmpty()) {
- "Forbidden empty list of source attachment IDs in '${fixupConnection.jarFile.name}'"
- }
- val target = parseIds(tokens[1])
- Pair(source, target)
- }.toList().asSequence()
+ } catch (faee: FileAlreadyExistsException) {
+ AttachmentId.create(faee.message!!)
}
- }
- }
-
- private fun isValidFixup(jarFile: JarFile): Boolean {
- return jarFile.entries().asSequence().all { it.name.startsWith("META-INF/") }.also { isValid ->
- if (!isValid) {
- log.warn("FixUp '{}' contains files outside META-INF/ - IGNORING!", jarFile.name)
- }
+ } to cordapp.jarPath
}
}
- private fun parseIds(ids: String): Set {
- return ids.split(",").map(String::trim)
- .filterNot(String::isEmpty)
- .mapTo(LinkedHashSet(), SecureHash.Companion::create)
- }
-
- /**
- * Apply this node's attachment fix-up rules to the given attachment IDs.
- *
- * @param attachmentIds A collection of [AttachmentId]s, e.g. as provided by a transaction.
- * @return The [attachmentIds] with the fix-up rules applied.
- */
override fun fixupAttachmentIds(attachmentIds: Collection): Set {
- val replacementIds = LinkedHashSet(attachmentIds)
- attachmentFixups.forEach { (source, target) ->
- if (replacementIds.containsAll(source)) {
- replacementIds.removeAll(source)
- replacementIds.addAll(target)
- }
- }
- return replacementIds
- }
-
- /**
- * Apply this node's attachment fix-up rules to the given attachments.
- *
- * @param attachments A collection of [Attachment] objects, e.g. as provided by a transaction.
- * @return The [attachments] with the node's fix-up rules applied.
- */
- override fun fixupAttachments(attachments: Collection): Collection {
- val attachmentsById = attachments.associateByTo(LinkedHashMap(), Attachment::id)
- val replacementIds = fixupAttachmentIds(attachmentsById.keys)
- attachmentsById.keys.retainAll(replacementIds)
- (replacementIds - attachmentsById.keys).forEach { extraId ->
- val extraAttachment = attachmentStorage.openAttachment(extraId)
- if (extraAttachment == null || !extraAttachment.isUploaderTrusted()) {
- throw MissingAttachmentsException(listOf(extraId))
- }
- attachmentsById[extraId] = extraAttachment
- }
- return attachmentsById.values
+ return attachmentFixups.fixupAttachmentIds(attachmentIds)
}
/**
diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderInternal.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderInternal.kt
deleted file mode 100644
index ed8f410bfa..0000000000
--- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderInternal.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package net.corda.node.internal.cordapp
-
-import net.corda.core.contracts.Attachment
-import net.corda.core.cordapp.Cordapp
-import net.corda.core.cordapp.CordappProvider
-import net.corda.core.flows.FlowLogic
-import net.corda.core.internal.CordappFixupInternal
-import net.corda.core.internal.cordapp.CordappImpl
-
-interface CordappProviderInternal : CordappProvider, CordappFixupInternal {
- val cordapps: List
- fun getCordappForFlow(flowLogic: FlowLogic<*>): Cordapp?
- fun fixupAttachments(attachments: Collection): Collection
-}
diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt
index 8fb8e8c671..75f0a759a5 100644
--- a/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt
@@ -9,15 +9,33 @@ import net.corda.core.CordaRuntimeException
import net.corda.core.cordapp.Cordapp
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
-import net.corda.core.flows.*
-import net.corda.core.internal.*
+import net.corda.core.flows.FlowLogic
+import net.corda.core.flows.InitiatedBy
+import net.corda.core.flows.SchedulableFlow
+import net.corda.core.flows.StartableByRPC
+import net.corda.core.flows.StartableByService
+import net.corda.core.internal.JAVA_17_CLASS_FILE_FORMAT_MAJOR_VERSION
+import net.corda.core.internal.JAVA_1_2_CLASS_FILE_FORMAT_MAJOR_VERSION
+import net.corda.core.internal.JarSignatureCollector
+import net.corda.core.internal.PlatformVersionSwitches
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.cordapp.CordappImpl.Companion.UNKNOWN_INFO
import net.corda.core.internal.cordapp.get
+import net.corda.core.internal.exists
+import net.corda.core.internal.hash
+import net.corda.core.internal.isAbstractClass
+import net.corda.core.internal.list
+import net.corda.core.internal.loadClassOfType
+import net.corda.core.internal.location
import net.corda.core.internal.notary.NotaryService
import net.corda.core.internal.notary.SinglePartyNotaryService
-import net.corda.core.node.services.CordaService
+import net.corda.core.internal.objectOrNewInstance
+import net.corda.core.internal.pooledScan
+import net.corda.core.internal.readFully
import net.corda.core.internal.telemetry.TelemetryComponent
+import net.corda.core.internal.toTypedArray
+import net.corda.core.internal.warnContractWithoutConstraintPropagation
+import net.corda.core.node.services.CordaService
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.CheckpointCustomSerializer
import net.corda.core.serialization.SerializationCustomSerializer
@@ -33,12 +51,12 @@ import java.math.BigInteger
import java.net.URL
import java.net.URLClassLoader
import java.nio.file.Path
-import java.util.*
+import java.util.Random
+import java.util.ServiceLoader
import java.util.concurrent.ConcurrentHashMap
import java.util.jar.JarInputStream
import java.util.jar.Manifest
import java.util.zip.ZipInputStream
-import kotlin.collections.LinkedHashSet
import kotlin.reflect.KClass
/**
@@ -363,7 +381,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
private fun loadClass(className: String, type: KClass): Class? {
return try {
- Class.forName(className, false, appClassLoader).asSubclass(type.java)
+ loadClassOfType(type.java, className, false, appClassLoader)
} catch (e: ClassCastException) {
logger.warn("As $className must be a sub-type of ${type.java.name}")
null
diff --git a/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManagerImpl.kt b/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManagerImpl.kt
index 605bf0bd71..f246c02330 100644
--- a/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManagerImpl.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/security/RPCSecurityManagerImpl.kt
@@ -1,8 +1,6 @@
package net.corda.node.internal.security
-
import com.github.benmanes.caffeine.cache.Cache
-import com.github.benmanes.caffeine.cache.Caffeine
import com.google.common.primitives.Ints
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.uncheckedCast
@@ -241,7 +239,7 @@ private class CaffeineCacheManager(val maxSize: Long,
private fun buildCache(name: String): ShiroCache {
logger.info("Constructing cache '$name' with maximumSize=$maxSize, TTL=${timeToLiveSeconds}s")
- return cacheFactory.buildNamed(Caffeine.newBuilder(), "RPCSecurityManagerShiroCache_$name").toShiroCache()
+ return cacheFactory.buildNamed("RPCSecurityManagerShiroCache_$name").toShiroCache()
}
companion object {
diff --git a/node/src/main/kotlin/net/corda/node/migration/CordaMigration.kt b/node/src/main/kotlin/net/corda/node/migration/CordaMigration.kt
index 4c28ab7304..717b94a5d1 100644
--- a/node/src/main/kotlin/net/corda/node/migration/CordaMigration.kt
+++ b/node/src/main/kotlin/net/corda/node/migration/CordaMigration.kt
@@ -7,17 +7,13 @@ import liquibase.database.jvm.JdbcConnection
import liquibase.exception.ValidationErrors
import liquibase.resource.ResourceAccessor
import net.corda.core.schemas.MappedSchema
-import net.corda.node.SimpleClock
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.persistence.AbstractPartyToX500NameAsStringConverter
-import net.corda.node.services.persistence.DBTransactionStorage
-import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.node.services.persistence.PublicKeyToTextConverter
import net.corda.nodeapi.internal.persistence.CordaPersistence
import java.io.PrintWriter
import java.sql.Connection
import java.sql.SQLFeatureNotSupportedException
-import java.time.Clock
import java.util.logging.Logger
import javax.sql.DataSource
@@ -39,11 +35,6 @@ abstract class CordaMigration : CustomTaskChange {
private lateinit var _cordaDB: CordaPersistence
- val servicesForResolution: MigrationServicesForResolution
- get() = _servicesForResolution
-
- private lateinit var _servicesForResolution: MigrationServicesForResolution
-
/**
* Initialise a subset of node services so that data from these can be used to perform migrations.
*
@@ -60,12 +51,6 @@ abstract class CordaMigration : CustomTaskChange {
_cordaDB = createDatabase(url, cacheFactory, identityService, schema)
cordaDB.start(dataSource)
identityService.database = cordaDB
-
- cordaDB.transaction {
- val dbTransactions = DBTransactionStorage(cordaDB, cacheFactory, SimpleClock(Clock.systemUTC()))
- val attachmentsService = NodeAttachmentService(metricRegistry, cacheFactory, cordaDB)
- _servicesForResolution = MigrationServicesForResolution(identityService, attachmentsService, dbTransactions, cordaDB, cacheFactory)
- }
}
private fun createDatabase(jdbcUrl: String,
diff --git a/node/src/main/kotlin/net/corda/node/migration/MigrationServicesForResolution.kt b/node/src/main/kotlin/net/corda/node/migration/MigrationServicesForResolution.kt
deleted file mode 100644
index 0186b9659c..0000000000
--- a/node/src/main/kotlin/net/corda/node/migration/MigrationServicesForResolution.kt
+++ /dev/null
@@ -1,175 +0,0 @@
-package net.corda.node.migration
-
-import net.corda.core.contracts.*
-import net.corda.core.cordapp.CordappContext
-import net.corda.core.cordapp.CordappProvider
-import net.corda.core.crypto.SecureHash
-import net.corda.core.internal.deserialiseComponentGroup
-import net.corda.core.internal.div
-import net.corda.core.internal.readObject
-import net.corda.core.node.NetworkParameters
-import net.corda.core.node.ServicesForResolution
-import net.corda.core.node.services.AttachmentId
-import net.corda.core.node.services.IdentityService
-import net.corda.core.node.services.NetworkParametersService
-import net.corda.core.node.services.TransactionStorage
-import net.corda.core.serialization.deserialize
-import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
-import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
-import net.corda.core.serialization.internal.AttachmentsClassLoaderCacheImpl
-import net.corda.core.transactions.ContractUpgradeLedgerTransaction
-import net.corda.core.transactions.NotaryChangeLedgerTransaction
-import net.corda.core.transactions.WireTransaction
-import net.corda.core.utilities.contextLogger
-import net.corda.node.internal.DBNetworkParametersStorage
-import net.corda.node.services.attachments.NodeAttachmentTrustCalculator
-import net.corda.node.services.persistence.AttachmentStorageInternal
-import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME
-import net.corda.nodeapi.internal.network.SignedNetworkParameters
-import net.corda.nodeapi.internal.persistence.CordaPersistence
-import net.corda.nodeapi.internal.persistence.SchemaMigration
-import java.nio.file.Paths
-import java.time.Clock
-import java.time.Duration
-import java.util.Comparator.comparingInt
-
-class MigrationServicesForResolution(
- override val identityService: IdentityService,
- override val attachments: AttachmentStorageInternal,
- private val transactions: TransactionStorage,
- private val cordaDB: CordaPersistence,
- cacheFactory: MigrationNamedCacheFactory
-): ServicesForResolution {
-
- companion object {
- val logger = contextLogger()
- }
- override val cordappProvider: CordappProvider
- get() = object : CordappProvider {
-
- val cordappLoader = SchemaMigration.loader.get()
-
- override fun getAppContext(): CordappContext {
- TODO("not implemented")
- }
-
- override fun getContractAttachmentID(contractClassName: ContractClassName): AttachmentId? {
- TODO("not implemented")
- }
- }
- private val cordappLoader = SchemaMigration.loader.get()
-
- private val attachmentTrustCalculator = NodeAttachmentTrustCalculator(
- attachments,
- cacheFactory
- )
-
- private val attachmentsClassLoaderCache: AttachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(cacheFactory)
-
- private fun defaultNetworkParameters(): NetworkParameters {
- logger.warn("Using a dummy set of network parameters for migration.")
- val clock = Clock.systemUTC()
- return NetworkParameters(
- 1,
- listOf(),
- 1,
- 1,
- clock.instant(),
- 1,
- mapOf(),
- Duration.ZERO,
- mapOf()
- )
- }
-
- private fun getNetworkParametersFromFile(): SignedNetworkParameters? {
- return try {
- val dir = System.getProperty(SchemaMigration.NODE_BASE_DIR_KEY)
- val path = Paths.get(dir) / NETWORK_PARAMS_FILE_NAME
- path.readObject()
- } catch (e: Exception) {
- logger.info("Couldn't find network parameters file: ${e.message}. This is expected if the node is starting for the first time.")
- null
- }
- }
-
- override val networkParametersService: NetworkParametersService = object : NetworkParametersService {
-
- private val storage = DBNetworkParametersStorage.createParametersMap(cacheFactory)
-
- private val filedParams = getNetworkParametersFromFile()
-
- override val defaultHash: SecureHash = filedParams?.raw?.hash ?: SecureHash.getZeroHash()
- override val currentHash: SecureHash = cordaDB.transaction {
- storage.allPersisted.use {
- it.max(comparingInt { it.second.verified().epoch }).map { it.first }.orElse(defaultHash)
- }
- }
-
- override fun lookup(hash: SecureHash): NetworkParameters? {
- // Note that the parameters in any file shouldn't be put into the database - this will be done by the node on startup.
- return if (hash == filedParams?.raw?.hash) {
- filedParams.raw.deserialize()
- } else {
- cordaDB.transaction { storage[hash]?.verified() }
- }
- }
- }
-
- override val networkParameters: NetworkParameters = networkParametersService.lookup(networkParametersService.currentHash)
- ?: getNetworkParametersFromFile()?.raw?.deserialize()
- ?: defaultNetworkParameters()
-
- private fun extractStateFromTx(tx: WireTransaction, stateIndices: Collection): List> {
- return try {
- val txAttachments = tx.attachments.mapNotNull { attachments.openAttachment(it)}
- val states = AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(
- txAttachments,
- networkParameters,
- tx.id,
- attachmentTrustCalculator::calculate,
- cordappLoader.appClassLoader,
- attachmentsClassLoaderCache) {
- deserialiseComponentGroup(tx.componentGroups, TransactionState::class, ComponentGroupEnum.OUTPUTS_GROUP, forceDeserialize = true)
- }
- states.filterIndexed {index, _ -> stateIndices.contains(index)}.toList()
- } catch (e: Exception) {
- // If there is no attachment that allows the state class to be deserialised correctly, then carpent a state class anyway. It
- // might still be possible to access the participants depending on how the state class was serialised.
- logger.debug("Could not use attachments to deserialise transaction output states for transaction ${tx.id}")
- tx.outputs.filterIndexed { index, _ -> stateIndices.contains(index)}
- }
- }
-
- override fun loadState(stateRef: StateRef): TransactionState<*> {
- val stx = transactions.getTransaction(stateRef.txhash)
- ?: throw MigrationException("Could not get transaction with hash ${stateRef.txhash} out of vault")
- val baseTx = stx.resolveBaseTransaction(this)
- return when (baseTx) {
- is NotaryChangeLedgerTransaction -> baseTx.outputs[stateRef.index]
- is ContractUpgradeLedgerTransaction -> baseTx.outputs[stateRef.index]
- is WireTransaction -> extractStateFromTx(baseTx, listOf(stateRef.index)).first()
- else -> throw MigrationException("Unknown transaction type ${baseTx::class.qualifiedName} found when loading a state")
- }
- }
-
- override fun loadStates(stateRefs: Set): Set> {
- return stateRefs.groupBy { it.txhash }.flatMap {
- val stx = transactions.getTransaction(it.key)
- ?: throw MigrationException("Could not get transaction with hash ${it.key} out of vault")
- val baseTx = stx.resolveBaseTransaction(this)
- val stateList = when (baseTx) {
- is NotaryChangeLedgerTransaction -> it.value.map { stateRef -> StateAndRef(baseTx.outputs[stateRef.index], stateRef) }
- is ContractUpgradeLedgerTransaction -> it.value.map { stateRef -> StateAndRef(baseTx.outputs[stateRef.index], stateRef) }
- is WireTransaction -> extractStateFromTx(baseTx, it.value.map { stateRef -> stateRef.index })
- .mapIndexed {index, state -> StateAndRef(state, StateRef(baseTx.id, index)) }
- else -> throw MigrationException("Unknown transaction type ${baseTx::class.qualifiedName} found when loading a state")
- }
- stateList
- }.toSet()
- }
-
- override fun loadContractAttachment(stateRef: StateRef): Attachment {
- throw NotImplementedError()
- }
-}
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/migration/VaultStateMigration.kt b/node/src/main/kotlin/net/corda/node/migration/VaultStateMigration.kt
index 28d8dc3a89..f47b80c374 100644
--- a/node/src/main/kotlin/net/corda/node/migration/VaultStateMigration.kt
+++ b/node/src/main/kotlin/net/corda/node/migration/VaultStateMigration.kt
@@ -1,13 +1,9 @@
package net.corda.node.migration
import liquibase.database.Database
-import net.corda.core.contracts.*
-import net.corda.core.identity.CordaX500Name
import net.corda.core.node.services.Vault
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentStateRef
-import net.corda.core.serialization.SerializationContext
-import net.corda.core.serialization.internal.*
import net.corda.core.utilities.contextLogger
import net.corda.node.internal.DBNetworkParametersStorage
import net.corda.node.internal.schemas.NodeInfoSchemaV1
@@ -16,103 +12,21 @@ import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.persistence.NodeAttachmentService
-import net.corda.node.services.vault.NodeVaultService
import net.corda.node.services.vault.VaultSchemaV1
-import net.corda.node.services.vault.toStateRef
import net.corda.nodeapi.internal.persistence.CordaPersistence
-import net.corda.nodeapi.internal.persistence.DatabaseTransaction
-import net.corda.nodeapi.internal.persistence.SchemaMigration
import net.corda.nodeapi.internal.persistence.currentDBSession
-import net.corda.serialization.internal.AMQP_P2P_CONTEXT
-import net.corda.serialization.internal.AMQP_STORAGE_CONTEXT
-import net.corda.serialization.internal.CordaSerializationMagic
-import net.corda.serialization.internal.SerializationFactoryImpl
-import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
-import net.corda.serialization.internal.amqp.amqpMagic
-import org.hibernate.Session
import org.hibernate.query.Query
-import java.util.concurrent.ForkJoinPool
-import java.util.concurrent.ForkJoinTask
-import java.util.concurrent.RecursiveAction
import javax.persistence.criteria.Root
import javax.persistence.criteria.Selection
class VaultStateMigration : CordaMigration() {
- companion object {
- private val logger = contextLogger()
- }
-
- private fun addStateParties(session: Session, stateAndRef: StateAndRef) {
- val state = stateAndRef.state.data
- val persistentStateRef = PersistentStateRef(stateAndRef.ref)
- try {
- state.participants.groupBy { it.owningKey }.forEach { participants ->
- val persistentParty = VaultSchemaV1.PersistentParty(persistentStateRef, participants.value.first())
- session.persist(persistentParty)
- }
- } catch (e: AbstractMethodError) {
- // This should only happen if there was no attachment that could be used to deserialise the output states, and the state was
- // serialised such that the participants list cannot be accessed (participants is calculated and not marked as a
- // SerializableCalculatedProperty.
- throw VaultStateMigrationException("Cannot add state parties for state ${stateAndRef.ref} as state class is not on the " +
- "classpath and participants cannot be synthesised")
- }
- }
-
- private fun getStateAndRef(persistentState: VaultSchemaV1.VaultStates): StateAndRef {
- val persistentStateRef = persistentState.stateRef ?:
- throw VaultStateMigrationException("Persistent state ref missing from state")
- val stateRef = persistentStateRef.toStateRef()
- val state = try {
- servicesForResolution.loadState(stateRef)
- } catch (e: Exception) {
- throw VaultStateMigrationException("Could not load state for stateRef $stateRef : ${e.message}", e)
- }
- return StateAndRef(state, stateRef)
- }
-
- override fun execute(database: Database?) {
- logger.info("Migrating vault state data to V4 tables")
- if (database == null) {
- logger.error("Cannot migrate vault states: Liquibase failed to provide a suitable database connection")
- throw VaultStateMigrationException("Cannot migrate vault states as liquibase failed to provide a suitable database connection")
- }
+ override fun execute(database: Database) {
initialiseNodeServices(database, setOf(VaultMigrationSchemaV1, VaultSchemaV1, NodeInfoSchemaV1))
- var statesSkipped = 0
val persistentStates = VaultStateIterator(cordaDB)
if (persistentStates.numStates > 0) {
- logger.warn("Found ${persistentStates.numStates} states to update from a previous version. This may take a while for large "
- + "volumes of data.")
+ throw VaultStateMigrationException("Found ${persistentStates.numStates} states that need to be updated to V4. Please upgrade " +
+ "to an older version of Corda first to perform this migration.")
}
- val ourName = CordaX500Name.parse(System.getProperty(SchemaMigration.NODE_X500_NAME))
- VaultStateIterator.withSerializationEnv {
- persistentStates.forEach {
- val session = currentDBSession()
- try {
- val stateAndRef = getStateAndRef(it)
-
- addStateParties(session, stateAndRef)
-
- // Can get away without checking for AbstractMethodErrors here as these will have already occurred when trying to add
- // state parties.
- val myKeys = stateAndRef.state.data.participants.map { participant -> participant.owningKey}
- .filter { key -> identityService.certificateFromKey(key)?.name == ourName }.toSet()
- if (!NodeVaultService.isRelevant(stateAndRef.state.data, myKeys)) {
- it.relevancyStatus = Vault.RelevancyStatus.NOT_RELEVANT
- }
- } catch (e: VaultStateMigrationException) {
- logger.warn("An error occurred while migrating a vault state: ${e.message}. Skipping. This will cause the " +
- "migration to fail.", e)
- statesSkipped++
- }
- }
- }
- if (statesSkipped > 0) {
- logger.error("$statesSkipped states could not be migrated as there was no class available for them.")
- throw VaultStateMigrationException("Failed to migrate $statesSkipped states in the vault. Check the logs for details of the " +
- "error for each state.")
- }
- logger.info("Finished performing vault state data migration for ${persistentStates.numStates - statesSkipped} states")
}
}
@@ -147,49 +61,9 @@ object VaultMigrationSchemaV1 : MappedSchema(schemaFamily = VaultMigrationSchema
* Currently, this class filters out those persistent states that have entries in the state party table. This behaviour is required for the
* vault state migration, as entries in this table should not be duplicated. Unconsumed states are also filtered out for performance.
*/
-class VaultStateIterator(private val database: CordaPersistence) : Iterator {
+class VaultStateIterator(private val database: CordaPersistence) {
companion object {
val logger = contextLogger()
-
- private object AMQPInspectorSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) {
- override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
- return magic == amqpMagic
- }
-
- override fun rpcClientSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
- override fun rpcServerSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
- }
-
- private fun initialiseSerialization() {
- // Deserialise with the lenient carpenter as we only care for the AMQP field getters
- _inheritableContextSerializationEnv.set(SerializationEnvironment.with(
- SerializationFactoryImpl().apply {
- registerScheme(AMQPInspectorSerializationScheme)
- },
- p2pContext = AMQP_P2P_CONTEXT.withLenientCarpenter(),
- storageContext = AMQP_STORAGE_CONTEXT.withLenientCarpenter()
- ))
- }
-
- private fun disableSerialization() {
- _inheritableContextSerializationEnv.set(null)
- }
-
- fun withSerializationEnv(block: () -> Unit) {
- val newEnv = if (_allEnabledSerializationEnvs.isEmpty()) {
- initialiseSerialization()
- true
- } else {
- false
- }
- effectiveSerializationEnv.serializationFactory.withCurrentContext(effectiveSerializationEnv.storageContext.withLenientCarpenter()) {
- block()
- }
-
- if (newEnv) {
- disableSerialization()
- }
- }
}
private val criteriaBuilder = database.entityManagerFactory.criteriaBuilder
val numStates = getTotalStates()
@@ -224,111 +98,6 @@ class VaultStateIterator(private val database: CordaPersistence) : Iterator {
- endTransaction()
- transaction = database.newTransaction()
- val query = createVaultStatesQuery(VaultSchemaV1.VaultStates::class.java) { it }
- // The above query excludes states that have entries in the state party table. As the iteration proceeds, each state has entries
- // added to this table. The result is that when the next page is retrieved, any results that were in the previous page are not in
- // the query at all! As such, the next set of states that need processing start at the first result.
- query.firstResult = 0
- query.maxResults = pageSize
- pageNumber++
- val result = query.resultList
- logger.debug("Loaded page $pageNumber of ${(numStates - 1 / pageNumber.toLong()) + 1}. Current page has ${result.size} vault states")
- return result
- }
-
- private var currentIndex = 0
-
- override fun hasNext(): Boolean {
- val nextElementPresent = currentIndex + ((pageNumber - 1) * pageSize) < numStates
- if (!nextElementPresent) {
- endTransaction()
- }
- return nextElementPresent
- }
-
- override fun next(): VaultSchemaV1.VaultStates {
- if (currentIndex == pageSize) {
- currentPage = getNextPage()
- currentIndex = 0
- }
- val stateToReturn = currentPage[currentIndex]
- currentIndex++
- return stateToReturn
- }
-
- // The rest of this class is an attempt at multithreading that was ultimately scuppered by liquibase not providing a connection pool.
- // This may be useful as a starting point for improving performance of the migration, so is left here. To start using it, remove the
- // serialization environment changes in the execute function in the migration, and change forEach -> parallelForEach.
- private val pool = ForkJoinPool.commonPool()
-
- private class VaultPageTask(val database: CordaPersistence,
- val page: List,
- val block: (VaultSchemaV1.VaultStates) -> Unit): RecursiveAction() {
-
- private val pageSize = page.size
- private val tolerance = 10
-
- override fun compute() {
- withSerializationEnv {
- if (pageSize > tolerance) {
- ForkJoinTask.invokeAll(createSubtasks())
- } else {
- applyBlock()
- }
- }
- }
-
- private fun createSubtasks(): List {
- return listOf(VaultPageTask(database, page.subList(0, pageSize / 2), block), VaultPageTask(database, page.subList(pageSize / 2, pageSize), block))
- }
-
- private fun applyBlock() {
- effectiveSerializationEnv.serializationFactory.withCurrentContext(effectiveSerializationEnv.storageContext.withLenientCarpenter()) {
- database.transaction {
- page.forEach { block(it) }
- }
- }
- }
- }
-
- private fun hasNextPage(): Boolean {
- val nextPagePresent = pageNumber * pageSize < numStates
- if (!nextPagePresent) {
- endTransaction()
- }
- return nextPagePresent
- }
-
- /**
- * Iterate through all states in the vault, parallelizing the work on each page of vault states.
- */
- fun parallelForEach(block: (VaultSchemaV1.VaultStates) -> Unit) {
- pool.invoke(VaultPageTask(database, currentPage, block))
- while (hasNextPage()) {
- currentPage = getNextPage()
- pool.invoke(VaultPageTask(database, currentPage, block))
- }
- }
}
class VaultStateMigrationException(msg: String, cause: Exception? = null) : Exception(msg, cause)
\ No newline at end of file
diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt
index 0ee732b210..bea46121fc 100644
--- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt
+++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt
@@ -5,8 +5,8 @@ import net.corda.core.context.InvocationContext
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.flows.FlowLogic
-import net.corda.core.flows.TransactionMetadata
import net.corda.core.flows.StateMachineRunId
+import net.corda.core.flows.TransactionMetadata
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.FlowStateMachineHandle
import net.corda.core.internal.NamedCacheFactory
@@ -17,6 +17,7 @@ import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.concurrent.OpenFuture
import net.corda.core.internal.dependencies
import net.corda.core.internal.requireSupportedHashType
+import net.corda.core.internal.verification.Verifier
import net.corda.core.internal.warnOnce
import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.StateMachineTransactionMapping
@@ -25,11 +26,13 @@ import net.corda.core.node.StatesToRecord
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.NetworkMapCacheBase
import net.corda.core.node.services.TransactionStorage
+import net.corda.core.serialization.SerializationContext
+import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
+import net.corda.core.transactions.defaultVerifier
import net.corda.core.utilities.contextLogger
import net.corda.node.internal.InitiatedFlowFactory
-import net.corda.node.internal.cordapp.CordappProviderInternal
import net.corda.node.services.DbTransactionsResolver
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.messaging.MessagingService
@@ -37,14 +40,11 @@ import net.corda.node.services.network.NetworkMapUpdater
import net.corda.node.services.persistence.AttachmentStorageInternal
import net.corda.node.services.statemachine.ExternalEvent
import net.corda.node.services.statemachine.FlowStateMachineImpl
+import net.corda.node.verification.NoDbAccessVerifier
import net.corda.nodeapi.internal.persistence.CordaPersistence
-import java.lang.IllegalStateException
+import java.security.PublicKey
import java.security.SignatureException
-import java.util.ArrayList
import java.util.Collections
-import java.util.HashMap
-import java.util.HashSet
-import java.util.LinkedHashSet
interface NetworkMapCacheInternal : NetworkMapCache, NetworkMapCacheBase {
override val nodeReady: OpenFuture
@@ -186,11 +186,14 @@ interface ServiceHubInternal : ServiceHubCoreInternal {
val configuration: NodeConfiguration
val nodeProperties: NodePropertiesStore
val networkMapUpdater: NetworkMapUpdater
- override val cordappProvider: CordappProviderInternal
fun getFlowFactory(initiatingFlowClass: Class>): InitiatedFlowFactory<*>?
val cacheFactory: NamedCacheFactory
+ override fun createVerifier(ltx: LedgerTransaction, serializationContext: SerializationContext): Verifier {
+ return NoDbAccessVerifier(defaultVerifier(ltx, serializationContext))
+ }
+
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) =
recordTransactions(statesToRecord, txs, SIGNATURE_VERIFICATION_DISABLED)
diff --git a/node/src/main/kotlin/net/corda/node/services/attachments/NodeAttachmentTrustCalculator.kt b/node/src/main/kotlin/net/corda/node/services/attachments/NodeAttachmentTrustCalculator.kt
index 2305203338..285f7fca5a 100644
--- a/node/src/main/kotlin/net/corda/node/services/attachments/NodeAttachmentTrustCalculator.kt
+++ b/node/src/main/kotlin/net/corda/node/services/attachments/NodeAttachmentTrustCalculator.kt
@@ -1,10 +1,16 @@
package net.corda.node.services.attachments
-import com.github.benmanes.caffeine.cache.Caffeine
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractAttachment
import net.corda.core.crypto.SecureHash
-import net.corda.core.internal.*
+import net.corda.core.internal.AbstractAttachment
+import net.corda.core.internal.AttachmentTrustCalculator
+import net.corda.core.internal.AttachmentTrustInfo
+import net.corda.core.internal.NamedCacheFactory
+import net.corda.core.internal.TRUSTED_UPLOADERS
+import net.corda.core.internal.VisibleForTesting
+import net.corda.core.internal.hash
+import net.corda.core.internal.isUploaderTrusted
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.vault.AttachmentQueryCriteria
import net.corda.core.node.services.vault.Builder
@@ -35,10 +41,7 @@ class NodeAttachmentTrustCalculator(
) : this(attachmentStorage, null, cacheFactory, blacklistedAttachmentSigningKeys)
// A cache for caching whether a signing key is trusted
- private val trustedKeysCache = cacheFactory.buildNamed(
- Caffeine.newBuilder(),
- "NodeAttachmentTrustCalculator_trustedKeysCache"
- )
+ private val trustedKeysCache = cacheFactory.buildNamed("NodeAttachmentTrustCalculator_trustedKeysCache")
override fun calculate(attachment: Attachment): Boolean {
@@ -92,9 +95,7 @@ class NodeAttachmentTrustCalculator(
val trustRoot = if (attachment.isSignedByBlacklistedKey()) {
null
} else {
- attachment.signerKeys
- .mapNotNull { publicKeyToTrustRootMap[it] }
- .firstOrNull()
+ attachment.signerKeys.firstNotNullOfOrNull { publicKeyToTrustRootMap[it] }
}
attachmentTrustInfos += AttachmentTrustInfo(
attachmentId = attachment.id,
diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/PublicKeyToOwningIdentityCacheImpl.kt b/node/src/main/kotlin/net/corda/node/services/persistence/PublicKeyToOwningIdentityCacheImpl.kt
index fd3d01431d..c40b9cad89 100644
--- a/node/src/main/kotlin/net/corda/node/services/persistence/PublicKeyToOwningIdentityCacheImpl.kt
+++ b/node/src/main/kotlin/net/corda/node/services/persistence/PublicKeyToOwningIdentityCacheImpl.kt
@@ -1,6 +1,5 @@
package net.corda.node.services.persistence
-import com.github.benmanes.caffeine.cache.Caffeine
import net.corda.core.crypto.toStringShort
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.utilities.contextLogger
@@ -19,10 +18,7 @@ class PublicKeyToOwningIdentityCacheImpl(private val database: CordaPersistence,
val log = contextLogger()
}
- private val cache = cacheFactory.buildNamed(
- Caffeine.newBuilder(),
- "PublicKeyToOwningIdentityCache_cache"
- )
+ private val cache = cacheFactory.buildNamed("PublicKeyToOwningIdentityCache_cache")
/**
* Return the owning identity associated with a given key.
diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt
index dfd2b1a8db..178182a9d9 100644
--- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt
+++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt
@@ -3,10 +3,12 @@ package net.corda.node.services.statemachine
import com.google.common.primitives.Primitives
import net.corda.core.flows.*
import net.corda.core.internal.VisibleForTesting
+import net.corda.core.internal.loadClassOfType
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.contextLogger
import org.slf4j.Logger
+import java.lang.ClassCastException
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.lang.reflect.TypeVariable
@@ -57,13 +59,13 @@ open class FlowLogicRefFactoryImpl(private val classloader: ClassLoader) : Singl
}
private fun validatedFlowClassFromName(flowClassName: String): Class> {
- val forName = try {
- Class.forName(flowClassName, true, classloader)
+ return try {
+ loadClassOfType>(flowClassName, true, classloader)
} catch (e: ClassNotFoundException) {
throw IllegalFlowLogicException(flowClassName, "Flow not found: $flowClassName")
- }
- return forName.asSubclass(FlowLogic::class.java) ?:
+ } catch (e: ClassCastException) {
throw IllegalFlowLogicException(flowClassName, "The class $flowClassName is not a subclass of FlowLogic.")
+ }
}
override fun createForRPC(flowClass: Class>, vararg args: Any?): FlowLogicRef {
diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/InMemoryTransactionVerifierService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/InMemoryTransactionVerifierService.kt
deleted file mode 100644
index e70712e9e0..0000000000
--- a/node/src/main/kotlin/net/corda/node/services/transactions/InMemoryTransactionVerifierService.kt
+++ /dev/null
@@ -1,120 +0,0 @@
-package net.corda.node.services.transactions
-
-import net.corda.core.concurrent.CordaFuture
-import net.corda.core.contracts.Attachment
-import net.corda.core.contracts.TransactionVerificationException.BrokenTransactionException
-import net.corda.core.internal.TransactionVerifierServiceInternal
-import net.corda.core.internal.concurrent.openFuture
-import net.corda.core.internal.internalFindTrustedAttachmentForClass
-import net.corda.core.internal.prepareVerify
-import net.corda.core.node.services.AttachmentStorage
-import net.corda.core.node.services.TransactionVerifierService
-import net.corda.core.serialization.SingletonSerializeAsToken
-import net.corda.core.transactions.LedgerTransaction
-import net.corda.core.utilities.contextLogger
-import net.corda.node.internal.cordapp.CordappProviderInternal
-import net.corda.nodeapi.internal.persistence.withoutDatabaseAccess
-
-class InMemoryTransactionVerifierService(
- @Suppress("UNUSED_PARAMETER") numberOfWorkers: Int,
- private val cordappProvider: CordappProviderInternal,
- private val attachments: AttachmentStorage
-) : SingletonSerializeAsToken(), TransactionVerifierService, TransactionVerifierServiceInternal, AutoCloseable {
- companion object {
- private val SEPARATOR = System.lineSeparator() + "-> "
- private val log = contextLogger()
-
- fun Collection<*>.deepEquals(other: Collection<*>): Boolean {
- return size == other.size && containsAll(other) && other.containsAll(this)
- }
-
- fun Collection.toPrettyString(): String {
- return joinToString(separator = SEPARATOR, prefix = SEPARATOR, postfix = System.lineSeparator()) { attachment ->
- attachment.id.toString()
- }
- }
- }
-
- override fun verify(transaction: LedgerTransaction): CordaFuture<*> {
- return openFuture().apply {
- capture {
- val verifier = transaction.prepareVerify(transaction.attachments)
- withoutDatabaseAccess {
- verifier.verify()
- }
- }
- }
- }
-
- private fun computeReplacementAttachmentsFor(ltx: LedgerTransaction, missingClass: String?): Collection {
- val replacements = cordappProvider.fixupAttachments(ltx.attachments)
- return if (replacements.deepEquals(ltx.attachments)) {
- /*
- * We cannot continue unless we have some idea which
- * class is missing from the attachments.
- */
- if (missingClass == null) {
- throw BrokenTransactionException(
- txId = ltx.id,
- message = "No fix-up rules provided for broken attachments:${replacements.toPrettyString()}"
- )
- }
-
- /*
- * The Node's fix-up rules have not been able to adjust the transaction's attachments,
- * so resort to the original mechanism of trying to find an attachment that contains
- * the missing class. (Do you feel lucky, Punk?)
- */
- val extraAttachment = requireNotNull(attachments.internalFindTrustedAttachmentForClass(missingClass)) {
- """Transaction $ltx is incorrectly formed. Most likely it was created during version 3 of Corda
- |when the verification logic was more lenient. Attempted to find local dependency for class: $missingClass,
- |but could not find one.
- |If you wish to verify this transaction, please contact the originator of the transaction and install the
- |provided missing JAR.
- |You can install it using the RPC command: `uploadAttachment` without restarting the node.
- |""".trimMargin()
- }
-
- replacements.toMutableSet().apply {
- /*
- * Check our transaction doesn't already contain this extra attachment.
- * It seems unlikely that we would, but better safe than sorry!
- */
- if (!add(extraAttachment)) {
- throw BrokenTransactionException(
- txId = ltx.id,
- message = "Unlinkable class $missingClass inside broken attachments:${replacements.toPrettyString()}"
- )
- }
-
- log.warn("""Detected that transaction $ltx does not contain all cordapp dependencies.
- |This may be the result of a bug in a previous version of Corda.
- |Attempting to verify using the additional trusted dependency: $extraAttachment for class $missingClass.
- |Please check with the originator that this is a valid transaction.
- |YOU ARE ONLY SEEING THIS MESSAGE BECAUSE THE CORDAPPS THAT CREATED THIS TRANSACTION ARE BROKEN!
- |WE HAVE TRIED TO REPAIR THE TRANSACTION AS BEST WE CAN, BUT CANNOT GUARANTEE WE HAVE SUCCEEDED!
- |PLEASE FIX THE CORDAPPS AND MIGRATE THESE BROKEN TRANSACTIONS AS SOON AS POSSIBLE!
- |THIS MESSAGE IS **SUPPOSED** TO BE SCARY!!
- |""".trimMargin()
- )
- }
- } else {
- replacements
- }
- }
-
- override fun reverifyWithFixups(transaction: LedgerTransaction, missingClass: String?): CordaFuture<*> {
- return openFuture().apply {
- capture {
- val replacementAttachments = computeReplacementAttachmentsFor(transaction, missingClass)
- log.warn("Reverifying transaction {} with attachments:{}", transaction.id, replacementAttachments.toPrettyString())
- val verifier = transaction.prepareVerify(replacementAttachments.toList())
- withoutDatabaseAccess {
- verifier.verify()
- }
- }
- }
- }
-
- override fun close() {}
-}
diff --git a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt
index 098c0d2155..6a20f2c4e5 100644
--- a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt
+++ b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt
@@ -19,8 +19,10 @@ import net.corda.core.internal.ThreadBox
import net.corda.core.internal.TransactionDeserialisationException
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.bufferUntilSubscribed
+import net.corda.core.internal.mapToSet
import net.corda.core.internal.tee
import net.corda.core.internal.uncheckedCast
+import net.corda.core.internal.verification.VerifyingServiceHub
import net.corda.core.internal.warnOnce
import net.corda.core.messaging.DataFeed
import net.corda.core.node.StatesToRecord
@@ -50,7 +52,6 @@ import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.core.utilities.toNonEmptySet
import net.corda.core.utilities.trace
-import net.corda.node.internal.NodeServicesForResolution
import net.corda.node.services.api.SchemaService
import net.corda.node.services.api.VaultServiceInternal
import net.corda.node.services.schema.PersistentStateService
@@ -80,8 +81,6 @@ import javax.persistence.criteria.CriteriaQuery
import javax.persistence.criteria.CriteriaUpdate
import javax.persistence.criteria.Predicate
import javax.persistence.criteria.Root
-import kotlin.collections.ArrayList
-import kotlin.collections.LinkedHashSet
import kotlin.collections.component1
import kotlin.collections.component2
@@ -98,7 +97,7 @@ import kotlin.collections.component2
class NodeVaultService(
private val clock: Clock,
private val keyManagementService: KeyManagementService,
- private val servicesForResolution: NodeServicesForResolution,
+ private val serviceHub: VerifyingServiceHub,
private val database: CordaPersistence,
schemaService: SchemaService,
private val appClassloader: ClassLoader
@@ -231,7 +230,7 @@ class NodeVaultService(
// Persist the consumed inputs.
consumedStateRefs.forEach { stateRef ->
- val state = session.get(VaultSchemaV1.VaultStates::class.java, PersistentStateRef(stateRef))
+ val state = session.get(VaultSchemaV1.VaultStates::class.java, PersistentStateRef(stateRef))
state?.run {
// Only update the state if it has not previously been consumed (this could have happened if the transaction is being
// re-recorded.
@@ -312,7 +311,7 @@ class NodeVaultService(
fun withValidDeserialization(list: List, txId: SecureHash): Map {
var error: TransactionDeserialisationException? = null
- val map = (0 until list.size).mapNotNull { idx ->
+ val map = list.indices.mapNotNull { idx ->
try {
idx to list[idx]
} catch (e: TransactionDeserialisationException) {
@@ -354,7 +353,7 @@ class NodeVaultService(
val outputRefs = tx.outRefsOfType().map { it.ref }
val seenRefs = loadStates(outputRefs).map { it.ref }
val unseenRefs = outputRefs - seenRefs
- val unseenOutputIdxs = unseenRefs.map { it.index }.toSet()
+ val unseenOutputIdxs = unseenRefs.mapToSet { it.index }
outputs.filter { it.key in unseenOutputIdxs }
} else {
outputs
@@ -383,7 +382,7 @@ class NodeVaultService(
StatesToRecord.ALL_VISIBLE, StatesToRecord.ONLY_RELEVANT -> {
val notSeenReferences = tx.references - loadStates(tx.references).map { it.ref }
// TODO: This is expensive - is there another way?
- tx.toLedgerTransaction(servicesForResolution).deserializableRefStates()
+ tx.toLedgerTransaction(serviceHub).deserializableRefStates()
.filter { (_, stateAndRef) -> stateAndRef.ref in notSeenReferences }
.values
}
@@ -398,8 +397,8 @@ class NodeVaultService(
// We also can't do filtering beforehand, since for notary change transactions output encumbrance pointers
// get recalculated based on input positions.
val ltx: FullTransaction = when (tx) {
- is NotaryChangeWireTransaction -> tx.resolve(servicesForResolution, emptyList())
- is ContractUpgradeWireTransaction -> tx.resolve(servicesForResolution, emptyList())
+ is NotaryChangeWireTransaction -> tx.resolve(serviceHub, emptyList())
+ is ContractUpgradeWireTransaction -> tx.resolve(serviceHub, emptyList())
else -> throw IllegalArgumentException("Unsupported transaction type: ${tx.javaClass.name}")
}
val myKeys by lazy { keyManagementService.filterMyKeys(ltx.outputs.flatMap { it.data.participants.map { it.owningKey } }) }
@@ -542,8 +541,8 @@ class NodeVaultService(
val stateStatusPredication = criteriaBuilder.equal(get(VaultSchemaV1.VaultStates::stateStatus.name), Vault.StateStatus.UNCONSUMED)
val lockIdPredicate = criteriaBuilder.or(get(VaultSchemaV1.VaultStates::lockId.name).isNull,
criteriaBuilder.equal(get(VaultSchemaV1.VaultStates::lockId.name), lockId.toString()))
- update.set(get(VaultSchemaV1.VaultStates::lockId.name), lockId.toString())
- update.set(get(VaultSchemaV1.VaultStates::lockUpdateTime.name), softLockTimestamp)
+ update.set(get(VaultSchemaV1.VaultStates::lockId.name), lockId.toString())
+ update.set(get(VaultSchemaV1.VaultStates::lockUpdateTime.name), softLockTimestamp)
update.where(stateStatusPredication, lockIdPredicate, *commonPredicates)
}
if (updatedRows > 0 && updatedRows == stateRefs.size) {
@@ -596,8 +595,8 @@ class NodeVaultService(
criteriaBuilder.executeUpdate(session, stateRefs) { update, persistentStateRefs ->
val stateStatusPredication = criteriaBuilder.equal(get(VaultSchemaV1.VaultStates::stateStatus.name), Vault.StateStatus.UNCONSUMED)
val lockIdPredicate = criteriaBuilder.equal(get(VaultSchemaV1.VaultStates::lockId.name), lockId.toString())
- update.set(get(VaultSchemaV1.VaultStates::lockId.name), criteriaBuilder.nullLiteral(String::class.java))
- update.set(get(VaultSchemaV1.VaultStates::lockUpdateTime.name), softLockTimestamp)
+ update.set(get(VaultSchemaV1.VaultStates::lockId.name), criteriaBuilder.nullLiteral(String::class.java))
+ update.set(get(VaultSchemaV1.VaultStates::lockUpdateTime.name), softLockTimestamp)
configure(update, arrayOf(stateStatusPredication, lockIdPredicate), persistentStateRefs)
}
@@ -748,16 +747,13 @@ class NodeVaultService(
if (result0 is VaultSchemaV1.VaultStates) {
statesMetadata.add(result0.toStateMetadata())
} else {
- log.debug { "OtherResults: ${Arrays.toString(result.toArray())}" }
+ log.debug { "OtherResults: ${result.toArray().contentToString()}" }
otherResults.addAll(result.toArray().asList())
}
}
}
- val states: List> = servicesForResolution.loadStates(
- statesMetadata.mapTo(LinkedHashSet()) { it.ref },
- ArrayList()
- )
+ val states: List> = serviceHub.loadStatesInternal(statesMetadata.mapToSet { it.ref }, ArrayList())
val totalStatesAvailable = when {
paging.isDefault -> -1L
@@ -844,9 +840,8 @@ class NodeVaultService(
log.warn("trackBy is called with an already existing, open DB transaction. As a result, there might be states missing from both the snapshot and observable, included in the returned data feed, because of race conditions.")
}
val snapshotResults = _queryBy(criteria, paging, sorting, contractStateType)
- val snapshotStatesRefs = snapshotResults.statesMetadata.map { it.ref }.toSet()
- val snapshotConsumedStatesRefs = snapshotResults.statesMetadata.filter { it.consumedTime != null }
- .map { it.ref }.toSet()
+ val snapshotStatesRefs = snapshotResults.statesMetadata.mapToSet { it.ref }
+ val snapshotConsumedStatesRefs = snapshotResults.statesMetadata.filter { it.consumedTime != null }.mapToSet { it.ref }
val filteredUpdates = updates.filter { it.containsType(contractStateType, snapshotResults.stateTypes) }
.map { filterContractStates(it, contractStateType) }
.filter { !hasBeenSeen(it, snapshotStatesRefs, snapshotConsumedStatesRefs) }
@@ -881,8 +876,8 @@ class NodeVaultService(
* the snapshot or in the observable).
*/
private fun hasBeenSeen(update: Vault.Update, snapshotStatesRefs: Set, snapshotConsumedStatesRefs: Set): Boolean {
- val updateProducedStatesRefs = update.produced.map { it.ref }.toSet()
- val updateConsumedStatesRefs = update.consumed.map { it.ref }.toSet()
+ val updateProducedStatesRefs = update.produced.mapToSet { it.ref }
+ val updateConsumedStatesRefs = update.consumed.mapToSet { it.ref }
return snapshotStatesRefs.containsAll(updateProducedStatesRefs) && snapshotConsumedStatesRefs.containsAll(updateConsumedStatesRefs)
}
diff --git a/node/src/main/kotlin/net/corda/node/utilities/InfrequentlyMutatedCache.kt b/node/src/main/kotlin/net/corda/node/utilities/InfrequentlyMutatedCache.kt
index 4121a92cb9..4902ada180 100644
--- a/node/src/main/kotlin/net/corda/node/utilities/InfrequentlyMutatedCache.kt
+++ b/node/src/main/kotlin/net/corda/node/utilities/InfrequentlyMutatedCache.kt
@@ -1,6 +1,5 @@
package net.corda.node.utilities
-import com.github.benmanes.caffeine.cache.Caffeine
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.VisibleForTesting
import net.corda.nodeapi.internal.persistence.contextTransactionOrNull
@@ -101,7 +100,7 @@ class InfrequentlyMutatedCache(name: String, cacheFactory: Nam
}
}
- private val backingCache = cacheFactory.buildNamed>(Caffeine.newBuilder(), name)
+ private val backingCache = cacheFactory.buildNamed>(name)
// This protects against the cache purging something that is marked as invalid and thus we "forget" it shouldn't be cached.
private val currentlyInvalid = ConcurrentHashMap>()
diff --git a/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingCache.kt b/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingCache.kt
index 25688233c2..42b7738a1d 100644
--- a/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingCache.kt
+++ b/node/src/main/kotlin/net/corda/node/utilities/NonInvalidatingCache.kt
@@ -15,8 +15,7 @@ class NonInvalidatingCache private constructor(
private companion object {
private fun buildCache(cacheFactory: NamedCacheFactory, name: String, loadFunction: (K) -> V): LoadingCache {
- val builder = Caffeine.newBuilder()
- return cacheFactory.buildNamed(builder, name, NonInvalidatingCacheLoader(loadFunction))
+ return cacheFactory.buildNamed(name, NonInvalidatingCacheLoader(loadFunction))
}
}
diff --git a/node/src/main/kotlin/net/corda/node/verification/ExternalVerifierHandle.kt b/node/src/main/kotlin/net/corda/node/verification/ExternalVerifierHandle.kt
new file mode 100644
index 0000000000..4b82ab3cf1
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/verification/ExternalVerifierHandle.kt
@@ -0,0 +1,229 @@
+package net.corda.node.verification
+
+import net.corda.core.contracts.Attachment
+import net.corda.core.internal.AbstractAttachment
+import net.corda.core.internal.copyTo
+import net.corda.core.internal.div
+import net.corda.core.internal.mapToSet
+import net.corda.core.internal.readFully
+import net.corda.core.serialization.serialize
+import net.corda.core.transactions.SignedTransaction
+import net.corda.core.utilities.Try
+import net.corda.core.utilities.contextLogger
+import net.corda.core.utilities.debug
+import net.corda.node.services.api.ServiceHubInternal
+import net.corda.serialization.internal.GeneratedAttachment
+import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme.Companion.customSerializers
+import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme.Companion.serializationWhitelists
+import net.corda.serialization.internal.verifier.AttachmentWithTrust
+import net.corda.serialization.internal.verifier.ExternalVerifierInbound.AttachmentResult
+import net.corda.serialization.internal.verifier.ExternalVerifierInbound.AttachmentsResult
+import net.corda.serialization.internal.verifier.ExternalVerifierInbound.Initialisation
+import net.corda.serialization.internal.verifier.ExternalVerifierInbound.NetworkParametersResult
+import net.corda.serialization.internal.verifier.ExternalVerifierInbound.PartiesResult
+import net.corda.serialization.internal.verifier.ExternalVerifierInbound.TrustedClassAttachmentResult
+import net.corda.serialization.internal.verifier.ExternalVerifierInbound.VerificationRequest
+import net.corda.serialization.internal.verifier.ExternalVerifierOutbound
+import net.corda.serialization.internal.verifier.ExternalVerifierOutbound.VerificationResult
+import net.corda.serialization.internal.verifier.ExternalVerifierOutbound.VerifierRequest
+import net.corda.serialization.internal.verifier.ExternalVerifierOutbound.VerifierRequest.GetAttachment
+import net.corda.serialization.internal.verifier.ExternalVerifierOutbound.VerifierRequest.GetAttachments
+import net.corda.serialization.internal.verifier.ExternalVerifierOutbound.VerifierRequest.GetNetworkParameters
+import net.corda.serialization.internal.verifier.ExternalVerifierOutbound.VerifierRequest.GetParties
+import net.corda.serialization.internal.verifier.ExternalVerifierOutbound.VerifierRequest.GetTrustedClassAttachment
+import net.corda.serialization.internal.verifier.readCordaSerializable
+import net.corda.serialization.internal.verifier.writeCordaSerializable
+import java.io.DataInputStream
+import java.io.DataOutputStream
+import java.io.IOException
+import java.lang.ProcessBuilder.Redirect
+import java.net.ServerSocket
+import java.net.Socket
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.StandardCopyOption.REPLACE_EXISTING
+import kotlin.io.path.createDirectories
+
+/**
+ * Handle to the node's external verifier. The verifier process is started lazily on the first verification request.
+ */
+@Suppress("TooGenericExceptionCaught")
+class ExternalVerifierHandle(private val serviceHub: ServiceHubInternal) : AutoCloseable {
+ companion object {
+ private val log = contextLogger()
+
+ private const val MAX_ATTEMPTS = 5
+
+ private val verifierJar: Path = Files.createTempFile("corda-external-verifier", ".jar")
+ init {
+ // Extract the embedded verifier jar
+ Companion::class.java.getResourceAsStream("external-verifier.jar")!!.use {
+ it.copyTo(verifierJar, REPLACE_EXISTING)
+ }
+ verifierJar.toFile().deleteOnExit()
+ }
+ }
+
+ private lateinit var server: ServerSocket
+ @Volatile
+ private var connection: Connection? = null
+
+ fun verifyTransaction(stx: SignedTransaction, checkSufficientSignatures: Boolean) {
+ log.info("Verify $stx externally, checkSufficientSignatures=$checkSufficientSignatures")
+ // By definition input states are unique, and so it makes sense to eagerly send them across with the transaction.
+ // Reference states are not, but for now we'll send them anyway and assume they aren't used often. If this assumption is not
+ // correct, and there's a benefit, then we can send them lazily.
+ val stxInputsAndReferences = (stx.inputs + stx.references).associateWith(serviceHub::getSerializedState)
+ val request = VerificationRequest(stx, stxInputsAndReferences, checkSufficientSignatures)
+
+ // To keep things simple the verifier only supports one verification request at a time.
+ synchronized(this) {
+ startServer()
+ var attempt = 1
+ while (true) {
+ val result = try {
+ tryVerification(request)
+ } catch (e: Exception) {
+ processError(attempt, e)
+ attempt += 1
+ continue
+ }
+ when (result) {
+ is Try.Success -> return
+ is Try.Failure -> throw result.exception
+ }
+ }
+ }
+ }
+
+ private fun startServer() {
+ if (::server.isInitialized) return
+ server = ServerSocket(0)
+ // Just in case...
+ Runtime.getRuntime().addShutdownHook(Thread(::close))
+ }
+
+ private fun processError(attempt: Int, e: Exception) {
+ if (attempt == MAX_ATTEMPTS) {
+ throw IOException("Unable to verify with external verifier", e)
+ } else {
+ log.warn("Unable to verify with external verifier, trying again...", e)
+ }
+ try {
+ connection?.close()
+ } catch (e: Exception) {
+ log.debug("Problem closing external verifier connection", e)
+ }
+ connection = null
+ }
+
+ private fun tryVerification(request: VerificationRequest): Try {
+ val connection = getConnection()
+ connection.toVerifier.writeCordaSerializable(request)
+ // Send the verification request and then wait for any requests from verifier for more information. The last message will either
+ // be a verification success or failure message.
+ while (true) {
+ val message = connection.fromVerifier.readCordaSerializable()
+ log.debug { "Received from external verifier: $message" }
+ when (message) {
+ // Process the information the verifier needs and then loop back and wait for more messages
+ is VerifierRequest -> processVerifierRequest(message, connection)
+ is VerificationResult -> return message.result
+ }
+ }
+ }
+
+ private fun getConnection(): Connection {
+ return connection ?: Connection().also { connection = it }
+ }
+
+ private fun processVerifierRequest(request: VerifierRequest, connection: Connection) {
+ val result = when (request) {
+ is GetParties -> PartiesResult(serviceHub.getParties(request.keys))
+ is GetAttachment -> AttachmentResult(prepare(serviceHub.attachments.openAttachment(request.id)))
+ is GetAttachments -> AttachmentsResult(serviceHub.getAttachments(request.ids).map(::prepare))
+ is GetNetworkParameters -> NetworkParametersResult(serviceHub.getNetworkParameters(request.id))
+ is GetTrustedClassAttachment -> TrustedClassAttachmentResult(serviceHub.getTrustedClassAttachment(request.className)?.id)
+ }
+ log.debug { "Sending response to external verifier: $result" }
+ connection.toVerifier.writeCordaSerializable(result)
+ }
+
+ private fun prepare(attachment: Attachment?): AttachmentWithTrust? {
+ if (attachment == null) return null
+ val isTrusted = serviceHub.isAttachmentTrusted(attachment)
+ val attachmentForSer = when (attachment) {
+ // The Attachment retrieved from the database is not serialisable, so we have to convert it into one
+ is AbstractAttachment -> GeneratedAttachment(attachment.open().readFully(), attachment.uploader)
+ // For everything else we keep as is, in particular preserving ContractAttachment
+ else -> attachment
+ }
+ return AttachmentWithTrust(attachmentForSer, isTrusted)
+ }
+
+ override fun close() {
+ connection?.let {
+ connection = null
+ try {
+ it.close()
+ } finally {
+ server.close()
+ }
+ }
+ }
+
+ private inner class Connection : AutoCloseable {
+ private val verifierProcess: Process
+ private val socket: Socket
+ val toVerifier: DataOutputStream
+ val fromVerifier: DataInputStream
+
+ init {
+ val logsDirectory = (serviceHub.configuration.baseDirectory / "logs").createDirectories()
+ val command = listOf(
+ "${System.getProperty("java.home") / "bin" / "java"}",
+ "-jar",
+ "$verifierJar",
+ "${server.localPort}",
+ System.getProperty("log4j2.level")?.lowercase() ?: "info" // TODO
+ )
+ log.debug { "Verifier command: $command" }
+ verifierProcess = ProcessBuilder(command)
+ .redirectOutput(Redirect.appendTo((logsDirectory / "verifier-stdout.log").toFile()))
+ .redirectError(Redirect.appendTo((logsDirectory / "verifier-stderr.log").toFile()))
+ .directory(serviceHub.configuration.baseDirectory.toFile())
+ .start()
+ log.info("External verifier process started; PID ${verifierProcess.pid()}")
+
+ verifierProcess.onExit().whenComplete { _, _ ->
+ if (connection != null) {
+ log.error("The external verifier has unexpectedly terminated with error code ${verifierProcess.exitValue()}. " +
+ "Please check verifier logs for more details.")
+ }
+ // Allow a new process to be started on the next verification request
+ connection = null
+ }
+
+ socket = server.accept()
+ toVerifier = DataOutputStream(socket.outputStream)
+ fromVerifier = DataInputStream(socket.inputStream)
+
+ val cordapps = serviceHub.cordappProvider.cordapps
+ val initialisation = Initialisation(
+ customSerializerClassNames = cordapps.customSerializers.mapToSet { it.javaClass.name },
+ serializationWhitelistClassNames = cordapps.serializationWhitelists.mapToSet { it.javaClass.name },
+ System.getProperty("experimental.corda.customSerializationScheme"), // See Node#initialiseSerialization
+ serializedCurrentNetworkParameters = serviceHub.networkParameters.serialize()
+ )
+ toVerifier.writeCordaSerializable(initialisation)
+ }
+
+ override fun close() {
+ try {
+ socket.close()
+ } finally {
+ verifierProcess.destroyForcibly()
+ }
+ }
+ }
+}
diff --git a/node/src/main/kotlin/net/corda/node/verification/NoDbAccessVerifier.kt b/node/src/main/kotlin/net/corda/node/verification/NoDbAccessVerifier.kt
new file mode 100644
index 0000000000..992031cb80
--- /dev/null
+++ b/node/src/main/kotlin/net/corda/node/verification/NoDbAccessVerifier.kt
@@ -0,0 +1,12 @@
+package net.corda.node.verification
+
+import net.corda.core.internal.verification.Verifier
+import net.corda.nodeapi.internal.persistence.withoutDatabaseAccess
+
+class NoDbAccessVerifier(private val delegate: Verifier) : Verifier {
+ override fun verify() {
+ withoutDatabaseAccess {
+ delegate.verify()
+ }
+ }
+}
diff --git a/node/src/test/kotlin/net/corda/node/internal/CustomSerializationSchemeScanningTest.kt b/node/src/test/kotlin/net/corda/node/internal/CustomSerializationSchemeScanningTest.kt
index 1837a8fb49..e920a197f0 100644
--- a/node/src/test/kotlin/net/corda/node/internal/CustomSerializationSchemeScanningTest.kt
+++ b/node/src/test/kotlin/net/corda/node/internal/CustomSerializationSchemeScanningTest.kt
@@ -1,10 +1,11 @@
package net.corda.node.internal
+import net.corda.core.CordaException
import net.corda.core.serialization.CustomSerializationScheme
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationSchemeContext
import net.corda.core.utilities.ByteSequence
-import net.corda.node.internal.classloading.scanForCustomSerializationScheme
+import net.corda.serialization.internal.verifier.loadCustomSerializationScheme
import org.junit.Test
import org.mockito.Mockito
import kotlin.test.assertFailsWith
@@ -33,7 +34,7 @@ class CustomSerializationSchemeScanningTest {
@Test(timeout = 300_000)
fun `Can scan for custom serialization scheme and build a serialization scheme`() {
- val scheme = scanForCustomSerializationScheme(DummySerializationScheme::class.java.name, this::class.java.classLoader)
+ val scheme = loadCustomSerializationScheme(DummySerializationScheme::class.java.name, this::class.java.classLoader)
val mockContext = Mockito.mock(SerializationContext::class.java)
assertFailsWith("Tried to serialize with DummySerializationScheme") {
scheme.serialize(Any::class.java, mockContext)
@@ -43,27 +44,27 @@ class CustomSerializationSchemeScanningTest {
@Test(timeout = 300_000)
fun `verification fails with a helpful error if the class is not found in the classloader`() {
val missingClassName = "org.testing.DoesNotExist"
- assertFailsWith("$missingClassName was declared as a custom serialization scheme but could not " +
+ assertFailsWith("$missingClassName was declared as a custom serialization scheme but could not " +
"be found.") {
- scanForCustomSerializationScheme(missingClassName, this::class.java.classLoader)
+ loadCustomSerializationScheme(missingClassName, this::class.java.classLoader)
}
}
@Test(timeout = 300_000)
fun `verification fails with a helpful error if the class is not a custom serialization scheme`() {
val schemeName = NonSerializationScheme::class.java.name
- assertFailsWith("$schemeName was declared as a custom serialization scheme but does not " +
+ assertFailsWith("$schemeName was declared as a custom serialization scheme but does not " +
"implement CustomSerializationScheme.") {
- scanForCustomSerializationScheme(schemeName, this::class.java.classLoader)
+ loadCustomSerializationScheme(schemeName, this::class.java.classLoader)
}
}
@Test(timeout = 300_000)
fun `verification fails with a helpful error if the class does not have a no arg constructor`() {
val schemeName = DummySerializationSchemeWithoutNoArgConstructor::class.java.name
- assertFailsWith("$schemeName was declared as a custom serialization scheme but does not " +
+ assertFailsWith("$schemeName was declared as a custom serialization scheme but does not " +
"have a no argument constructor.") {
- scanForCustomSerializationScheme(schemeName, this::class.java.classLoader)
+ loadCustomSerializationScheme(schemeName, this::class.java.classLoader)
}
}
}
diff --git a/node/src/test/kotlin/net/corda/node/migration/VaultStateMigrationTest.kt b/node/src/test/kotlin/net/corda/node/migration/VaultStateMigrationTest.kt
deleted file mode 100644
index b0a5a0e778..0000000000
--- a/node/src/test/kotlin/net/corda/node/migration/VaultStateMigrationTest.kt
+++ /dev/null
@@ -1,637 +0,0 @@
-package net.corda.node.migration
-
-import liquibase.database.Database
-import liquibase.database.jvm.JdbcConnection
-import net.corda.core.contracts.Amount
-import net.corda.core.contracts.ContractState
-import net.corda.core.contracts.Issued
-import net.corda.core.contracts.StateAndRef
-import net.corda.core.contracts.StateRef
-import net.corda.core.contracts.TransactionState
-import net.corda.core.contracts.UniqueIdentifier
-import net.corda.core.crypto.Crypto
-import net.corda.core.crypto.SecureHash
-import net.corda.core.crypto.SignableData
-import net.corda.core.crypto.SignatureMetadata
-import net.corda.core.crypto.toStringShort
-import net.corda.core.identity.AbstractParty
-import net.corda.core.identity.CordaX500Name
-import net.corda.core.identity.PartyAndCertificate
-import net.corda.core.internal.NotaryChangeTransactionBuilder
-import net.corda.core.internal.packageName
-import net.corda.core.internal.signWithCert
-import net.corda.core.node.NetworkParameters
-import net.corda.core.node.NotaryInfo
-import net.corda.core.node.services.Vault
-import net.corda.core.schemas.PersistentStateRef
-import net.corda.core.serialization.SerializationDefaults
-import net.corda.core.serialization.serialize
-import net.corda.core.transactions.SignedTransaction
-import net.corda.core.transactions.TransactionBuilder
-import net.corda.core.utilities.contextLogger
-import net.corda.finance.DOLLARS
-import net.corda.finance.contracts.Commodity
-import net.corda.finance.contracts.asset.Cash
-import net.corda.finance.contracts.asset.Obligation
-import net.corda.finance.contracts.asset.OnLedgerAsset
-import net.corda.finance.schemas.CashSchemaV1
-import net.corda.node.internal.DBNetworkParametersStorage
-import net.corda.node.internal.schemas.NodeInfoSchemaV1
-import net.corda.node.services.identity.PersistentIdentityService
-import net.corda.node.services.keys.BasicHSMKeyManagementService
-import net.corda.node.services.persistence.DBTransactionStorage
-import net.corda.node.services.vault.VaultSchemaV1
-import net.corda.nodeapi.internal.crypto.X509Utilities
-import net.corda.nodeapi.internal.persistence.CordaPersistence
-import net.corda.nodeapi.internal.persistence.DatabaseConfig
-import net.corda.nodeapi.internal.persistence.contextTransactionOrNull
-import net.corda.nodeapi.internal.persistence.currentDBSession
-import net.corda.testing.core.ALICE_NAME
-import net.corda.testing.core.BOB_NAME
-import net.corda.testing.core.BOC_NAME
-import net.corda.testing.core.CHARLIE_NAME
-import net.corda.testing.core.DUMMY_NOTARY_NAME
-import net.corda.testing.core.SerializationEnvironmentRule
-import net.corda.testing.core.TestIdentity
-import net.corda.testing.core.dummyCommand
-import net.corda.testing.internal.configureDatabase
-import net.corda.testing.internal.vault.CommodityState
-import net.corda.testing.internal.vault.DUMMY_LINEAR_CONTRACT_PROGRAM_ID
-import net.corda.testing.internal.vault.DummyLinearContract
-import net.corda.testing.internal.vault.DummyLinearStateSchemaV1
-import net.corda.testing.node.MockServices
-import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
-import net.corda.testing.node.TestClock
-import net.corda.testing.node.makeTestIdentityService
-import org.assertj.core.api.Assertions.assertThatExceptionOfType
-import org.junit.After
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.Ignore
-import org.junit.Test
-import org.mockito.Mockito
-import java.security.KeyPair
-import java.time.Clock
-import java.time.Duration
-import java.time.Instant
-import java.util.Currency
-import java.util.Properties
-import kotlin.collections.List
-import kotlin.collections.component1
-import kotlin.collections.component2
-import kotlin.collections.first
-import kotlin.collections.forEach
-import kotlin.collections.forEachIndexed
-import kotlin.collections.groupBy
-import kotlin.collections.listOf
-import kotlin.collections.map
-import kotlin.collections.mapOf
-import kotlin.collections.plus
-import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
-import kotlin.test.assertFalse
-
-/**
- * These tests aim to verify that migrating vault states from V3 to later versions works correctly. While these unit tests verify the
- * migrating behaviour is correct (tables populated, columns updated for the right states), it comes with a caveat: they do not test that
- * deserialising states with the attachment classloader works correctly.
- *
- * The reason for this is that it is impossible to do so. There is no real way of writing a unit or integration test to upgrade from one
- * version to another (at the time of writing). These tests simulate a small part of the upgrade process by directly using hibernate to
- * populate a database as a V3 node would, then running the migration class. However, it is impossible to do this for attachments as there
- * is no contract state jar to serialise.
- */
-class VaultStateMigrationTest {
- companion object {
- val alice = TestIdentity(ALICE_NAME, 70)
- val bankOfCorda = TestIdentity(BOC_NAME)
- val bob = TestIdentity(BOB_NAME, 80)
- private val charlie = TestIdentity(CHARLIE_NAME, 90)
- val dummyCashIssuer = TestIdentity(CordaX500Name("Snake Oil Issuer", "London", "GB"), 10)
- val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
- val ALICE get() = alice.party
- val ALICE_IDENTITY get() = alice.identity
- val BOB get() = bob.party
- val BOB_IDENTITY get() = bob.identity
- val BOC_IDENTITY get() = bankOfCorda.identity
- val BOC_KEY get() = bankOfCorda.keyPair
- val CHARLIE get() = charlie.party
- val DUMMY_NOTARY get() = dummyNotary.party
- val bob2 = TestIdentity(BOB_NAME, 40)
- val BOB2 = bob2.party
- val BOB2_IDENTITY = bob2.identity
-
- val clock: TestClock = TestClock(Clock.systemUTC())
-
- @ClassRule
- @JvmField
- val testSerialization = SerializationEnvironmentRule()
-
- val logger = contextLogger()
- }
-
- val cordappPackages = listOf(
- "net.corda.finance.contracts",
- CashSchemaV1::class.packageName,
- DummyLinearStateSchemaV1::class.packageName)
-
- lateinit var liquibaseDB: Database
- lateinit var cordaDB: CordaPersistence
- lateinit var notaryServices: MockServices
-
- @Before
- fun setUp() {
- val identityService = makeTestIdentityService(dummyNotary.identity, BOB_IDENTITY, ALICE_IDENTITY)
- notaryServices = MockServices(cordappPackages, dummyNotary, identityService, dummyCashIssuer.keyPair, BOC_KEY)
- cordaDB = configureDatabase(
- makeTestDataSourceProperties(),
- DatabaseConfig(),
- notaryServices.identityService::wellKnownPartyFromX500Name,
- notaryServices.identityService::wellKnownPartyFromAnonymous,
- ourName = BOB_IDENTITY.name)
- val liquibaseConnection = Mockito.mock(JdbcConnection::class.java)
- Mockito.`when`(liquibaseConnection.url).thenReturn(cordaDB.jdbcUrl)
- Mockito.`when`(liquibaseConnection.wrappedConnection).thenReturn(cordaDB.dataSource.connection)
- liquibaseDB = Mockito.mock(Database::class.java)
- Mockito.`when`(liquibaseDB.connection).thenReturn(liquibaseConnection)
-
- saveOurKeys(listOf(bob.keyPair, bob2.keyPair))
- saveAllIdentities(listOf(BOB_IDENTITY, ALICE_IDENTITY, BOC_IDENTITY, dummyNotary.identity, BOB2_IDENTITY))
- addNetworkParameters()
- }
-
- @After
- fun close() {
- contextTransactionOrNull?.close()
- cordaDB.close()
- }
-
- private fun addNetworkParameters() {
- cordaDB.transaction {
- val clock = Clock.systemUTC()
- val params = NetworkParameters(
- 1,
- listOf(NotaryInfo(DUMMY_NOTARY, false), NotaryInfo(CHARLIE, false)),
- 1,
- 1,
- clock.instant(),
- 1,
- mapOf(),
- Duration.ZERO,
- mapOf()
- )
- val signedParams = params.signWithCert(bob.keyPair.private, BOB_IDENTITY.certificate)
- val persistentParams = DBNetworkParametersStorage.PersistentNetworkParameters(
- SecureHash.allOnesHash.toString(),
- params.epoch,
- signedParams.raw.bytes,
- signedParams.sig.bytes,
- signedParams.sig.by.encoded,
- X509Utilities.buildCertPath(signedParams.sig.parentCertsChain).encoded
- )
- session.save(persistentParams)
- }
- }
-
- private fun createCashTransaction(cash: Cash, value: Amount, owner: AbstractParty): SignedTransaction {
- val tx = TransactionBuilder(DUMMY_NOTARY)
- cash.generateIssue(tx, Amount(value.quantity, Issued(bankOfCorda.ref(1), value.token)), owner, DUMMY_NOTARY)
- return notaryServices.signInitialTransaction(tx, bankOfCorda.party.owningKey)
- }
-
- private fun createVaultStatesFromTransaction(tx: SignedTransaction, stateStatus: Vault.StateStatus = Vault.StateStatus.UNCONSUMED) {
- cordaDB.transaction {
- tx.coreTransaction.outputs.forEachIndexed { index, state ->
- val constraintInfo = Vault.ConstraintInfo(state.constraint)
- val persistentState = VaultSchemaV1.VaultStates(
- notary = state.notary,
- contractStateClassName = state.data.javaClass.name,
- stateStatus = stateStatus,
- recordedTime = clock.instant(),
- relevancyStatus = Vault.RelevancyStatus.RELEVANT, //Always persist as relevant to mimic V3
- constraintType = constraintInfo.type(),
- constraintData = constraintInfo.data()
- )
- persistentState.stateRef = PersistentStateRef(tx.id.toString(), index)
- session.save(persistentState)
- }
- }
- }
-
- private fun saveOurKeys(keys: List) {
- cordaDB.transaction {
- keys.forEach {
- val persistentKey = BasicHSMKeyManagementService.PersistentKey(it.public, it.private)
- session.save(persistentKey)
- }
- }
- }
-
- private fun saveAllIdentities(identities: List) {
- cordaDB.transaction {
- identities.groupBy { it.name }.forEach { (_, certs) ->
- val persistentIDs = certs.map { PersistentIdentityService.PersistentPublicKeyHashToCertificate(it.owningKey.toStringShort(), it.certPath.encoded) }
- persistentIDs.forEach { session.save(it) }
- val networkIdentity = NodeInfoSchemaV1.DBPartyAndCertificate(certs.first(), true)
- val persistentNodeInfo = NodeInfoSchemaV1.PersistentNodeInfo(0, "", listOf(), listOf(networkIdentity), 0, 0)
- session.save(persistentNodeInfo)
- }
- }
- }
-
- private fun storeTransaction(tx: SignedTransaction) {
- cordaDB.transaction {
- val persistentTx = DBTransactionStorage.DBTransaction(
- txId = tx.id.toString(),
- stateMachineRunId = null,
- transaction = tx.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes,
- status = DBTransactionStorage.TransactionStatus.VERIFIED,
- timestamp = Instant.now(),
- signatures = null
- )
- session.save(persistentTx)
- }
- }
-
- private fun getVaultStateCount(relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL): Long {
- return cordaDB.transaction {
- val criteriaBuilder = cordaDB.entityManagerFactory.criteriaBuilder
- val criteriaQuery = criteriaBuilder.createQuery(Long::class.java)
- val queryRootStates = criteriaQuery.from(VaultSchemaV1.VaultStates::class.java)
- criteriaQuery.select(criteriaBuilder.count(queryRootStates))
- if (relevancyStatus != Vault.RelevancyStatus.ALL) {
- criteriaQuery.where(criteriaBuilder.equal(queryRootStates.get("relevancyStatus"), relevancyStatus))
- }
- val query = session.createQuery(criteriaQuery)
- query.singleResult
- }
- }
-
- private fun getStatePartyCount(): Long {
- return cordaDB.transaction {
- val criteriaBuilder = cordaDB.entityManagerFactory.criteriaBuilder
- val criteriaQuery = criteriaBuilder.createQuery(Long::class.java)
- val queryRootStates = criteriaQuery.from(VaultSchemaV1.PersistentParty::class.java)
- criteriaQuery.select(criteriaBuilder.count(queryRootStates))
- val query = session.createQuery(criteriaQuery)
- query.singleResult
- }
- }
-
- private fun addCashStates(statesToAdd: Int, owner: AbstractParty, stateStatus: Vault.StateStatus = Vault.StateStatus.UNCONSUMED) {
- val cash = Cash()
- cordaDB.transaction {
- (1..statesToAdd).map { createCashTransaction(cash, it.DOLLARS, owner) }.forEach {
- storeTransaction(it)
- createVaultStatesFromTransaction(it, stateStatus)
- }
- }
- }
-
- private fun createLinearStateTransaction(idString: String,
- parties: List = listOf(),
- linearString: String = "foo",
- linearNumber: Long = 0L,
- linearBoolean: Boolean = false): SignedTransaction {
- val tx = TransactionBuilder(notary = dummyNotary.party).apply {
- addOutputState(DummyLinearContract.State(
- linearId = UniqueIdentifier(idString),
- participants = parties,
- linearString = linearString,
- linearNumber = linearNumber,
- linearBoolean = linearBoolean,
- linearTimestamp = clock.instant()), DUMMY_LINEAR_CONTRACT_PROGRAM_ID
- )
- addCommand(dummyCommand())
- }
- return notaryServices.signInitialTransaction(tx)
- }
-
- private fun addLinearStates(statesToAdd: Int, parties: List) {
- cordaDB.transaction {
- (1..statesToAdd).map { createLinearStateTransaction("A".repeat(it), parties) }.forEach {
- storeTransaction(it)
- createVaultStatesFromTransaction(it)
- }
- }
- }
-
- private fun createCommodityTransaction(amount: Amount>, owner: AbstractParty): SignedTransaction {
- val txBuilder = TransactionBuilder(notary = dummyNotary.party)
- OnLedgerAsset.generateIssue(txBuilder, TransactionState(CommodityState(amount, owner), Obligation.PROGRAM_ID, dummyNotary.party), Obligation.Commands.Issue())
- return notaryServices.signInitialTransaction(txBuilder)
- }
-
- private fun addCommodityStates(statesToAdd: Int, owner: AbstractParty) {
- cordaDB.transaction {
- (1..statesToAdd).map {
- createCommodityTransaction(Amount(it.toLong(), Issued(bankOfCorda.ref(2), Commodity.getInstance("FCOJ")!!)), owner)
- }.forEach {
- storeTransaction(it)
- createVaultStatesFromTransaction(it)
- }
- }
- }
-
- private fun createNotaryChangeTransaction(inputs: List, paramsHash: SecureHash): SignedTransaction {
- val notaryTx = NotaryChangeTransactionBuilder(inputs, DUMMY_NOTARY, CHARLIE, paramsHash).build()
- val notaryKey = DUMMY_NOTARY.owningKey
- val signableData = SignableData(notaryTx.id, SignatureMetadata(3, Crypto.findSignatureScheme(notaryKey).schemeNumberID))
- val notarySignature = notaryServices.keyManagementService.sign(signableData, notaryKey)
- return SignedTransaction(notaryTx, listOf(notarySignature))
- }
-
- private fun createVaultStatesFromNotaryChangeTransaction(tx: SignedTransaction, inputs: List>) {
- cordaDB.transaction {
- inputs.forEachIndexed { index, state ->
- val constraintInfo = Vault.ConstraintInfo(state.constraint)
- val persistentState = VaultSchemaV1.VaultStates(
- notary = tx.notary!!,
- contractStateClassName = state.data.javaClass.name,
- stateStatus = Vault.StateStatus.UNCONSUMED,
- recordedTime = clock.instant(),
- relevancyStatus = Vault.RelevancyStatus.RELEVANT, //Always persist as relevant to mimic V3
- constraintType = constraintInfo.type(),
- constraintData = constraintInfo.data()
- )
- persistentState.stateRef = PersistentStateRef(tx.id.toString(), index)
- session.save(persistentState)
- }
- }
- }
-
- private fun getState(clazz: Class): T {
- return cordaDB.transaction {
- val criteriaBuilder = cordaDB.entityManagerFactory.criteriaBuilder
- val criteriaQuery = criteriaBuilder.createQuery(clazz)
- val queryRootStates = criteriaQuery.from(clazz)
- criteriaQuery.select(queryRootStates)
- val query = session.createQuery(criteriaQuery)
- query.singleResult
- }
- }
-
- private fun checkStatesEqual(expected: VaultSchemaV1.VaultStates, actual: VaultSchemaV1.VaultStates) {
- assertEquals(expected.notary, actual.notary)
- assertEquals(expected.stateStatus, actual.stateStatus)
- assertEquals(expected.relevancyStatus, actual.relevancyStatus)
- }
-
- private fun addToStatePartyTable(stateAndRef: StateAndRef) {
- cordaDB.transaction {
- val persistentStateRef = PersistentStateRef(stateAndRef.ref.txhash.toString(), stateAndRef.ref.index)
- val session = currentDBSession()
- stateAndRef.state.data.participants.forEach {
- val persistentParty = VaultSchemaV1.PersistentParty(
- persistentStateRef,
- it
- )
- session.save(persistentParty)
- }
- }
- }
-
- @Test(timeout=300_000)
- fun `Check a simple migration works`() {
- addCashStates(10, BOB)
- addCashStates(10, ALICE)
- assertEquals(20, getVaultStateCount())
- assertEquals(0, getStatePartyCount())
- val migration = VaultStateMigration()
- migration.execute(liquibaseDB)
- assertEquals(20, getVaultStateCount())
- assertEquals(20, getStatePartyCount())
- assertEquals(10, getVaultStateCount(Vault.RelevancyStatus.RELEVANT))
- }
-
- @Test(timeout=300_000)
- fun `Check state paging works`() {
- addCashStates(1010, BOB)
-
- assertEquals(0, getStatePartyCount())
- val migration = VaultStateMigration()
- migration.execute(liquibaseDB)
- assertEquals(1010, getStatePartyCount())
- assertEquals(1010, getVaultStateCount())
- assertEquals(0, getVaultStateCount(Vault.RelevancyStatus.NOT_RELEVANT))
- }
-
- @Test(timeout=300_000)
- fun `Check state fields are correct`() {
- val tx = createCashTransaction(Cash(), 100.DOLLARS, ALICE)
- storeTransaction(tx)
- createVaultStatesFromTransaction(tx)
- val expectedPersistentParty = VaultSchemaV1.PersistentParty(
- PersistentStateRef(tx.id.toString(), 0),
- ALICE
- )
- val state = tx.coreTransaction.outputs.first()
- val constraintInfo = Vault.ConstraintInfo(state.constraint)
- val expectedPersistentState = VaultSchemaV1.VaultStates(
- notary = state.notary,
- contractStateClassName = state.data.javaClass.name,
- stateStatus = Vault.StateStatus.UNCONSUMED,
- recordedTime = clock.instant(),
- relevancyStatus = Vault.RelevancyStatus.NOT_RELEVANT,
- constraintType = constraintInfo.type(),
- constraintData = constraintInfo.data()
- )
-
- val migration = VaultStateMigration()
- migration.execute(liquibaseDB)
- val persistentStateParty = getState(VaultSchemaV1.PersistentParty::class.java)
- val persistentState = getState(VaultSchemaV1.VaultStates::class.java)
- checkStatesEqual(expectedPersistentState, persistentState)
- assertEquals(expectedPersistentParty.x500Name, persistentStateParty.x500Name)
- assertEquals(expectedPersistentParty.compositeKey, persistentStateParty.compositeKey)
- }
-
- @Test(timeout=300_000)
- fun `Check the connection is open post migration`() {
- // Liquibase automatically closes the database connection when doing an actual migration. This test ensures the custom migration
- // leaves it open.
- addCashStates(12, ALICE)
-
- val migration = VaultStateMigration()
- migration.execute(liquibaseDB)
- assertFalse(cordaDB.dataSource.connection.isClosed)
- }
-
- @Test(timeout=300_000)
- fun `All parties added to state party table`() {
- val stx = createLinearStateTransaction("test", parties = listOf(ALICE, BOB, CHARLIE))
- storeTransaction(stx)
- createVaultStatesFromTransaction(stx)
-
- val migration = VaultStateMigration()
- migration.execute(liquibaseDB)
- assertEquals(3, getStatePartyCount())
- assertEquals(1, getVaultStateCount())
- assertEquals(0, getVaultStateCount(Vault.RelevancyStatus.NOT_RELEVANT))
- }
-
- @Test(timeout=300_000)
- fun `State with corresponding transaction missing fails migration`() {
- val cash = Cash()
- val unknownTx = createCashTransaction(cash, 100.DOLLARS, BOB)
- createVaultStatesFromTransaction(unknownTx)
-
- addCashStates(10, BOB)
- val migration = VaultStateMigration()
- assertFailsWith { migration.execute(liquibaseDB) }
- assertEquals(10, getStatePartyCount())
-
- // Now add the missing transaction and ensure that the migration succeeds
- storeTransaction(unknownTx)
- migration.execute(liquibaseDB)
- assertEquals(11, getStatePartyCount())
- }
-
- @Test(timeout=300_000)
- fun `State with unknown ID is handled correctly`() {
- addCashStates(1, CHARLIE)
- addCashStates(10, BOB)
- val migration = VaultStateMigration()
- migration.execute(liquibaseDB)
- assertEquals(11, getStatePartyCount())
- assertEquals(1, getVaultStateCount(Vault.RelevancyStatus.NOT_RELEVANT))
- assertEquals(10, getVaultStateCount(Vault.RelevancyStatus.RELEVANT))
- }
-
- @Test(timeout = 300_000)
- fun `Null database causes migration to fail`() {
- val migration = VaultStateMigration()
- // Just check this does not throw an exception
- assertThatExceptionOfType(VaultStateMigrationException::class.java).isThrownBy {
- migration.execute(null)
- }
- }
-
- @Test(timeout=300_000)
- fun `State with non-owning key for our name marked as relevant`() {
- val tx = createCashTransaction(Cash(), 100.DOLLARS, BOB2)
- storeTransaction(tx)
- createVaultStatesFromTransaction(tx)
- val state = tx.coreTransaction.outputs.first()
- val constraintInfo = Vault.ConstraintInfo(state.constraint)
- val expectedPersistentState = VaultSchemaV1.VaultStates(
- notary = state.notary,
- contractStateClassName = state.data.javaClass.name,
- stateStatus = Vault.StateStatus.UNCONSUMED,
- recordedTime = clock.instant(),
- relevancyStatus = Vault.RelevancyStatus.RELEVANT,
- constraintType = constraintInfo.type(),
- constraintData = constraintInfo.data()
- )
- val migration = VaultStateMigration()
- migration.execute(liquibaseDB)
- val persistentState = getState(VaultSchemaV1.VaultStates::class.java)
- checkStatesEqual(expectedPersistentState, persistentState)
- }
-
- @Test(timeout=300_000)
- fun `State already in state party table is excluded`() {
- val tx = createCashTransaction(Cash(), 100.DOLLARS, BOB)
- storeTransaction(tx)
- createVaultStatesFromTransaction(tx)
- addToStatePartyTable(tx.coreTransaction.outRef(0))
- addCashStates(5, BOB)
- assertEquals(1, getStatePartyCount())
- val migration = VaultStateMigration()
- migration.execute(liquibaseDB)
- assertEquals(6, getStatePartyCount())
- }
-
- @Test(timeout=300_000)
- fun `Consumed states are not migrated`() {
- addCashStates(1010, BOB, Vault.StateStatus.CONSUMED)
- assertEquals(0, getStatePartyCount())
- val migration = VaultStateMigration()
- migration.execute(liquibaseDB)
- assertEquals(0, getStatePartyCount())
- }
-
- @Test(timeout=300_000)
- fun `State created with notary change transaction can be migrated`() {
- // This test is a little bit of a hack - it checks that these states are migrated correctly by looking at params in the database,
- // but these will not be there for V3 nodes. Handling for this must be tested manually.
- val cashTx = createCashTransaction(Cash(), 5.DOLLARS, BOB)
- val cashTx2 = createCashTransaction(Cash(), 10.DOLLARS, BOB)
- val notaryTx = createNotaryChangeTransaction(listOf(StateRef(cashTx.id, 0), StateRef(cashTx2.id, 0)), SecureHash.allOnesHash)
- createVaultStatesFromTransaction(cashTx, stateStatus = Vault.StateStatus.CONSUMED)
- createVaultStatesFromTransaction(cashTx2, stateStatus = Vault.StateStatus.CONSUMED)
- createVaultStatesFromNotaryChangeTransaction(notaryTx, cashTx.coreTransaction.outputs + cashTx2.coreTransaction.outputs)
- storeTransaction(cashTx)
- storeTransaction(cashTx2)
- storeTransaction(notaryTx)
- val migration = VaultStateMigration()
- migration.execute(liquibaseDB)
- assertEquals(2, getStatePartyCount())
- }
-
- // Used to test migration performance
- @Test(timeout=300_000)
-@Ignore
- fun `Migrate large database`() {
- val statesAtOnce = 500L
- val stateMultiplier = 300L
- logger.info("Start adding states to vault")
- (1..stateMultiplier).forEach {
- addCashStates(statesAtOnce.toInt(), BOB)
- }
- logger.info("Finish adding states to vault")
- val migration = VaultStateMigration()
- migration.execute(liquibaseDB)
- assertEquals((statesAtOnce * stateMultiplier), getStatePartyCount())
- }
-
- private fun makePersistentDataSourceProperties(): Properties {
- val props = Properties()
- props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource")
- props.setProperty("dataSource.url", "jdbc:h2:~/test/persistence;DB_CLOSE_ON_EXIT=TRUE")
- props.setProperty("dataSource.user", "sa")
- props.setProperty("dataSource.password", "")
- return props
- }
-
- // Used to generate a persistent database for further testing.
- @Test(timeout=300_000)
-@Ignore
- fun `Create persistent DB`() {
- val cashStatesToAdd = 1000
- val linearStatesToAdd = 0
- val commodityStatesToAdd = 0
- val stateMultiplier = 10
-
- cordaDB = configureDatabase(makePersistentDataSourceProperties(), DatabaseConfig(), notaryServices.identityService::wellKnownPartyFromX500Name, notaryServices.identityService::wellKnownPartyFromAnonymous)
-
- // Starting the database this way runs the migration under test. This is fine for the unit tests (as the changelog table is ignored),
- // but when starting an actual node using these databases the migration will be skipped, as it has an entry in the changelog table.
- // This must therefore be removed.
- cordaDB.dataSource.connection.createStatement().use {
- it.execute("DELETE FROM DATABASECHANGELOG WHERE FILENAME IN ('migration/vault-schema.changelog-v9.xml')")
- }
-
- for (i in 1..stateMultiplier) {
- addCashStates(cashStatesToAdd, BOB)
- addLinearStates(linearStatesToAdd, listOf(BOB, ALICE))
- addCommodityStates(commodityStatesToAdd, BOB)
- }
- saveOurKeys(listOf(bob.keyPair))
- saveAllIdentities(listOf(BOB_IDENTITY, ALICE_IDENTITY, BOC_IDENTITY, dummyNotary.identity))
- cordaDB.close()
- }
-
- @Test(timeout=300_000)
-@Ignore
- fun `Run on persistent DB`() {
- cordaDB = configureDatabase(makePersistentDataSourceProperties(), DatabaseConfig(), notaryServices.identityService::wellKnownPartyFromX500Name, notaryServices.identityService::wellKnownPartyFromAnonymous)
- val connection = (liquibaseDB.connection as JdbcConnection)
- Mockito.`when`(connection.url).thenReturn(cordaDB.jdbcUrl)
- Mockito.`when`(connection.wrappedConnection).thenReturn(cordaDB.dataSource.connection)
- val migration = VaultStateMigration()
- migration.execute(liquibaseDB)
- cordaDB.close()
- }
-}
-
diff --git a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt
index 44b44bcf40..2da031f5b2 100644
--- a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt
+++ b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt
@@ -9,6 +9,7 @@ import net.corda.core.flows.NotaryFlow
import net.corda.core.flows.StateReplacementException
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
+import net.corda.core.internal.getRequiredTransaction
import net.corda.core.node.ServiceHub
import net.corda.core.node.StatesToRecord
import net.corda.core.transactions.TransactionBuilder
@@ -116,7 +117,7 @@ class NotaryChangeTests {
val newState = future.getOrThrow()
assertEquals(newState.state.notary, newNotary)
- val recordedTx = clientNodeA.services.validatedTransactions.getTransaction(newState.ref.txhash)!!
+ val recordedTx = clientNodeA.services.getRequiredTransaction(newState.ref.txhash)
val notaryChangeTx = recordedTx.resolveNotaryChangeTransaction(clientNodeA.services)
// Check that all encumbrances have been propagated to the outputs
@@ -140,7 +141,7 @@ class NotaryChangeTests {
// We don't to tx resolution when moving state to another node, so need to add the issue transaction manually
// to node B. The resolution process is tested later during notarisation.
- clientNodeB.services.recordTransactions(clientNodeA.services.validatedTransactions.getTransaction(issued.ref.txhash)!!)
+ clientNodeB.services.recordTransactions(clientNodeA.services.getRequiredTransaction(issued.ref.txhash))
val changedNotary = changeNotary(moved, clientNodeB, newNotaryParty)
val movedBack = moveState(changedNotary, clientNodeB, clientNodeA)
diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt
index b67921cf8c..f1c45cf30d 100644
--- a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt
+++ b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt
@@ -1,6 +1,5 @@
package net.corda.node.services.persistence
-import org.mockito.kotlin.*
import net.corda.core.contracts.Amount
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
@@ -10,6 +9,7 @@ import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
+import net.corda.core.internal.verification.toVerifyingServiceHub
import net.corda.core.node.StatesToRecord
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.Vault
@@ -28,7 +28,6 @@ import net.corda.finance.schemas.CashSchemaV1
import net.corda.finance.test.SampleCashSchemaV1
import net.corda.finance.test.SampleCashSchemaV2
import net.corda.finance.test.SampleCashSchemaV3
-import net.corda.node.internal.NodeServicesForResolution
import net.corda.node.services.api.WritableTransactionStorage
import net.corda.node.services.schema.ContractStateAndRef
import net.corda.node.services.schema.NodeSchemaService
@@ -41,7 +40,14 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.HibernateConfiguration
import net.corda.nodeapi.internal.persistence.HibernateSchemaChangeException
-import net.corda.testing.core.*
+import net.corda.testing.core.ALICE_NAME
+import net.corda.testing.core.BOB_NAME
+import net.corda.testing.core.BOC_NAME
+import net.corda.testing.core.CHARLIE_NAME
+import net.corda.testing.core.DUMMY_NOTARY_NAME
+import net.corda.testing.core.SerializationEnvironmentRule
+import net.corda.testing.core.TestIdentity
+import net.corda.testing.core.singleIdentity
import net.corda.testing.internal.configureDatabase
import net.corda.testing.internal.vault.DummyDealStateSchemaV1
import net.corda.testing.internal.vault.DummyLinearStateSchemaV1
@@ -49,15 +55,25 @@ import net.corda.testing.internal.vault.DummyLinearStateSchemaV2
import net.corda.testing.internal.vault.VaultFiller
import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
-import org.assertj.core.api.Assertions
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.hibernate.SessionFactory
-import org.junit.*
+import org.junit.After
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argThat
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
import java.math.BigDecimal
import java.time.Clock
import java.time.Instant
-import java.util.*
+import java.util.Currency
+import java.util.Random
+import java.util.UUID
import javax.persistence.EntityManager
import javax.persistence.Tuple
import javax.persistence.criteria.CriteriaBuilder
@@ -85,7 +101,7 @@ class HibernateConfigurationTest {
val vault: VaultService get() = services.vaultService
// Hibernate configuration objects
- lateinit var hibernateConfig: HibernateConfiguration
+ private lateinit var hibernateConfig: HibernateConfiguration
private lateinit var hibernatePersister: PersistentStateService
private lateinit var sessionFactory: SessionFactory
private lateinit var entityManager: EntityManager
@@ -126,7 +142,7 @@ class HibernateConfigurationTest {
override val vaultService = NodeVaultService(
Clock.systemUTC(),
keyManagementService,
- servicesForResolution as NodeServicesForResolution,
+ toVerifyingServiceHub(),
database,
schemaService,
cordappClassloader
@@ -236,7 +252,7 @@ class HibernateConfigurationTest {
// execute query
val queryResults = entityManager.createQuery(criteriaQuery).resultList
- Assertions.assertThat(queryResults.size).isEqualTo(3)
+ assertThat(queryResults.size).isEqualTo(3)
}
@Test(timeout=300_000)
@@ -327,7 +343,7 @@ class HibernateConfigurationTest {
// execute query
val queryResults = query.resultList
- Assertions.assertThat(queryResults.size).isEqualTo(15)
+ assertThat(queryResults.size).isEqualTo(15)
// try towards end
query.firstResult = 100
@@ -335,7 +351,7 @@ class HibernateConfigurationTest {
val lastQueryResults = query.resultList
- Assertions.assertThat(lastQueryResults.size).isEqualTo(10)
+ assertThat(lastQueryResults.size).isEqualTo(10)
}
/**
diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt
index 7bd3690925..424d6810f2 100644
--- a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt
+++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt
@@ -28,7 +28,6 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.testing.core.singleIdentity
import net.corda.testing.flows.registerCoreFlowFactory
import net.corda.coretesting.internal.rigorousMock
-import net.corda.node.internal.NodeServicesForResolution
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.enclosedCordapp
import net.corda.testing.node.internal.startFlow
@@ -86,11 +85,10 @@ class VaultSoftLockManagerTest {
private val mockNet = InternalMockNetwork(cordappsForAllNodes = listOf(enclosedCordapp()), defaultFactory = { args ->
object : InternalMockNetwork.MockNode(args) {
override fun makeVaultService(keyManagementService: KeyManagementService,
- services: NodeServicesForResolution,
database: CordaPersistence,
cordappLoader: CordappLoader): VaultServiceInternal {
val node = this
- val realVault = super.makeVaultService(keyManagementService, services, database, cordappLoader)
+ val realVault = super.makeVaultService(keyManagementService, database, cordappLoader)
return object : SingletonSerializeAsToken(), VaultServiceInternal by realVault {
override fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet?) {
// Should be called before flow is removed
diff --git a/node/src/test/kotlin/net/corda/notary/experimental/raft/RaftNotaryServiceTests.kt b/node/src/test/kotlin/net/corda/notary/experimental/raft/RaftNotaryServiceTests.kt
index cde76833d9..9c69d86b17 100644
--- a/node/src/test/kotlin/net/corda/notary/experimental/raft/RaftNotaryServiceTests.kt
+++ b/node/src/test/kotlin/net/corda/notary/experimental/raft/RaftNotaryServiceTests.kt
@@ -1,19 +1,17 @@
package net.corda.notary.experimental.raft
import net.corda.core.contracts.StateAndRef
-import net.corda.core.contracts.StateRef
import net.corda.core.flows.NotaryError
import net.corda.core.flows.NotaryException
import net.corda.core.flows.NotaryFlow
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.map
-import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.testing.contracts.DummyContract
+import net.corda.testing.contracts.DummyContract.SingleOwnerState
import net.corda.testing.core.DUMMY_BANK_A_NAME
-import net.corda.testing.core.dummyCommand
import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.InProcess
@@ -22,7 +20,7 @@ import net.corda.testing.node.ClusterSpec
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.internal.DUMMY_CONTRACTS_CORDAPP
import org.junit.Test
-import java.util.*
+import java.util.Random
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@@ -39,20 +37,13 @@ class RaftNotaryServiceTests {
val bankA = startNode(providedName = DUMMY_BANK_A_NAME).map { (it as InProcess) }.getOrThrow()
val inputState = issueState(bankA, defaultNotaryIdentity)
- val firstTxBuilder = TransactionBuilder(defaultNotaryIdentity)
- .addInputState(inputState)
- .addCommand(dummyCommand(bankA.services.myInfo.singleIdentity().owningKey))
+ val firstTxBuilder = DummyContract.move(inputState, bankA.services.myInfo.singleIdentity())
val firstSpendTx = bankA.services.signInitialTransaction(firstTxBuilder)
val firstSpend = bankA.startFlow(NotaryFlow.Client(firstSpendTx))
firstSpend.getOrThrow()
- val secondSpendBuilder = TransactionBuilder(defaultNotaryIdentity).withItems(inputState).run {
- val dummyState = DummyContract.SingleOwnerState(0, bankA.services.myInfo.singleIdentity())
- addOutputState(dummyState, DummyContract.PROGRAM_ID)
- addCommand(dummyCommand(bankA.services.myInfo.singleIdentity().owningKey))
- this
- }
+ val secondSpendBuilder = DummyContract.move(inputState, bankA.services.myInfo.singleIdentity())
val secondSpendTx = bankA.services.signInitialTransaction(secondSpendBuilder)
val secondSpend = bankA.startFlow(NotaryFlow.Client(secondSpendTx))
@@ -78,10 +69,10 @@ class RaftNotaryServiceTests {
}
}
- private fun issueState(nodeHandle: InProcess, notary: Party): StateAndRef<*> {
+ private fun issueState(nodeHandle: InProcess, notary: Party): StateAndRef {
val builder = DummyContract.generateInitial(Random().nextInt(), notary, nodeHandle.services.myInfo.singleIdentity().ref(0))
val stx = nodeHandle.services.signInitialTransaction(builder)
nodeHandle.services.recordTransactions(stx)
- return StateAndRef(stx.coreTransaction.outputs.first(), StateRef(stx.id, 0))
+ return stx.coreTransaction.outRef(0)
}
}
diff --git a/serialization/build.gradle b/serialization/build.gradle
index 6f3cffa35a..7393bae2e7 100644
--- a/serialization/build.gradle
+++ b/serialization/build.gradle
@@ -28,8 +28,6 @@ dependencies {
// For caches rather than guava
implementation "com.github.ben-manes.caffeine:caffeine:$caffeine_version"
- testImplementation project(":serialization")
-
testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
testImplementation "junit:junit:$junit_version"
diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/model/RemoteTypeCarpenter.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/model/RemoteTypeCarpenter.kt
index e7b97cf45b..df90dbc534 100644
--- a/serialization/src/main/kotlin/net/corda/serialization/internal/model/RemoteTypeCarpenter.kt
+++ b/serialization/src/main/kotlin/net/corda/serialization/internal/model/RemoteTypeCarpenter.kt
@@ -32,7 +32,7 @@ class SchemaBuildingRemoteTypeCarpenter(private val carpenter: ClassCarpenter):
} // Anything else, such as arrays, will be taken care of by the above
}
} catch (e: ClassCarpenterException) {
- throw NotSerializableException("${typeInformation.typeIdentifier.name}: ${e.message}")
+ throw NotSerializableException("${typeInformation.typeIdentifier.name}: ${e.message}").apply { initCause(e) }
}
return try {
@@ -40,7 +40,7 @@ class SchemaBuildingRemoteTypeCarpenter(private val carpenter: ClassCarpenter):
} catch (e: ClassNotFoundException) {
// This might happen if we've been asked to carpent up a parameterised type, and it's the rawtype itself
// rather than any of its type parameters that were missing.
- throw NotSerializableException("Could not carpent ${typeInformation.typeIdentifier.prettyPrint(false)}")
+ throw NotSerializableException("Could not carpent ${typeInformation.typeIdentifier.prettyPrint(false)}").apply { initCause(e) }
}
}
@@ -87,6 +87,6 @@ class SchemaBuildingRemoteTypeCarpenter(private val carpenter: ClassCarpenter):
}
private fun RemoteTypeInformation.AnEnum.carpentEnum() {
- carpenter.build(EnumSchema(name = typeIdentifier.name, fields = members.associate { it to EnumField() }))
+ carpenter.build(EnumSchema(name = typeIdentifier.name, fields = members.associateWith { EnumField() }))
}
-}
\ No newline at end of file
+}
diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CustomSerializationSchemeAdapter.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/verifier/CustomSerializationSchemeAdapter.kt
similarity index 66%
rename from node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CustomSerializationSchemeAdapter.kt
rename to serialization/src/main/kotlin/net/corda/serialization/internal/verifier/CustomSerializationSchemeAdapter.kt
index f656f81502..3bbdb170a9 100644
--- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/CustomSerializationSchemeAdapter.kt
+++ b/serialization/src/main/kotlin/net/corda/serialization/internal/verifier/CustomSerializationSchemeAdapter.kt
@@ -1,8 +1,10 @@
-package net.corda.nodeapi.internal.serialization
+package net.corda.serialization.internal.verifier
-import net.corda.core.serialization.SerializationSchemeContext
+import net.corda.core.CordaException
+import net.corda.core.internal.loadClassOfType
import net.corda.core.serialization.CustomSerializationScheme
import net.corda.core.serialization.SerializationContext
+import net.corda.core.serialization.SerializationSchemeContext
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.internal.CustomSerializationSchemeUtils.Companion.getCustomSerializationMagicFromSchemeId
import net.corda.core.utilities.ByteSequence
@@ -12,8 +14,7 @@ import java.io.ByteArrayOutputStream
import java.io.NotSerializableException
class CustomSerializationSchemeAdapter(private val customScheme: CustomSerializationScheme): SerializationScheme {
-
- val serializationSchemeMagic = getCustomSerializationMagicFromSchemeId(customScheme.getSchemeId())
+ private val serializationSchemeMagic = getCustomSerializationMagicFromSchemeId(customScheme.getSchemeId())
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
return magic == serializationSchemeMagic
@@ -44,4 +45,21 @@ class CustomSerializationSchemeAdapter(private val customScheme: CustomSerializa
override val whitelist = context.whitelist
override val properties = context.properties
}
-}
\ No newline at end of file
+}
+
+@Suppress("ThrowsCount")
+fun loadCustomSerializationScheme(className: String, classLoader: ClassLoader): SerializationScheme {
+ val schemeClass = try {
+ loadClassOfType(className, false, classLoader)
+ } catch (e: ClassNotFoundException) {
+ throw CordaException("$className was declared as a custom serialization scheme but could not be found.")
+ } catch (e: ClassCastException) {
+ throw CordaException("$className was declared as a custom serialization scheme but does not implement CustomSerializationScheme")
+ }
+ val constructor = try {
+ schemeClass.getDeclaredConstructor()
+ } catch (e: NoSuchMethodException) {
+ throw CordaException("$className was declared as a custom serialization scheme but does not have a no argument constructor.")
+ }
+ return CustomSerializationSchemeAdapter(constructor.newInstance())
+}
diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/verifier/ExternalVerifierTypes.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/verifier/ExternalVerifierTypes.kt
new file mode 100644
index 0000000000..61f00b2b98
--- /dev/null
+++ b/serialization/src/main/kotlin/net/corda/serialization/internal/verifier/ExternalVerifierTypes.kt
@@ -0,0 +1,87 @@
+package net.corda.serialization.internal.verifier
+
+import net.corda.core.contracts.Attachment
+import net.corda.core.contracts.StateRef
+import net.corda.core.crypto.SecureHash
+import net.corda.core.crypto.toStringShort
+import net.corda.core.identity.Party
+import net.corda.core.internal.SerializedTransactionState
+import net.corda.core.node.NetworkParameters
+import net.corda.core.serialization.CordaSerializable
+import net.corda.core.serialization.SerializedBytes
+import net.corda.core.serialization.deserialize
+import net.corda.core.serialization.serialize
+import net.corda.core.transactions.SignedTransaction
+import net.corda.core.utilities.Try
+import java.io.DataInputStream
+import java.io.DataOutputStream
+import java.io.EOFException
+import java.security.PublicKey
+
+typealias SerializedNetworkParameters = SerializedBytes
+
+@CordaSerializable
+sealed interface ExternalVerifierInbound {
+ data class Initialisation(
+ val customSerializerClassNames: Set,
+ val serializationWhitelistClassNames: Set,
+ val customSerializationSchemeClassName: String?,
+ val serializedCurrentNetworkParameters: SerializedNetworkParameters
+ ) : ExternalVerifierInbound {
+ val currentNetworkParameters: NetworkParameters by lazy(serializedCurrentNetworkParameters::deserialize)
+
+ override fun toString(): String {
+ return "Initialisation(" +
+ "customSerializerClassNames=$customSerializerClassNames, " +
+ "serializationWhitelistClassNames=$serializationWhitelistClassNames, " +
+ "customSerializationSchemeClassName=$customSerializationSchemeClassName, " +
+ "currentNetworkParameters=$currentNetworkParameters)"
+ }
+ }
+
+ data class VerificationRequest(
+ val stx: SignedTransaction,
+ val stxInputsAndReferences: Map,
+ val checkSufficientSignatures: Boolean
+ ) : ExternalVerifierInbound
+
+ data class PartiesResult(val parties: List) : ExternalVerifierInbound
+ data class AttachmentResult(val attachment: AttachmentWithTrust?) : ExternalVerifierInbound
+ data class AttachmentsResult(val attachments: List) : ExternalVerifierInbound
+ data class NetworkParametersResult(val networkParameters: NetworkParameters?) : ExternalVerifierInbound
+ data class TrustedClassAttachmentResult(val id: SecureHash?) : ExternalVerifierInbound
+}
+
+@CordaSerializable
+data class AttachmentWithTrust(val attachment: Attachment, val isTrusted: Boolean)
+
+@CordaSerializable
+sealed interface ExternalVerifierOutbound {
+ sealed interface VerifierRequest : ExternalVerifierOutbound {
+ data class GetParties(val keys: Set) : VerifierRequest {
+ override fun toString(): String = "GetParty(keys=${keys.map { it.toStringShort() }}})"
+ }
+ data class GetAttachment(val id: SecureHash) : VerifierRequest
+ data class GetAttachments(val ids: Set) : VerifierRequest
+ data class GetNetworkParameters(val id: SecureHash) : VerifierRequest
+ data class GetTrustedClassAttachment(val className: String) : VerifierRequest
+ }
+
+ data class VerificationResult(val result: Try) : ExternalVerifierOutbound
+}
+
+fun DataOutputStream.writeCordaSerializable(payload: Any) {
+ val serialised = payload.serialize()
+ writeInt(serialised.size)
+ serialised.writeTo(this)
+ flush()
+}
+
+inline fun DataInputStream.readCordaSerializable(): T {
+ val length = readInt()
+ val bytes = readNBytes(length)
+ if (bytes.size != length) {
+ throw EOFException("Incomplete read of ${T::class.java.name}")
+ }
+ return bytes.deserialize()
+}
diff --git a/settings.gradle b/settings.gradle
index 69c3ea22d3..8f2543aebb 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -48,6 +48,7 @@ include 'node-api'
include 'node-api-tests'
include 'node'
include 'node:capsule'
+include 'verifier'
include 'client:jackson'
include 'client:jfx'
include 'client:mock'
diff --git a/testing/core-test-utils/src/main/kotlin/net/corda/coretesting/internal/InternalSerializationTestHelpers.kt b/testing/core-test-utils/src/main/kotlin/net/corda/coretesting/internal/InternalSerializationTestHelpers.kt
index 6345dd7549..28cf91fc2b 100644
--- a/testing/core-test-utils/src/main/kotlin/net/corda/coretesting/internal/InternalSerializationTestHelpers.kt
+++ b/testing/core-test-utils/src/main/kotlin/net/corda/coretesting/internal/InternalSerializationTestHelpers.kt
@@ -7,7 +7,7 @@ import net.corda.core.serialization.CustomSerializationScheme
import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.internal.SerializationEnvironment
-import net.corda.nodeapi.internal.serialization.CustomSerializationSchemeAdapter
+import net.corda.serialization.internal.verifier.CustomSerializationSchemeAdapter
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
import net.corda.nodeapi.internal.serialization.kryo.KRYO_CHECKPOINT_CONTEXT
import net.corda.nodeapi.internal.serialization.kryo.KryoCheckpointSerializer
diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt
index 7c19eb0265..ecf26a5211 100644
--- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt
+++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt
@@ -1,9 +1,13 @@
package net.corda.testing.node
import com.google.common.collect.MutableClassToInstanceMap
+import net.corda.core.CordaInternal
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractClassName
+import net.corda.core.contracts.ContractState
+import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
+import net.corda.core.contracts.TransactionState
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
@@ -13,9 +17,13 @@ import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.VisibleForTesting
+import net.corda.core.internal.cordapp.CordappProviderInternal
+import net.corda.core.internal.getRequiredTransaction
+import net.corda.core.internal.mapToSet
import net.corda.core.internal.requireSupportedHashType
import net.corda.core.internal.telemetry.TelemetryComponent
import net.corda.core.internal.telemetry.TelemetryServiceImpl
+import net.corda.core.internal.verification.VerifyingServiceHub
import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.FlowHandle
import net.corda.core.messaging.FlowProgressHandle
@@ -34,23 +42,22 @@ import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.NetworkParametersService
import net.corda.core.node.services.ServiceLifecycleObserver
import net.corda.core.node.services.TransactionStorage
-import net.corda.core.node.services.TransactionVerifierService
import net.corda.core.node.services.VaultService
import net.corda.core.node.services.diagnostics.DiagnosticsService
import net.corda.core.node.services.vault.CordaTransactionSupport
import net.corda.core.serialization.SerializeAsToken
+import net.corda.core.serialization.internal.AttachmentsClassLoaderCacheImpl
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.coretesting.internal.DEV_ROOT_CA
import net.corda.node.VersionInfo
-import net.corda.node.internal.ServicesForResolutionImpl
-import net.corda.node.internal.NodeServicesForResolution
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.node.services.api.SchemaService
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage
import net.corda.node.services.api.VaultServiceInternal
import net.corda.node.services.api.WritableTransactionStorage
+import net.corda.node.services.attachments.NodeAttachmentTrustCalculator
import net.corda.node.services.diagnostics.NodeDiagnosticsService
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.services.identity.PersistentIdentityService
@@ -58,7 +65,6 @@ import net.corda.node.services.keys.BasicHSMKeyManagementService
import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.persistence.PublicKeyToOwningIdentityCacheImpl
import net.corda.node.services.schema.NodeSchemaService
-import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.services.vault.NodeVaultService
import net.corda.nodeapi.internal.cordapp.CordappLoader
import net.corda.nodeapi.internal.persistence.CordaPersistence
@@ -69,6 +75,7 @@ import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.MockCordappProvider
import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.testing.internal.configureDatabase
+import net.corda.testing.internal.services.InternalMockAttachmentStorage
import net.corda.testing.node.internal.DriverDSLImpl
import net.corda.testing.node.internal.MockCryptoService
import net.corda.testing.node.internal.MockKeyManagementService
@@ -116,7 +123,6 @@ open class MockServices private constructor(
*arrayOf(initialIdentity.keyPair) + moreKeys
)
) : ServiceHub {
-
companion object {
private fun cordappLoaderForPackages(packages: Iterable, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader {
return JarScanningCordappLoader.fromJarUrls(cordappsForPackages(packages).map { it.jarFile.toUri().toURL() }, versionInfo)
@@ -485,24 +491,20 @@ open class MockServices private constructor(
private val mockCordappProvider: MockCordappProvider = MockCordappProvider(cordappLoader, attachments).also {
it.start()
}
- override val transactionVerifierService: TransactionVerifierService
- get() = InMemoryTransactionVerifierService(
- numberOfWorkers = 2,
- cordappProvider = mockCordappProvider,
- attachments = attachments
- )
override val cordappProvider: CordappProvider get() = mockCordappProvider
override var networkParametersService: NetworkParametersService = MockNetworkParametersStorage(initialNetworkParameters)
override val diagnosticsService: DiagnosticsService = NodeDiagnosticsService()
- protected val servicesForResolution: ServicesForResolution
- get() = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersService, validatedTransactions)
+ // This is kept here for backwards compatibility, otherwise this has no extra utility.
+ protected val servicesForResolution: ServicesForResolution get() = verifyingView
+
+ private val verifyingView: VerifyingServiceHub by lazy { VerifyingView(this) }
internal fun makeVaultService(schemaService: SchemaService, database: CordaPersistence, cordappLoader: CordappLoader): VaultServiceInternal {
return NodeVaultService(
clock,
keyManagementService,
- servicesForResolution as NodeServicesForResolution,
+ verifyingView,
database,
schemaService,
cordappLoader.appClassLoader
@@ -511,9 +513,9 @@ open class MockServices private constructor(
// This needs to be internal as MutableClassToInstanceMap is a guava type and shouldn't be part of our public API
/** A map of available [CordaService] implementations */
- internal val cordappServices: MutableClassToInstanceMap = MutableClassToInstanceMap.create()
+ internal val cordappServices: MutableClassToInstanceMap = MutableClassToInstanceMap.create()
- internal val cordappTelemetryComponents: MutableClassToInstanceMap = MutableClassToInstanceMap.create()
+ private val cordappTelemetryComponents: MutableClassToInstanceMap = MutableClassToInstanceMap.create()
override fun cordaService(type: Class): T {
require(type.isAnnotationPresent(CordaService::class.java)) { "${type.name} is not a Corda service" }
@@ -543,19 +545,43 @@ open class MockServices private constructor(
mockCordappProvider.addMockCordapp(contractClassName, attachments)
}
- override fun loadState(stateRef: StateRef) = servicesForResolution.loadState(stateRef)
- override fun loadStates(stateRefs: Set) = servicesForResolution.loadStates(stateRefs)
+ override fun loadState(stateRef: StateRef): TransactionState {
+ return getRequiredTransaction(stateRef.txhash).resolveBaseTransaction(this).outputs[stateRef.index]
+ }
+
+ override fun loadStates(stateRefs: Set): Set> = stateRefs.mapToSet(::toStateAndRef)
/** Returns a dummy Attachment, in context of signature constrains non-downgrade rule this default to contract class version `1`. */
override fun loadContractAttachment(stateRef: StateRef) = dummyAttachment
-}
-/**
- * Function which can be used to create a mock [CordaService] for use within testing, such as an Oracle.
- */
-fun createMockCordaService(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T): T {
- class MockAppServiceHubImpl(val serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T) : AppServiceHub, ServiceHub by serviceHub {
- val serviceInstance: T = serviceConstructor(this)
+
+ /**
+ * All [ServiceHub]s must also implement [VerifyingServiceHub]. However, since [MockServices] is part of the public API, making it
+ * extend [VerifyingServiceHub] would leak internal APIs. Instead we have this private view class and have the `toVerifyingServiceHub`
+ * extension method return it.
+ */
+ private class VerifyingView(private val mockServices: MockServices) : VerifyingServiceHub, ServiceHub by mockServices {
+ override val attachmentTrustCalculator = NodeAttachmentTrustCalculator(
+ attachmentStorage = InternalMockAttachmentStorage(mockServices.attachments),
+ cacheFactory = TestingNamedCacheFactory()
+ )
+
+ override val attachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory())
+
+ override val cordappProvider: CordappProviderInternal get() = mockServices.mockCordappProvider
+
+ override fun loadContractAttachment(stateRef: StateRef): Attachment = mockServices.loadContractAttachment(stateRef)
+
+ override fun loadState(stateRef: StateRef): TransactionState<*> = mockServices.loadState(stateRef)
+
+ override fun loadStates(stateRefs: Set): Set> = mockServices.loadStates(stateRefs)
+ }
+
+
+ @CordaInternal
+ internal class MockAppServiceHubImpl(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T) :
+ AppServiceHub, VerifyingServiceHub by serviceHub.verifyingView {
+ internal val serviceInstance: T = serviceConstructor(this)
init {
serviceHub.cordappServices.putInstance(serviceInstance.javaClass, serviceInstance)
@@ -576,5 +602,11 @@ fun createMockCordaService(serviceHub: MockServices, serv
throw UnsupportedOperationException()
}
}
- return MockAppServiceHubImpl(serviceHub, serviceConstructor).serviceInstance
+}
+
+/**
+ * Function which can be used to create a mock [CordaService] for use within testing, such as an Oracle.
+ */
+fun createMockCordaService(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T): T {
+ return MockServices.MockAppServiceHubImpl(serviceHub, serviceConstructor).serviceInstance
}
diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt
index 50a0a2c017..c370129c15 100644
--- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt
+++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt
@@ -352,7 +352,6 @@ open class InternalMockNetwork(cordappPackages: List = emptyList(),
private val entropyCounter = AtomicReference(args.entropyRoot)
override val log get() = staticLog
- override val transactionVerifierWorkerCount: Int get() = 1
private var _rxIoScheduler: Scheduler? = null
override val rxIoScheduler: Scheduler
diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt
index b460d02f30..eb52d9d614 100644
--- a/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt
+++ b/testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt
@@ -3,7 +3,6 @@ package net.corda.testing.dsl
import com.google.common.util.concurrent.ThreadFactoryBuilder
import net.corda.core.DoNotImplement
import net.corda.core.contracts.*
-import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.NullKeys.NULL_SIGNATURE
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
@@ -12,6 +11,7 @@ import net.corda.core.flows.TransactionMetadata
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.*
+import net.corda.core.internal.cordapp.CordappProviderInternal
import net.corda.core.internal.notary.NotaryService
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution
@@ -95,7 +95,6 @@ data class TestTransactionDSLInterpreter private constructor(
// Implementing [ServiceHubCoreInternal] allows better use in internal Corda tests
val services: ServicesForResolution = object : ServiceHubCoreInternal, ServiceHub by ledgerInterpreter.services {
-
// [validatedTransactions.getTransaction] needs overriding as there are no calls to
// [ServiceHub.recordTransactions] in the test dsl
override val validatedTransactions: TransactionStorage =
@@ -129,17 +128,17 @@ data class TestTransactionDSLInterpreter private constructor(
override fun loadState(stateRef: StateRef) =
ledgerInterpreter.resolveStateRef(stateRef)
- override fun loadStates(stateRefs: Set): Set> {
- return stateRefs.map { StateAndRef(loadState(it), it) }.toSet()
- }
-
- override val cordappProvider: CordappProvider =
- ledgerInterpreter.services.cordappProvider
+ override val cordappProvider: CordappProviderInternal
+ get() = ledgerInterpreter.services.cordappProvider as CordappProviderInternal
override val notaryService: NotaryService? = null
override val attachmentsClassLoaderCache: AttachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory())
+ override fun loadContractAttachment(stateRef: StateRef): Attachment {
+ return ledgerInterpreter.services.loadContractAttachment(stateRef)
+ }
+
override fun recordUnnotarisedTransaction(txn: SignedTransaction) {}
override fun removeUnnotarisedTransaction(id: SecureHash) {}
@@ -169,7 +168,6 @@ data class TestTransactionDSLInterpreter private constructor(
override fun reference(stateRef: StateRef) {
val state = ledgerInterpreter.resolveStateRef(stateRef)
- @Suppress("DEPRECATION") // Will remove when feature finalised.
transactionBuilder.addReferenceState(StateAndRef(state, stateRef).referenced())
}
diff --git a/verifier/build.gradle b/verifier/build.gradle
new file mode 100644
index 0000000000..e794026070
--- /dev/null
+++ b/verifier/build.gradle
@@ -0,0 +1,35 @@
+plugins {
+ id "org.jetbrains.kotlin.jvm"
+ id "application"
+ id "com.github.johnrengelman.shadow"
+}
+
+application {
+ mainClass.set("net.corda.verifier.Main")
+}
+
+dependencies {
+ implementation project(":core")
+ implementation project(":serialization")
+ implementation "com.github.ben-manes.caffeine:caffeine:$caffeine_version"
+ implementation "org.slf4j:jul-to-slf4j:$slf4j_version"
+
+ runtimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
+}
+
+jar {
+ manifest {
+ attributes("Add-Opens":
+ "java.base/java.lang " +
+ "java.base/java.lang.reflect " +
+ "java.base/java.lang.invoke " +
+ "java.base/java.util " +
+ "java.base/java.time " +
+ "java.base/java.io " +
+ "java.base/java.net " +
+ "java.base/javax.net.ssl " +
+ "java.base/java.security.cert " +
+ "java.base/java.nio"
+ )
+ }
+}
diff --git a/verifier/src/main/kotlin/net/corda/verifier/ExternalVerificationContext.kt b/verifier/src/main/kotlin/net/corda/verifier/ExternalVerificationContext.kt
new file mode 100644
index 0000000000..3db0294a2f
--- /dev/null
+++ b/verifier/src/main/kotlin/net/corda/verifier/ExternalVerificationContext.kt
@@ -0,0 +1,42 @@
+package net.corda.verifier
+
+import net.corda.core.contracts.Attachment
+import net.corda.core.contracts.StateRef
+import net.corda.core.crypto.SecureHash
+import net.corda.core.identity.Party
+import net.corda.core.internal.SerializedTransactionState
+import net.corda.core.internal.verification.VerificationSupport
+import net.corda.core.node.NetworkParameters
+import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
+import java.security.PublicKey
+
+class ExternalVerificationContext(
+ override val appClassLoader: ClassLoader,
+ override val attachmentsClassLoaderCache: AttachmentsClassLoaderCache,
+ private val externalVerifier: ExternalVerifier,
+ private val transactionInputsAndReferences: Map
+) : VerificationSupport {
+ override val isResolutionLazy: Boolean get() = false
+
+ override fun getParties(keys: Collection): List = externalVerifier.getParties(keys)
+
+ override fun getAttachment(id: SecureHash): Attachment? = externalVerifier.getAttachment(id)?.attachment
+
+ override fun getAttachments(ids: Collection): List {
+ return externalVerifier.getAttachments(ids).map { it?.attachment }
+ }
+
+ override fun isAttachmentTrusted(attachment: Attachment): Boolean = externalVerifier.getAttachment(attachment.id)!!.isTrusted
+
+ override fun getTrustedClassAttachment(className: String): Attachment? {
+ return externalVerifier.getTrustedClassAttachment(className)
+ }
+
+ override fun getNetworkParameters(id: SecureHash?): NetworkParameters? = externalVerifier.getNetworkParameters(id)
+
+ override fun getSerializedState(stateRef: StateRef): SerializedTransactionState = transactionInputsAndReferences.getValue(stateRef)
+
+ override fun fixupAttachmentIds(attachmentIds: Collection): Set {
+ return externalVerifier.fixupAttachmentIds(attachmentIds)
+ }
+}
diff --git a/verifier/src/main/kotlin/net/corda/verifier/ExternalVerifier.kt b/verifier/src/main/kotlin/net/corda/verifier/ExternalVerifier.kt
new file mode 100644
index 0000000000..fc1b5ec121
--- /dev/null
+++ b/verifier/src/main/kotlin/net/corda/verifier/ExternalVerifier.kt
@@ -0,0 +1,237 @@
+package net.corda.verifier
+
+import com.github.benmanes.caffeine.cache.Cache
+import net.corda.core.contracts.Attachment
+import net.corda.core.crypto.SecureHash
+import net.corda.core.identity.Party
+import net.corda.core.internal.loadClassOfType
+import net.corda.core.internal.mapToSet
+import net.corda.core.internal.objectOrNewInstance
+import net.corda.core.internal.toSynchronised
+import net.corda.core.internal.toTypedArray
+import net.corda.core.internal.verification.AttachmentFixups
+import net.corda.core.node.NetworkParameters
+import net.corda.core.serialization.SerializationContext
+import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
+import net.corda.core.serialization.internal.AttachmentsClassLoaderCacheImpl
+import net.corda.core.serialization.internal.SerializationEnvironment
+import net.corda.core.serialization.internal._contextSerializationEnv
+import net.corda.core.utilities.Try
+import net.corda.core.utilities.contextLogger
+import net.corda.core.utilities.debug
+import net.corda.serialization.internal.AMQP_P2P_CONTEXT
+import net.corda.serialization.internal.CordaSerializationMagic
+import net.corda.serialization.internal.SerializationFactoryImpl
+import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
+import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap
+import net.corda.serialization.internal.amqp.SerializationFactoryCacheKey
+import net.corda.serialization.internal.amqp.SerializerFactory
+import net.corda.serialization.internal.amqp.amqpMagic
+import net.corda.serialization.internal.verifier.AttachmentWithTrust
+import net.corda.serialization.internal.verifier.ExternalVerifierInbound.AttachmentResult
+import net.corda.serialization.internal.verifier.ExternalVerifierInbound.AttachmentsResult
+import net.corda.serialization.internal.verifier.ExternalVerifierInbound.Initialisation
+import net.corda.serialization.internal.verifier.ExternalVerifierInbound.NetworkParametersResult
+import net.corda.serialization.internal.verifier.ExternalVerifierInbound.PartiesResult
+import net.corda.serialization.internal.verifier.ExternalVerifierInbound.TrustedClassAttachmentResult
+import net.corda.serialization.internal.verifier.ExternalVerifierInbound.VerificationRequest
+import net.corda.serialization.internal.verifier.ExternalVerifierOutbound.VerificationResult
+import net.corda.serialization.internal.verifier.ExternalVerifierOutbound.VerifierRequest.GetAttachment
+import net.corda.serialization.internal.verifier.ExternalVerifierOutbound.VerifierRequest.GetAttachments
+import net.corda.serialization.internal.verifier.ExternalVerifierOutbound.VerifierRequest.GetNetworkParameters
+import net.corda.serialization.internal.verifier.ExternalVerifierOutbound.VerifierRequest.GetParties
+import net.corda.serialization.internal.verifier.ExternalVerifierOutbound.VerifierRequest.GetTrustedClassAttachment
+import net.corda.serialization.internal.verifier.loadCustomSerializationScheme
+import net.corda.serialization.internal.verifier.readCordaSerializable
+import net.corda.serialization.internal.verifier.writeCordaSerializable
+import java.io.DataInputStream
+import java.io.DataOutputStream
+import java.net.URLClassLoader
+import java.nio.file.Path
+import java.security.PublicKey
+import java.util.Optional
+import kotlin.io.path.div
+import kotlin.io.path.listDirectoryEntries
+
+@Suppress("TooGenericExceptionCaught", "MagicNumber")
+class ExternalVerifier(
+ private val baseDirectory: Path,
+ private val fromNode: DataInputStream,
+ private val toNode: DataOutputStream
+) {
+ companion object {
+ private val log = contextLogger()
+ }
+
+ private val attachmentsClassLoaderCache: AttachmentsClassLoaderCache
+ private val attachmentFixups = AttachmentFixups()
+ private val parties: OptionalCache
+ private val attachments: OptionalCache
+ private val networkParametersMap: OptionalCache
+ private val trustedClassAttachments: OptionalCache
+
+ private lateinit var appClassLoader: ClassLoader
+ private lateinit var currentNetworkParameters: NetworkParameters
+
+ init {
+ val cacheFactory = ExternalVerifierNamedCacheFactory()
+ attachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(cacheFactory)
+ parties = cacheFactory.buildNamed("ExternalVerifier_parties")
+ attachments = cacheFactory.buildNamed("ExternalVerifier_attachments")
+ networkParametersMap = cacheFactory.buildNamed("ExternalVerifier_networkParameters")
+ trustedClassAttachments = cacheFactory.buildNamed("ExternalVerifier_trustedClassAttachments")
+ }
+
+ fun run() {
+ initialise()
+ while (true) {
+ val request = fromNode.readCordaSerializable()
+ log.debug { "Received $request" }
+ verifyTransaction(request)
+ }
+ }
+
+ private fun initialise() {
+ // Use a preliminary serialization context to receive the initialisation message
+ _contextSerializationEnv.set(SerializationEnvironment.with(
+ verifierSerializationFactory(),
+ p2pContext = AMQP_P2P_CONTEXT
+ ))
+
+ log.info("Waiting for initialisation message from node...")
+ val initialisation = fromNode.readCordaSerializable()
+ log.info("Received $initialisation")
+
+ appClassLoader = createAppClassLoader()
+
+ // Then use the initialisation message to create the correct serialization context
+ _contextSerializationEnv.set(null)
+ _contextSerializationEnv.set(SerializationEnvironment.with(
+ verifierSerializationFactory(initialisation, appClassLoader).apply {
+ initialisation.customSerializationSchemeClassName?.let {
+ registerScheme(loadCustomSerializationScheme(it, appClassLoader))
+ }
+ },
+ p2pContext = AMQP_P2P_CONTEXT.withClassLoader(appClassLoader)
+ ))
+
+ attachmentFixups.load(appClassLoader)
+
+ currentNetworkParameters = initialisation.currentNetworkParameters
+ networkParametersMap.put(initialisation.serializedCurrentNetworkParameters.hash, Optional.of(currentNetworkParameters))
+
+ log.info("External verifier initialised")
+ }
+
+ private fun createAppClassLoader(): ClassLoader {
+ val cordappJarUrls = (baseDirectory / "cordapps").listDirectoryEntries()
+ .stream()
+ .filter { it.toString().endsWith(".jar") }
+ .map { it.toUri().toURL() }
+ .toTypedArray()
+ log.debug { "CorDapps: ${cordappJarUrls?.joinToString()}" }
+ return URLClassLoader(cordappJarUrls, javaClass.classLoader)
+ }
+
+ private fun verifyTransaction(request: VerificationRequest) {
+ val verificationContext = ExternalVerificationContext(appClassLoader, attachmentsClassLoaderCache, this, request.stxInputsAndReferences)
+ val result = try {
+ request.stx.verifyInternal(verificationContext, request.checkSufficientSignatures)
+ log.info("${request.stx} verified")
+ Try.Success(Unit)
+ } catch (t: Throwable) {
+ log.info("${request.stx} failed to verify", t)
+ Try.Failure(t)
+ }
+ toNode.writeCordaSerializable(VerificationResult(result))
+ }
+
+ fun getParties(keys: Collection): List {
+ return parties.retrieveAll(keys) {
+ request(GetParties(it)).parties
+ }
+ }
+
+ fun getAttachment(id: SecureHash): AttachmentWithTrust? {
+ return attachments.retrieve(id) {
+ request(GetAttachment(id)).attachment
+ }
+ }
+
+ fun getAttachments(ids: Collection): List {
+ return attachments.retrieveAll(ids) {
+ request(GetAttachments(it)).attachments
+ }
+ }
+
+ fun getTrustedClassAttachment(className: String): Attachment? {
+ val attachmentId = trustedClassAttachments.retrieve(className) {
+ // GetTrustedClassAttachment returns back the attachment ID, not the whole attachment. This lets us avoid downloading the whole
+ // attachment again if we already have it.
+ request(GetTrustedClassAttachment(className)).id
+ }
+ return attachmentId?.let(::getAttachment)?.attachment
+ }
+
+ fun getNetworkParameters(id: SecureHash?): NetworkParameters? {
+ return if (id == null) {
+ currentNetworkParameters
+ } else {
+ networkParametersMap.retrieve(id) {
+ request(GetNetworkParameters(id)).networkParameters
+ }
+ }
+ }
+
+ fun fixupAttachmentIds(attachmentIds: Collection): Set = attachmentFixups.fixupAttachmentIds(attachmentIds)
+
+ private inline fun request(request: Any): T {
+ log.debug { "Sending request to node: $request" }
+ toNode.writeCordaSerializable(request)
+ val response = fromNode.readCordaSerializable()
+ log.debug { "Received response from node: $response" }
+ return response
+ }
+
+ private fun verifierSerializationFactory(initialisation: Initialisation? = null, classLoader: ClassLoader? = null): SerializationFactoryImpl {
+ val serializationFactory = SerializationFactoryImpl()
+ serializationFactory.registerScheme(AMQPVerifierSerializationScheme(initialisation, classLoader))
+ return serializationFactory
+ }
+
+
+ private class AMQPVerifierSerializationScheme(initialisation: Initialisation?, classLoader: ClassLoader?) : AbstractAMQPSerializationScheme(
+ initialisation?.customSerializerClassNames.load(classLoader),
+ initialisation?.serializationWhitelistClassNames.load(classLoader),
+ AccessOrderLinkedHashMap(128).toSynchronised()
+ ) {
+ override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
+ return magic == amqpMagic && target == SerializationContext.UseCase.P2P
+ }
+
+ override fun rpcClientSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
+ override fun rpcServerSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
+
+ companion object {
+ inline fun Set?.load(classLoader: ClassLoader?): Set {
+ return this?.mapToSet { loadClassOfType(it, classLoader = classLoader).kotlin.objectOrNewInstance() } ?: emptySet()
+ }
+ }
+ }
+}
+
+private typealias OptionalCache = Cache>
+
+private fun OptionalCache.retrieve(key: K, request: () -> V?): V? {
+ return get(key) { Optional.ofNullable(request()) }!!.orElse(null)
+}
+
+@Suppress("UNCHECKED_CAST")
+private fun OptionalCache.retrieveAll(keys: Collection, request: (Set) -> List): List {
+ val optionalResults = getAll(keys) {
+ val missingKeys = if (it is Set<*>) it as Set else it.toSet()
+ val response = request(missingKeys)
+ missingKeys.zip(response) { key, value -> key to Optional.ofNullable(value) }.toMap()
+ }
+ return keys.map { optionalResults.getValue(it).orElse(null) }
+}
diff --git a/verifier/src/main/kotlin/net/corda/verifier/ExternalVerifierNamedCacheFactory.kt b/verifier/src/main/kotlin/net/corda/verifier/ExternalVerifierNamedCacheFactory.kt
new file mode 100644
index 0000000000..cc3887eab8
--- /dev/null
+++ b/verifier/src/main/kotlin/net/corda/verifier/ExternalVerifierNamedCacheFactory.kt
@@ -0,0 +1,35 @@
+package net.corda.verifier
+
+import com.github.benmanes.caffeine.cache.Cache
+import com.github.benmanes.caffeine.cache.CacheLoader
+import com.github.benmanes.caffeine.cache.Caffeine
+import com.github.benmanes.caffeine.cache.LoadingCache
+import net.corda.core.internal.NamedCacheFactory
+
+@Suppress("MagicNumber")
+class ExternalVerifierNamedCacheFactory : NamedCacheFactory {
+ companion object {
+ private const val DEFAULT_CACHE_SIZE = 1024L
+ }
+
+ override fun buildNamed(caffeine: Caffeine, name: String): Cache