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
This commit is contained in:
Shams Asari 2023-12-07 11:29:27 +00:00 committed by GitHub
parent 74ca2c6734
commit 11d0054fcc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
86 changed files with 2520 additions and 2374 deletions

View File

@ -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 <init>(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()

View File

@ -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"

View File

@ -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}"

View File

@ -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<AttachmentStorage>().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<NetworkParametersService>().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<AttachmentStorage>()
doReturn(attachmentStorage).whenever(services).attachments
doReturn(mock<TransactionStorage>()).whenever(services).validatedTransactions
doReturn(mock<IdentityService>()).whenever(services).identityService
val attachment = rigorousMock<ContractAttachment>()
doReturn(attachment).whenever(attachmentStorage).openAttachment(attachmentId)
doReturn(attachmentId).whenever(attachment).id
doReturn(emptyList<Party>()).whenever(attachment).signerKeys
doReturn(setOf(DummyContract.PROGRAM_ID)).whenever(attachment).allContracts
doReturn("app").whenever(attachment).uploader
val wtx = TransactionBuilder(
notary = DUMMY_NOTARY,

View File

@ -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<FROM, TO>())
private fun TestStartedNode.getContractUpgradeTransaction(state: StateAndRef<ContractState>) =
services.validatedTransactions.getTransaction(state.ref.txhash)!!
.resolveContractUpgradeTransaction(services)
services.getRequiredTransaction(state.ref.txhash).resolveContractUpgradeTransaction(services)
private inline fun <reified FROM : Any, reified TO : Any> isUpgrade() =
isUpgradeFrom<FROM>() and isUpgradeTo<TO>()

View File

@ -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<Issued<Currency>>, val owners: List<AbstractParty>) : FungibleAsset<Currency> {
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<Issued<Currency>>, newOwner: AbstractParty) = copy(amount = amount.copy(newAmount.quantity), owners = listOf(newOwner))
@ -182,8 +205,7 @@ class ContractUpgradeFlowTest : WithContracts, WithFinality {
isUpgrade<FROM, TO>())
private fun TestStartedNode.getContractUpgradeTransaction(state: StateAndRef<ContractState>) =
services.validatedTransactions.getTransaction(state.ref.txhash)!!
.resolveContractUpgradeTransaction(services)
services.getRequiredTransaction(state.ref.txhash).resolveContractUpgradeTransaction(services)
private inline fun <reified FROM : Any, reified TO : Any> isUpgrade() =
isUpgradeFrom<FROM>() and isUpgradeTo<TO>()

View File

@ -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<SignedTransaction> {
return startFlow(WithFinality::FinalityInvoker, stx, recipients.toSet(), emptySet()).andRunNetwork()

View File

@ -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<IllegalArgumentException> {
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<IllegalArgumentException> {
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<IllegalArgumentException> {
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) }
}
}

View File

@ -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<ServicesForResolution>()
private val contractAttachmentId = SecureHash.randomSHA256()
private val attachments = rigorousMock<AttachmentStorage>()
private val networkParametersService = mock<NetworkParametersService>()
@Before
fun setup() {
val cordappProvider = rigorousMock<CordappProvider>()
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<IdentityService>()).whenever(services).identityService
val attachmentStorage = rigorousMock<AttachmentStorage>()
doReturn(attachmentStorage).whenever(services).attachments
val attachment = rigorousMock<ContractAttachment>()
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<Party>()).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<PublicKey> 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<PublicKey> 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)

View File

@ -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"

View File

@ -27,6 +27,7 @@ class ContractAttachment private constructor(
companion object {
@CordaInternal
@JvmSynthetic
fun create(attachment: Attachment,
contract: ContractClassName,
additionalContracts: Set<ContractClassName> = emptySet(),

View File

@ -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<out DigestAlgorithm> = Class.forName(className, false, javaClass.classLoader)
.asSubclass(DigestAlgorithm::class.java)
.getConstructor()
private val constructor = loadClassOfType<DigestAlgorithm>(className, false, javaClass.classLoader).getConstructor()
override val algorithm: String = constructor.newInstance().algorithm
override fun create(): DigestAlgorithm {

View File

@ -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<FlowSession>, 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<FlowSession>, val payload: Any
@Suspendable
private fun getInputTransactions(tx: SignedTransaction): Set<SecureHash> {
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<SecureHash> = mutableSetOf(), val acceptAll: Boolean = false) {

View File

@ -21,8 +21,7 @@ import net.corda.core.serialization.internal.AttachmentURLStreamHandlerFactory.a
fun <T: Any> createInstancesOfClassesImplementing(classloader: ClassLoader, clazz: Class<T>,
classVersionRange: IntRange? = null): Set<T> {
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 <T: Any> 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 <reified T> loadClassOfType(className: String, initialize: Boolean = true, classLoader: ClassLoader? = null): Class<out T> {
return loadClassOfType(T::class.java, className, initialize, classLoader)
}
fun <T> loadClassOfType(type: Class<T>, className: String, initialize: Boolean = true, classLoader: ClassLoader? = null): Class<out T> {
return Class.forName(className, initialize, classLoader).asSubclass(type)
}
fun <T: Any?> executeWithThreadContextClassLoader(classloader: ClassLoader, fn: () -> T): T {
val threadClassLoader = Thread.currentThread().contextClassLoader
try {
@ -68,5 +80,4 @@ fun <T: Any?> executeWithThreadContextClassLoader(classloader: ClassLoader, fn:
} finally {
Thread.currentThread().contextClassLoader = threadClassLoader
}
}

View File

@ -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<out FlowLogic<*>>.isIdempotentFlow(): Boolean {
return IdempotentFlow::class.java.isAssignableFrom(this)
@ -125,40 +113,6 @@ fun noPackageOverlap(packages: Collection<String>): 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<AttachmentId>): Set<AttachmentId> {
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)
}

View File

@ -1,7 +0,0 @@
package net.corda.core.internal
import net.corda.core.node.services.AttachmentId
interface CordappFixupInternal {
fun fixupAttachmentIds(attachmentIds: Collection<AttachmentId>): Set<AttachmentId>
}

View File

@ -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 <T> List<T>.randomOrNull(): T? {
/** Returns the index of the given item or throws [IllegalArgumentException] if not found. */
fun <T> List<T>.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 <T, R> Iterable<T>.mapToSet(transform: (T) -> R): Set<R> {
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 <T, R> Iterable<T>.flatMapToSet(transform: (T) -> Iterable<R>): Set<R> {
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 <reified T : Any> 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 <T> Iterable<T>.sumByLong(selector: (T) -> Long): Long = this.map { selector(it) }.sum()
fun <T : Any> SerializedBytes<Any>.checkPayloadIs(type: Class<T>): UntrustworthyData<T> {
val payloadData: T = try {
val serializer = SerializationDefaults.SERIALIZATION_FACTORY
@ -563,6 +575,10 @@ fun <K, V> MutableMap<K, V>.toSynchronised(): MutableMap<K, V> = Collections.syn
/** @see Collections.synchronizedSet */
fun <E> MutableSet<E>.toSynchronised(): MutableSet<E> = 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].

View File

@ -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 <K, V> buildNamed(name: String): Cache<K, V> = buildNamed(Caffeine.newBuilder(), name)
fun <K, V> buildNamed(caffeine: Caffeine<in K, in V>, name: String): Cache<K, V>
fun <K, V> buildNamed(name: String, loader: CacheLoader<K, V>): LoadingCache<K, V> = buildNamed(Caffeine.newBuilder(), name, loader)
fun <K, V> buildNamed(caffeine: Caffeine<in K, in V>, name: String, loader: CacheLoader<K, V>): LoadingCache<K, V>
}
private val allowedChars = Regex("^[0-9A-Za-z_.]*\$")

View File

@ -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.

View File

@ -172,11 +172,13 @@ fun createComponentGroups(inputs: List<StateRef>,
return componentGroupMap
}
typealias SerializedTransactionState = SerializedBytes<TransactionState<ContractState>>
/**
* A SerializedStateAndRef is a pair (BinaryStateRepresentation, StateRef).
* The [serializedState] is the actual component from the original wire transaction.
*/
data class SerializedStateAndRef(val serializedState: SerializedBytes<TransactionState<ContractState>>, 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<ContractState> {
val factory = SerializationFactory.defaultFactory

View File

@ -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<CordappImpl>
fun getCordappForFlow(flowLogic: FlowLogic<*>): Cordapp?
fun fixupAttachmentIds(attachmentIds: Collection<AttachmentId>): Set<AttachmentId>
}

View File

@ -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<AttachmentFixup>()
/**
* Loads the "fixup" rules from all META-INF/Corda-Fixups files.
* These files have the following format:
* <AttachmentId>,<AttachmentId>...=><AttachmentId>,<AttachmentId>,...
* where each <AttachmentId> 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<AttachmentFixups>().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<AttachmentId> {
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<AttachmentId>): Set<AttachmentId> {
val replacementIds = LinkedHashSet(attachmentIds)
for ((sourceIds, targetIds) in fixupRules) {
if (replacementIds.containsAll(sourceIds)) {
replacementIds.removeAll(sourceIds)
replacementIds.addAll(targetIds)
}
}
return replacementIds
}
}

View File

@ -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<PublicKey>): List<Party?>
fun getAttachment(id: SecureHash): Attachment?
// TODO Use SequencedCollection if upgraded to Java 21
fun getAttachments(ids: Collection<SecureHash>): List<Attachment?> = 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<SecureHash>): Set<SecureHash>
fun createVerifier(ltx: LedgerTransaction, serializationContext: SerializationContext): Verifier {
return defaultVerifier(ltx, serializationContext)
}
}

View File

@ -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<Attachment>) = internalPrepareVerify(attachments)
interface Verifier {
/**
@ -142,10 +145,12 @@ private class Validator(private val ltx: LedgerTransaction, private val transact
*/
@Suppress("ThrowsCount")
private fun getUniqueContractAttachmentsByContract(): Map<ContractClassName, ContractAttachment> {
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<ContractClassName, ContractAttachment>) {
// 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<out Contract> {
return try {
Class.forName(contractClassName, false, transactionClassLoader).asSubclass(Contract::class.java)
loadClassOfType<Contract>(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<Contract> {
return (ltx.inputs.map(StateAndRef<ContractState>::state) + ltx.outputs)
.mapTo(LinkedHashSet(), TransactionState<*>::contract)
.mapToSet { it.contract }
.map { contractClassName ->
createContractClass(ltx.id, contractClassName)
}.map { contractClass ->

View File

@ -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<ContractState>(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<StateRef>): Set<StateAndRef<ContractState>> = loadStatesInternal(stateRefs, LinkedHashSet())
fun <T : ContractState, C : MutableCollection<StateAndRef<T>>> loadStatesInternal(input: Iterable<StateRef>, output: C): C {
return input.mapTo(output, ::toStateAndRef)
}
// TODO Bulk party lookup?
override fun getParties(keys: Collection<PublicKey>): List<Party?> = 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<SecureHash>): Set<SecureHash> {
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")
}
}

View File

@ -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 <T : ContractState> toStateAndRef(stateRef: StateRef): StateAndRef<T> {
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()

View File

@ -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<*>
}

View File

@ -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 <T : ContractState, S : ContractState> calculateUpgradedState(state: TransactionState<T>, upgradedContract: UpgradedContract<T, S>, upgradedContractAttachment: Attachment): TransactionState<S> {
@JvmSynthetic
internal fun <T : ContractState, S : ContractState> calculateUpgradedState(state: TransactionState<T>,
upgradedContract: UpgradedContract<T, S>,
upgradedContractAttachment: Attachment): TransactionState<S> {
// 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<TransactionSignature>): 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<ContractState, ContractState> = try {
@Suppress("UNCHECKED_CAST")
Class.forName(className, false, classLoader).asSubclass(UpgradedContract::class.java).getDeclaredConstructor().newInstance() as UpgradedContract<ContractState, ContractState>
} 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<TransactionState<ContractState>> {
val binaryInput: SerializedBytes<TransactionState<ContractState>> = 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<StateAndRef<ContractState>>,
notary: Party,
legacyContractAttachment: Attachment,
upgradedContractAttachment: Attachment,
id: SecureHash,
privacySalt: PrivacySalt,
sigs: List<TransactionSignature>,
networkParameters: NetworkParameters,
upgradedContract: UpgradedContract<ContractState, *>
): ContractUpgradeLedgerTransaction {
return ContractUpgradeLedgerTransaction(inputs, notary, legacyContractAttachment, upgradedContractAttachment, id, privacySalt, sigs, networkParameters, upgradedContract)
@JvmSynthetic
@Suppress("ThrowsCount")
internal fun resolve(verificationSupport: VerificationSupport,
wtx: ContractUpgradeWireTransaction,
sigs: List<TransactionSignature>): 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<ContractState, *> {
@Suppress("UNCHECKED_CAST")
return Class.forName(upgradedContractClassName, false, classLoader)
.asSubclass(Contract::class.java)
.getConstructor()
.newInstance() as UpgradedContract<ContractState, *>
}
// 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<ContractState, *> {
return try {
loadClassOfType<UpgradedContract<ContractState, *>>(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<PublicKey>
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<PublicKey>): List<String> {
return keys.map { it.toBase58String() }
@ -401,7 +380,7 @@ private constructor(
privacySalt: PrivacySalt,
sigs: List<TransactionSignature>,
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(

View File

@ -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<SerializedStateAndRef>? = null,
serializedReferences: List<SerializedStateAndRef>? = 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<Attachment>): Verifier {
@JvmSynthetic
internal fun verifyInternal(txAttachments: List<Attachment> = 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 <reified T : ContractState> filterInputs(crossinline predicate: (T) -> Boolean): List<T> {
return filterInputs(T::class.java, Predicate { predicate(it) })
return filterInputs(T::class.java) { predicate(it) }
}
/**
@ -479,7 +481,7 @@ private constructor(
}
inline fun <reified T : ContractState> filterReferenceInputs(crossinline predicate: (T) -> Boolean): List<T> {
return filterReferenceInputs(T::class.java, Predicate { predicate(it) })
return filterReferenceInputs(T::class.java) { predicate(it) }
}
/**
@ -495,7 +497,7 @@ private constructor(
}
inline fun <reified T : ContractState> filterInRefs(crossinline predicate: (T) -> Boolean): List<StateAndRef<T>> {
return filterInRefs(T::class.java, Predicate { predicate(it) })
return filterInRefs(T::class.java) { predicate(it) }
}
/**
@ -511,7 +513,7 @@ private constructor(
}
inline fun <reified T : ContractState> filterReferenceInputRefs(crossinline predicate: (T) -> Boolean): List<StateAndRef<T>> {
return filterReferenceInputRefs(T::class.java, Predicate { predicate(it) })
return filterReferenceInputRefs(T::class.java) { predicate(it) }
}
/**
@ -528,7 +530,7 @@ private constructor(
}
inline fun <reified T : ContractState> 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 <T : ContractState> findReference(clazz: Class<T>, predicate: Predicate<T>): T {
return referenceInputsOfType(clazz).single { predicate.test(it) }
return referenceInputsOfType(clazz).single(predicate::test)
}
inline fun <reified T : ContractState> 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 <reified T : ContractState> findInRef(crossinline predicate: (T) -> Boolean): StateAndRef<T> {
return findInRef(T::class.java, Predicate { predicate(it) })
return findInRef(T::class.java) { predicate(it) }
}
/**
@ -579,7 +581,7 @@ private constructor(
}
inline fun <reified T : ContractState> findReferenceInputRef(crossinline predicate: (T) -> Boolean): StateAndRef<T> {
return findReferenceInputRef(T::class.java, Predicate { predicate(it) })
return findReferenceInputRef(T::class.java) { predicate(it) }
}
/**
@ -614,7 +616,7 @@ private constructor(
}
inline fun <reified T : CommandData> filterCommands(crossinline predicate: (T) -> Boolean): List<Command<T>> {
return filterCommands(T::class.java, Predicate { predicate(it) })
return filterCommands(T::class.java) { predicate(it) }
}
/**
@ -631,7 +633,7 @@ private constructor(
}
inline fun <reified T : CommandData> findCommand(crossinline predicate: (T) -> Boolean): Command<T> {
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?!

View File

@ -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<TransactionSignature>): 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<TransactionSignature>) = 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<TransactionState<ContractState>> {
return services.loadState(stateRef).serialize()
fun resolve(services: ServiceHub, sigs: List<TransactionSignature>): 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<StateAndRef<ContractState>>,
notary: Party,
newNotary: Party,
id: SecureHash,
sigs: List<TransactionSignature>,
networkParameters: NetworkParameters): NotaryChangeLedgerTransaction {
return NotaryChangeLedgerTransaction(inputs, notary, newNotary, id, sigs, networkParameters)
@JvmSynthetic
internal fun resolve(verificationSupport: VerificationSupport,
wireTx: NotaryChangeWireTransaction,
sigs: List<TransactionSignature>): 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<StateRef>): 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<TransactionState<ContractState>>
get() = computeOutputs()
private fun computeOutputs(): List<TransactionState<ContractState>> {
val inputPositionIndex: Map<StateRef, Int> = 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<ContractState>::ref) } }
override val requiredSigningKeys: Set<PublicKey>
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<PublicKey>): List<String> {
return keys.map { it.toBase58String() }

View File

@ -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<CoreTransaction>,
@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<CoreTransaction>,
} 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<CoreTransaction>,
@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<CoreTransaction>,
}
/** 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<CoreTransaction>,
// 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<CoreTransaction>,
}
@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 ?: "<unknown>", 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<CoreTransaction>,
// 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<Attachment> {
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<Attachment>): Collection<Attachment> {
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<CoreTransaction>,
}
/**
* 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<CoreTransaction>,
}
/**
* 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

View File

@ -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<AttachmentId>.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<AttachmentId>, resolvedOutputs: List<TransactionState<ContractState>>)
= 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<AttachmentId>,
services: ServicesForResolution,
originalException: Throwable
txAttachments: List<AttachmentId>,
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<*>) {

View File

@ -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
* </ul></p>
*/
@CordaSerializable
@Suppress("ThrowsCount")
class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: PrivacySalt, digestService: DigestService) : TraversableTransaction(componentGroups, digestService) {
constructor(componentGroups: List<ComponentGroup>) : this(componentGroups, PrivacySalt())
@ -71,7 +92,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, 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<ComponentGroup>, 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<ComponentGroup>, val privacySalt: Pr
@Suppress("UNUSED_PARAMETER") resolveContractAttachment: (TransactionState<ContractState>) -> 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<PublicKey>): List<Party?> = 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<SecureHash>) = throw AbstractMethodError()
})
}
@Suppress("LongParameterList", "ThrowsCount")
private fun toLedgerTransactionInternal(
resolveIdentity: (PublicKey) -> Party?,
resolveAttachment: (SecureHash) -> Attachment?,
resolveStateRefAsSerialized: (StateRef) -> SerializedBytes<TransactionState<ContractState>>?,
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<ComponentGroup>, 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<ComponentGroup>, val privacySalt: Pr
componentGroups,
serializedResolvedInputs,
serializedResolvedReferences,
isAttachmentTrusted,
attachmentsClassLoaderCache,
verificationSupport::isAttachmentTrusted,
verificationSupport::createVerifier,
verificationSupport.attachmentsClassLoaderCache,
digestService
)
@ -230,15 +248,15 @@ class WireTransaction(componentGroups: List<ComponentGroup>, 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<ComponentGroup>, 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<ComponentGroup>, val privacySalt: Pr
timeWindow: TimeWindow?): List<ComponentGroup> {
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<TransactionState<ContractState>>? {
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<TransactionState<ContractState>>?
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 {

View File

@ -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<LedgerTransaction>

View File

@ -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)

View File

@ -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<NetworkParametersService>().also {
doReturn(networkParameters.serialize().hash).whenever(it).currentHash
}
private val serviceHub get() = rigorousMock<ServicesForResolution>().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<AttachmentStorage>()
doReturn(attachmentStorage).whenever(it).attachments
val attachment = rigorousMock<ContractAttachment>()
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<Party>()).whenever(attachment).signerKeys
val contractAttachmentId = SecureHash.randomSHA256()
doReturn(listOf(contractAttachmentId)).whenever(attachmentStorage)
.getLatestContractAttachments(AttachmentDummyContract.ATTACHMENT_PROGRAM_ID)
doReturn(mock<IdentityService>()).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<String>): CordappLoader {
return JarScanningCordappLoader.fromJarUrls(listOf(cordappWithPackages(*packages.toTypedArray()).jarFile.toUri().toURL()))
}
}

View File

@ -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<Set<MappedSchema>, SessionFactory>(Caffeine.newBuilder(), "HibernateConfiguration_sessionFactories")
private val sessionFactories = cacheFactory.buildNamed<Set<MappedSchema>, SessionFactory>("HibernateConfiguration_sessionFactories")
val sessionFactoryForRegisteredSchemas = schemas.let {
logger.info("Init HibernateConfiguration for schemas: $it")

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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<SecureHash>() {
class WriteTxToLedgerFlow(private val counterparty: Party, val notary: Party) : FlowLogic<SecureHash>() {
@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<SignedTransaction>() {
@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<Boolean>() {
class SendFlow(private val counterparty: Party) : FlowLogic<Boolean>() {
@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<WireTransaction>() {
class CreateWireTxFlow(private val counterparty: Party) : FlowLogic<WireTransaction>() {
@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<Unit>() {
@Suspendable
@ -301,6 +305,7 @@ class CustomSerializationSchemeDriverTest {
kryo.isRegistrationRequired = false
kryo.instantiatorStrategy = CustomInstantiatorStrategy()
kryo.classLoader = classLoader
@Suppress("ReplaceJavaStaticMethodWithKotlinAnalog")
kryo.register(Arrays.asList("").javaClass, ArraysAsListSerializer())
}

View File

@ -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<Command>().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<AbstractParty> get() = listOf(party)
}
data class Command(val failForParty: Party) : CommandData
}
@StartableByRPC
@InitiatingFlow
class FailExternallyFlow(private val inputState: StateAndRef<State>?,
private val failForParty: NodeInfo,
private val recipient: NodeInfo) : FlowLogic<StateAndRef<State>>() {
@Suspendable
override fun call(): StateAndRef<State> {
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<Unit>() {
@Suspendable
override fun call() {
subFlow(ReceiveFinalityFlow(otherSide))
}
}
@StartableByRPC
class IssueAndChangeNotaryFlow(private val oldNotary: Party, private val newNotary: Party) : FlowLogic<SecureHash>() {
@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
}
}
}

View File

@ -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<S>(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<S>(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<S>(val configuration: NodeConfiguration,
private val cordappTelemetryComponents = MutableClassToInstanceMap.create<TelemetryComponent>()
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<S>(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<S>(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<S>(val configuration: NodeConfiguration,
networkMapCache,
NodeInfoWatcher(
configuration.baseDirectory,
@Suppress("LeakingThis")
rxIoScheduler,
Duration.ofMillis(configuration.additionalNodeInfoPollingFrequencyMsec)
),
@ -846,7 +832,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
platformClock,
database,
flowStarter,
servicesForResolution,
services,
flowLogicRefFactory,
nodeProperties,
configuration.drainingModePollPeriod,
@ -1160,12 +1146,19 @@ abstract class AbstractNode<S>(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<S>(val configuration: NodeConfiguration,
}
}
inner class ServiceHubInternalImpl : SingletonSerializeAsToken(), ServiceHubInternal, ServicesForResolution by servicesForResolution, NetworkParameterUpdateListener {
inner class ServiceHubImpl : SingletonSerializeAsToken(), ServiceHubInternal, NetworkParameterUpdateListener {
override val rpcFlows = ArrayList<Class<out FlowLogic<*>>>()
override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage(database)
override val identityService: IdentityService get() = this@AbstractNode.identityService
@ -1191,7 +1184,6 @@ abstract class AbstractNode<S>(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<S>(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<S>(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)
}
}
}

View File

@ -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<T : SerializeAsToken>(private val serviceHub: ServiceHub, private val flowStarter: FlowStarter,
internal class AppServiceHubImpl<T : SerializeAsToken>(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 {

View File

@ -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<NetworkHostAndPort> = 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)

View File

@ -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<StateRef>): Set<StateAndRef<ContractState>> = loadStates(stateRefs, LinkedHashSet())
fun <T : ContractState, C : MutableCollection<StateAndRef<T>>> loadStates(input: Iterable<StateRef>, output: C): C
}

View File

@ -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 <T : ContractState, C : MutableCollection<StateAndRef<T>>> loadStates(input: Iterable<StateRef>, output: C): C {
val baseTxs = HashMap<SecureHash, BaseTransaction>()
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<ContractState>(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)
}
}

View File

@ -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 <reified A : Annotation> 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.")
}

View File

@ -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<Cordapp, CordappContext>()
private val cordappAttachments = HashBiMap.create<SecureHash, URL>()
private val attachmentFixups = arrayListOf<AttachmentFixup>()
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<SecureHash, URL> =
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<SecureHash, URL> {
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:
* <AttachmentId>,<AttachmentId>...=><AttachmentId>,<AttachmentId>,...
* where each <AttachmentId> 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<AttachmentFixup> {
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<AttachmentId> {
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<AttachmentId>): Set<AttachmentId> {
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<Attachment>): Collection<Attachment> {
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)
}
/**

View File

@ -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<CordappImpl>
fun getCordappForFlow(flowLogic: FlowLogic<*>): Cordapp?
fun fixupAttachments(attachments: Collection<Attachment>): Collection<Attachment>
}

View File

@ -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 <T : Any> loadClass(className: String, type: KClass<T>): Class<out T>? {
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

View File

@ -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 <K : Any, V> buildCache(name: String): ShiroCache<K, V> {
logger.info("Constructing cache '$name' with maximumSize=$maxSize, TTL=${timeToLiveSeconds}s")
return cacheFactory.buildNamed<K, V>(Caffeine.newBuilder(), "RPCSecurityManagerShiroCache_$name").toShiroCache()
return cacheFactory.buildNamed<K, V>("RPCSecurityManagerShiroCache_$name").toShiroCache()
}
companion object {

View File

@ -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,

View File

@ -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<Int>): List<TransactionState<ContractState>> {
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<StateRef>): Set<StateAndRef<ContractState>> {
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()
}
}

View File

@ -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<ContractState>) {
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<ContractState> {
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<VaultSchemaV1.VaultStates> {
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<Vaul
result
}
}
private val pageSize = 1000
private var pageNumber = 0
private var transaction: DatabaseTransaction? = null
private var currentPage = getNextPage()
private fun endTransaction() {
try {
transaction?.commit()
} catch (e: Exception) {
transaction?.rollback()
logger.error("Failed to commit transaction while iterating vault states: ${e.message}", e)
} finally {
transaction?.close()
}
}
private fun getNextPage(): List<VaultSchemaV1.VaultStates> {
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<VaultSchemaV1.VaultStates>,
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<VaultPageTask> {
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)

View File

@ -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<Void?>
@ -186,11 +186,14 @@ interface ServiceHubInternal : ServiceHubCoreInternal {
val configuration: NodeConfiguration
val nodeProperties: NodePropertiesStore
val networkMapUpdater: NetworkMapUpdater
override val cordappProvider: CordappProviderInternal
fun getFlowFactory(initiatingFlowClass: Class<out FlowLogic<*>>): InitiatedFlowFactory<*>?
val cacheFactory: NamedCacheFactory
override fun createVerifier(ltx: LedgerTransaction, serializationContext: SerializationContext): Verifier {
return NoDbAccessVerifier(defaultVerifier(ltx, serializationContext))
}
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) =
recordTransactions(statesToRecord, txs, SIGNATURE_VERIFICATION_DISABLED)

View File

@ -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<PublicKey, Boolean>(
Caffeine.newBuilder(),
"NodeAttachmentTrustCalculator_trustedKeysCache"
)
private val trustedKeysCache = cacheFactory.buildNamed<PublicKey, Boolean>("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,

View File

@ -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<PublicKey, KeyOwningIdentity>(
Caffeine.newBuilder(),
"PublicKeyToOwningIdentityCache_cache"
)
private val cache = cacheFactory.buildNamed<PublicKey, KeyOwningIdentity>("PublicKeyToOwningIdentityCache_cache")
/**
* Return the owning identity associated with a given key.

View File

@ -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<out FlowLogic<*>> {
val forName = try {
Class.forName(flowClassName, true, classloader)
return try {
loadClassOfType<FlowLogic<*>>(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<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef {

View File

@ -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<Attachment>.toPrettyString(): String {
return joinToString(separator = SEPARATOR, prefix = SEPARATOR, postfix = System.lineSeparator()) { attachment ->
attachment.id.toString()
}
}
}
override fun verify(transaction: LedgerTransaction): CordaFuture<*> {
return openFuture<Unit>().apply {
capture {
val verifier = transaction.prepareVerify(transaction.attachments)
withoutDatabaseAccess {
verifier.verify()
}
}
}
}
private fun computeReplacementAttachmentsFor(ltx: LedgerTransaction, missingClass: String?): Collection<Attachment> {
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<Unit>().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() {}
}

View File

@ -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>(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 <T> withValidDeserialization(list: List<T>, txId: SecureHash): Map<Int, T> {
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<ContractState>().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<Vault.StateStatus>(VaultSchemaV1.VaultStates::stateStatus.name), Vault.StateStatus.UNCONSUMED)
val lockIdPredicate = criteriaBuilder.or(get<String>(VaultSchemaV1.VaultStates::lockId.name).isNull,
criteriaBuilder.equal(get<String>(VaultSchemaV1.VaultStates::lockId.name), lockId.toString()))
update.set(get<String>(VaultSchemaV1.VaultStates::lockId.name), lockId.toString())
update.set(get<Instant>(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<Vault.StateStatus>(VaultSchemaV1.VaultStates::stateStatus.name), Vault.StateStatus.UNCONSUMED)
val lockIdPredicate = criteriaBuilder.equal(get<String>(VaultSchemaV1.VaultStates::lockId.name), lockId.toString())
update.set<String>(get<String>(VaultSchemaV1.VaultStates::lockId.name), criteriaBuilder.nullLiteral(String::class.java))
update.set(get<Instant>(VaultSchemaV1.VaultStates::lockUpdateTime.name), softLockTimestamp)
update.set(get<String>(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<StateAndRef<T>> = servicesForResolution.loadStates(
statesMetadata.mapTo(LinkedHashSet()) { it.ref },
ArrayList()
)
val states: List<StateAndRef<T>> = 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 <T: ContractState> hasBeenSeen(update: Vault.Update<T>, snapshotStatesRefs: Set<StateRef>, snapshotConsumedStatesRefs: Set<StateRef>): 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)
}

View File

@ -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<K : Any, V : Any>(name: String, cacheFactory: Nam
}
}
private val backingCache = cacheFactory.buildNamed<K, Wrapper<V>>(Caffeine.newBuilder(), name)
private val backingCache = cacheFactory.buildNamed<K, Wrapper<V>>(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<K, Wrapper.Invalidated<V>>()

View File

@ -15,8 +15,7 @@ class NonInvalidatingCache<K, V> private constructor(
private companion object {
private fun <K, V> buildCache(cacheFactory: NamedCacheFactory, name: String, loadFunction: (K) -> V): LoadingCache<K, V> {
val builder = Caffeine.newBuilder()
return cacheFactory.buildNamed(builder, name, NonInvalidatingCacheLoader(loadFunction))
return cacheFactory.buildNamed(name, NonInvalidatingCacheLoader(loadFunction))
}
}

View File

@ -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<Unit> {
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<ExternalVerifierOutbound>()
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()
}
}
}
}

View File

@ -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()
}
}
}

View File

@ -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<DummySerializationSchemeException>("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<ConfigurationException>("$missingClassName was declared as a custom serialization scheme but could not " +
assertFailsWith<CordaException>("$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<ConfigurationException>("$schemeName was declared as a custom serialization scheme but does not " +
assertFailsWith<CordaException>("$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<ConfigurationException>("$schemeName was declared as a custom serialization scheme but does not " +
assertFailsWith<CordaException>("$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)
}
}
}

View File

@ -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<Currency>, 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<KeyPair>) {
cordaDB.transaction {
keys.forEach {
val persistentKey = BasicHSMKeyManagementService.PersistentKey(it.public, it.private)
session.save(persistentKey)
}
}
}
private fun saveAllIdentities(identities: List<PartyAndCertificate>) {
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<Vault.RelevancyStatus>("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<AbstractParty> = 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<AbstractParty>) {
cordaDB.transaction {
(1..statesToAdd).map { createLinearStateTransaction("A".repeat(it), parties) }.forEach {
storeTransaction(it)
createVaultStatesFromTransaction(it)
}
}
}
private fun createCommodityTransaction(amount: Amount<Issued<Commodity>>, 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<StateRef>, 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<TransactionState<ContractState>>) {
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 <T> getState(clazz: Class<T>): 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<ContractState>) {
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<VaultStateMigrationException> { 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()
}
}

View File

@ -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)

View File

@ -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)
}
/**

View File

@ -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<StateRef>?) {
// Should be called before flow is removed

View File

@ -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<SingleOwnerState> {
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)
}
}

View File

@ -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"

View File

@ -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() }))
}
}
}

View File

@ -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
}
}
}
@Suppress("ThrowsCount")
fun loadCustomSerializationScheme(className: String, classLoader: ClassLoader): SerializationScheme {
val schemeClass = try {
loadClassOfType<CustomSerializationScheme>(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())
}

View File

@ -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<NetworkParameters>
@CordaSerializable
sealed interface ExternalVerifierInbound {
data class Initialisation(
val customSerializerClassNames: Set<String>,
val serializationWhitelistClassNames: Set<String>,
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<StateRef, SerializedTransactionState>,
val checkSufficientSignatures: Boolean
) : ExternalVerifierInbound
data class PartiesResult(val parties: List<Party?>) : ExternalVerifierInbound
data class AttachmentResult(val attachment: AttachmentWithTrust?) : ExternalVerifierInbound
data class AttachmentsResult(val attachments: List<AttachmentWithTrust?>) : 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<PublicKey>) : VerifierRequest {
override fun toString(): String = "GetParty(keys=${keys.map { it.toStringShort() }}})"
}
data class GetAttachment(val id: SecureHash) : VerifierRequest
data class GetAttachments(val ids: Set<SecureHash>) : VerifierRequest
data class GetNetworkParameters(val id: SecureHash) : VerifierRequest
data class GetTrustedClassAttachment(val className: String) : VerifierRequest
}
data class VerificationResult(val result: Try<Unit>) : ExternalVerifierOutbound
}
fun DataOutputStream.writeCordaSerializable(payload: Any) {
val serialised = payload.serialize()
writeInt(serialised.size)
serialised.writeTo(this)
flush()
}
inline fun <reified T : Any> 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<T>()
}

View File

@ -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'

View File

@ -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

View File

@ -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<String>, 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<SerializeAsToken> = MutableClassToInstanceMap.create<SerializeAsToken>()
internal val cordappServices: MutableClassToInstanceMap<SerializeAsToken> = MutableClassToInstanceMap.create()
internal val cordappTelemetryComponents: MutableClassToInstanceMap<TelemetryComponent> = MutableClassToInstanceMap.create<TelemetryComponent>()
private val cordappTelemetryComponents: MutableClassToInstanceMap<TelemetryComponent> = MutableClassToInstanceMap.create()
override fun <T : SerializeAsToken> cordaService(type: Class<T>): 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<StateRef>) = servicesForResolution.loadStates(stateRefs)
override fun loadState(stateRef: StateRef): TransactionState<ContractState> {
return getRequiredTransaction(stateRef.txhash).resolveBaseTransaction(this).outputs[stateRef.index]
}
override fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> = 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 <T : SerializeAsToken> createMockCordaService(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T): T {
class MockAppServiceHubImpl<out T : SerializeAsToken>(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<StateRef>): Set<StateAndRef<ContractState>> = mockServices.loadStates(stateRefs)
}
@CordaInternal
internal class MockAppServiceHubImpl<out T : SerializeAsToken>(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 <T : SerializeAsToken> 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 <T : SerializeAsToken> createMockCordaService(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T): T {
return MockServices.MockAppServiceHubImpl(serviceHub, serviceConstructor).serviceInstance
}

View File

@ -352,7 +352,6 @@ open class InternalMockNetwork(cordappPackages: List<String> = 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

View File

@ -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<ContractState>(stateRef)
override fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> {
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<ContractState>(stateRef)
@Suppress("DEPRECATION") // Will remove when feature finalised.
transactionBuilder.addReferenceState(StateAndRef(state, stateRef).referenced())
}

35
verifier/build.gradle Normal file
View File

@ -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"
)
}
}

View File

@ -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<StateRef, SerializedTransactionState>
) : VerificationSupport {
override val isResolutionLazy: Boolean get() = false
override fun getParties(keys: Collection<PublicKey>): List<Party?> = externalVerifier.getParties(keys)
override fun getAttachment(id: SecureHash): Attachment? = externalVerifier.getAttachment(id)?.attachment
override fun getAttachments(ids: Collection<SecureHash>): List<Attachment?> {
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<SecureHash>): Set<SecureHash> {
return externalVerifier.fixupAttachmentIds(attachmentIds)
}
}

View File

@ -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<PublicKey, Party>
private val attachments: OptionalCache<SecureHash, AttachmentWithTrust>
private val networkParametersMap: OptionalCache<SecureHash, NetworkParameters>
private val trustedClassAttachments: OptionalCache<String, SecureHash>
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<VerificationRequest>()
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<Initialisation>()
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<PublicKey>): List<Party?> {
return parties.retrieveAll(keys) {
request<PartiesResult>(GetParties(it)).parties
}
}
fun getAttachment(id: SecureHash): AttachmentWithTrust? {
return attachments.retrieve(id) {
request<AttachmentResult>(GetAttachment(id)).attachment
}
}
fun getAttachments(ids: Collection<SecureHash>): List<AttachmentWithTrust?> {
return attachments.retrieveAll(ids) {
request<AttachmentsResult>(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<TrustedClassAttachmentResult>(GetTrustedClassAttachment(className)).id
}
return attachmentId?.let(::getAttachment)?.attachment
}
fun getNetworkParameters(id: SecureHash?): NetworkParameters? {
return if (id == null) {
currentNetworkParameters
} else {
networkParametersMap.retrieve(id) {
request<NetworkParametersResult>(GetNetworkParameters(id)).networkParameters
}
}
}
fun fixupAttachmentIds(attachmentIds: Collection<SecureHash>): Set<SecureHash> = attachmentFixups.fixupAttachmentIds(attachmentIds)
private inline fun <reified T : Any> request(request: Any): T {
log.debug { "Sending request to node: $request" }
toNode.writeCordaSerializable(request)
val response = fromNode.readCordaSerializable<T>()
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<SerializationFactoryCacheKey, SerializerFactory>(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 <reified T> Set<String>?.load(classLoader: ClassLoader?): Set<T> {
return this?.mapToSet { loadClassOfType<T>(it, classLoader = classLoader).kotlin.objectOrNewInstance() } ?: emptySet()
}
}
}
}
private typealias OptionalCache<K, V> = Cache<K, Optional<V>>
private fun <K : Any, V : Any> OptionalCache<K, V>.retrieve(key: K, request: () -> V?): V? {
return get(key) { Optional.ofNullable(request()) }!!.orElse(null)
}
@Suppress("UNCHECKED_CAST")
private fun <K : Any, V : Any> OptionalCache<K, V>.retrieveAll(keys: Collection<K>, request: (Set<K>) -> List<V?>): List<V?> {
val optionalResults = getAll(keys) {
val missingKeys = if (it is Set<*>) it as Set<K> 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) }
}

View File

@ -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 <K, V> buildNamed(caffeine: Caffeine<in K, in V>, name: String): Cache<K, V> {
checkCacheName(name)
return configure(caffeine, name).build()
}
override fun <K, V> buildNamed(caffeine: Caffeine<in K, in V>, name: String, loader: CacheLoader<K, V>): LoadingCache<K, V> {
checkCacheName(name)
return configure(caffeine, name).build(loader)
}
private fun<K, V> configure(caffeine: Caffeine<in K, in V>, name: String): Caffeine<in K, in V> {
return when (name) {
"AttachmentsClassLoader_cache" -> caffeine.maximumSize(32)
"ExternalVerifier_parties" -> caffeine.maximumSize(DEFAULT_CACHE_SIZE)
"ExternalVerifier_attachments" -> caffeine.maximumSize(DEFAULT_CACHE_SIZE)
"ExternalVerifier_networkParameters" -> caffeine.maximumSize(DEFAULT_CACHE_SIZE)
"ExternalVerifier_trustedClassAttachments" -> caffeine.maximumSize(DEFAULT_CACHE_SIZE)
else -> throw IllegalArgumentException("Unexpected cache name $name. Did you add a new cache?")
}
}
}

View File

@ -0,0 +1,46 @@
package net.corda.verifier
import net.corda.core.utilities.loggerFor
import org.slf4j.bridge.SLF4JBridgeHandler
import java.io.DataInputStream
import java.io.DataOutputStream
import java.net.Socket
import java.nio.file.Path
import kotlin.io.path.div
import kotlin.system.exitProcess
@Suppress("TooGenericExceptionCaught")
object Main {
private val log = loggerFor<Main>()
@JvmStatic
fun main(args: Array<String>) {
val port = args[0].toInt()
val loggingLevel = args[0]
val baseDirectory = Path.of("").toAbsolutePath()
initLogging(baseDirectory, loggingLevel)
log.info("External verifier started; PID ${ProcessHandle.current().pid()}")
log.info("Node base directory: $baseDirectory")
try {
val socket = Socket("localhost", port)
log.info("Connected to node on port $port")
val fromNode = DataInputStream(socket.getInputStream())
val toNode = DataOutputStream(socket.getOutputStream())
ExternalVerifier(baseDirectory, fromNode, toNode).run()
} catch (t: Throwable) {
log.error("Unexpected error which has terminated the verifier", t)
exitProcess(1)
}
}
private fun initLogging(baseDirectory: Path, loggingLevel: String) {
System.setProperty("logPath", (baseDirectory / "logs").toString())
System.setProperty("defaultLogLevel", loggingLevel)
SLF4JBridgeHandler.removeHandlersForRootLogger() // The default j.u.l config adds a ConsoleHandler.
SLF4JBridgeHandler.install()
}
}

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info" shutdownHook="disable">
<Properties>
<Property name="log_path">${sys:logPath:-logs}</Property>
<Property name="log_name">verifier-${hostName}</Property>
<Property name="archive">${log_path}/archive</Property>
<Property name="default_log_level">${sys:defaultLogLevel:-info}</Property>
</Properties>
<Appenders>
<!-- Will generate up to 500 log files for a given day. Adjust this number according to the available storage.
During every rollover it will delete those that are older than 60 days, but keep the most recent 10 GB -->
<RollingRandomAccessFile name="RollingFile-Appender"
fileName="${log_path}/${log_name}.log"
filePattern="${archive}/${log_name}.%date{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="[%-5level] %date{ISO8601}{UTC}Z [%t] %c{2}.%method - %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100MB"/>
</Policies>
<DefaultRolloverStrategy min="1" max="500">
<Delete basePath="${archive}" maxDepth="1">
<IfFileName glob="${log_name}*.log.gz"/>
<IfLastModified age="60d">
<IfAny>
<IfAccumulatedFileSize exceeds="10 GB"/>
</IfAny>
</IfLastModified>
</Delete>
</DefaultRolloverStrategy>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<Root level="${default_log_level}">
<AppenderRef ref="RollingFile-Appender"/>
</Root>
</Loggers>
</Configuration>