From 11d0054fcc64b865b55666471a109d06508b0c28 Mon Sep 17 00:00:00 2001
From: Shams Asari
Date: Thu, 7 Dec 2023 11:29:27 +0000
Subject: [PATCH] ENT-11055: Basic external verification (#7545)
* ENT-11055: Basic external verification
Introduction of the external transaction verifier, a separate JVM process for verifying `SignedTransaction`s. The end goal is for this verifier to be built with Kotlin 1.2 so that it creates a compatible verification environment for transactions with 4.11 contracts. For now however the verifier is built against Kotlin 1.8, same as the node.
External verification is enabled when the the system property `net.corda.node.verification.external` is set to `true`. When enabled, all verification requests made via `SignedTransaction.verify` are sent to the external verifier, regardless of the transaction content. It will do the vast bulk of the verification and then send the result back, namely if an exception occurred. If it did, then it's re-thrown in the node.
The external verifier is a stateless process, with no connection to the node's database. All transaction resolution information needed to create the relevant ledger transaction object are made to the node, which waits in a loop servicing these requests until it receives the result. The verifier Jar is embedded in the Corda node Jar, and is extracted and run when needed for the first time. The node opens up a local port for the verifier to communicate with, which is specified to the verifier in the process command line. This all means there is no extra configuration or deployment required to support external verification.
The existing code had some initial attempts and abstractions to support a future external verification feature. However,
they were either incorrect or didn't quite fit. One such example was `TransactionVerifierService`. It incorrectly operated on the `LedgerTransaction` level, which doesn't work since the transaction needs to be first serialised. Instead a new abstraction, `VerificationSupport` has been introduced, which represents all the operations needed to resolve and verify a `SignedTransaction`, essentially replacing `ServicesForResolution` (a lot of the changes are due to this). The external verifier implements this with a simple RPC mechanism, whilst the node needed a new (internal) `ServiceHub` abstraction, `VerifyingServiceHub`. `ServicesForResolution` hasn't been deleted since it's public API, however all classes implementing it must also implement `VerifyingServiceHub`. This is possible to do without breaking compatibility since `ServicesForResolution` is annotated with `@DoNotImplement`.
Changes to `api-current.txt` were made due to the removal of `TransactionVerifierService`, which was clearly indicated as an internal class, and returning `TransactionBuilder.toLedgerTransactionWithContext` back to an internal method.
* Address review comments
* One bulk load states method
* Merge fix
---
.ci/api-current.txt | 11 -
build.gradle | 2 +-
client/jackson/build.gradle | 4 +-
.../client/jackson/JacksonSupportTest.kt | 99 +--
.../flows/ContractUpgradeFlowRPCTest.kt | 4 +-
.../flows/ContractUpgradeFlowTest.kt | 42 +-
.../net/corda/coretests/flows/WithFinality.kt | 5 +-
.../verification/AttachmentFixupsTest.kt | 137 ++++
.../transactions/TransactionBuilderTest.kt | 137 ++--
core/build.gradle | 8 +-
.../core/contracts/ContractAttachment.kt | 1 +
.../crypto/internal/DigestAlgorithmFactory.kt | 9 +-
.../corda/core/flows/SendTransactionFlow.kt | 15 +-
.../corda/core/internal/ClassLoadingUtils.kt | 19 +-
.../net/corda/core/internal/CordaUtils.kt | 56 +-
.../core/internal/CordappFixupInternal.kt | 7 -
.../net/corda/core/internal/InternalUtils.kt | 42 +-
.../net/corda/core/internal/NamedCache.kt | 14 +-
.../core/internal/ServiceHubCoreInternal.kt | 10 +-
.../corda/core/internal/TransactionUtils.kt | 4 +-
.../cordapp/CordappProviderInternal.kt | 13 +
.../internal/verification/AttachmentFixups.kt | 79 +++
.../verification/VerificationSupport.kt | 50 ++
.../Verifier.kt} | 39 +-
.../verification/VerifyingServiceHub.kt | 221 ++++++
.../kotlin/net/corda/core/node/ServiceHub.kt | 14 +-
.../services/TransactionVerifierService.kt | 18 -
.../ContractUpgradeTransactions.kt | 161 ++---
.../core/transactions/LedgerTransaction.kt | 94 +--
.../transactions/NotaryChangeTransactions.kt | 85 ++-
.../core/transactions/SignedTransaction.kt | 169 ++++-
.../core/transactions/TransactionBuilder.kt | 73 +-
.../core/transactions/WireTransaction.kt | 171 +++--
...elpers.kt => InternalAccessTestHelpers.kt} | 38 +-
.../finance/contracts/CommercialPaperTests.kt | 5 +-
...tachmentsClassLoaderStaticContractTests.kt | 56 +-
.../persistence/HibernateConfiguration.kt | 3 +-
.../CustomSerializationSchemeAdapterTests.kt | 1 +
node/build.gradle | 4 +
.../contracts/mutator/MutatorContract.kt | 2 +-
.../CustomSerializationSchemeDriverTest.kt | 29 +-
.../verification/ExternalVerificationTest.kt | 219 ++++++
.../net/corda/node/internal/AbstractNode.kt | 64 +-
.../corda/node/internal/AppServiceHubImpl.kt | 9 +-
.../kotlin/net/corda/node/internal/Node.kt | 59 +-
.../internal/NodeServicesForResolution.kt | 15 -
.../internal/ServicesForResolutionImpl.kt | 85 ---
.../corda/node/internal/classloading/Utils.kt | 25 -
.../internal/cordapp/CordappProviderImpl.kt | 136 +---
.../cordapp/CordappProviderInternal.kt | 14 -
.../cordapp/JarScanningCordappLoader.kt | 30 +-
.../security/RPCSecurityManagerImpl.kt | 4 +-
.../corda/node/migration/CordaMigration.kt | 15 -
.../MigrationServicesForResolution.kt | 175 -----
.../node/migration/VaultStateMigration.kt | 239 +------
.../node/services/api/ServiceHubInternal.kt | 19 +-
.../NodeAttachmentTrustCalculator.kt | 19 +-
.../PublicKeyToOwningIdentityCacheImpl.kt | 6 +-
.../statemachine/FlowLogicRefFactoryImpl.kt | 10 +-
.../InMemoryTransactionVerifierService.kt | 120 ----
.../node/services/vault/NodeVaultService.kt | 43 +-
.../utilities/InfrequentlyMutatedCache.kt | 3 +-
.../node/utilities/NonInvalidatingCache.kt | 3 +-
.../verification/ExternalVerifierHandle.kt | 229 +++++++
.../node/verification/NoDbAccessVerifier.kt | 12 +
.../CustomSerializationSchemeScanningTest.kt | 17 +-
.../node/migration/VaultStateMigrationTest.kt | 637 ------------------
.../corda/node/services/NotaryChangeTests.kt | 5 +-
.../persistence/HibernateConfigurationTest.kt | 38 +-
.../vault/VaultSoftLockManagerTest.kt | 4 +-
.../raft/RaftNotaryServiceTests.kt | 21 +-
serialization/build.gradle | 2 -
.../internal/model/RemoteTypeCarpenter.kt | 8 +-
.../CustomSerializationSchemeAdapter.kt | 28 +-
.../verifier/ExternalVerifierTypes.kt | 87 +++
settings.gradle | 1 +
.../InternalSerializationTestHelpers.kt | 2 +-
.../net/corda/testing/node/MockServices.kt | 84 ++-
.../node/internal/InternalMockNetwork.kt | 1 -
.../kotlin/net/corda/testing/dsl/TestDSL.kt | 16 +-
verifier/build.gradle | 35 +
.../verifier/ExternalVerificationContext.kt | 42 ++
.../net/corda/verifier/ExternalVerifier.kt | 237 +++++++
.../ExternalVerifierNamedCacheFactory.kt | 35 +
.../main/kotlin/net/corda/verifier/Main.kt | 46 ++
verifier/src/main/resources/log4j2.xml | 44 ++
86 files changed, 2520 insertions(+), 2374 deletions(-)
create mode 100644 core-tests/src/test/kotlin/net/corda/coretests/internal/verification/AttachmentFixupsTest.kt
delete mode 100644 core/src/main/kotlin/net/corda/core/internal/CordappFixupInternal.kt
create mode 100644 core/src/main/kotlin/net/corda/core/internal/cordapp/CordappProviderInternal.kt
create mode 100644 core/src/main/kotlin/net/corda/core/internal/verification/AttachmentFixups.kt
create mode 100644 core/src/main/kotlin/net/corda/core/internal/verification/VerificationSupport.kt
rename core/src/main/kotlin/net/corda/core/internal/{TransactionVerifierServiceInternal.kt => verification/Verifier.kt} (95%)
create mode 100644 core/src/main/kotlin/net/corda/core/internal/verification/VerifyingServiceHub.kt
delete mode 100644 core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt
rename core/src/test/kotlin/net/corda/core/internal/{internalAccessTestHelpers.kt => InternalAccessTestHelpers.kt} (68%)
create mode 100644 node/src/integration-test/kotlin/net/corda/node/verification/ExternalVerificationTest.kt
delete mode 100644 node/src/main/kotlin/net/corda/node/internal/NodeServicesForResolution.kt
delete mode 100644 node/src/main/kotlin/net/corda/node/internal/ServicesForResolutionImpl.kt
delete mode 100644 node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderInternal.kt
delete mode 100644 node/src/main/kotlin/net/corda/node/migration/MigrationServicesForResolution.kt
delete mode 100644 node/src/main/kotlin/net/corda/node/services/transactions/InMemoryTransactionVerifierService.kt
create mode 100644 node/src/main/kotlin/net/corda/node/verification/ExternalVerifierHandle.kt
create mode 100644 node/src/main/kotlin/net/corda/node/verification/NoDbAccessVerifier.kt
delete mode 100644 node/src/test/kotlin/net/corda/node/migration/VaultStateMigrationTest.kt
rename {node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization => serialization/src/main/kotlin/net/corda/serialization/internal/verifier}/CustomSerializationSchemeAdapter.kt (66%)
create mode 100644 serialization/src/main/kotlin/net/corda/serialization/internal/verifier/ExternalVerifierTypes.kt
create mode 100644 verifier/build.gradle
create mode 100644 verifier/src/main/kotlin/net/corda/verifier/ExternalVerificationContext.kt
create mode 100644 verifier/src/main/kotlin/net/corda/verifier/ExternalVerifier.kt
create mode 100644 verifier/src/main/kotlin/net/corda/verifier/ExternalVerifierNamedCacheFactory.kt
create mode 100644 verifier/src/main/kotlin/net/corda/verifier/Main.kt
create mode 100644 verifier/src/main/resources/log4j2.xml
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