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 @NotNull
public abstract net.corda.core.node.services.TelemetryService getTelemetryService() public abstract net.corda.core.node.services.TelemetryService getTelemetryService()
@NotNull @NotNull
public abstract net.corda.core.node.services.TransactionVerifierService getTransactionVerifierService()
@NotNull
public abstract net.corda.core.node.services.TransactionStorage getValidatedTransactions() public abstract net.corda.core.node.services.TransactionStorage getValidatedTransactions()
@NotNull @NotNull
public abstract net.corda.core.node.services.VaultService getVaultService() public abstract net.corda.core.node.services.VaultService getVaultService()
@ -5087,11 +5085,6 @@ public interface net.corda.core.node.services.TransactionStorage
@NotNull @NotNull
public abstract net.corda.core.concurrent.CordaFuture trackTransaction(net.corda.core.crypto.SecureHash) 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 @CordaSerializable
public final class net.corda.core.node.services.UnknownAnonymousPartyException extends net.corda.core.CordaException public final class net.corda.core.node.services.UnknownAnonymousPartyException extends net.corda.core.CordaException
public <init>(String) public <init>(String)
@ -7846,8 +7839,6 @@ public class net.corda.core.transactions.TransactionBuilder extends java.lang.Ob
@NotNull @NotNull
public final net.corda.core.transactions.LedgerTransaction toLedgerTransaction(net.corda.core.node.ServiceHub) public final net.corda.core.transactions.LedgerTransaction toLedgerTransaction(net.corda.core.node.ServiceHub)
@NotNull @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) 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 @NotNull
public final net.corda.core.transactions.WireTransaction toWireTransaction(net.corda.core.node.ServicesForResolution) 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 @NotNull
public net.corda.core.internal.telemetry.TelemetryServiceImpl getTelemetryService() public net.corda.core.internal.telemetry.TelemetryServiceImpl getTelemetryService()
@NotNull @NotNull
public net.corda.core.node.services.TransactionVerifierService getTransactionVerifierService()
@NotNull
public net.corda.core.node.services.TransactionStorage getValidatedTransactions() public net.corda.core.node.services.TransactionStorage getValidatedTransactions()
@NotNull @NotNull
public net.corda.core.node.services.VaultService getVaultService() 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.jvm' apply false
id 'org.jetbrains.kotlin.plugin.allopen' apply false id 'org.jetbrains.kotlin.plugin.allopen' apply false
id 'org.jetbrains.kotlin.plugin.jpa' 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 "org.ajoberstar.grgit" version "4.0.0"
id 'corda.root-publish' id 'corda.root-publish'
id "org.jetbrains.dokka" version "1.8.20" 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' apply plugin: 'corda.common-publishing'
dependencies { dependencies {
implementation project(':core') api project(':core')
implementation project(':serialization') implementation project(':serialization')
// Jackson and its plugins: parsing to/from JSON and other textual formats. // Jackson and its plugins: parsing to/from JSON and other textual formats.
@ -27,6 +28,7 @@ dependencies {
testImplementation project(':test-common') testImplementation project(':test-common')
testImplementation project(':core-test-utils') testImplementation project(':core-test-utils')
testImplementation project(':test-utils') testImplementation project(':test-utils')
testImplementation project(":node-driver")
testImplementation project(path: ':core', configuration: 'testArtifacts') testImplementation project(path: ':core', configuration: 'testArtifacts')
testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}" 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.databind.node.TextNode
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.convertValue 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.childrenAs
import net.corda.client.jackson.internal.valueAs 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.cordapp.CordappProvider
import net.corda.core.crypto.*
import net.corda.core.crypto.CompositeKey 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.crypto.PartialMerkleTree.PartialTree
import net.corda.core.identity.* import net.corda.core.crypto.SecureHash
import net.corda.core.internal.AbstractAttachment 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.internal.DigitalSignatureWithCert
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub 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.CordaSerializable
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize 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.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction 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.finance.USD
import net.corda.nodeapi.internal.crypto.x509Certificates import net.corda.nodeapi.internal.crypto.x509Certificates
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.* import net.corda.testing.core.ALICE_NAME
import net.corda.coretesting.internal.createNodeInfoAndSigned import net.corda.testing.core.BOB_NAME
import net.corda.coretesting.internal.rigorousMock 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.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Before import org.junit.Before
@ -54,15 +76,22 @@ import org.junit.jupiter.api.TestFactory
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.Parameterized import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters import org.junit.runners.Parameterized.Parameters
import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever
import java.math.BigInteger import java.math.BigInteger
import java.nio.charset.StandardCharsets.UTF_8 import java.nio.charset.StandardCharsets.UTF_8
import java.security.PublicKey import java.security.PublicKey
import java.security.cert.CertPath import java.security.cert.CertPath
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.time.Instant 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 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) @RunWith(Parameterized::class)
class JacksonSupportTest(@Suppress("unused") private val name: String, factory: JsonFactory) { class JacksonSupportTest(@Suppress("unused") private val name: String, factory: JsonFactory) {
@ -90,23 +119,12 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
@Before @Before
fun setup() { fun setup() {
val unsignedAttachment = object : AbstractAttachment({ byteArrayOf() }, "test") { services = MockServices(
override val id: SecureHash get() = throw UnsupportedOperationException() listOf("net.corda.testing.contracts"),
} MINI_CORP,
testNetworkParameters(minimumPlatformVersion = 4)
val attachments = rigorousMock<AttachmentStorage>().also { )
doReturn(unsignedAttachment).whenever(it).openAttachment(any())
}
services = rigorousMock()
cordappProvider = rigorousMock() 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) @Test(timeout=300_000)
@ -263,17 +281,6 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
@Test(timeout=300_000) @Test(timeout=300_000)
fun `SignedTransaction (WireTransaction)`() { fun `SignedTransaction (WireTransaction)`() {
val attachmentId = SecureHash.randomSHA256() 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( val wtx = TransactionBuilder(
notary = DUMMY_NOTARY, 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.ContractState
import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateAndRef
import net.corda.core.flows.ContractUpgradeFlow import net.corda.core.flows.ContractUpgradeFlow
import net.corda.core.internal.getRequiredTransaction
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.transactions.ContractUpgradeLedgerTransaction import net.corda.core.transactions.ContractUpgradeLedgerTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
@ -120,8 +121,7 @@ class ContractUpgradeFlowRPCTest : WithContracts, WithFinality {
isUpgrade<FROM, TO>()) isUpgrade<FROM, TO>())
private fun TestStartedNode.getContractUpgradeTransaction(state: StateAndRef<ContractState>) = private fun TestStartedNode.getContractUpgradeTransaction(state: StateAndRef<ContractState>) =
services.validatedTransactions.getTransaction(state.ref.txhash)!! services.getRequiredTransaction(state.ref.txhash).resolveContractUpgradeTransaction(services)
.resolveContractUpgradeTransaction(services)
private inline fun <reified FROM : Any, reified TO : Any> isUpgrade() = private inline fun <reified FROM : Any, reified TO : Any> isUpgrade() =
isUpgradeFrom<FROM>() and isUpgradeTo<TO>() isUpgradeFrom<FROM>() and isUpgradeTo<TO>()

View File

@ -1,32 +1,55 @@
package net.corda.coretests.flows 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 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.flows.UnexpectedFlowEndException
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.internal.Emoji 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.ContractUpgradeLedgerTransaction
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow 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.USD
import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.Cash import net.corda.finance.contracts.asset.Cash
import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.`issued by`
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyContractV2 import net.corda.testing.contracts.DummyContractV2
import net.corda.testing.contracts.DummyContractV3 import net.corda.testing.contracts.DummyContractV3
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity import net.corda.testing.core.singleIdentity
import net.corda.coretesting.internal.matchers.flow.willReturn import net.corda.testing.node.internal.DUMMY_CONTRACTS_CORDAPP
import net.corda.coretesting.internal.matchers.flow.willThrow import net.corda.testing.node.internal.FINANCE_CONTRACTS_CORDAPP
import net.corda.testing.node.internal.* 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.AfterClass
import org.junit.Ignore import org.junit.Ignore
import org.junit.Test import org.junit.Test
import java.util.* import java.util.Currency
@Ignore("TODO JDK17: class cast exception") @Ignore("TODO JDK17: class cast exception")
class ContractUpgradeFlowTest : WithContracts, WithFinality { class ContractUpgradeFlowTest : WithContracts, WithFinality {
@ -161,7 +184,7 @@ class ContractUpgradeFlowTest : WithContracts, WithFinality {
@BelongsToContract(CashV2::class) @BelongsToContract(CashV2::class)
data class State(override val amount: Amount<Issued<Currency>>, val owners: List<AbstractParty>) : FungibleAsset<Currency> { data class State(override val amount: Amount<Issued<Currency>>, val owners: List<AbstractParty>) : FungibleAsset<Currency> {
override val owner: AbstractParty = owners.first() 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 val participants = owners
override fun withNewOwnerAndAmount(newAmount: Amount<Issued<Currency>>, newOwner: AbstractParty) = copy(amount = amount.copy(newAmount.quantity), owners = listOf(newOwner)) 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>()) isUpgrade<FROM, TO>())
private fun TestStartedNode.getContractUpgradeTransaction(state: StateAndRef<ContractState>) = private fun TestStartedNode.getContractUpgradeTransaction(state: StateAndRef<ContractState>) =
services.validatedTransactions.getTransaction(state.ref.txhash)!! services.getRequiredTransaction(state.ref.txhash).resolveContractUpgradeTransaction(services)
.resolveContractUpgradeTransaction(services)
private inline fun <reified FROM : Any, reified TO : Any> isUpgrade() = private inline fun <reified FROM : Any, reified TO : Any> isUpgrade() =
isUpgradeFrom<FROM>() and isUpgradeTo<TO>() 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.flows.StartableByRPC
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.FlowStateMachineHandle import net.corda.core.internal.FlowStateMachineHandle
import net.corda.core.internal.getRequiredTransaction
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.FlowHandle import net.corda.core.messaging.FlowHandle
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
@ -26,9 +27,7 @@ interface WithFinality : WithMockNet {
return startFlowAndRunNetwork(FinalityInvoker(stx, recipients.toSet(), emptySet())) return startFlowAndRunNetwork(FinalityInvoker(stx, recipients.toSet(), emptySet()))
} }
fun TestStartedNode.getValidatedTransaction(stx: SignedTransaction): SignedTransaction { fun TestStartedNode.getValidatedTransaction(stx: SignedTransaction): SignedTransaction = services.getRequiredTransaction(stx.id)
return services.validatedTransactions.getTransaction(stx.id)!!
}
fun CordaRPCOps.finalise(stx: SignedTransaction, vararg recipients: Party): FlowHandle<SignedTransaction> { fun CordaRPCOps.finalise(stx: SignedTransaction, vararg recipients: Party): FlowHandle<SignedTransaction> {
return startFlow(WithFinality::FinalityInvoker, stx, recipients.toSet(), emptySet()).andRunNetwork() 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 package net.corda.coretests.transactions
import net.corda.core.contracts.Command import net.corda.core.contracts.Command
import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.HashAttachmentConstraint import net.corda.core.contracts.HashAttachmentConstraint
import net.corda.core.contracts.PrivacySalt import net.corda.core.contracts.PrivacySalt
import net.corda.core.contracts.SignatureAttachmentConstraint 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.TimeWindow
import net.corda.core.contracts.TransactionState import net.corda.core.contracts.TransactionState
import net.corda.core.contracts.TransactionVerificationException.UnsupportedHashTypeException 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.DigestService
import net.corda.core.crypto.SecureHash 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.HashAgility
import net.corda.core.internal.PLATFORM_VERSION import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.digestService import net.corda.core.internal.digestService
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.ZoneVersionTooLowException import net.corda.core.node.ZoneVersionTooLowException
import net.corda.core.node.services.AttachmentStorage import net.corda.core.serialization.internal._driverSerializationEnv
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.NetworkParametersService
import net.corda.core.serialization.serialize
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.coretesting.internal.rigorousMock
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyState import net.corda.testing.contracts.DummyState
import net.corda.testing.core.ALICE_NAME 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.DUMMY_NOTARY_NAME
import net.corda.testing.core.DummyCommandData import net.corda.testing.core.DummyCommandData
import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity 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.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Ignore import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test 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 java.time.Instant
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
@ -58,33 +45,12 @@ class TransactionBuilderTest {
val testSerialization = SerializationEnvironmentRule() val testSerialization = SerializationEnvironmentRule()
private val notary = TestIdentity(DUMMY_NOTARY_NAME).party private val notary = TestIdentity(DUMMY_NOTARY_NAME).party
private val services = rigorousMock<ServicesForResolution>() private val services = MockServices(
private val contractAttachmentId = SecureHash.randomSHA256() listOf("net.corda.testing.contracts"),
private val attachments = rigorousMock<AttachmentStorage>() TestIdentity(ALICE_NAME),
private val networkParametersService = mock<NetworkParametersService>() testNetworkParameters(minimumPlatformVersion = PLATFORM_VERSION)
)
@Before private val contractAttachmentId = services.attachments.getLatestContractAttachments(DummyContract.PROGRAM_ID)[0]
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")
}
@Test(timeout=300_000) @Test(timeout=300_000)
fun `bare minimum issuance tx`() { fun `bare minimum issuance tx`() {
@ -100,13 +66,11 @@ class TransactionBuilderTest {
val wtx = builder.toWireTransaction(services) val wtx = builder.toWireTransaction(services)
assertThat(wtx.outputs).containsOnly(outputState) assertThat(wtx.outputs).containsOnly(outputState)
assertThat(wtx.commands).containsOnly(Command(DummyCommandData, notary.owningKey)) 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) @Test(timeout=300_000)
fun `automatic hash constraint`() { fun `automatic hash constraint`() {
doReturn(unsignedAttachment).whenever(attachments).openAttachment(contractAttachmentId)
val outputState = TransactionState(data = DummyState(), contract = DummyContract.PROGRAM_ID, notary = notary) val outputState = TransactionState(data = DummyState(), contract = DummyContract.PROGRAM_ID, notary = notary)
val builder = TransactionBuilder() val builder = TransactionBuilder()
.addOutputState(outputState) .addOutputState(outputState)
@ -117,8 +81,6 @@ class TransactionBuilderTest {
@Test(timeout=300_000) @Test(timeout=300_000)
fun `reference states`() { fun `reference states`() {
doReturn(unsignedAttachment).whenever(attachments).openAttachment(contractAttachmentId)
val referenceState = TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary) val referenceState = TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary)
val referenceStateRef = StateRef(SecureHash.randomSHA256(), 1) val referenceStateRef = StateRef(SecureHash.randomSHA256(), 1)
val builder = TransactionBuilder(notary) val builder = TransactionBuilder(notary)
@ -126,54 +88,55 @@ class TransactionBuilderTest {
.addOutputState(TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary)) .addOutputState(TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary))
.addCommand(DummyCommandData, notary.owningKey) .addCommand(DummyCommandData, notary.owningKey)
doReturn(testNetworkParameters(minimumPlatformVersion = 3)).whenever(services).networkParameters with(testNetworkParameters(minimumPlatformVersion = 3)) {
assertThatThrownBy { builder.toWireTransaction(services) } val services = MockServices(listOf("net.corda.testing.contracts"), TestIdentity(ALICE_NAME), this)
.isInstanceOf(ZoneVersionTooLowException::class.java) assertThatThrownBy { builder.toWireTransaction(services) }
.hasMessageContaining("Reference states") .isInstanceOf(ZoneVersionTooLowException::class.java)
.hasMessageContaining("Reference states")
}
doReturn(testNetworkParameters(minimumPlatformVersion = 4)).whenever(services).networkParameters with(testNetworkParameters(minimumPlatformVersion = 4)) {
doReturn(referenceState).whenever(services).loadState(referenceStateRef) val services = MockServices(listOf("net.corda.testing.contracts"), TestIdentity(ALICE_NAME), this)
val wtx = builder.toWireTransaction(services) val wtx = builder.toWireTransaction(services)
assertThat(wtx.references).containsOnly(referenceStateRef) assertThat(wtx.references).containsOnly(referenceStateRef)
}
} }
@Test(timeout=300_000) @Test(timeout=300_000)
fun `automatic signature constraint`() { fun `automatic signature constraint`() {
val aliceParty = TestIdentity(ALICE_NAME).party // We need to use a MockNetwork so that we can create a signed attachment. However, SerializationEnvironmentRule and MockNetwork
val bobParty = TestIdentity(BOB_NAME).party // don't work well together, so we temporarily clear out the driverSerializationEnv for this test.
val compositeKey = CompositeKey.Builder().addKeys(aliceParty.owningKey, bobParty.owningKey).build() val driverSerializationEnv = _driverSerializationEnv.get()
val expectedConstraint = SignatureAttachmentConstraint(compositeKey) _driverSerializationEnv.set(null)
val signedAttachment = signedAttachment(aliceParty, bobParty) val mockNetwork = MockNetwork(
MockNetworkParameters(
networkParameters = testNetworkParameters(minimumPlatformVersion = PLATFORM_VERSION),
cordappsForAllNodes = listOf(cordappWithPackages("net.corda.testing.contracts").signed())
)
)
assertTrue(expectedConstraint.isSatisfiedBy(signedAttachment)) try {
assertFalse(expectedConstraint.isSatisfiedBy(unsignedAttachment)) val services = mockNetwork.notaryNodes[0].services
doReturn(attachments).whenever(services).attachments val attachment = services.attachments.openAttachment(services.attachments.getLatestContractAttachments(DummyContract.PROGRAM_ID)[0])
doReturn(signedAttachment).whenever(attachments).openAttachment(contractAttachmentId) val attachmentSigner = attachment!!.signerKeys.single()
doReturn(listOf(contractAttachmentId)).whenever(attachments)
.getLatestContractAttachments("net.corda.testing.contracts.DummyContract")
val outputState = TransactionState(data = DummyState(), contract = DummyContract.PROGRAM_ID, notary = notary) val expectedConstraint = SignatureAttachmentConstraint(attachmentSigner)
val builder = TransactionBuilder() assertTrue(expectedConstraint.isSatisfiedBy(attachment))
.addOutputState(outputState)
.addCommand(DummyCommandData, notary.owningKey)
val wtx = builder.toWireTransaction(services)
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) @Test(timeout=300_000)
fun `list accessors are mutable copies`() { fun `list accessors are mutable copies`() {
val inputState1 = TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary) val inputState1 = TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary)

View File

@ -40,9 +40,6 @@ dependencies {
// Hamkrest, for fluent, composable matchers // Hamkrest, for fluent, composable matchers
testImplementation "com.natpryce:hamkrest:$hamkrest_version" 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 // SLF4J: commons-logging bindings for a SLF4J back end
implementation "org.slf4j:jcl-over-slf4j:$slf4j_version" implementation "org.slf4j:jcl-over-slf4j:$slf4j_version"
implementation "org.slf4j:slf4j-api:$slf4j_version" implementation "org.slf4j:slf4j-api:$slf4j_version"
@ -66,10 +63,7 @@ dependencies {
// Bouncy castle support needed for X509 certificate manipulation // Bouncy castle support needed for X509 certificate manipulation
implementation "org.bouncycastle:bcprov-jdk18on:${bouncycastle_version}" implementation "org.bouncycastle:bcprov-jdk18on:${bouncycastle_version}"
implementation "org.bouncycastle:bcpkix-jdk18on:${bouncycastle_version}" testImplementation "org.bouncycastle:bcpkix-jdk18on:${bouncycastle_version}"
// JPA 2.2 annotations.
implementation "javax.persistence:javax.persistence-api:2.2"
// required to use @Type annotation // required to use @Type annotation
implementation "org.hibernate:hibernate-core:$hibernate_version" implementation "org.hibernate:hibernate-core:$hibernate_version"

View File

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

View File

@ -1,10 +1,10 @@
package net.corda.core.crypto.internal package net.corda.core.crypto.internal
import net.corda.core.crypto.DigestAlgorithm import net.corda.core.crypto.DigestAlgorithm
import java.lang.reflect.Constructor import net.corda.core.internal.loadClassOfType
import java.security.MessageDigest import java.security.MessageDigest
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import java.util.* import java.util.Collections
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
sealed class DigestAlgorithmFactory { sealed class DigestAlgorithmFactory {
@ -28,9 +28,8 @@ sealed class DigestAlgorithmFactory {
} }
private class CustomAlgorithmFactory(className: String) : DigestAlgorithmFactory() { private class CustomAlgorithmFactory(className: String) : DigestAlgorithmFactory() {
val constructor: Constructor<out DigestAlgorithm> = Class.forName(className, false, javaClass.classLoader) private val constructor = loadClassOfType<DigestAlgorithm>(className, false, javaClass.classLoader).getConstructor()
.asSubclass(DigestAlgorithm::class.java)
.getConstructor()
override val algorithm: String = constructor.newInstance().algorithm override val algorithm: String = constructor.newInstance().algorithm
override fun create(): DigestAlgorithm { 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.PlatformVersionSwitches
import net.corda.core.internal.RetrieveAnyTransactionPayload import net.corda.core.internal.RetrieveAnyTransactionPayload
import net.corda.core.internal.ServiceHubCoreInternal 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.internal.readFully
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.node.StatesToRecord 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 SignedTransaction -> TransactionAuthorisationFilter().addAuthorised(getInputTransactions(payload))
is RetrieveAnyTransactionPayload -> TransactionAuthorisationFilter(acceptAll = true) is RetrieveAnyTransactionPayload -> TransactionAuthorisationFilter(acceptAll = true)
is List<*> -> TransactionAuthorisationFilter().addAuthorised(payload.flatMap { someObject -> is List<*> -> TransactionAuthorisationFilter().addAuthorised(payload.flatMap { someObject ->
if (someObject is StateAndRef<*>) { when (someObject) {
getInputTransactions(serviceHub.validatedTransactions.getTransaction(someObject.ref.txhash)!!) + someObject.ref.txhash is StateAndRef<*> -> getInputTransactions(serviceHub.getRequiredTransaction(someObject.ref.txhash)) + someObject.ref.txhash
} is NamedByHash -> setOf(someObject.id)
else if (someObject is NamedByHash) { else -> throw Exception("Unknown payload type: ${someObject!!::class.java} ?")
setOf(someObject.id)
} else {
throw Exception("Unknown payload type: ${someObject!!::class.java} ?")
} }
}.toSet()) }.toSet())
else -> throw Exception("Unknown payload type: ${payload::class.java} ?") else -> throw Exception("Unknown payload type: ${payload::class.java} ?")
@ -308,7 +307,7 @@ open class DataVendingFlow(val otherSessions: Set<FlowSession>, val payload: Any
@Suspendable @Suspendable
private fun getInputTransactions(tx: SignedTransaction): Set<SecureHash> { 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) { 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>, fun <T: Any> createInstancesOfClassesImplementing(classloader: ClassLoader, clazz: Class<T>,
classVersionRange: IntRange? = null): Set<T> { classVersionRange: IntRange? = null): Set<T> {
return getNamesOfClassesImplementing(classloader, clazz, classVersionRange) return getNamesOfClassesImplementing(classloader, clazz, classVersionRange)
.map { Class.forName(it, false, classloader).asSubclass(clazz) } .mapToSet { loadClassOfType(clazz, it, false, classloader).kotlin.objectOrNewInstance() }
.mapTo(LinkedHashSet()) { it.kotlin.objectOrNewInstance() }
} }
/** /**
@ -56,10 +55,23 @@ fun <T: Any> getNamesOfClassesImplementing(classloader: ClassLoader, clazz: Clas
} }
result.getClassesImplementing(clazz.name) result.getClassesImplementing(clazz.name)
.filterNot(ClassInfo::isAbstract) .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 { fun <T: Any?> executeWithThreadContextClassLoader(classloader: ClassLoader, fn: () -> T): T {
val threadClassLoader = Thread.currentThread().contextClassLoader val threadClassLoader = Thread.currentThread().contextClassLoader
try { try {
@ -68,5 +80,4 @@ fun <T: Any?> executeWithThreadContextClassLoader(classloader: ClassLoader, fn:
} finally { } finally {
Thread.currentThread().contextClassLoader = threadClassLoader Thread.currentThread().contextClassLoader = threadClassLoader
} }
} }

View File

@ -1,29 +1,22 @@
@file:Suppress("TooManyFunctions") @file:Suppress("TooManyFunctions")
package net.corda.core.internal package net.corda.core.internal
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractClassName 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.DataVendingFlow
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.node.ZoneVersionTooLowException 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.CordaSerializable
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import org.slf4j.MDC import org.slf4j.MDC
import java.security.PublicKey import java.security.PublicKey
import java.util.jar.JarInputStream
// *Internal* Corda-specific utilities. // *Internal* Corda-specific utilities.
@ -68,11 +61,6 @@ fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serial
return toWireTransactionWithContext(services, serializationContext) 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. */ /** Checks if this flow is an idempotent flow. */
fun Class<out FlowLogic<*>>.isIdempotentFlow(): Boolean { fun Class<out FlowLogic<*>>.isIdempotentFlow(): Boolean {
return IdempotentFlow::class.java.isAssignableFrom(this) 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 packages.all { outer -> packages.none { inner -> inner != outer && inner.startsWith("$outer.") } }
} }
/** fun ServiceHub.getRequiredTransaction(txhash: SecureHash): SignedTransaction {
* @return The set of [AttachmentId]s after the node's fix-up rules have been applied to [attachmentIds]. return validatedTransactions.getTransaction(txhash) ?: throw TransactionResolutionException(txhash)
*/
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
}
}
} }

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.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream
import java.lang.reflect.Field import java.lang.reflect.Field
import java.lang.reflect.Member import java.lang.reflect.Member
import java.lang.reflect.Modifier 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. */ /** Returns the index of the given item or throws [IllegalArgumentException] if not found. */
fun <T> List<T>.indexOfOrThrow(item: T): Int { fun <T> List<T>.indexOfOrThrow(item: T): Int {
val i = indexOf(item) val i = indexOf(item)
require(i != -1){"No such element"} require(i != -1) { "No such element" }
return i 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) fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.copy(this, target, *options)
/** Same as [InputStream.readBytes] but also closes the stream. */ /** 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() 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) + "" fun String.abbreviate(maxWidth: Int): String = if (length <= maxWidth) this else take(maxWidth - 1) + ""
/** Return the sum of an Iterable of [BigDecimal]s. */ /** 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() 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> { fun <T : Any> SerializedBytes<Any>.checkPayloadIs(type: Class<T>): UntrustworthyData<T> {
val payloadData: T = try { val payloadData: T = try {
val serializer = SerializationDefaults.SERIALIZATION_FACTORY val serializer = SerializationDefaults.SERIALIZATION_FACTORY
@ -563,6 +575,10 @@ fun <K, V> MutableMap<K, V>.toSynchronised(): MutableMap<K, V> = Collections.syn
/** @see Collections.synchronizedSet */ /** @see Collections.synchronizedSet */
fun <E> MutableSet<E>.toSynchronised(): MutableSet<E> = Collections.synchronizedSet(this) 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. * 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]. * 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. * Allow extra functionality to be injected to our caches.
*/ */
interface NamedCacheFactory { 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 * 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. * the name can be used to create a file name or a metric name.
*/ */
fun checkCacheName(name: String) { fun checkCacheName(name: String) {
require(!name.isBlank()){"Name must not be empty or only whitespace"} require(name.isNotBlank()) { "Name must not be empty or only whitespace" }
require(allowedChars.matches(name)){"Invalid characters in cache name"} 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(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> 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.flows.TransactionMetadata
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.notary.NotaryService 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.node.StatesToRecord
import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import java.util.concurrent.ExecutorService 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. // 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 externalOperationExecutor: ExecutorService
val attachmentTrustCalculator: AttachmentTrustCalculator
/** /**
* Optional `NotaryService` which will be `null` for all non-Notary nodes. * Optional `NotaryService` which will be `null` for all non-Notary nodes.
*/ */
@ -26,8 +22,6 @@ interface ServiceHubCoreInternal : ServiceHub {
fun createTransactionsResolver(flow: ResolveTransactionsFlow): TransactionsResolver fun createTransactionsResolver(flow: ResolveTransactionsFlow): TransactionsResolver
val attachmentsClassLoaderCache: AttachmentsClassLoaderCache
/** /**
* Stores [SignedTransaction] and participant signatures without the notary signature in the local transaction storage, * Stores [SignedTransaction] and participant signatures without the notary signature in the local transaction storage,
* inclusive of flow recovery metadata. * inclusive of flow recovery metadata.

View File

@ -172,11 +172,13 @@ fun createComponentGroups(inputs: List<StateRef>,
return componentGroupMap return componentGroupMap
} }
typealias SerializedTransactionState = SerializedBytes<TransactionState<ContractState>>
/** /**
* A SerializedStateAndRef is a pair (BinaryStateRepresentation, StateRef). * A SerializedStateAndRef is a pair (BinaryStateRepresentation, StateRef).
* The [serializedState] is the actual component from the original wire transaction. * 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(factory: SerializationFactory, context: SerializationContext) = StateAndRef(serializedState.deserialize(factory, context), ref)
fun toStateAndRef(): StateAndRef<ContractState> { fun toStateAndRef(): StateAndRef<ContractState> {
val factory = SerializationFactory.defaultFactory 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.Contract
import net.corda.core.contracts.ContractAttachment import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.ContractClassName 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.contracts.TransactionVerificationException.TransactionRequiredContractUnspecifiedException
import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.SecureHash 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.rules.StateContractValidationEnforcementRule
import net.corda.core.internal.warnContractWithoutConstraintPropagation
import net.corda.core.internal.warnOnce
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import java.util.function.Function import java.util.function.Function
import java.util.function.Supplier 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 { interface Verifier {
/** /**
@ -142,10 +145,12 @@ private class Validator(private val ltx: LedgerTransaction, private val transact
*/ */
@Suppress("ThrowsCount") @Suppress("ThrowsCount")
private fun getUniqueContractAttachmentsByContract(): Map<ContractClassName, ContractAttachment> { 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. // 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. // For each attachment this finds all the relevant state contracts that it provides.
// And then maps them to the attachment. // And then maps them to the attachment.
@ -393,7 +398,7 @@ private class Validator(private val ltx: LedgerTransaction, private val transact
@Suppress("NestedBlockDepth", "MagicNumber") @Suppress("NestedBlockDepth", "MagicNumber")
private fun verifyConstraints(contractAttachmentsByContract: Map<ContractClassName, ContractAttachment>) { private fun verifyConstraints(contractAttachmentsByContract: Map<ContractClassName, ContractAttachment>) {
// For each contract/constraint pair check that the relevant attachment is valid. // 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) { if (constraint is SignatureAttachmentConstraint) {
/** /**
* Support for signature constraints has been added on * 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. // Loads the contract class from the transactionClassLoader.
private fun createContractClass(id: SecureHash, contractClassName: ContractClassName): Class<out Contract> { private fun createContractClass(id: SecureHash, contractClassName: ContractClassName): Class<out Contract> {
return try { return try {
Class.forName(contractClassName, false, transactionClassLoader).asSubclass(Contract::class.java) loadClassOfType<Contract>(contractClassName, false, transactionClassLoader)
} catch (e: Exception) { } catch (e: Exception) {
throw ContractCreationError(id, contractClassName, e) throw ContractCreationError(id, contractClassName, e)
} }
@ -448,7 +453,7 @@ class TransactionVerifier(private val transactionClassLoader: ClassLoader) : Fun
private fun generateContracts(ltx: LedgerTransaction): List<Contract> { private fun generateContracts(ltx: LedgerTransaction): List<Contract> {
return (ltx.inputs.map(StateAndRef<ContractState>::state) + ltx.outputs) return (ltx.inputs.map(StateAndRef<ContractState>::state) + ltx.outputs)
.mapTo(LinkedHashSet(), TransactionState<*>::contract) .mapToSet { it.contract }
.map { contractClassName -> .map { contractClassName ->
createContractClass(ltx.id, contractClassName) createContractClass(ltx.id, contractClassName)
}.map { contractClass -> }.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.flows.ContractUpgradeFlow
import net.corda.core.internal.PlatformVersionSwitches.TWO_PHASE_FINALITY import net.corda.core.internal.PlatformVersionSwitches.TWO_PHASE_FINALITY
import net.corda.core.internal.telemetry.TelemetryComponent 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.*
import net.corda.core.node.services.diagnostics.DiagnosticsService import net.corda.core.node.services.diagnostics.DiagnosticsService
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
@ -170,12 +171,6 @@ interface ServiceHub : ServicesForResolution {
*/ */
val telemetryService: TelemetryService 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 * 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. * clock so the current time can be controlled during unit testing.
@ -283,8 +278,7 @@ interface ServiceHub : ServicesForResolution {
*/ */
@Throws(TransactionResolutionException::class) @Throws(TransactionResolutionException::class)
fun <T : ContractState> toStateAndRef(stateRef: StateRef): StateAndRef<T> { fun <T : ContractState> toStateAndRef(stateRef: StateRef): StateAndRef<T> {
val stx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash) return StateAndRef(uncheckedCast(loadState(stateRef)), stateRef)
return stx.resolveBaseTransaction(this).outRef(stateRef.index)
} }
private val legalIdentityKey: PublicKey get() = this.myInfo.legalIdentitiesAndCerts.first().owningKey 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, * 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. * 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. * We want to make sure users have a restricted access to administrative functions, this function will return a [Connection] instance
* The following methods are blocked: * with the following methods blocked:
* - abort(executor: Executor?) * - abort(executor: Executor?)
* - clearWarnings() * - clearWarnings()
* - close() * - 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 package net.corda.core.transactions
import net.corda.core.CordaInternal 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.DigestService
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.AttachmentWithContext import net.corda.core.internal.AttachmentWithContext
import net.corda.core.internal.ServiceHubCoreInternal
import net.corda.core.internal.combinedHash 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.NetworkParameters
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize 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.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.Companion.calculateUpgradedState
import net.corda.core.transactions.ContractUpgradeWireTransaction.Component.* import net.corda.core.transactions.ContractUpgradeWireTransaction.Component.INPUTS
import net.corda.core.transactions.WireTransaction.Companion.resolveStateRefBinaryComponent 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.OpaqueBytes
import net.corda.core.utilities.toBase58String import net.corda.core.utilities.toBase58String
import java.security.PublicKey import java.security.PublicKey
@ -52,7 +68,10 @@ data class ContractUpgradeWireTransaction(
* Runs the explicit upgrade logic. * Runs the explicit upgrade logic.
*/ */
@CordaInternal @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 // TODO: if there are encumbrance states in the inputs, just copy them across without modifying
val upgradedState: S = upgradedContract.upgrade(state.data) val upgradedState: S = upgradedContract.upgrade(state.data)
val inputConstraint = state.constraint val inputConstraint = state.constraint
@ -121,60 +140,12 @@ data class ContractUpgradeWireTransaction(
/** Resolves input states and contract attachments, and builds a ContractUpgradeLedgerTransaction. */ /** Resolves input states and contract attachments, and builds a ContractUpgradeLedgerTransaction. */
fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>): ContractUpgradeLedgerTransaction { fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>): ContractUpgradeLedgerTransaction {
val resolvedInputs = services.loadStates(inputs.toSet()).toList() return ContractUpgradeLedgerTransaction.resolve(services.toVerifyingServiceHub(), this, sigs)
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()
}
} }
/** Constructs a filtered transaction: the inputs, the notary party and network parameters hash are always visible, while the rest are hidden. */ /** Constructs a filtered transaction: the inputs, the notary party and network parameters hash are always visible, while the rest are hidden. */
fun buildFilteredTransaction(): ContractUpgradeFilteredTransaction { fun buildFilteredTransaction(): ContractUpgradeFilteredTransaction {
val totalComponents = (0 until serializedComponents.size).toSet() val totalComponents = serializedComponents.indices.toSet()
val visibleComponents = mapOf( val visibleComponents = mapOf(
INPUTS.ordinal to FilteredComponent(serializedComponents[INPUTS.ordinal], nonces[INPUTS.ordinal]), INPUTS.ordinal to FilteredComponent(serializedComponents[INPUTS.ordinal], nonces[INPUTS.ordinal]),
NOTARY.ordinal to FilteredComponent(serializedComponents[NOTARY.ordinal], nonces[NOTARY.ordinal]), NOTARY.ordinal to FilteredComponent(serializedComponents[NOTARY.ordinal], nonces[NOTARY.ordinal]),
@ -287,39 +258,47 @@ private constructor(
get() = upgradedContract::class.java.name get() = upgradedContract::class.java.name
companion object { companion object {
@CordaInternal @CordaInternal
internal fun create( @JvmSynthetic
inputs: List<StateAndRef<ContractState>>, @Suppress("ThrowsCount")
notary: Party, internal fun resolve(verificationSupport: VerificationSupport,
legacyContractAttachment: Attachment, wtx: ContractUpgradeWireTransaction,
upgradedContractAttachment: Attachment, sigs: List<TransactionSignature>): ContractUpgradeLedgerTransaction {
id: SecureHash, val inputs = wtx.inputs.map(verificationSupport::getStateAndRef)
privacySalt: PrivacySalt, val (legacyContractAttachment, upgradedContractAttachment) = verificationSupport.getAttachments(listOf(
sigs: List<TransactionSignature>, wtx.legacyContractAttachmentId,
networkParameters: NetworkParameters, wtx.upgradedContractAttachmentId
upgradedContract: UpgradedContract<ContractState, *> ))
): ContractUpgradeLedgerTransaction { val networkParameters = verificationSupport.getNetworkParameters(wtx.networkParametersHash)
return ContractUpgradeLedgerTransaction(inputs, notary, legacyContractAttachment, upgradedContractAttachment, id, privacySalt, sigs, networkParameters, upgradedContract) ?: 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 @CordaInternal
internal fun loadUpgradedContract(upgradedContractClassName: ContractClassName, classLoader: ClassLoader): UpgradedContract<ContractState, *> { @JvmSynthetic
@Suppress("UNCHECKED_CAST") @Suppress("TooGenericExceptionCaught")
return Class.forName(upgradedContractClassName, false, classLoader) internal fun loadUpgradedContract(className: ContractClassName, id: SecureHash, classLoader: ClassLoader): UpgradedContract<ContractState, *> {
.asSubclass(Contract::class.java) return try {
.getConstructor() loadClassOfType<UpgradedContract<ContractState, *>>(className, false, classLoader)
.newInstance() as UpgradedContract<ContractState, *> .getDeclaredConstructor()
} .newInstance()
} catch (e: Exception) {
// This is a "hack" to retrieve the CordappsClassloader from the services without having access to all classes. throw TransactionVerificationException.ContractCreationError(id, className, e)
@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
} }
} }
@ -366,7 +345,7 @@ private constructor(
/** The required signers are the set of all input states' participants. */ /** The required signers are the set of all input states' participants. */
override val requiredSigningKeys: Set<PublicKey> 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> { override fun getKeyDescriptions(keys: Set<PublicKey>): List<String> {
return keys.map { it.toBase58String() } return keys.map { it.toBase58String() }
@ -401,7 +380,7 @@ private constructor(
privacySalt: PrivacySalt, privacySalt: PrivacySalt,
sigs: List<TransactionSignature>, sigs: List<TransactionSignature>,
networkParameters: NetworkParameters 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.") @Deprecated("ContractUpgradeLedgerTransaction should not be created directly, use ContractUpgradeWireTransaction.resolve instead.")
fun copy( fun copy(

View File

@ -16,21 +16,21 @@ import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.AbstractVerifier
import net.corda.core.internal.SerializedStateAndRef import net.corda.core.internal.SerializedStateAndRef
import net.corda.core.internal.Verifier
import net.corda.core.internal.castIfPossible import net.corda.core.internal.castIfPossible
import net.corda.core.internal.deserialiseCommands import net.corda.core.internal.deserialiseCommands
import net.corda.core.internal.deserialiseComponentGroup import net.corda.core.internal.deserialiseComponentGroup
import net.corda.core.internal.eagerDeserialise import net.corda.core.internal.eagerDeserialise
import net.corda.core.internal.isUploaderTrusted import net.corda.core.internal.isUploaderTrusted
import net.corda.core.internal.uncheckedCast 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.node.NetworkParameters
import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationFactory 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.AttachmentsClassLoaderBuilder
import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import java.util.Collections.unmodifiableList import java.util.Collections.unmodifiableList
import java.util.function.Predicate import java.util.function.Predicate
@ -153,34 +153,35 @@ private constructor(
serializedInputs: List<SerializedStateAndRef>? = null, serializedInputs: List<SerializedStateAndRef>? = null,
serializedReferences: List<SerializedStateAndRef>? = null, serializedReferences: List<SerializedStateAndRef>? = null,
isAttachmentTrusted: (Attachment) -> Boolean, isAttachmentTrusted: (Attachment) -> Boolean,
verifierFactory: (LedgerTransaction, SerializationContext) -> Verifier,
attachmentsClassLoaderCache: AttachmentsClassLoaderCache?, attachmentsClassLoaderCache: AttachmentsClassLoaderCache?,
digestService: DigestService digestService: DigestService
): LedgerTransaction { ): LedgerTransaction {
return LedgerTransaction( return LedgerTransaction(
inputs = inputs, inputs = inputs,
outputs = outputs, outputs = outputs,
commands = commands, commands = commands,
attachments = attachments, attachments = attachments,
id = id, id = id,
notary = notary, notary = notary,
timeWindow = timeWindow, timeWindow = timeWindow,
privacySalt = privacySalt, privacySalt = privacySalt,
networkParameters = networkParameters, networkParameters = networkParameters,
references = references, references = references,
componentGroups = protectOrNull(componentGroups), componentGroups = protectOrNull(componentGroups),
serializedInputs = protectOrNull(serializedInputs), serializedInputs = protectOrNull(serializedInputs),
serializedReferences = protectOrNull(serializedReferences), serializedReferences = protectOrNull(serializedReferences),
isAttachmentTrusted = isAttachmentTrusted, isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = ::BasicVerifier, verifierFactory = verifierFactory,
attachmentsClassLoaderCache = attachmentsClassLoaderCache, attachmentsClassLoaderCache = attachmentsClassLoaderCache,
digestService = digestService digestService = digestService
) )
} }
/** /**
* This factory function will create an instance of [LedgerTransaction] * This factory function will create an instance of [LedgerTransaction]
* that will be used for contract verification. * that will be used for contract verification.
* @see BasicVerifier * @see DefaultVerifier
*/ */
@CordaInternal @CordaInternal
fun createForContractVerify( fun createForContractVerify(
@ -243,26 +244,26 @@ private constructor(
*/ */
@Throws(TransactionVerificationException::class) @Throws(TransactionVerificationException::class)
fun verify() { fun verify() {
internalPrepareVerify(attachments).verify() verifyInternal()
} }
/** /**
* This method has to be called in a context where it has access to the database. * This method has to be called in a context where it has access to the database.
*/ */
@CordaInternal @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 // 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. // like no-overlap, package namespace ownership and (in future) deterministic Java.
return AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext( val verifier = AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(
txAttachments, txAttachments,
getParamsWithGoo(), getParamsWithGoo(),
id, id,
isAttachmentTrusted = isAttachmentTrusted, isAttachmentTrusted,
attachmentsClassLoaderCache = attachmentsClassLoaderCache) { serializationContext -> attachmentsClassLoaderCache = attachmentsClassLoaderCache
) { serializationContext ->
// Legacy check - warns if the LedgerTransaction was created incorrectly. // Legacy check - warns if the LedgerTransaction was created incorrectly.
checkLtxForVerification() checkLtxForVerification()
// Create a copy of the outer LedgerTransaction which deserializes all fields using // Create a copy of the outer LedgerTransaction which deserializes all fields using
// the serialization context (or its deserializationClassloader). // the serialization context (or its deserializationClassloader).
// Only the copy will be used for verification, and the outer shell will be discarded. // 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. // NOTE: The Verifier creates the copies of the LedgerTransaction object now.
verifierFactory(this, serializationContext) verifierFactory(this, serializationContext)
} }
verifier.verify()
} }
/** /**
@ -463,7 +465,7 @@ private constructor(
} }
inline fun <reified T : ContractState> filterInputs(crossinline predicate: (T) -> Boolean): List<T> { 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> { 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>> { 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>> { 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 { 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. * @throws IllegalArgumentException if no item, or multiple items are found matching the requirements.
*/ */
fun <T : ContractState> findReference(clazz: Class<T>, predicate: Predicate<T>): T { 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 { 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> { 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> { 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>> { 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> { 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, serializedInputs = null,
serializedReferences = null, serializedReferences = null,
isAttachmentTrusted = Attachment::isUploaderTrusted, isAttachmentTrusted = Attachment::isUploaderTrusted,
verifierFactory = ::BasicVerifier, verifierFactory = ::DefaultVerifier,
attachmentsClassLoaderCache = null attachmentsClassLoaderCache = null
) )
@ -736,7 +738,7 @@ private constructor(
serializedInputs = null, serializedInputs = null,
serializedReferences = null, serializedReferences = null,
isAttachmentTrusted = Attachment::isUploaderTrusted, isAttachmentTrusted = Attachment::isUploaderTrusted,
verifierFactory = ::BasicVerifier, verifierFactory = ::DefaultVerifier,
attachmentsClassLoaderCache = null 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 * This is the default [Verifier] that configures Corda
* to execute [Contract.verify(LedgerTransaction)]. * to execute [Contract.verify(LedgerTransaction)].
* *
* THIS CLASS IS NOT PUBLIC API, AND IS DELIBERATELY PRIVATE! * THIS CLASS IS NOT PUBLIC API, AND IS DELIBERATELY PRIVATE!
*/ */
@CordaInternal private class DefaultVerifier(
private class BasicVerifier(
ltx: LedgerTransaction, ltx: LedgerTransaction,
private val serializationContext: SerializationContext private val serializationContext: SerializationContext
) : AbstractVerifier(ltx, serializationContext.deserializationClassLoader) { ) : AbstractVerifier(ltx, serializationContext.deserializationClassLoader) {
@ -874,7 +881,6 @@ private class BasicVerifier(
* THIS CLASS IS NOT PUBLIC API, AND IS DELIBERATELY PRIVATE! * THIS CLASS IS NOT PUBLIC API, AND IS DELIBERATELY PRIVATE!
*/ */
@Suppress("unused_parameter") @Suppress("unused_parameter")
@CordaInternal
private class NoOpVerifier(ltx: LedgerTransaction, serializationContext: SerializationContext) : Verifier { private class NoOpVerifier(ltx: LedgerTransaction, serializationContext: SerializationContext) : Verifier {
// Invoking LedgerTransaction.verify() from Contract.verify(LedgerTransaction) // Invoking LedgerTransaction.verify() from Contract.verify(LedgerTransaction)
// will execute this function. But why would anyone do that?! // will execute this function. But why would anyone do that?!

View File

@ -1,20 +1,31 @@
package net.corda.core.transactions package net.corda.core.transactions
import net.corda.core.CordaInternal 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.DigestService
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.Party 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.NetworkParameters
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize 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.OpaqueBytes
import net.corda.core.utilities.toBase58String import net.corda.core.utilities.toBase58String
import java.security.PublicKey import java.security.PublicKey
@ -88,32 +99,12 @@ data class NotaryChangeWireTransaction(
/** Resolves input states and network parameters and builds a [NotaryChangeLedgerTransaction]. */ /** Resolves input states and network parameters and builds a [NotaryChangeLedgerTransaction]. */
fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>): NotaryChangeLedgerTransaction { fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>): NotaryChangeLedgerTransaction {
val resolvedInputs = services.loadStates(inputs.toSet()).toList() return NotaryChangeLedgerTransaction.resolve(services.toVerifyingServiceHub(), this, sigs)
val hashToResolve = networkParametersHash ?: services.networkParametersService.defaultHash
val resolvedNetworkParameters = services.networkParametersService.lookup(hashToResolve)
?: throw TransactionResolutionException(id)
return NotaryChangeLedgerTransaction.create(resolvedInputs, notary, newNotary, id, sigs, resolvedNetworkParameters)
} }
/** Resolves input states and builds a [NotaryChangeLedgerTransaction]. */ /** Resolves input states and builds a [NotaryChangeLedgerTransaction]. */
fun resolve(services: ServiceHub, sigs: List<TransactionSignature>) = resolve(services as ServicesForResolution, sigs) fun resolve(services: ServiceHub, sigs: List<TransactionSignature>): NotaryChangeLedgerTransaction {
return 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()
} }
enum class Component { enum class Component {
@ -140,13 +131,25 @@ private constructor(
) : FullTransaction(), TransactionWithSignatures { ) : FullTransaction(), TransactionWithSignatures {
companion object { companion object {
@CordaInternal @CordaInternal
internal fun create(inputs: List<StateAndRef<ContractState>>, @JvmSynthetic
notary: Party, internal fun resolve(verificationSupport: VerificationSupport,
newNotary: Party, wireTx: NotaryChangeWireTransaction,
id: SecureHash, sigs: List<TransactionSignature>): NotaryChangeLedgerTransaction {
sigs: List<TransactionSignature>, val inputs = wireTx.inputs.map(verificationSupport::getStateAndRef)
networkParameters: NetworkParameters): NotaryChangeLedgerTransaction { val networkParameters = verificationSupport.getNetworkParameters(wireTx.networkParametersHash)
return NotaryChangeLedgerTransaction(inputs, notary, newNotary, id, sigs, networkParameters) ?: 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. */ /** We compute the outputs on demand by applying the notary field modification to the inputs. */
override val outputs: List<TransactionState<ContractState>> override val outputs: List<TransactionState<ContractState>>
get() = computeOutputs() get() = inputs.map { computeOutput(it, newNotary) { inputs.map(StateAndRef<ContractState>::ref) } }
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)
}
}
override val requiredSigningKeys: Set<PublicKey> 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> { override fun getKeyDescriptions(keys: Set<PublicKey>): List<String> {
return keys.map { it.toBase58String() } return keys.map { it.toBase58String() }

View File

@ -1,27 +1,40 @@
package net.corda.core.transactions package net.corda.core.transactions
import net.corda.core.CordaException import net.corda.core.CordaException
import net.corda.core.CordaInternal
import net.corda.core.CordaThrowable import net.corda.core.CordaThrowable
import net.corda.core.contracts.* import net.corda.core.contracts.Attachment
import net.corda.core.crypto.* 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.identity.Party
import net.corda.core.internal.TransactionDeserialisationException import net.corda.core.internal.TransactionDeserialisationException
import net.corda.core.internal.TransactionVerifierServiceInternal
import net.corda.core.internal.VisibleForTesting 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.ServiceHub
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.MissingAttachmentsException
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.internal.MissingSerializerException import net.corda.core.serialization.internal.MissingSerializerException
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.getOrThrow
import java.io.NotSerializableException import java.io.NotSerializableException
import java.security.KeyPair import java.security.KeyPair
import java.security.PublicKey import java.security.PublicKey
import java.security.SignatureException import java.security.SignatureException
import java.util.*
import java.util.function.Predicate import java.util.function.Predicate
/** /**
@ -142,6 +155,12 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
@JvmOverloads @JvmOverloads
@Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class) @Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServiceHub, checkSufficientSignatures: Boolean = true): LedgerTransaction { 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 // 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. // 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. // b) omit verifying signatures when threshold requirement is met.
@ -154,9 +173,7 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
} else { } else {
checkSignaturesAreValid() checkSignaturesAreValid()
} }
// We need parameters check here, because finality flow calls stx.toLedgerTransaction() and then verify. return tx.toLedgerTransactionInternal(verificationSupport)
resolveAndCheckNetworkParameters(services)
return tx.toLedgerTransaction(services)
} }
/** /**
@ -173,10 +190,19 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
@Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class) @Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class)
fun verify(services: ServiceHub, checkSufficientSignatures: Boolean = true) { fun verify(services: ServiceHub, checkSufficientSignatures: Boolean = true) {
resolveAndCheckNetworkParameters(services) resolveAndCheckNetworkParameters(services)
val verifyingServiceHub = services.toVerifyingServiceHub()
if (verifyingServiceHub.tryExternalVerification(this, checkSufficientSignatures)) {
verifyInternal(verifyingServiceHub, checkSufficientSignatures)
}
}
@CordaInternal
@JvmSynthetic
fun verifyInternal(verificationSupport: VerificationSupport, checkSufficientSignatures: Boolean) {
when (coreTransaction) { when (coreTransaction) {
is NotaryChangeWireTransaction -> verifyNotaryChangeTransaction(services, checkSufficientSignatures) is NotaryChangeWireTransaction -> verifyNotaryChangeTransaction(verificationSupport, checkSufficientSignatures)
is ContractUpgradeWireTransaction -> verifyContractUpgradeTransaction(services, checkSufficientSignatures) is ContractUpgradeWireTransaction -> verifyContractUpgradeTransaction(verificationSupport, checkSufficientSignatures)
else -> verifyRegularTransaction(services, 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. */ /** 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) { private fun verifyNotaryChangeTransaction(verificationSupport: VerificationSupport, checkSufficientSignatures: Boolean) {
val ntx = resolveNotaryChangeTransaction(services) val ntx = NotaryChangeLedgerTransaction.resolve(verificationSupport, coreTransaction as NotaryChangeWireTransaction, sigs)
if (checkSufficientSignatures) ntx.verifyRequiredSignatures() if (checkSufficientSignatures) ntx.verifyRequiredSignatures()
else checkSignaturesAreValid() else checkSignaturesAreValid()
} }
/** No contract code is run when verifying contract upgrade transactions, it is sufficient to check invariants during initialisation. */ /** 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) { private fun verifyContractUpgradeTransaction(verificationSupport: VerificationSupport, checkSufficientSignatures: Boolean) {
val ctx = resolveContractUpgradeTransaction(services) val ctx = ContractUpgradeLedgerTransaction.resolve(verificationSupport, coreTransaction as ContractUpgradeWireTransaction, sigs)
if (checkSufficientSignatures) ctx.verifyRequiredSignatures() if (checkSufficientSignatures) ctx.verifyRequiredSignatures()
else checkSignaturesAreValid() 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 // 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 // from the attachment is trusted. This will require some partial serialisation work to not load the ContractState
// objects from the TransactionState. // objects from the TransactionState.
private fun verifyRegularTransaction(services: ServiceHub, checkSufficientSignatures: Boolean) { private fun verifyRegularTransaction(verificationSupport: VerificationSupport, checkSufficientSignatures: Boolean) {
val ltx = toLedgerTransaction(services, checkSufficientSignatures) val ltx = toLedgerTransactionInternal(verificationSupport, checkSufficientSignatures)
try { try {
// TODO: allow non-blocking verification. ltx.verify()
services.transactionVerifierService.verify(ltx).getOrThrow()
} catch (e: NoClassDefFoundError) { } catch (e: NoClassDefFoundError) {
checkReverifyAllowed(e) checkReverifyAllowed(e)
val missingClass = e.message ?: throw e val missingClass = e.message ?: throw e
log.warn("Transaction {} has missing class: {}", ltx.id, missingClass) log.warn("Transaction {} has missing class: {}", ltx.id, missingClass)
reverifyWithFixups(ltx, services, missingClass) reverifyWithFixups(ltx, verificationSupport, missingClass)
} catch (e: NotSerializableException) { } catch (e: NotSerializableException) {
checkReverifyAllowed(e) checkReverifyAllowed(e)
retryVerification(e, e, ltx, services) retryVerification(e, e, ltx, verificationSupport)
} catch (e: TransactionDeserialisationException) { } catch (e: TransactionDeserialisationException) {
checkReverifyAllowed(e) 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") @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) { when (cause) {
is MissingSerializerException -> { is MissingSerializerException -> {
log.warn("Missing serializers: typeDescriptor={}, typeNames={}", cause.typeDescriptor ?: "<unknown>", cause.typeNames) log.warn("Missing serializers: typeDescriptor={}, typeNames={}", cause.typeDescriptor ?: "<unknown>", cause.typeNames)
reverifyWithFixups(ltx, services, null) reverifyWithFixups(ltx, verificationSupport, null)
} }
is NotSerializableException -> { is NotSerializableException -> {
val underlying = cause.cause val underlying = cause.cause
if (underlying is ClassNotFoundException) { if (underlying is ClassNotFoundException) {
val missingClass = underlying.message?.replace('.', '/') ?: throw ex val missingClass = underlying.message?.replace('.', '/') ?: throw ex
log.warn("Transaction {} has missing class: {}", ltx.id, missingClass) log.warn("Transaction {} has missing class: {}", ltx.id, missingClass)
reverifyWithFixups(ltx, services, missingClass) reverifyWithFixups(ltx, verificationSupport, missingClass)
} else { } else {
throw ex 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. // 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. // 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. // 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. 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. |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. |Attempting to re-verify having applied this node's fix-up rules.
|Please check with the originator that this is a valid transaction.""".trimMargin()) |Please check with the originator that this is a valid transaction.""".trimMargin())
(services.transactionVerifierService as TransactionVerifierServiceInternal) val replacementAttachments = computeReplacementAttachments(ltx, verificationSupport, missingClass)
.reverifyWithFixups(ltx, missingClass) log.warn("Reverifying transaction {} with attachments:{}", ltx.id, replacementAttachments)
.getOrThrow() 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. * [NotaryChangeLedgerTransaction] so the signatures can be verified.
*/ */
fun resolveNotaryChangeTransaction(services: ServicesForResolution): NotaryChangeLedgerTransaction { 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. * [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 * 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.crypto.SignatureMetadata
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.* 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.NetworkParameters
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
@ -28,7 +30,6 @@ import java.time.Duration
import java.time.Instant import java.time.Instant
import java.util.* import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
import kotlin.collections.ArrayList
import kotlin.collections.component1 import kotlin.collections.component1
import kotlin.collections.component2 import kotlin.collections.component2
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -77,9 +78,6 @@ open class TransactionBuilder(
private const val ID_PATTERN = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*" private const val ID_PATTERN = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"
private val FQCP: Pattern = Pattern.compile("$ID_PATTERN(/$ID_PATTERN)+") private val FQCP: Pattern = Pattern.compile("$ID_PATTERN(/$ID_PATTERN)+")
private fun isValidJavaClass(identifier: String) = FQCP.matcher(identifier).matches() 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( private fun Collection<AttachmentId>.toPrettyString(): String = sorted().joinToString(
separator = System.lineSeparator(), separator = System.lineSeparator(),
prefix = System.lineSeparator() prefix = System.lineSeparator()
@ -178,24 +176,25 @@ open class TransactionBuilder(
} }
@CordaInternal @CordaInternal
fun toWireTransactionWithContext( @JvmSynthetic
internal fun toWireTransactionWithContext(
services: ServicesForResolution, services: ServicesForResolution,
serializationContext: SerializationContext? serializationContext: SerializationContext?
) : WireTransaction = toWireTransactionWithContext(services, serializationContext, 0) ) : WireTransaction = toWireTransactionWithContext(services.toVerifyingServiceHub(), serializationContext, 0)
private tailrec fun toWireTransactionWithContext( private tailrec fun toWireTransactionWithContext(
services: ServicesForResolution, serviceHub: VerifyingServiceHub,
serializationContext: SerializationContext?, serializationContext: SerializationContext?,
tryCount: Int tryCount: Int
): WireTransaction { ): WireTransaction {
val referenceStates = referenceStates() val referenceStates = referenceStates()
if (referenceStates.isNotEmpty()) { 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>>) val (allContractAttachments: Collection<AttachmentId>, resolvedOutputs: List<TransactionState<ContractState>>)
= selectContractAttachmentsAndOutputStateConstraints(services, serializationContext) = selectContractAttachmentsAndOutputStateConstraints(serviceHub, serializationContext)
// Final sanity check that all states have the correct constraints. // Final sanity check that all states have the correct constraints.
for (state in (inputsWithTransactionState.map { it.state } + resolvedOutputs)) { for (state in (inputsWithTransactionState.map { it.state } + resolvedOutputs)) {
@ -213,9 +212,9 @@ open class TransactionBuilder(
notary, notary,
window, window,
referenceStates, referenceStates,
services.networkParametersService.currentHash), serviceHub.networkParametersService.currentHash),
privacySalt, 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. // 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. // 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. // 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) return if (addedDependency)
toWireTransactionWithContext(services, serializationContext, tryCount + 1) toWireTransactionWithContext(serviceHub, serializationContext, tryCount + 1)
else else
wireTx wireTx
} }
@ -241,9 +240,9 @@ open class TransactionBuilder(
/** /**
* @return true if a new dependency was successfully added. * @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 { return try {
wireTx.toLedgerTransaction(services).verify() wireTx.toLedgerTransactionInternal(serviceHub).verify()
// The transaction verified successfully without adding any extra dependency. // The transaction verified successfully without adding any extra dependency.
false false
} catch (e: Throwable) { } catch (e: Throwable) {
@ -253,12 +252,12 @@ open class TransactionBuilder(
// Handle various exceptions that can be thrown during verification and drill down the wrappings. // Handle various exceptions that can be thrown during verification and drill down the wrappings.
// Note: this is a best effort to preserve backwards compatibility. // Note: this is a best effort to preserve backwards compatibility.
rootError is ClassNotFoundException -> { rootError is ClassNotFoundException -> {
((tryCount == 0) && fixupAttachments(wireTx.attachments, services, e)) ((tryCount == 0) && fixupAttachments(wireTx.attachments, serviceHub, e))
|| addMissingAttachment((rootError.message ?: throw e).replace('.', '/'), services, e) || addMissingAttachment((rootError.message ?: throw e).replace('.', '/'), serviceHub, e)
} }
rootError is NoClassDefFoundError -> { rootError is NoClassDefFoundError -> {
((tryCount == 0) && fixupAttachments(wireTx.attachments, services, e)) ((tryCount == 0) && fixupAttachments(wireTx.attachments, serviceHub, e))
|| addMissingAttachment(rootError.message ?: throw e, services, e) || addMissingAttachment(rootError.message ?: throw e, serviceHub, e)
} }
// Ignore these exceptions as they will break unit tests. // Ignore these exceptions as they will break unit tests.
@ -281,18 +280,18 @@ open class TransactionBuilder(
} }
private fun fixupAttachments( private fun fixupAttachments(
txAttachments: List<AttachmentId>, txAttachments: List<AttachmentId>,
services: ServicesForResolution, serviceHub: VerifyingServiceHub,
originalException: Throwable originalException: Throwable
): Boolean { ): Boolean {
val replacementAttachments = services.cordappProvider.internalFixupAttachmentIds(txAttachments) val replacementAttachments = serviceHub.fixupAttachmentIds(txAttachments)
if (replacementAttachments.deepEquals(txAttachments)) { if (replacementAttachments.equivalent(txAttachments)) {
return false return false
} }
val extraAttachments = replacementAttachments - txAttachments val extraAttachments = replacementAttachments - txAttachments
extraAttachments.forEach { id -> extraAttachments.forEach { id ->
val attachment = services.attachments.openAttachment(id) val attachment = serviceHub.attachments.openAttachment(id)
if (attachment == null || !attachment.isUploaderTrusted()) { if (attachment == null || !attachment.isUploaderTrusted()) {
log.warn("""The node's fix-up rules suggest including attachment {}, which cannot be found either. 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. |Please contact the developer of the CorDapp for further instructions.
@ -315,7 +314,7 @@ open class TransactionBuilder(
return true 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)) { if (!isValidJavaClass(missingClass)) {
log.warn("Could not autodetect a valid attachment for the transaction being built.") log.warn("Could not autodetect a valid attachment for the transaction being built.")
throw originalException throw originalException
@ -324,7 +323,7 @@ open class TransactionBuilder(
throw originalException throw originalException
} }
val attachment = services.attachments.internalFindTrustedAttachmentForClass(missingClass) val attachment = serviceHub.getTrustedClassAttachment(missingClass)
if (attachment == null) { if (attachment == null) {
log.error("""The transaction currently built is missing an attachment for class: $missingClass. 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. // Determine if there are any HashConstraints that pin the version of a contract. If there are, check if we trust them.
val hashAttachments = inputsAndOutputs val hashAttachments = inputsAndOutputs
.filter { it.constraint is HashAttachmentConstraint } .filter { it.constraint is HashAttachmentConstraint }
.map { state -> .mapToSet { state ->
val attachment = services.attachments.openAttachment((state.constraint as HashAttachmentConstraint).attachmentId) val attachment = services.attachments.openAttachment((state.constraint as HashAttachmentConstraint).attachmentId)
if (attachment == null || attachment !is ContractAttachment || !isUploaderTrusted(attachment.uploader)) { if (attachment == null || attachment !is ContractAttachment || !isUploaderTrusted(attachment.uploader)) {
// This should never happen because these are input states that should have been validated already. // This should never happen because these are input states that should have been validated already.
throw MissingContractAttachments(listOf(state)) throw MissingContractAttachments(listOf(state))
} }
attachment attachment
}.toSet() }
// Check that states with the HashConstraint don't conflict between themselves or with an explicitly set attachment. // Check that states with the HashConstraint don't conflict between themselves or with an explicitly set attachment.
require(hashAttachments.size <= 1) { require(hashAttachments.size <= 1) {
@ -490,7 +489,7 @@ open class TransactionBuilder(
} }
if (explicitContractAttachment != null && hashAttachments.singleOrNull() != null) { 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." "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) @Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServiceHub) = toWireTransaction(services).toLedgerTransaction(services) 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) @Throws(AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class)
fun verify(services: ServiceHub) { fun verify(services: ServiceHub) {
toLedgerTransaction(services).verify() toLedgerTransaction(services).verify()
@ -692,7 +687,7 @@ open class TransactionBuilder(
} }
// Transaction can combine different identities of the same notary after key rotation. // 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 // Automatically correct notary after its key rotation
private fun resolveNotary(services: ServicesForResolution) { 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 * 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. * 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. * @throws IllegalStateException if no [ServiceHub] is provided and no flow context is available.
*/ */
private fun resolveStatePointers(transactionState: TransactionState<*>) { private fun resolveStatePointers(transactionState: TransactionState<*>) {

View File

@ -1,21 +1,41 @@
package net.corda.core.transactions package net.corda.core.transactions
import net.corda.core.CordaInternal 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.COMMANDS_GROUP
import net.corda.core.contracts.ComponentGroupEnum.OUTPUTS_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.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.NetworkParameters
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializationFactory 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.serialization.serialize
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import java.security.PublicKey import java.security.PublicKey
@ -47,6 +67,7 @@ import java.util.function.Predicate
* </ul></p> * </ul></p>
*/ */
@CordaSerializable @CordaSerializable
@Suppress("ThrowsCount")
class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: PrivacySalt, digestService: DigestService) : TraversableTransaction(componentGroups, digestService) { class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: PrivacySalt, digestService: DigestService) : TraversableTransaction(componentGroups, digestService) {
constructor(componentGroups: List<ComponentGroup>) : this(componentGroups, PrivacySalt()) constructor(componentGroups: List<ComponentGroup>) : this(componentGroups, PrivacySalt())
@ -71,7 +92,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
init { init {
check(componentGroups.all { it.components.isNotEmpty() }) { "Empty component groups are not allowed" } 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() checkBaseInvariants()
check(inputs.isNotEmpty() || outputs.isNotEmpty()) { "A transaction must contain at least one input or output state" } 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" } 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) @Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServicesForResolution): LedgerTransaction { fun toLedgerTransaction(services: ServicesForResolution): LedgerTransaction {
return services.specialise( return toLedgerTransactionInternal(services.toVerifyingServiceHub())
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()
}
} }
/** /**
@ -143,29 +143,37 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
@Suppress("UNUSED_PARAMETER") resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId? @Suppress("UNUSED_PARAMETER") resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId?
): LedgerTransaction { ): LedgerTransaction {
// This reverts to serializing the resolved transaction state. // This reverts to serializing the resolved transaction state.
return toLedgerTransactionInternal( return toLedgerTransactionInternal(object : VerificationSupport {
resolveIdentity, override fun getParties(keys: Collection<PublicKey>): List<Party?> = keys.map(resolveIdentity)
resolveAttachment, override fun getAttachment(id: SecureHash): Attachment? = resolveAttachment(id)
{ stateRef -> resolveStateRef(stateRef)?.serialize() }, override fun getNetworkParameters(id: SecureHash?): NetworkParameters? = null
{ null }, override fun isAttachmentTrusted(attachment: Attachment): Boolean = attachment.isUploaderTrusted()
Attachment::isUploaderTrusted, override fun getSerializedState(stateRef: StateRef): SerializedTransactionState {
null 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") @CordaInternal
private fun toLedgerTransactionInternal( @JvmSynthetic
resolveIdentity: (PublicKey) -> Party?, internal fun toLedgerTransactionInternal(verificationSupport: VerificationSupport): LedgerTransaction {
resolveAttachment: (SecureHash) -> Attachment?,
resolveStateRefAsSerialized: (StateRef) -> SerializedBytes<TransactionState<ContractState>>?,
resolveParameters: (SecureHash?) -> NetworkParameters?,
isAttachmentTrusted: (Attachment) -> Boolean,
attachmentsClassLoaderCache: AttachmentsClassLoaderCache?
): LedgerTransaction {
// Look up public keys to authenticated identities. // Look up public keys to authenticated identities.
val authenticatedCommands = commands.lazyMapped { cmd, _ -> val authenticatedCommands = if (verificationSupport.isResolutionLazy) {
val parties = cmd.signers.mapNotNull(resolveIdentity) commands.lazyMapped { cmd, _ ->
CommandWithParties(cmd.signers, parties, cmd.value) 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. // 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) ssar.toStateAndRef(serializationFactory, serializationContext)
} }
val serializedResolvedInputs = inputs.map { ref -> val serializedResolvedInputs = inputs.map {
SerializedStateAndRef(resolveStateRefAsSerialized(ref) ?: throw TransactionResolutionException(ref.txhash), ref) SerializedStateAndRef(verificationSupport.getSerializedState(it), it)
} }
val resolvedInputs = serializedResolvedInputs.lazyMapped(toStateAndRef) val resolvedInputs = serializedResolvedInputs.lazyMapped(toStateAndRef)
val serializedResolvedReferences = references.map { ref -> val serializedResolvedReferences = references.map {
SerializedStateAndRef(resolveStateRefAsSerialized(ref) ?: throw TransactionResolutionException(ref.txhash), ref) SerializedStateAndRef(verificationSupport.getSerializedState(it), it)
} }
val resolvedReferences = serializedResolvedReferences.lazyMapped(toStateAndRef) 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( val ltx = LedgerTransaction.create(
resolvedInputs, resolvedInputs,
@ -203,8 +220,9 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
componentGroups, componentGroups,
serializedResolvedInputs, serializedResolvedInputs,
serializedResolvedReferences, serializedResolvedReferences,
isAttachmentTrusted, verificationSupport::isAttachmentTrusted,
attachmentsClassLoaderCache, verificationSupport::createVerifier,
verificationSupport.attachmentsClassLoaderCache,
digestService 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. // 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 { 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 // 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. // 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) } ltx.attachments.distinctBy { it.id }.forEach { minus(it.size) }
minus(resolvedSerializedInputs.sumBy { it.serializedState.size }) minus(resolvedSerializedInputs.sumOf { it.serializedState.size })
minus(resolvedSerializedReferences.sumBy { it.serializedState.size }) minus(resolvedSerializedReferences.sumOf { it.serializedState.size })
// For Commands and outputs we can use the component groups as they are already serialized. // For Commands and outputs we can use the component groups as they are already serialized.
minus(componentGroupSize(COMMANDS_GROUP)) 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 // 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. // or received but unknown (thus, bigger than known ordinal) component groups.
val allOnesHash = digestService.allOnesHash 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 val root = groupsMerkleRoots[i] ?: allOnesHash
listOfLeaves.add(root) listOfLeaves.add(root)
} }
@ -340,37 +358,6 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
timeWindow: TimeWindow?): List<ComponentGroup> { timeWindow: TimeWindow?): List<ComponentGroup> {
return createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList(), null) 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 { override fun toString(): String {

View File

@ -1,9 +1,19 @@
package net.corda.core.internal 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.DigestService
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.verification.AbstractVerifier
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.internal.AttachmentsClassLoaderCache import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
@ -40,15 +50,35 @@ fun createLedgerTransaction(
isAttachmentTrusted: (Attachment) -> Boolean, isAttachmentTrusted: (Attachment) -> Boolean,
attachmentsClassLoaderCache: AttachmentsClassLoaderCache, attachmentsClassLoaderCache: AttachmentsClassLoaderCache,
digestService: DigestService = DigestService.default digestService: DigestService = DigestService.default
): LedgerTransaction = LedgerTransaction.create( ): LedgerTransaction {
inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references, componentGroups, serializedInputs, serializedReferences, isAttachmentTrusted, attachmentsClassLoaderCache, digestService return LedgerTransaction.create(
).specialise(::PassthroughVerifier) 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 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) fun createContractRejection(txId: SecureHash, contract: Contract, cause: Throwable) = TransactionVerificationException.ContractRejection(txId, contract, cause)
/** /**
* Verify the [LedgerTransaction] we already have. * Verify the [LedgerTransaction] we already have.
*
* Note, this is not secure!
*/ */
private class PassthroughVerifier(ltx: LedgerTransaction, context: SerializationContext) : AbstractVerifier(ltx, context.deserializationClassLoader) { private class PassthroughVerifier(ltx: LedgerTransaction, context: SerializationContext) : AbstractVerifier(ltx, context.deserializationClassLoader) {
override val transaction: Supplier<LedgerTransaction> 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.AnonymousParty
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.getRequiredTransaction
import net.corda.core.node.NotaryInfo import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
@ -284,8 +285,8 @@ class CommercialPaperTestsGeneric {
} }
// Propagate the cash transactions to each side. // Propagate the cash transactions to each side.
aliceServices.recordTransactions(bigCorpCash.states.map { megaCorpServices.validatedTransactions.getTransaction(it.ref.txhash)!! }) aliceServices.recordTransactions(bigCorpCash.states.map { megaCorpServices.getRequiredTransaction(it.ref.txhash) })
megaCorpServices.recordTransactions(aliceCash.states.map { aliceServices.validatedTransactions.getTransaction(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. // 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) val faceValue = 10000.DOLLARS `issued by` dummyCashIssuer.ref(1)

View File

@ -1,34 +1,24 @@
package net.corda.nodeapitests.internal package net.corda.nodeapitests.internal
import org.mockito.kotlin.any import net.corda.core.contracts.Command
import org.mockito.kotlin.doReturn import net.corda.core.contracts.CommandData
import org.mockito.kotlin.mock import net.corda.core.contracts.Contract
import org.mockito.kotlin.whenever import net.corda.core.contracts.ContractState
import net.corda.core.contracts.* import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.SecureHash import net.corda.core.contracts.StateAndContract
import net.corda.core.contracts.TypeOnlyCommandData
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party 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.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder 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.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.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.MockCordappConfigProvider import net.corda.testing.node.MockServices
import net.corda.coretesting.internal.rigorousMock
import net.corda.testing.node.internal.cordappWithPackages
import net.corda.testing.services.MockAttachmentStorage
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Rule import org.junit.Rule
@ -69,31 +59,7 @@ class AttachmentsClassLoaderStaticContractTests {
} }
} }
private val networkParameters = testNetworkParameters() private val serviceHub = MockServices()
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
}
@Test(timeout=300_000) @Test(timeout=300_000)
fun `test serialization of WireTransaction with statically loaded contract`() { fun `test serialization of WireTransaction with statically loaded contract`() {
@ -112,8 +78,4 @@ class AttachmentsClassLoaderStaticContractTests {
val contractClass = Class.forName(ATTACHMENT_PROGRAM_ID) val contractClass = Class.forName(ATTACHMENT_PROGRAM_ID)
assertThat(contractClass.getDeclaredConstructor().newInstance()).isInstanceOf(Contract::class.java) 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 package net.corda.nodeapi.internal.persistence
import com.github.benmanes.caffeine.cache.Caffeine
import net.corda.core.internal.NamedCacheFactory import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.castIfPossible import net.corda.core.internal.castIfPossible
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
@ -54,7 +53,7 @@ class HibernateConfiguration(
val sessionFactoryFactory = findSessionFactoryFactory(jdbcUrl, customClassLoader) 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 { val sessionFactoryForRegisteredSchemas = schemas.let {
logger.info("Init HibernateConfiguration for schemas: $it") 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.serialization.CustomSerializationScheme
import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.ByteSequence
import net.corda.nodeapi.internal.serialization.testutils.serializationContext import net.corda.nodeapi.internal.serialization.testutils.serializationContext
import net.corda.serialization.internal.verifier.CustomSerializationSchemeAdapter
import org.junit.Test import org.junit.Test
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
import java.io.NotSerializableException import java.io.NotSerializableException

View File

@ -73,6 +73,10 @@ jib.container {
processResources { processResources {
from file("$rootDir/config/dev/log4j2.xml") from file("$rootDir/config/dev/log4j2.xml")
from file("$rootDir/config/dev/jolokia-access.xml") from file("$rootDir/config/dev/jolokia-access.xml")
from(tasks.findByPath(":verifier:shadowJar")) {
into("net/corda/node/verification")
rename { "external-verifier.jar" }
}
} }
processTestResources { processTestResources {

View File

@ -8,7 +8,7 @@ import net.corda.core.contracts.TransactionState
import net.corda.core.contracts.requireSingleCommand import net.corda.core.contracts.requireSingleCommand
import net.corda.core.contracts.requireThat import net.corda.core.contracts.requireThat
import net.corda.core.identity.AbstractParty 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.serialization.SerializationContext
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction

View File

@ -58,7 +58,7 @@ import org.objenesis.strategy.StdInstantiatorStrategy
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.lang.reflect.Modifier import java.lang.reflect.Modifier
import java.security.PublicKey import java.security.PublicKey
import java.util.* import java.util.Arrays
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue import kotlin.test.assertTrue
@ -94,7 +94,10 @@ class CustomSerializationSchemeDriverTest {
@Test(timeout = 300_000) @Test(timeout = 300_000)
fun `flow can write a wire transaction serialized with custom kryo serializer to the ledger`() { 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( val (alice, bob) = listOf(
startNode(NodeParameters(providedName = ALICE_NAME)), startNode(NodeParameters(providedName = ALICE_NAME)),
startNode(NodeParameters(providedName = BOB_NAME)) startNode(NodeParameters(providedName = BOB_NAME))
@ -135,7 +138,7 @@ class CustomSerializationSchemeDriverTest {
@StartableByRPC @StartableByRPC
@InitiatingFlow @InitiatingFlow
class WriteTxToLedgerFlow(val counterparty: Party, val notary: Party) : FlowLogic<SecureHash>() { class WriteTxToLedgerFlow(private val counterparty: Party, val notary: Party) : FlowLogic<SecureHash>() {
@Suspendable @Suspendable
override fun call(): SecureHash { override fun call(): SecureHash {
val wireTx = createWireTx(serviceHub, notary, counterparty.owningKey, KryoScheme.SCHEME_ID) val wireTx = createWireTx(serviceHub, notary, counterparty.owningKey, KryoScheme.SCHEME_ID)
@ -146,7 +149,7 @@ class CustomSerializationSchemeDriverTest {
return fullySignedTx.id return fullySignedTx.id
} }
fun signWireTx(wireTx: WireTransaction) : SignedTransaction { private fun signWireTx(wireTx: WireTransaction) : SignedTransaction {
val signatureMetadata = SignatureMetadata( val signatureMetadata = SignatureMetadata(
serviceHub.myInfo.platformVersion, serviceHub.myInfo.platformVersion,
Crypto.findSignatureScheme(serviceHub.myInfo.legalIdentitiesAndCerts.first().owningKey).schemeNumberID Crypto.findSignatureScheme(serviceHub.myInfo.legalIdentitiesAndCerts.first().owningKey).schemeNumberID
@ -157,18 +160,18 @@ class CustomSerializationSchemeDriverTest {
} }
} }
@Suppress("unused")
@InitiatedBy(WriteTxToLedgerFlow::class) @InitiatedBy(WriteTxToLedgerFlow::class)
class SignWireTxFlow(private val session: FlowSession): FlowLogic<SignedTransaction>() { class SignWireTxFlow(private val session: FlowSession): FlowLogic<SignedTransaction>() {
@Suspendable @Suspendable
override fun call(): SignedTransaction { override fun call(): SignedTransaction {
val signTransactionFlow = object : SignTransactionFlow(session) { val txId = subFlow(NoCheckSignTransactionFlow(session)).id
override fun checkTransaction(stx: SignedTransaction) {
return
}
}
val txId = subFlow(signTransactionFlow).id
return subFlow(ReceiveFinalityFlow(session, expectedTxId = txId)) return subFlow(ReceiveFinalityFlow(session, expectedTxId = txId))
} }
class NoCheckSignTransactionFlow(session: FlowSession) : SignTransactionFlow(session) {
override fun checkTransaction(stx: SignedTransaction) = Unit
}
} }
@StartableByRPC @StartableByRPC
@ -226,7 +229,7 @@ class CustomSerializationSchemeDriverTest {
@StartableByRPC @StartableByRPC
@InitiatingFlow @InitiatingFlow
class SendFlow(val counterparty: Party) : FlowLogic<Boolean>() { class SendFlow(private val counterparty: Party) : FlowLogic<Boolean>() {
@Suspendable @Suspendable
override fun call(): Boolean { override fun call(): Boolean {
val wtx = createWireTx(serviceHub, counterparty, counterparty.owningKey, KryoScheme.SCHEME_ID) val wtx = createWireTx(serviceHub, counterparty, counterparty.owningKey, KryoScheme.SCHEME_ID)
@ -237,13 +240,14 @@ class CustomSerializationSchemeDriverTest {
} }
@StartableByRPC @StartableByRPC
class CreateWireTxFlow(val counterparty: Party) : FlowLogic<WireTransaction>() { class CreateWireTxFlow(private val counterparty: Party) : FlowLogic<WireTransaction>() {
@Suspendable @Suspendable
override fun call(): WireTransaction { override fun call(): WireTransaction {
return createWireTx(serviceHub, counterparty, counterparty.owningKey, KryoScheme.SCHEME_ID) return createWireTx(serviceHub, counterparty, counterparty.owningKey, KryoScheme.SCHEME_ID)
} }
} }
@Suppress("unused")
@InitiatedBy(SendFlow::class) @InitiatedBy(SendFlow::class)
class ReceiveFlow(private val session: FlowSession): FlowLogic<Unit>() { class ReceiveFlow(private val session: FlowSession): FlowLogic<Unit>() {
@Suspendable @Suspendable
@ -301,6 +305,7 @@ class CustomSerializationSchemeDriverTest {
kryo.isRegistrationRequired = false kryo.isRegistrationRequired = false
kryo.instantiatorStrategy = CustomInstantiatorStrategy() kryo.instantiatorStrategy = CustomInstantiatorStrategy()
kryo.classLoader = classLoader kryo.classLoader = classLoader
@Suppress("ReplaceJavaStaticMethodWithKotlinAnalog")
kryo.register(Arrays.asList("").javaClass, ArraysAsListSerializer()) 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.flatMap
import net.corda.core.internal.concurrent.map import net.corda.core.internal.concurrent.map
import net.corda.core.internal.concurrent.openFuture 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.div
import net.corda.core.internal.messaging.AttachmentTrustInfoRPCOps import net.corda.core.internal.messaging.AttachmentTrustInfoRPCOps
import net.corda.core.internal.notary.NotaryService 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.TelemetryComponent
import net.corda.core.internal.telemetry.TelemetryServiceImpl import net.corda.core.internal.telemetry.TelemetryServiceImpl
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
import net.corda.core.internal.verification.VerifyingServiceHub
import net.corda.core.messaging.ClientRpcSslOptions import net.corda.core.messaging.ClientRpcSslOptions
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.RPCOps 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.NetworkParameters
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub 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.ContractUpgradeService
import net.corda.core.node.services.CordaService import net.corda.core.node.services.CordaService
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.KeyManagementService import net.corda.core.node.services.KeyManagementService
import net.corda.core.node.services.TelemetryService 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.node.services.diagnostics.DiagnosticsService
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializationWhitelist 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.AttachmentsClassLoaderCache
import net.corda.core.serialization.internal.AttachmentsClassLoaderCacheImpl import net.corda.core.serialization.internal.AttachmentsClassLoaderCacheImpl
import net.corda.core.toFuture 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.NetworkHostAndPort
import net.corda.core.utilities.days import net.corda.core.utilities.days
import net.corda.core.utilities.millis 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.classloading.requireAnnotation
import net.corda.node.internal.cordapp.CordappConfigFileProvider import net.corda.node.internal.cordapp.CordappConfigFileProvider
import net.corda.node.internal.cordapp.CordappProviderImpl 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.JarScanningCordappLoader
import net.corda.node.internal.cordapp.VirtualCordapp import net.corda.node.internal.cordapp.VirtualCordapp
import net.corda.node.internal.rpc.proxies.AuthenticatedRpcOpsProxy 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.network.PersistentPartyInfoCache
import net.corda.node.services.persistence.AbstractPartyDescriptor import net.corda.node.services.persistence.AbstractPartyDescriptor
import net.corda.node.services.persistence.AbstractPartyToX500NameAsStringConverter 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.AttachmentStorageInternal
import net.corda.node.services.persistence.DBCheckpointPerformanceRecorder import net.corda.node.services.persistence.DBCheckpointPerformanceRecorder
import net.corda.node.services.persistence.DBCheckpointStorage 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.DBTransactionMappingStorage
import net.corda.node.services.persistence.DBTransactionStorageLedgerRecovery import net.corda.node.services.persistence.DBTransactionStorageLedgerRecovery
import net.corda.node.services.persistence.NodeAttachmentService 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.FlowStateMachineImpl
import net.corda.node.services.statemachine.SingleThreadedStateMachineManager import net.corda.node.services.statemachine.SingleThreadedStateMachineManager
import net.corda.node.services.statemachine.StateMachineManager 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.upgrade.ContractUpgradeServiceImpl
import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.NodeVaultService
import net.corda.node.utilities.AffinityExecutor 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.NodeLifecycleEvent
import net.corda.nodeapi.internal.lifecycle.NodeLifecycleEventsDistributor import net.corda.nodeapi.internal.lifecycle.NodeLifecycleEventsDistributor
import net.corda.nodeapi.internal.lifecycle.NodeServicesContext 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.CordaPersistence
import net.corda.nodeapi.internal.persistence.CordaTransactionSupportImpl import net.corda.nodeapi.internal.persistence.CordaTransactionSupportImpl
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException 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.SchemaMigration
import net.corda.nodeapi.internal.persistence.contextDatabase import net.corda.nodeapi.internal.persistence.contextDatabase
import net.corda.nodeapi.internal.persistence.withoutDatabaseAccess import net.corda.nodeapi.internal.persistence.withoutDatabaseAccess
import net.corda.nodeapi.internal.namedThreadPoolExecutor
import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.activemq.artemis.utils.ReusableLatch
import org.jolokia.jvmagent.JolokiaServer import org.jolokia.jvmagent.JolokiaServer
import org.jolokia.jvmagent.JolokiaServerConfig import org.jolokia.jvmagent.JolokiaServerConfig
@ -181,7 +179,6 @@ import java.sql.Savepoint
import java.time.Clock import java.time.Clock
import java.time.Duration import java.time.Duration
import java.time.format.DateTimeParseException import java.time.format.DateTimeParseException
import java.util.ArrayList
import java.util.Properties import java.util.Properties
import java.util.concurrent.ExecutorService import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors import java.util.concurrent.Executors
@ -299,22 +296,11 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val pkToIdCache = PublicKeyToOwningIdentityCacheImpl(database, cacheFactory) val pkToIdCache = PublicKeyToOwningIdentityCacheImpl(database, cacheFactory)
@Suppress("LeakingThis") @Suppress("LeakingThis")
val keyManagementService = makeKeyManagementService(identityService).tokenize() 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 nodeProperties = NodePropertiesPersistentStore(StubbedNodeUniqueIdProvider::value, database, cacheFactory)
val flowLogicRefFactory = makeFlowLogicRefFactoryImpl() val flowLogicRefFactory = makeFlowLogicRefFactoryImpl()
// TODO Cancelling parameters updates - if we do that, how we ensure that no one uses cancelled parameters in the transactions? // TODO Cancelling parameters updates - if we do that, how we ensure that no one uses cancelled parameters in the transactions?
val networkMapUpdater = makeNetworkMapUpdater() val networkMapUpdater = makeNetworkMapUpdater()
@Suppress("LeakingThis")
val transactionVerifierService = InMemoryTransactionVerifierService(
numberOfWorkers = transactionVerifierWorkerCount,
cordappProvider = cordappProvider,
attachments = attachments
).tokenize()
private val attachmentsClassLoaderCache: AttachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(cacheFactory).tokenize() private val attachmentsClassLoaderCache: AttachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(cacheFactory).tokenize()
val contractUpgradeService = ContractUpgradeServiceImpl(cacheFactory).tokenize() val contractUpgradeService = ContractUpgradeServiceImpl(cacheFactory).tokenize()
val auditService = DummyAuditService().tokenize() val auditService = DummyAuditService().tokenize()
@ -326,7 +312,9 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
log.warn("MessagingService subscription error", it) 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) val checkpointStorage = DBCheckpointStorage(DBCheckpointPerformanceRecorder(services.monitoringService.metrics), platformClock)
@Suppress("LeakingThis") @Suppress("LeakingThis")
val smm = makeStateMachineManager() val smm = makeStateMachineManager()
@ -338,7 +326,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
private val cordappTelemetryComponents = MutableClassToInstanceMap.create<TelemetryComponent>() private val cordappTelemetryComponents = MutableClassToInstanceMap.create<TelemetryComponent>()
private val shutdownExecutor = Executors.newSingleThreadExecutor(DefaultThreadFactory("Shutdown")) private val shutdownExecutor = Executors.newSingleThreadExecutor(DefaultThreadFactory("Shutdown"))
protected abstract val transactionVerifierWorkerCount: Int
/** /**
* Should be [rx.schedulers.Schedulers.io] for production, * Should be [rx.schedulers.Schedulers.io] for production,
* or [rx.internal.schedulers.CachedThreadScheduler] (with shutdown registered with [runOnStop]) for shared-JVM testing. * 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 ...") Node.printBasicNodeInfo("Running database schema migration scripts ...")
val props = configuration.dataSourceProperties val props = configuration.dataSourceProperties
if (props.isEmpty) throw DatabaseConfigurationException("There must be a database configured.") if (props.isEmpty) throw DatabaseConfigurationException("There must be a database configured.")
var pendingAppChanges: Int = 0 var pendingAppChanges = 0
var pendingCoreChanges: Int = 0 var pendingCoreChanges = 0
database.startHikariPool(props, metricRegistry) { dataSource, haveCheckpoints -> database.startHikariPool(props, metricRegistry) { dataSource, haveCheckpoints ->
val schemaMigration = SchemaMigration(dataSource, cordappLoader, configuration.networkParametersPath, configuration.myLegalName) val schemaMigration = SchemaMigration(dataSource, cordappLoader, configuration.networkParametersPath, configuration.myLegalName)
if(updateCoreSchemas) { if(updateCoreSchemas) {
@ -505,13 +492,13 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val updatedSchemas = listOfNotNull( val updatedSchemas = listOfNotNull(
("core").takeIf { updateCoreSchemas }, ("core").takeIf { updateCoreSchemas },
("app").takeIf { updateAppSchemas } ("app").takeIf { updateAppSchemas }
).joinToString(separator = " and "); ).joinToString(separator = " and ")
val pendingChanges = listOfNotNull( val pendingChanges = listOfNotNull(
("no outstanding").takeIf { pendingAppChanges == 0 && pendingCoreChanges == 0 }, ("no outstanding").takeIf { pendingAppChanges == 0 && pendingCoreChanges == 0 },
("$pendingCoreChanges outstanding core").takeIf { !updateCoreSchemas && pendingCoreChanges > 0 }, ("$pendingCoreChanges outstanding core").takeIf { !updateCoreSchemas && pendingCoreChanges > 0 },
("$pendingAppChanges outstanding app").takeIf { !updateAppSchemas && pendingAppChanges > 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") Node.printBasicNodeInfo("Database migration scripts for $updatedSchemas schemas complete. $pendingChanges")
} }
@ -832,7 +819,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
networkMapCache, networkMapCache,
NodeInfoWatcher( NodeInfoWatcher(
configuration.baseDirectory, configuration.baseDirectory,
@Suppress("LeakingThis")
rxIoScheduler, rxIoScheduler,
Duration.ofMillis(configuration.additionalNodeInfoPollingFrequencyMsec) Duration.ofMillis(configuration.additionalNodeInfoPollingFrequencyMsec)
), ),
@ -846,7 +832,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
platformClock, platformClock,
database, database,
flowStarter, flowStarter,
servicesForResolution, services,
flowLogicRefFactory, flowLogicRefFactory,
nodeProperties, nodeProperties,
configuration.drainingModePollPeriod, configuration.drainingModePollPeriod,
@ -1160,12 +1146,19 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
networkParameters: NetworkParameters) networkParameters: NetworkParameters)
protected open fun makeVaultService(keyManagementService: KeyManagementService, protected open fun makeVaultService(keyManagementService: KeyManagementService,
services: NodeServicesForResolution,
database: CordaPersistence, database: CordaPersistence,
cordappLoader: CordappLoader): VaultServiceInternal { cordappLoader: CordappLoader): VaultServiceInternal {
return NodeVaultService(platformClock, keyManagementService, services, database, schemaService, cordappLoader.appClassLoader) 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, // 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). // 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) // 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 rpcFlows = ArrayList<Class<out FlowLogic<*>>>()
override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage(database) override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage(database)
override val identityService: IdentityService get() = this@AbstractNode.identityService 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 nodeProperties: NodePropertiesStore get() = this@AbstractNode.nodeProperties
override val database: CordaPersistence get() = this@AbstractNode.database override val database: CordaPersistence get() = this@AbstractNode.database
override val monitoringService: MonitoringService get() = this@AbstractNode.monitoringService 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 contractUpgradeService: ContractUpgradeService get() = this@AbstractNode.contractUpgradeService
override val auditService: AuditService get() = this@AbstractNode.auditService override val auditService: AuditService get() = this@AbstractNode.auditService
override val attachments: AttachmentStorageInternal get() = this@AbstractNode.attachments override val attachments: AttachmentStorageInternal get() = this@AbstractNode.attachments
@ -1216,6 +1208,10 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
private lateinit var _networkParameters: NetworkParameters private lateinit var _networkParameters: NetworkParameters
override val networkParameters: NetworkParameters get() = _networkParameters override val networkParameters: NetworkParameters get() = _networkParameters
init {
this@AbstractNode.attachments.servicesForResolution = this
}
fun start(myInfo: NodeInfo, networkParameters: NetworkParameters) { fun start(myInfo: NodeInfo, networkParameters: NetworkParameters) {
this._myInfo = myInfo this._myInfo = myInfo
this._networkParameters = networkParameters this._networkParameters = networkParameters
@ -1300,13 +1296,13 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
this@AbstractNode.runOnStop += runOnStop this@AbstractNode.runOnStop += runOnStop
} }
override fun specialise(ltx: LedgerTransaction): LedgerTransaction {
return servicesForResolution.specialise(ltx)
}
override fun onNewNetworkParameters(networkParameters: NetworkParameters) { override fun onNewNetworkParameters(networkParameters: NetworkParameters) {
this._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.FlowLogic
import net.corda.core.flows.StartableByService import net.corda.core.flows.StartableByService
import net.corda.core.internal.FlowStateMachineHandle import net.corda.core.internal.FlowStateMachineHandle
import net.corda.core.internal.ServiceHubCoreInternal
import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.doneFuture
import net.corda.core.messaging.FlowHandle import net.corda.core.messaging.FlowHandle
import net.corda.core.messaging.FlowHandleImpl import net.corda.core.messaging.FlowHandleImpl
import net.corda.core.messaging.FlowProgressHandle import net.corda.core.messaging.FlowProgressHandle
import net.corda.core.messaging.FlowProgressHandleImpl import net.corda.core.messaging.FlowProgressHandleImpl
import net.corda.core.node.AppServiceHub 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.ServiceLifecycleEvent
import net.corda.core.node.services.ServiceLifecycleObserver import net.corda.core.node.services.ServiceLifecycleObserver
import net.corda.core.node.services.vault.CordaTransactionSupport 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
import net.corda.nodeapi.internal.lifecycle.NodeLifecycleObserver.Companion.reportSuccess import net.corda.nodeapi.internal.lifecycle.NodeLifecycleObserver.Companion.reportSuccess
import rx.Observable 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. * 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, override val database: CordaTransactionSupport,
private val nodeLifecycleEventsDistributor: NodeLifecycleEventsDistributor) private val nodeLifecycleEventsDistributor: NodeLifecycleEventsDistributor)
: AppServiceHub, ServiceHub by serviceHub { : AppServiceHub, ServiceHubCoreInternal by serviceHub {
companion object { 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.AllEnabledMetricAttributeFilter
import com.palominolabs.metrics.newrelic.NewRelicReporter import com.palominolabs.metrics.newrelic.NewRelicReporter
import io.netty.util.NettyRuntime import io.netty.util.NettyRuntime
import net.corda.nodeapi.internal.rpc.client.AMQPClientSerializationScheme
import net.corda.cliutils.ShellConstants import net.corda.cliutils.ShellConstants
import net.corda.common.logging.errorReporting.NodeDatabaseErrors
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.identity.CordaX500Name 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.node.ServiceHub
import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal.SerializationEnvironment
import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.node.CordaClock 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.RPCSecurityManager
import net.corda.node.internal.security.RPCSecurityManagerImpl import net.corda.node.internal.security.RPCSecurityManagerImpl
import net.corda.node.internal.security.RPCSecurityManagerWithAdditionalUser 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.Permissions
import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.FlowStarter
import net.corda.node.services.api.ServiceHubInternal 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.DefaultNamedCacheFactory
import net.corda.node.utilities.DemoClock import net.corda.node.utilities.DemoClock
import net.corda.node.utilities.errorAndTerminate import net.corda.node.utilities.errorAndTerminate
import net.corda.node.verification.ExternalVerifierHandle
import net.corda.nodeapi.internal.ArtemisMessagingClient 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.ShutdownHook
import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.addShutdownHook
import net.corda.nodeapi.internal.bridging.BridgeControlListener 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.crypto.X509Utilities
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
import net.corda.nodeapi.internal.protonwrapper.netty.toRevocationConfig 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_P2P_CONTEXT
import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
import net.corda.serialization.internal.AMQP_RPC_SERVER_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.SerializationFactoryImpl
import net.corda.serialization.internal.amqp.SerializationFactoryCacheKey import net.corda.serialization.internal.amqp.SerializationFactoryCacheKey
import net.corda.serialization.internal.amqp.SerializerFactory 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.JavaVersion
import org.apache.commons.lang3.SystemUtils import org.apache.commons.lang3.SystemUtils
import org.h2.jdbc.JdbcSQLNonTransientConnectionException import org.h2.jdbc.JdbcSQLNonTransientConnectionException
@ -92,7 +94,6 @@ import java.lang.Long.max
import java.lang.Long.min import java.lang.Long.min
import java.net.BindException import java.net.BindException
import java.net.InetAddress import java.net.InetAddress
import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import java.time.Clock import java.time.Clock
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -194,13 +195,14 @@ open class Node(configuration: NodeConfiguration,
} }
override val log: Logger get() = staticLog override val log: Logger get() = staticLog
override val transactionVerifierWorkerCount: Int get() = 4
private var internalRpcMessagingClient: InternalRPCMessagingClient? = null private var internalRpcMessagingClient: InternalRPCMessagingClient? = null
private var rpcBroker: ArtemisBroker? = null private var rpcBroker: ArtemisBroker? = null
protected open val journalBufferTimeout : Int? = null protected open val journalBufferTimeout : Int? = null
private val externalVerifierHandle = ExternalVerifierHandle(services).also { runOnStop += it::close }
private var shutdownHook: ShutdownHook? = null private var shutdownHook: ShutdownHook? = null
// DISCUSSION // DISCUSSION
@ -297,7 +299,7 @@ open class Node(configuration: NodeConfiguration,
printBasicNodeInfo("Advertised P2P messaging addresses", nodeInfo.addresses.joinToString()) printBasicNodeInfo("Advertised P2P messaging addresses", nodeInfo.addresses.joinToString())
val rpcServerConfiguration = RPCServerConfiguration.DEFAULT 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) 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 connection address", it.primary.toString())
printBasicNodeInfo("RPC admin connection address", it.admin.toString()) printBasicNodeInfo("RPC admin connection address", it.admin.toString())
@ -353,22 +355,18 @@ open class Node(configuration: NodeConfiguration,
) )
} }
private fun startLocalRpcBroker(securityManager: RPCSecurityManager): BrokerAddresses? { private fun startLocalRpcBroker(securityManager: RPCSecurityManager): BrokerAddresses {
return with(configuration) { val rpcBrokerDirectory = configuration.baseDirectory / "brokers" / "rpc"
rpcOptions.address.let { with(configuration.rpcOptions) {
val rpcBrokerDirectory: Path = baseDirectory / "brokers" / "rpc" rpcBroker = if (useSsl) {
with(rpcOptions) { ArtemisRpcBroker.withSsl(configuration.p2pSslOptions, this.address, adminAddress, sslConfig!!, securityManager, MAX_RPC_MESSAGE_SIZE,
rpcBroker = if (useSsl) { journalBufferTimeout, configuration.jmxMonitoringHttpPort != null, rpcBrokerDirectory, configuration.shouldStartLocalShell())
ArtemisRpcBroker.withSsl(configuration.p2pSslOptions, this.address, adminAddress, sslConfig!!, securityManager, MAX_RPC_MESSAGE_SIZE, } else {
journalBufferTimeout, jmxMonitoringHttpPort != null, rpcBrokerDirectory, shouldStartLocalShell()) ArtemisRpcBroker.withoutSsl(configuration.p2pSslOptions, this.address, adminAddress, securityManager, MAX_RPC_MESSAGE_SIZE,
} else { journalBufferTimeout, configuration.jmxMonitoringHttpPort != null, rpcBrokerDirectory, configuration.shouldStartLocalShell())
ArtemisRpcBroker.withoutSsl(configuration.p2pSslOptions, this.address, adminAddress, securityManager, MAX_RPC_MESSAGE_SIZE,
journalBufferTimeout, jmxMonitoringHttpPort != null, rpcBrokerDirectory, shouldStartLocalShell())
}
}
rpcBroker!!.addresses
} }
} }
return rpcBroker!!.addresses
} }
override fun myAddresses(): List<NetworkHostAndPort> = listOf(getAdvertisedAddress()) + configuration.additionalP2PAddresses 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. * machine's public IP address to be used instead by looking through the network interfaces.
*/ */
private fun tryDetectIfNotPublicHost(host: String): String? { 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." + 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") "To disable autodetect set detectPublicIp = false in the node.conf, or consider using messagingServerAddress and messagingServerExternal")
val foundPublicIP = AddressUtils.tryDetectPublicIP() val foundPublicIP = AddressUtils.tryDetectPublicIP()
@ -572,7 +570,7 @@ open class Node(configuration: NodeConfiguration,
if (!initialiseSerialization) return if (!initialiseSerialization) return
val classloader = cordappLoader.appClassLoader val classloader = cordappLoader.appClassLoader
val customScheme = System.getProperty("experimental.corda.customSerializationScheme")?.let { val customScheme = System.getProperty("experimental.corda.customSerializationScheme")?.let {
scanForCustomSerializationScheme(it, classloader) loadCustomSerializationScheme(it, classloader)
} }
nodeSerializationEnv = SerializationEnvironment.with( nodeSerializationEnv = SerializationEnvironment.with(
SerializationFactoryImpl().apply { 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. */ /** Starts a blocking event loop for message dispatch. */
fun run() { fun run() {
internalRpcMessagingClient?.start(rpcBroker!!.serverControl) 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 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 { inline fun <reified A : Annotation> Class<*>.requireAnnotation(): A {
return requireNotNull(getDeclaredAnnotation(A::class.java)) { "$name needs to be annotated with ${A::class.java.name}" } 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 package net.corda.node.internal.cordapp
import com.google.common.collect.HashBiMap import com.google.common.collect.HashBiMap
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractClassName import net.corda.core.contracts.ContractClassName
import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappContext 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.flows.FlowLogic
import net.corda.core.internal.DEPLOYED_CORDAPP_UPLOADER import net.corda.core.internal.DEPLOYED_CORDAPP_UPLOADER
import net.corda.core.internal.cordapp.CordappImpl import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.isUploaderTrusted import net.corda.core.internal.cordapp.CordappProviderInternal
import net.corda.core.node.services.AttachmentFixup import net.corda.core.internal.verification.AttachmentFixups
import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.AttachmentStorage
import net.corda.core.serialization.MissingAttachmentsException
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.contextLogger
import net.corda.node.services.persistence.AttachmentStorageInternal import net.corda.node.services.persistence.AttachmentStorageInternal
import net.corda.nodeapi.internal.cordapp.CordappLoader import net.corda.nodeapi.internal.cordapp.CordappLoader
import java.net.JarURLConnection
import java.net.URL import java.net.URL
import java.nio.file.FileAlreadyExistsException
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.jar.JarFile
/** /**
* Cordapp provider and store. For querying CorDapps for their attachment and vice versa. * 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, open class CordappProviderImpl(val cordappLoader: CordappLoader,
private val cordappConfigProvider: CordappConfigProvider, private val cordappConfigProvider: CordappConfigProvider,
private val attachmentStorage: AttachmentStorage) : SingletonSerializeAsToken(), CordappProviderInternal { private val attachmentStorage: AttachmentStorage) : SingletonSerializeAsToken(), CordappProviderInternal {
companion object {
const val COMMENT_MARKER = '#'
private val log = contextLogger()
}
private val contextCache = ConcurrentHashMap<Cordapp, CordappContext>() private val contextCache = ConcurrentHashMap<Cordapp, CordappContext>()
private val cordappAttachments = HashBiMap.create<SecureHash, URL>() 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 * Current known CorDapps loaded on this node
@ -47,7 +40,7 @@ open class CordappProviderImpl(val cordappLoader: CordappLoader,
cordappAttachments.putAll(loadContractsIntoAttachmentStore()) cordappAttachments.putAll(loadContractsIntoAttachmentStore())
verifyInstalledCordapps() verifyInstalledCordapps()
// Load the fix-ups after uploading any new contracts into attachment storage. // Load the fix-ups after uploading any new contracts into attachment storage.
attachmentFixups.addAll(loadAttachmentFixups()) attachmentFixups.load(cordappLoader.appClassLoader)
} }
private fun verifyInstalledCordapps() { private fun verifyInstalledCordapps() {
@ -79,116 +72,35 @@ open class CordappProviderImpl(val cordappLoader: CordappLoader,
*/ */
fun getCordappAttachmentId(cordapp: Cordapp): SecureHash? = cordappAttachments.inverse()[cordapp.jarPath] fun getCordappAttachmentId(cordapp: Cordapp): SecureHash? = cordappAttachments.inverse()[cordapp.jarPath]
private fun loadContractsIntoAttachmentStore(): Map<SecureHash, URL> = private fun loadContractsIntoAttachmentStore(): Map<SecureHash, URL> {
cordapps.filter { it.contractClassNames.isNotEmpty() }.map { cordapp -> return cordapps.filter { it.contractClassNames.isNotEmpty() }.associate { cordapp ->
cordapp.jarPath.openStream().use { stream -> cordapp.jarPath.openStream().use { stream ->
try { try {
// This code can be reached by [MockNetwork] tests which uses [MockAttachmentStorage] // This code can be reached by [MockNetwork] tests which uses [MockAttachmentStorage]
// [MockAttachmentStorage] cannot implement [AttachmentStorageInternal] because // [MockAttachmentStorage] cannot implement [AttachmentStorageInternal] because
// doing so results in internal functions being exposed in the public API. // doing so results in internal functions being exposed in the public API.
if (attachmentStorage is AttachmentStorageInternal) { if (attachmentStorage is AttachmentStorageInternal) {
attachmentStorage.privilegedImportAttachment( attachmentStorage.privilegedImportAttachment(
stream, stream,
DEPLOYED_CORDAPP_UPLOADER, DEPLOYED_CORDAPP_UPLOADER,
cordapp.info.shortName cordapp.info.shortName
) )
} else { } else {
attachmentStorage.importAttachment( attachmentStorage.importAttachment(
stream, stream,
DEPLOYED_CORDAPP_UPLOADER, DEPLOYED_CORDAPP_UPLOADER,
cordapp.info.shortName cordapp.info.shortName
) )
}
} catch (faee: java.nio.file.FileAlreadyExistsException) {
AttachmentId.create(faee.message!!)
} }
} to cordapp.jarPath } catch (faee: FileAlreadyExistsException) {
}.toMap() AttachmentId.create(faee.message!!)
/**
* 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()
} }
} } to cordapp.jarPath
}
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)
}
} }
} }
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> { override fun fixupAttachmentIds(attachmentIds: Collection<AttachmentId>): Set<AttachmentId> {
val replacementIds = LinkedHashSet(attachmentIds) return attachmentFixups.fixupAttachmentIds(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
} }
/** /**

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.cordapp.Cordapp
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256 import net.corda.core.crypto.sha256
import net.corda.core.flows.* import net.corda.core.flows.FlowLogic
import net.corda.core.internal.* 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
import net.corda.core.internal.cordapp.CordappImpl.Companion.UNKNOWN_INFO import net.corda.core.internal.cordapp.CordappImpl.Companion.UNKNOWN_INFO
import net.corda.core.internal.cordapp.get 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.NotaryService
import net.corda.core.internal.notary.SinglePartyNotaryService 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.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.schemas.MappedSchema
import net.corda.core.serialization.CheckpointCustomSerializer import net.corda.core.serialization.CheckpointCustomSerializer
import net.corda.core.serialization.SerializationCustomSerializer import net.corda.core.serialization.SerializationCustomSerializer
@ -33,12 +51,12 @@ import java.math.BigInteger
import java.net.URL import java.net.URL
import java.net.URLClassLoader import java.net.URLClassLoader
import java.nio.file.Path import java.nio.file.Path
import java.util.* import java.util.Random
import java.util.ServiceLoader
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.jar.JarInputStream import java.util.jar.JarInputStream
import java.util.jar.Manifest import java.util.jar.Manifest
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
import kotlin.collections.LinkedHashSet
import kotlin.reflect.KClass 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>? { private fun <T : Any> loadClass(className: String, type: KClass<T>): Class<out T>? {
return try { return try {
Class.forName(className, false, appClassLoader).asSubclass(type.java) loadClassOfType(type.java, className, false, appClassLoader)
} catch (e: ClassCastException) { } catch (e: ClassCastException) {
logger.warn("As $className must be a sub-type of ${type.java.name}") logger.warn("As $className must be a sub-type of ${type.java.name}")
null null

View File

@ -1,8 +1,6 @@
package net.corda.node.internal.security package net.corda.node.internal.security
import com.github.benmanes.caffeine.cache.Cache import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine
import com.google.common.primitives.Ints import com.google.common.primitives.Ints
import net.corda.core.internal.NamedCacheFactory import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.uncheckedCast 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> { private fun <K : Any, V> buildCache(name: String): ShiroCache<K, V> {
logger.info("Constructing cache '$name' with maximumSize=$maxSize, TTL=${timeToLiveSeconds}s") 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 { companion object {

View File

@ -7,17 +7,13 @@ import liquibase.database.jvm.JdbcConnection
import liquibase.exception.ValidationErrors import liquibase.exception.ValidationErrors
import liquibase.resource.ResourceAccessor import liquibase.resource.ResourceAccessor
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.node.SimpleClock
import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.persistence.AbstractPartyToX500NameAsStringConverter 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.node.services.persistence.PublicKeyToTextConverter
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import java.io.PrintWriter import java.io.PrintWriter
import java.sql.Connection import java.sql.Connection
import java.sql.SQLFeatureNotSupportedException import java.sql.SQLFeatureNotSupportedException
import java.time.Clock
import java.util.logging.Logger import java.util.logging.Logger
import javax.sql.DataSource import javax.sql.DataSource
@ -39,11 +35,6 @@ abstract class CordaMigration : CustomTaskChange {
private lateinit var _cordaDB: CordaPersistence 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. * 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 = createDatabase(url, cacheFactory, identityService, schema)
cordaDB.start(dataSource) cordaDB.start(dataSource)
identityService.database = cordaDB 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, 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 package net.corda.node.migration
import liquibase.database.Database 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.node.services.Vault
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentStateRef 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.core.utilities.contextLogger
import net.corda.node.internal.DBNetworkParametersStorage import net.corda.node.internal.DBNetworkParametersStorage
import net.corda.node.internal.schemas.NodeInfoSchemaV1 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.network.PersistentNetworkMapCache
import net.corda.node.services.persistence.DBTransactionStorage import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.persistence.NodeAttachmentService 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.VaultSchemaV1
import net.corda.node.services.vault.toStateRef
import net.corda.nodeapi.internal.persistence.CordaPersistence 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.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 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.Root
import javax.persistence.criteria.Selection import javax.persistence.criteria.Selection
class VaultStateMigration : CordaMigration() { class VaultStateMigration : CordaMigration() {
companion object { override fun execute(database: Database) {
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")
}
initialiseNodeServices(database, setOf(VaultMigrationSchemaV1, VaultSchemaV1, NodeInfoSchemaV1)) initialiseNodeServices(database, setOf(VaultMigrationSchemaV1, VaultSchemaV1, NodeInfoSchemaV1))
var statesSkipped = 0
val persistentStates = VaultStateIterator(cordaDB) val persistentStates = VaultStateIterator(cordaDB)
if (persistentStates.numStates > 0) { if (persistentStates.numStates > 0) {
logger.warn("Found ${persistentStates.numStates} states to update from a previous version. This may take a while for large " throw VaultStateMigrationException("Found ${persistentStates.numStates} states that need to be updated to V4. Please upgrade " +
+ "volumes of data.") "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 * 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. * 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 { companion object {
val logger = contextLogger() 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 private val criteriaBuilder = database.entityManagerFactory.criteriaBuilder
val numStates = getTotalStates() val numStates = getTotalStates()
@ -224,111 +98,6 @@ class VaultStateIterator(private val database: CordaPersistence) : Iterator<Vaul
result 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) 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.SecureHash
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.TransactionMetadata
import net.corda.core.flows.StateMachineRunId import net.corda.core.flows.StateMachineRunId
import net.corda.core.flows.TransactionMetadata
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.FlowStateMachineHandle import net.corda.core.internal.FlowStateMachineHandle
import net.corda.core.internal.NamedCacheFactory 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.concurrent.OpenFuture
import net.corda.core.internal.dependencies import net.corda.core.internal.dependencies
import net.corda.core.internal.requireSupportedHashType import net.corda.core.internal.requireSupportedHashType
import net.corda.core.internal.verification.Verifier
import net.corda.core.internal.warnOnce import net.corda.core.internal.warnOnce
import net.corda.core.messaging.DataFeed import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.StateMachineTransactionMapping 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.NetworkMapCache
import net.corda.core.node.services.NetworkMapCacheBase import net.corda.core.node.services.NetworkMapCacheBase
import net.corda.core.node.services.TransactionStorage 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.SignedTransaction
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import net.corda.core.transactions.defaultVerifier
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.internal.InitiatedFlowFactory
import net.corda.node.internal.cordapp.CordappProviderInternal
import net.corda.node.services.DbTransactionsResolver import net.corda.node.services.DbTransactionsResolver
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.messaging.MessagingService 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.persistence.AttachmentStorageInternal
import net.corda.node.services.statemachine.ExternalEvent import net.corda.node.services.statemachine.ExternalEvent
import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.statemachine.FlowStateMachineImpl
import net.corda.node.verification.NoDbAccessVerifier
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import java.lang.IllegalStateException import java.security.PublicKey
import java.security.SignatureException import java.security.SignatureException
import java.util.ArrayList
import java.util.Collections import java.util.Collections
import java.util.HashMap
import java.util.HashSet
import java.util.LinkedHashSet
interface NetworkMapCacheInternal : NetworkMapCache, NetworkMapCacheBase { interface NetworkMapCacheInternal : NetworkMapCache, NetworkMapCacheBase {
override val nodeReady: OpenFuture<Void?> override val nodeReady: OpenFuture<Void?>
@ -186,11 +186,14 @@ interface ServiceHubInternal : ServiceHubCoreInternal {
val configuration: NodeConfiguration val configuration: NodeConfiguration
val nodeProperties: NodePropertiesStore val nodeProperties: NodePropertiesStore
val networkMapUpdater: NetworkMapUpdater val networkMapUpdater: NetworkMapUpdater
override val cordappProvider: CordappProviderInternal
fun getFlowFactory(initiatingFlowClass: Class<out FlowLogic<*>>): InitiatedFlowFactory<*>? fun getFlowFactory(initiatingFlowClass: Class<out FlowLogic<*>>): InitiatedFlowFactory<*>?
val cacheFactory: NamedCacheFactory val cacheFactory: NamedCacheFactory
override fun createVerifier(ltx: LedgerTransaction, serializationContext: SerializationContext): Verifier {
return NoDbAccessVerifier(defaultVerifier(ltx, serializationContext))
}
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) = override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) =
recordTransactions(statesToRecord, txs, SIGNATURE_VERIFICATION_DISABLED) recordTransactions(statesToRecord, txs, SIGNATURE_VERIFICATION_DISABLED)

View File

@ -1,10 +1,16 @@
package net.corda.node.services.attachments package net.corda.node.services.attachments
import com.github.benmanes.caffeine.cache.Caffeine
import net.corda.core.contracts.Attachment import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractAttachment import net.corda.core.contracts.ContractAttachment
import net.corda.core.crypto.SecureHash 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.AttachmentId
import net.corda.core.node.services.vault.AttachmentQueryCriteria import net.corda.core.node.services.vault.AttachmentQueryCriteria
import net.corda.core.node.services.vault.Builder import net.corda.core.node.services.vault.Builder
@ -35,10 +41,7 @@ class NodeAttachmentTrustCalculator(
) : this(attachmentStorage, null, cacheFactory, blacklistedAttachmentSigningKeys) ) : this(attachmentStorage, null, cacheFactory, blacklistedAttachmentSigningKeys)
// A cache for caching whether a signing key is trusted // A cache for caching whether a signing key is trusted
private val trustedKeysCache = cacheFactory.buildNamed<PublicKey, Boolean>( private val trustedKeysCache = cacheFactory.buildNamed<PublicKey, Boolean>("NodeAttachmentTrustCalculator_trustedKeysCache")
Caffeine.newBuilder(),
"NodeAttachmentTrustCalculator_trustedKeysCache"
)
override fun calculate(attachment: Attachment): Boolean { override fun calculate(attachment: Attachment): Boolean {
@ -92,9 +95,7 @@ class NodeAttachmentTrustCalculator(
val trustRoot = if (attachment.isSignedByBlacklistedKey()) { val trustRoot = if (attachment.isSignedByBlacklistedKey()) {
null null
} else { } else {
attachment.signerKeys attachment.signerKeys.firstNotNullOfOrNull { publicKeyToTrustRootMap[it] }
.mapNotNull { publicKeyToTrustRootMap[it] }
.firstOrNull()
} }
attachmentTrustInfos += AttachmentTrustInfo( attachmentTrustInfos += AttachmentTrustInfo(
attachmentId = attachment.id, attachmentId = attachment.id,

View File

@ -1,6 +1,5 @@
package net.corda.node.services.persistence package net.corda.node.services.persistence
import com.github.benmanes.caffeine.cache.Caffeine
import net.corda.core.crypto.toStringShort import net.corda.core.crypto.toStringShort
import net.corda.core.internal.NamedCacheFactory import net.corda.core.internal.NamedCacheFactory
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
@ -19,10 +18,7 @@ class PublicKeyToOwningIdentityCacheImpl(private val database: CordaPersistence,
val log = contextLogger() val log = contextLogger()
} }
private val cache = cacheFactory.buildNamed<PublicKey, KeyOwningIdentity>( private val cache = cacheFactory.buildNamed<PublicKey, KeyOwningIdentity>("PublicKeyToOwningIdentityCache_cache")
Caffeine.newBuilder(),
"PublicKeyToOwningIdentityCache_cache"
)
/** /**
* Return the owning identity associated with a given key. * 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 com.google.common.primitives.Primitives
import net.corda.core.flows.* import net.corda.core.flows.*
import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.loadClassOfType
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import org.slf4j.Logger import org.slf4j.Logger
import java.lang.ClassCastException
import java.lang.reflect.ParameterizedType import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type import java.lang.reflect.Type
import java.lang.reflect.TypeVariable 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<*>> { private fun validatedFlowClassFromName(flowClassName: String): Class<out FlowLogic<*>> {
val forName = try { return try {
Class.forName(flowClassName, true, classloader) loadClassOfType<FlowLogic<*>>(flowClassName, true, classloader)
} catch (e: ClassNotFoundException) { } catch (e: ClassNotFoundException) {
throw IllegalFlowLogicException(flowClassName, "Flow not found: $flowClassName") throw IllegalFlowLogicException(flowClassName, "Flow not found: $flowClassName")
} } catch (e: ClassCastException) {
return forName.asSubclass(FlowLogic::class.java) ?:
throw IllegalFlowLogicException(flowClassName, "The class $flowClassName is not a subclass of FlowLogic.") throw IllegalFlowLogicException(flowClassName, "The class $flowClassName is not a subclass of FlowLogic.")
}
} }
override fun createForRPC(flowClass: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef { 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.TransactionDeserialisationException
import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.bufferUntilSubscribed import net.corda.core.internal.bufferUntilSubscribed
import net.corda.core.internal.mapToSet
import net.corda.core.internal.tee import net.corda.core.internal.tee
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
import net.corda.core.internal.verification.VerifyingServiceHub
import net.corda.core.internal.warnOnce import net.corda.core.internal.warnOnce
import net.corda.core.messaging.DataFeed import net.corda.core.messaging.DataFeed
import net.corda.core.node.StatesToRecord 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.debug
import net.corda.core.utilities.toNonEmptySet import net.corda.core.utilities.toNonEmptySet
import net.corda.core.utilities.trace 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.SchemaService
import net.corda.node.services.api.VaultServiceInternal import net.corda.node.services.api.VaultServiceInternal
import net.corda.node.services.schema.PersistentStateService 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.CriteriaUpdate
import javax.persistence.criteria.Predicate import javax.persistence.criteria.Predicate
import javax.persistence.criteria.Root import javax.persistence.criteria.Root
import kotlin.collections.ArrayList
import kotlin.collections.LinkedHashSet
import kotlin.collections.component1 import kotlin.collections.component1
import kotlin.collections.component2 import kotlin.collections.component2
@ -98,7 +97,7 @@ import kotlin.collections.component2
class NodeVaultService( class NodeVaultService(
private val clock: Clock, private val clock: Clock,
private val keyManagementService: KeyManagementService, private val keyManagementService: KeyManagementService,
private val servicesForResolution: NodeServicesForResolution, private val serviceHub: VerifyingServiceHub,
private val database: CordaPersistence, private val database: CordaPersistence,
schemaService: SchemaService, schemaService: SchemaService,
private val appClassloader: ClassLoader private val appClassloader: ClassLoader
@ -231,7 +230,7 @@ class NodeVaultService(
// Persist the consumed inputs. // Persist the consumed inputs.
consumedStateRefs.forEach { stateRef -> 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 { state?.run {
// Only update the state if it has not previously been consumed (this could have happened if the transaction is being // Only update the state if it has not previously been consumed (this could have happened if the transaction is being
// re-recorded. // re-recorded.
@ -312,7 +311,7 @@ class NodeVaultService(
fun <T> withValidDeserialization(list: List<T>, txId: SecureHash): Map<Int, T> { fun <T> withValidDeserialization(list: List<T>, txId: SecureHash): Map<Int, T> {
var error: TransactionDeserialisationException? = null var error: TransactionDeserialisationException? = null
val map = (0 until list.size).mapNotNull { idx -> val map = list.indices.mapNotNull { idx ->
try { try {
idx to list[idx] idx to list[idx]
} catch (e: TransactionDeserialisationException) { } catch (e: TransactionDeserialisationException) {
@ -354,7 +353,7 @@ class NodeVaultService(
val outputRefs = tx.outRefsOfType<ContractState>().map { it.ref } val outputRefs = tx.outRefsOfType<ContractState>().map { it.ref }
val seenRefs = loadStates(outputRefs).map { it.ref } val seenRefs = loadStates(outputRefs).map { it.ref }
val unseenRefs = outputRefs - seenRefs val unseenRefs = outputRefs - seenRefs
val unseenOutputIdxs = unseenRefs.map { it.index }.toSet() val unseenOutputIdxs = unseenRefs.mapToSet { it.index }
outputs.filter { it.key in unseenOutputIdxs } outputs.filter { it.key in unseenOutputIdxs }
} else { } else {
outputs outputs
@ -383,7 +382,7 @@ class NodeVaultService(
StatesToRecord.ALL_VISIBLE, StatesToRecord.ONLY_RELEVANT -> { StatesToRecord.ALL_VISIBLE, StatesToRecord.ONLY_RELEVANT -> {
val notSeenReferences = tx.references - loadStates(tx.references).map { it.ref } val notSeenReferences = tx.references - loadStates(tx.references).map { it.ref }
// TODO: This is expensive - is there another way? // TODO: This is expensive - is there another way?
tx.toLedgerTransaction(servicesForResolution).deserializableRefStates() tx.toLedgerTransaction(serviceHub).deserializableRefStates()
.filter { (_, stateAndRef) -> stateAndRef.ref in notSeenReferences } .filter { (_, stateAndRef) -> stateAndRef.ref in notSeenReferences }
.values .values
} }
@ -398,8 +397,8 @@ class NodeVaultService(
// We also can't do filtering beforehand, since for notary change transactions output encumbrance pointers // We also can't do filtering beforehand, since for notary change transactions output encumbrance pointers
// get recalculated based on input positions. // get recalculated based on input positions.
val ltx: FullTransaction = when (tx) { val ltx: FullTransaction = when (tx) {
is NotaryChangeWireTransaction -> tx.resolve(servicesForResolution, emptyList()) is NotaryChangeWireTransaction -> tx.resolve(serviceHub, emptyList())
is ContractUpgradeWireTransaction -> tx.resolve(servicesForResolution, emptyList()) is ContractUpgradeWireTransaction -> tx.resolve(serviceHub, emptyList())
else -> throw IllegalArgumentException("Unsupported transaction type: ${tx.javaClass.name}") else -> throw IllegalArgumentException("Unsupported transaction type: ${tx.javaClass.name}")
} }
val myKeys by lazy { keyManagementService.filterMyKeys(ltx.outputs.flatMap { it.data.participants.map { it.owningKey } }) } 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 stateStatusPredication = criteriaBuilder.equal(get<Vault.StateStatus>(VaultSchemaV1.VaultStates::stateStatus.name), Vault.StateStatus.UNCONSUMED)
val lockIdPredicate = criteriaBuilder.or(get<String>(VaultSchemaV1.VaultStates::lockId.name).isNull, val lockIdPredicate = criteriaBuilder.or(get<String>(VaultSchemaV1.VaultStates::lockId.name).isNull,
criteriaBuilder.equal(get<String>(VaultSchemaV1.VaultStates::lockId.name), lockId.toString())) criteriaBuilder.equal(get<String>(VaultSchemaV1.VaultStates::lockId.name), lockId.toString()))
update.set(get<String>(VaultSchemaV1.VaultStates::lockId.name), lockId.toString()) update.set(get(VaultSchemaV1.VaultStates::lockId.name), lockId.toString())
update.set(get<Instant>(VaultSchemaV1.VaultStates::lockUpdateTime.name), softLockTimestamp) update.set(get(VaultSchemaV1.VaultStates::lockUpdateTime.name), softLockTimestamp)
update.where(stateStatusPredication, lockIdPredicate, *commonPredicates) update.where(stateStatusPredication, lockIdPredicate, *commonPredicates)
} }
if (updatedRows > 0 && updatedRows == stateRefs.size) { if (updatedRows > 0 && updatedRows == stateRefs.size) {
@ -596,8 +595,8 @@ class NodeVaultService(
criteriaBuilder.executeUpdate(session, stateRefs) { update, persistentStateRefs -> criteriaBuilder.executeUpdate(session, stateRefs) { update, persistentStateRefs ->
val stateStatusPredication = criteriaBuilder.equal(get<Vault.StateStatus>(VaultSchemaV1.VaultStates::stateStatus.name), Vault.StateStatus.UNCONSUMED) 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()) 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<String>(VaultSchemaV1.VaultStates::lockId.name), criteriaBuilder.nullLiteral(String::class.java))
update.set(get<Instant>(VaultSchemaV1.VaultStates::lockUpdateTime.name), softLockTimestamp) update.set(get(VaultSchemaV1.VaultStates::lockUpdateTime.name), softLockTimestamp)
configure(update, arrayOf(stateStatusPredication, lockIdPredicate), persistentStateRefs) configure(update, arrayOf(stateStatusPredication, lockIdPredicate), persistentStateRefs)
} }
@ -748,16 +747,13 @@ class NodeVaultService(
if (result0 is VaultSchemaV1.VaultStates) { if (result0 is VaultSchemaV1.VaultStates) {
statesMetadata.add(result0.toStateMetadata()) statesMetadata.add(result0.toStateMetadata())
} else { } else {
log.debug { "OtherResults: ${Arrays.toString(result.toArray())}" } log.debug { "OtherResults: ${result.toArray().contentToString()}" }
otherResults.addAll(result.toArray().asList()) otherResults.addAll(result.toArray().asList())
} }
} }
} }
val states: List<StateAndRef<T>> = servicesForResolution.loadStates( val states: List<StateAndRef<T>> = serviceHub.loadStatesInternal(statesMetadata.mapToSet { it.ref }, ArrayList())
statesMetadata.mapTo(LinkedHashSet()) { it.ref },
ArrayList()
)
val totalStatesAvailable = when { val totalStatesAvailable = when {
paging.isDefault -> -1L 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.") 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 snapshotResults = _queryBy(criteria, paging, sorting, contractStateType)
val snapshotStatesRefs = snapshotResults.statesMetadata.map { it.ref }.toSet() val snapshotStatesRefs = snapshotResults.statesMetadata.mapToSet { it.ref }
val snapshotConsumedStatesRefs = snapshotResults.statesMetadata.filter { it.consumedTime != null } val snapshotConsumedStatesRefs = snapshotResults.statesMetadata.filter { it.consumedTime != null }.mapToSet { it.ref }
.map { it.ref }.toSet()
val filteredUpdates = updates.filter { it.containsType(contractStateType, snapshotResults.stateTypes) } val filteredUpdates = updates.filter { it.containsType(contractStateType, snapshotResults.stateTypes) }
.map { filterContractStates(it, contractStateType) } .map { filterContractStates(it, contractStateType) }
.filter { !hasBeenSeen(it, snapshotStatesRefs, snapshotConsumedStatesRefs) } .filter { !hasBeenSeen(it, snapshotStatesRefs, snapshotConsumedStatesRefs) }
@ -881,8 +876,8 @@ class NodeVaultService(
* the snapshot or in the observable). * the snapshot or in the observable).
*/ */
private fun <T: ContractState> hasBeenSeen(update: Vault.Update<T>, snapshotStatesRefs: Set<StateRef>, snapshotConsumedStatesRefs: Set<StateRef>): Boolean { 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 updateProducedStatesRefs = update.produced.mapToSet { it.ref }
val updateConsumedStatesRefs = update.consumed.map { it.ref }.toSet() val updateConsumedStatesRefs = update.consumed.mapToSet { it.ref }
return snapshotStatesRefs.containsAll(updateProducedStatesRefs) && snapshotConsumedStatesRefs.containsAll(updateConsumedStatesRefs) return snapshotStatesRefs.containsAll(updateProducedStatesRefs) && snapshotConsumedStatesRefs.containsAll(updateConsumedStatesRefs)
} }

View File

@ -1,6 +1,5 @@
package net.corda.node.utilities package net.corda.node.utilities
import com.github.benmanes.caffeine.cache.Caffeine
import net.corda.core.internal.NamedCacheFactory import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.VisibleForTesting
import net.corda.nodeapi.internal.persistence.contextTransactionOrNull 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. // 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>>() private val currentlyInvalid = ConcurrentHashMap<K, Wrapper.Invalidated<V>>()

View File

@ -15,8 +15,7 @@ class NonInvalidatingCache<K, V> private constructor(
private companion object { private companion object {
private fun <K, V> buildCache(cacheFactory: NamedCacheFactory, name: String, loadFunction: (K) -> V): LoadingCache<K, V> { private fun <K, V> buildCache(cacheFactory: NamedCacheFactory, name: String, loadFunction: (K) -> V): LoadingCache<K, V> {
val builder = Caffeine.newBuilder() return cacheFactory.buildNamed(name, NonInvalidatingCacheLoader(loadFunction))
return cacheFactory.buildNamed(builder, 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 package net.corda.node.internal
import net.corda.core.CordaException
import net.corda.core.serialization.CustomSerializationScheme import net.corda.core.serialization.CustomSerializationScheme
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationSchemeContext import net.corda.core.serialization.SerializationSchemeContext
import net.corda.core.utilities.ByteSequence 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.junit.Test
import org.mockito.Mockito import org.mockito.Mockito
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
@ -33,7 +34,7 @@ class CustomSerializationSchemeScanningTest {
@Test(timeout = 300_000) @Test(timeout = 300_000)
fun `Can scan for custom serialization scheme and build a serialization scheme`() { 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) val mockContext = Mockito.mock(SerializationContext::class.java)
assertFailsWith<DummySerializationSchemeException>("Tried to serialize with DummySerializationScheme") { assertFailsWith<DummySerializationSchemeException>("Tried to serialize with DummySerializationScheme") {
scheme.serialize(Any::class.java, mockContext) scheme.serialize(Any::class.java, mockContext)
@ -43,27 +44,27 @@ class CustomSerializationSchemeScanningTest {
@Test(timeout = 300_000) @Test(timeout = 300_000)
fun `verification fails with a helpful error if the class is not found in the classloader`() { fun `verification fails with a helpful error if the class is not found in the classloader`() {
val missingClassName = "org.testing.DoesNotExist" 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.") { "be found.") {
scanForCustomSerializationScheme(missingClassName, this::class.java.classLoader) loadCustomSerializationScheme(missingClassName, this::class.java.classLoader)
} }
} }
@Test(timeout = 300_000) @Test(timeout = 300_000)
fun `verification fails with a helpful error if the class is not a custom serialization scheme`() { fun `verification fails with a helpful error if the class is not a custom serialization scheme`() {
val schemeName = NonSerializationScheme::class.java.name 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.") { "implement CustomSerializationScheme.") {
scanForCustomSerializationScheme(schemeName, this::class.java.classLoader) loadCustomSerializationScheme(schemeName, this::class.java.classLoader)
} }
} }
@Test(timeout = 300_000) @Test(timeout = 300_000)
fun `verification fails with a helpful error if the class does not have a no arg constructor`() { fun `verification fails with a helpful error if the class does not have a no arg constructor`() {
val schemeName = DummySerializationSchemeWithoutNoArgConstructor::class.java.name 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.") { "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.flows.StateReplacementException
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.getRequiredTransaction
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.StatesToRecord import net.corda.core.node.StatesToRecord
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
@ -116,7 +117,7 @@ class NotaryChangeTests {
val newState = future.getOrThrow() val newState = future.getOrThrow()
assertEquals(newState.state.notary, newNotary) 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) val notaryChangeTx = recordedTx.resolveNotaryChangeTransaction(clientNodeA.services)
// Check that all encumbrances have been propagated to the outputs // 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 // 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. // 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 changedNotary = changeNotary(moved, clientNodeB, newNotaryParty)
val movedBack = moveState(changedNotary, clientNodeB, clientNodeA) val movedBack = moveState(changedNotary, clientNodeB, clientNodeA)

View File

@ -1,6 +1,5 @@
package net.corda.node.services.persistence package net.corda.node.services.persistence
import org.mockito.kotlin.*
import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount
import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef 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.AbstractParty
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.verification.toVerifyingServiceHub
import net.corda.core.node.StatesToRecord import net.corda.core.node.StatesToRecord
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.Vault 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.SampleCashSchemaV1
import net.corda.finance.test.SampleCashSchemaV2 import net.corda.finance.test.SampleCashSchemaV2
import net.corda.finance.test.SampleCashSchemaV3 import net.corda.finance.test.SampleCashSchemaV3
import net.corda.node.internal.NodeServicesForResolution
import net.corda.node.services.api.WritableTransactionStorage import net.corda.node.services.api.WritableTransactionStorage
import net.corda.node.services.schema.ContractStateAndRef import net.corda.node.services.schema.ContractStateAndRef
import net.corda.node.services.schema.NodeSchemaService 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.DatabaseConfig
import net.corda.nodeapi.internal.persistence.HibernateConfiguration import net.corda.nodeapi.internal.persistence.HibernateConfiguration
import net.corda.nodeapi.internal.persistence.HibernateSchemaChangeException 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.configureDatabase
import net.corda.testing.internal.vault.DummyDealStateSchemaV1 import net.corda.testing.internal.vault.DummyDealStateSchemaV1
import net.corda.testing.internal.vault.DummyLinearStateSchemaV1 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.internal.vault.VaultFiller
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties 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.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.Assertions.assertThatThrownBy
import org.hibernate.SessionFactory 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.math.BigDecimal
import java.time.Clock import java.time.Clock
import java.time.Instant 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.EntityManager
import javax.persistence.Tuple import javax.persistence.Tuple
import javax.persistence.criteria.CriteriaBuilder import javax.persistence.criteria.CriteriaBuilder
@ -85,7 +101,7 @@ class HibernateConfigurationTest {
val vault: VaultService get() = services.vaultService val vault: VaultService get() = services.vaultService
// Hibernate configuration objects // Hibernate configuration objects
lateinit var hibernateConfig: HibernateConfiguration private lateinit var hibernateConfig: HibernateConfiguration
private lateinit var hibernatePersister: PersistentStateService private lateinit var hibernatePersister: PersistentStateService
private lateinit var sessionFactory: SessionFactory private lateinit var sessionFactory: SessionFactory
private lateinit var entityManager: EntityManager private lateinit var entityManager: EntityManager
@ -126,7 +142,7 @@ class HibernateConfigurationTest {
override val vaultService = NodeVaultService( override val vaultService = NodeVaultService(
Clock.systemUTC(), Clock.systemUTC(),
keyManagementService, keyManagementService,
servicesForResolution as NodeServicesForResolution, toVerifyingServiceHub(),
database, database,
schemaService, schemaService,
cordappClassloader cordappClassloader
@ -236,7 +252,7 @@ class HibernateConfigurationTest {
// execute query // execute query
val queryResults = entityManager.createQuery(criteriaQuery).resultList val queryResults = entityManager.createQuery(criteriaQuery).resultList
Assertions.assertThat(queryResults.size).isEqualTo(3) assertThat(queryResults.size).isEqualTo(3)
} }
@Test(timeout=300_000) @Test(timeout=300_000)
@ -327,7 +343,7 @@ class HibernateConfigurationTest {
// execute query // execute query
val queryResults = query.resultList val queryResults = query.resultList
Assertions.assertThat(queryResults.size).isEqualTo(15) assertThat(queryResults.size).isEqualTo(15)
// try towards end // try towards end
query.firstResult = 100 query.firstResult = 100
@ -335,7 +351,7 @@ class HibernateConfigurationTest {
val lastQueryResults = query.resultList 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.core.singleIdentity
import net.corda.testing.flows.registerCoreFlowFactory import net.corda.testing.flows.registerCoreFlowFactory
import net.corda.coretesting.internal.rigorousMock 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.InternalMockNetwork
import net.corda.testing.node.internal.enclosedCordapp import net.corda.testing.node.internal.enclosedCordapp
import net.corda.testing.node.internal.startFlow import net.corda.testing.node.internal.startFlow
@ -86,11 +85,10 @@ class VaultSoftLockManagerTest {
private val mockNet = InternalMockNetwork(cordappsForAllNodes = listOf(enclosedCordapp()), defaultFactory = { args -> private val mockNet = InternalMockNetwork(cordappsForAllNodes = listOf(enclosedCordapp()), defaultFactory = { args ->
object : InternalMockNetwork.MockNode(args) { object : InternalMockNetwork.MockNode(args) {
override fun makeVaultService(keyManagementService: KeyManagementService, override fun makeVaultService(keyManagementService: KeyManagementService,
services: NodeServicesForResolution,
database: CordaPersistence, database: CordaPersistence,
cordappLoader: CordappLoader): VaultServiceInternal { cordappLoader: CordappLoader): VaultServiceInternal {
val node = this val node = this
val realVault = super.makeVaultService(keyManagementService, services, database, cordappLoader) val realVault = super.makeVaultService(keyManagementService, database, cordappLoader)
return object : SingletonSerializeAsToken(), VaultServiceInternal by realVault { return object : SingletonSerializeAsToken(), VaultServiceInternal by realVault {
override fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet<StateRef>?) { override fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet<StateRef>?) {
// Should be called before flow is removed // Should be called before flow is removed

View File

@ -1,19 +1,17 @@
package net.corda.notary.experimental.raft package net.corda.notary.experimental.raft
import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.flows.NotaryError import net.corda.core.flows.NotaryError
import net.corda.core.flows.NotaryException import net.corda.core.flows.NotaryException
import net.corda.core.flows.NotaryFlow import net.corda.core.flows.NotaryFlow
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.map import net.corda.core.internal.concurrent.map
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.testing.contracts.DummyContract 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.DUMMY_BANK_A_NAME
import net.corda.testing.core.dummyCommand
import net.corda.testing.core.singleIdentity import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.InProcess 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.NotarySpec
import net.corda.testing.node.internal.DUMMY_CONTRACTS_CORDAPP import net.corda.testing.node.internal.DUMMY_CONTRACTS_CORDAPP
import org.junit.Test import org.junit.Test
import java.util.* import java.util.Random
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
@ -39,20 +37,13 @@ class RaftNotaryServiceTests {
val bankA = startNode(providedName = DUMMY_BANK_A_NAME).map { (it as InProcess) }.getOrThrow() val bankA = startNode(providedName = DUMMY_BANK_A_NAME).map { (it as InProcess) }.getOrThrow()
val inputState = issueState(bankA, defaultNotaryIdentity) val inputState = issueState(bankA, defaultNotaryIdentity)
val firstTxBuilder = TransactionBuilder(defaultNotaryIdentity) val firstTxBuilder = DummyContract.move(inputState, bankA.services.myInfo.singleIdentity())
.addInputState(inputState)
.addCommand(dummyCommand(bankA.services.myInfo.singleIdentity().owningKey))
val firstSpendTx = bankA.services.signInitialTransaction(firstTxBuilder) val firstSpendTx = bankA.services.signInitialTransaction(firstTxBuilder)
val firstSpend = bankA.startFlow(NotaryFlow.Client(firstSpendTx)) val firstSpend = bankA.startFlow(NotaryFlow.Client(firstSpendTx))
firstSpend.getOrThrow() firstSpend.getOrThrow()
val secondSpendBuilder = TransactionBuilder(defaultNotaryIdentity).withItems(inputState).run { val secondSpendBuilder = DummyContract.move(inputState, bankA.services.myInfo.singleIdentity())
val dummyState = DummyContract.SingleOwnerState(0, bankA.services.myInfo.singleIdentity())
addOutputState(dummyState, DummyContract.PROGRAM_ID)
addCommand(dummyCommand(bankA.services.myInfo.singleIdentity().owningKey))
this
}
val secondSpendTx = bankA.services.signInitialTransaction(secondSpendBuilder) val secondSpendTx = bankA.services.signInitialTransaction(secondSpendBuilder)
val secondSpend = bankA.startFlow(NotaryFlow.Client(secondSpendTx)) 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 builder = DummyContract.generateInitial(Random().nextInt(), notary, nodeHandle.services.myInfo.singleIdentity().ref(0))
val stx = nodeHandle.services.signInitialTransaction(builder) val stx = nodeHandle.services.signInitialTransaction(builder)
nodeHandle.services.recordTransactions(stx) 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 // For caches rather than guava
implementation "com.github.ben-manes.caffeine:caffeine:$caffeine_version" implementation "com.github.ben-manes.caffeine:caffeine:$caffeine_version"
testImplementation project(":serialization")
testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}" testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
testImplementation "junit:junit:$junit_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 } // Anything else, such as arrays, will be taken care of by the above
} }
} catch (e: ClassCarpenterException) { } catch (e: ClassCarpenterException) {
throw NotSerializableException("${typeInformation.typeIdentifier.name}: ${e.message}") throw NotSerializableException("${typeInformation.typeIdentifier.name}: ${e.message}").apply { initCause(e) }
} }
return try { return try {
@ -40,7 +40,7 @@ class SchemaBuildingRemoteTypeCarpenter(private val carpenter: ClassCarpenter):
} catch (e: ClassNotFoundException) { } catch (e: ClassNotFoundException) {
// This might happen if we've been asked to carpent up a parameterised type, and it's the rawtype itself // 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. // 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() { 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.CustomSerializationScheme
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationSchemeContext
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.internal.CustomSerializationSchemeUtils.Companion.getCustomSerializationMagicFromSchemeId import net.corda.core.serialization.internal.CustomSerializationSchemeUtils.Companion.getCustomSerializationMagicFromSchemeId
import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.ByteSequence
@ -12,8 +14,7 @@ import java.io.ByteArrayOutputStream
import java.io.NotSerializableException import java.io.NotSerializableException
class CustomSerializationSchemeAdapter(private val customScheme: CustomSerializationScheme): SerializationScheme { class CustomSerializationSchemeAdapter(private val customScheme: CustomSerializationScheme): SerializationScheme {
private val serializationSchemeMagic = getCustomSerializationMagicFromSchemeId(customScheme.getSchemeId())
val serializationSchemeMagic = getCustomSerializationMagicFromSchemeId(customScheme.getSchemeId())
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean { override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
return magic == serializationSchemeMagic return magic == serializationSchemeMagic
@ -45,3 +46,20 @@ class CustomSerializationSchemeAdapter(private val customScheme: CustomSerializa
override val properties = context.properties 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-api-tests'
include 'node' include 'node'
include 'node:capsule' include 'node:capsule'
include 'verifier'
include 'client:jackson' include 'client:jackson'
include 'client:jfx' include 'client:jfx'
include 'client:mock' 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.SerializationCustomSerializer
import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.internal.SerializationEnvironment 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.amqp.AMQPServerSerializationScheme
import net.corda.nodeapi.internal.serialization.kryo.KRYO_CHECKPOINT_CONTEXT import net.corda.nodeapi.internal.serialization.kryo.KRYO_CHECKPOINT_CONTEXT
import net.corda.nodeapi.internal.serialization.kryo.KryoCheckpointSerializer import net.corda.nodeapi.internal.serialization.kryo.KryoCheckpointSerializer

View File

@ -1,9 +1,13 @@
package net.corda.testing.node package net.corda.testing.node
import com.google.common.collect.MutableClassToInstanceMap import com.google.common.collect.MutableClassToInstanceMap
import net.corda.core.CordaInternal
import net.corda.core.contracts.Attachment import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractClassName 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.StateRef
import net.corda.core.contracts.TransactionState
import net.corda.core.cordapp.CordappProvider import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic 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.identity.PartyAndCertificate
import net.corda.core.internal.PLATFORM_VERSION import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.VisibleForTesting 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.requireSupportedHashType
import net.corda.core.internal.telemetry.TelemetryComponent import net.corda.core.internal.telemetry.TelemetryComponent
import net.corda.core.internal.telemetry.TelemetryServiceImpl 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.DataFeed
import net.corda.core.messaging.FlowHandle import net.corda.core.messaging.FlowHandle
import net.corda.core.messaging.FlowProgressHandle 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.NetworkParametersService
import net.corda.core.node.services.ServiceLifecycleObserver import net.corda.core.node.services.ServiceLifecycleObserver
import net.corda.core.node.services.TransactionStorage 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.VaultService
import net.corda.core.node.services.diagnostics.DiagnosticsService import net.corda.core.node.services.diagnostics.DiagnosticsService
import net.corda.core.node.services.vault.CordaTransactionSupport import net.corda.core.node.services.vault.CordaTransactionSupport
import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.internal.AttachmentsClassLoaderCacheImpl
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.coretesting.internal.DEV_ROOT_CA import net.corda.coretesting.internal.DEV_ROOT_CA
import net.corda.node.VersionInfo 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.internal.cordapp.JarScanningCordappLoader
import net.corda.node.services.api.SchemaService import net.corda.node.services.api.SchemaService
import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage
import net.corda.node.services.api.VaultServiceInternal import net.corda.node.services.api.VaultServiceInternal
import net.corda.node.services.api.WritableTransactionStorage 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.diagnostics.NodeDiagnosticsService
import net.corda.node.services.identity.InMemoryIdentityService import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.services.identity.PersistentIdentityService 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.network.PersistentNetworkMapCache
import net.corda.node.services.persistence.PublicKeyToOwningIdentityCacheImpl import net.corda.node.services.persistence.PublicKeyToOwningIdentityCacheImpl
import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.NodeVaultService
import net.corda.nodeapi.internal.cordapp.CordappLoader import net.corda.nodeapi.internal.cordapp.CordappLoader
import net.corda.nodeapi.internal.persistence.CordaPersistence 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.MockCordappProvider
import net.corda.testing.internal.TestingNamedCacheFactory import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.testing.internal.configureDatabase 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.DriverDSLImpl
import net.corda.testing.node.internal.MockCryptoService import net.corda.testing.node.internal.MockCryptoService
import net.corda.testing.node.internal.MockKeyManagementService import net.corda.testing.node.internal.MockKeyManagementService
@ -116,7 +123,6 @@ open class MockServices private constructor(
*arrayOf(initialIdentity.keyPair) + moreKeys *arrayOf(initialIdentity.keyPair) + moreKeys
) )
) : ServiceHub { ) : ServiceHub {
companion object { companion object {
private fun cordappLoaderForPackages(packages: Iterable<String>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader { private fun cordappLoaderForPackages(packages: Iterable<String>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader {
return JarScanningCordappLoader.fromJarUrls(cordappsForPackages(packages).map { it.jarFile.toUri().toURL() }, versionInfo) 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 { private val mockCordappProvider: MockCordappProvider = MockCordappProvider(cordappLoader, attachments).also {
it.start() it.start()
} }
override val transactionVerifierService: TransactionVerifierService
get() = InMemoryTransactionVerifierService(
numberOfWorkers = 2,
cordappProvider = mockCordappProvider,
attachments = attachments
)
override val cordappProvider: CordappProvider get() = mockCordappProvider override val cordappProvider: CordappProvider get() = mockCordappProvider
override var networkParametersService: NetworkParametersService = MockNetworkParametersStorage(initialNetworkParameters) override var networkParametersService: NetworkParametersService = MockNetworkParametersStorage(initialNetworkParameters)
override val diagnosticsService: DiagnosticsService = NodeDiagnosticsService() override val diagnosticsService: DiagnosticsService = NodeDiagnosticsService()
protected val servicesForResolution: ServicesForResolution // This is kept here for backwards compatibility, otherwise this has no extra utility.
get() = ServicesForResolutionImpl(identityService, attachments, cordappProvider, networkParametersService, validatedTransactions) protected val servicesForResolution: ServicesForResolution get() = verifyingView
private val verifyingView: VerifyingServiceHub by lazy { VerifyingView(this) }
internal fun makeVaultService(schemaService: SchemaService, database: CordaPersistence, cordappLoader: CordappLoader): VaultServiceInternal { internal fun makeVaultService(schemaService: SchemaService, database: CordaPersistence, cordappLoader: CordappLoader): VaultServiceInternal {
return NodeVaultService( return NodeVaultService(
clock, clock,
keyManagementService, keyManagementService,
servicesForResolution as NodeServicesForResolution, verifyingView,
database, database,
schemaService, schemaService,
cordappLoader.appClassLoader 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 // 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 */ /** 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 { override fun <T : SerializeAsToken> cordaService(type: Class<T>): T {
require(type.isAnnotationPresent(CordaService::class.java)) { "${type.name} is not a Corda service" } 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) mockCordappProvider.addMockCordapp(contractClassName, attachments)
} }
override fun loadState(stateRef: StateRef) = servicesForResolution.loadState(stateRef) override fun loadState(stateRef: StateRef): TransactionState<ContractState> {
override fun loadStates(stateRefs: Set<StateRef>) = servicesForResolution.loadStates(stateRefs) 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`. */ /** 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 override fun loadContractAttachment(stateRef: StateRef) = dummyAttachment
}
/**
* Function which can be used to create a mock [CordaService] for use within testing, such as an Oracle. /**
*/ * All [ServiceHub]s must also implement [VerifyingServiceHub]. However, since [MockServices] is part of the public API, making it
fun <T : SerializeAsToken> createMockCordaService(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T): T { * extend [VerifyingServiceHub] would leak internal APIs. Instead we have this private view class and have the `toVerifyingServiceHub`
class MockAppServiceHubImpl<out T : SerializeAsToken>(val serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T) : AppServiceHub, ServiceHub by serviceHub { * extension method return it.
val serviceInstance: T = serviceConstructor(this) */
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 { init {
serviceHub.cordappServices.putInstance(serviceInstance.javaClass, serviceInstance) serviceHub.cordappServices.putInstance(serviceInstance.javaClass, serviceInstance)
@ -576,5 +602,11 @@ fun <T : SerializeAsToken> createMockCordaService(serviceHub: MockServices, serv
throw UnsupportedOperationException() 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) private val entropyCounter = AtomicReference(args.entropyRoot)
override val log get() = staticLog override val log get() = staticLog
override val transactionVerifierWorkerCount: Int get() = 1
private var _rxIoScheduler: Scheduler? = null private var _rxIoScheduler: Scheduler? = null
override val rxIoScheduler: Scheduler override val rxIoScheduler: Scheduler

View File

@ -3,7 +3,6 @@ package net.corda.testing.dsl
import com.google.common.util.concurrent.ThreadFactoryBuilder import com.google.common.util.concurrent.ThreadFactoryBuilder
import net.corda.core.DoNotImplement import net.corda.core.DoNotImplement
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.NullKeys.NULL_SIGNATURE import net.corda.core.crypto.NullKeys.NULL_SIGNATURE
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature 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.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.core.internal.cordapp.CordappProviderInternal
import net.corda.core.internal.notary.NotaryService import net.corda.core.internal.notary.NotaryService
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
@ -95,7 +95,6 @@ data class TestTransactionDSLInterpreter private constructor(
// Implementing [ServiceHubCoreInternal] allows better use in internal Corda tests // Implementing [ServiceHubCoreInternal] allows better use in internal Corda tests
val services: ServicesForResolution = object : ServiceHubCoreInternal, ServiceHub by ledgerInterpreter.services { val services: ServicesForResolution = object : ServiceHubCoreInternal, ServiceHub by ledgerInterpreter.services {
// [validatedTransactions.getTransaction] needs overriding as there are no calls to // [validatedTransactions.getTransaction] needs overriding as there are no calls to
// [ServiceHub.recordTransactions] in the test dsl // [ServiceHub.recordTransactions] in the test dsl
override val validatedTransactions: TransactionStorage = override val validatedTransactions: TransactionStorage =
@ -129,17 +128,17 @@ data class TestTransactionDSLInterpreter private constructor(
override fun loadState(stateRef: StateRef) = override fun loadState(stateRef: StateRef) =
ledgerInterpreter.resolveStateRef<ContractState>(stateRef) ledgerInterpreter.resolveStateRef<ContractState>(stateRef)
override fun loadStates(stateRefs: Set<StateRef>): Set<StateAndRef<ContractState>> { override val cordappProvider: CordappProviderInternal
return stateRefs.map { StateAndRef(loadState(it), it) }.toSet() get() = ledgerInterpreter.services.cordappProvider as CordappProviderInternal
}
override val cordappProvider: CordappProvider =
ledgerInterpreter.services.cordappProvider
override val notaryService: NotaryService? = null override val notaryService: NotaryService? = null
override val attachmentsClassLoaderCache: AttachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory()) override val attachmentsClassLoaderCache: AttachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory())
override fun loadContractAttachment(stateRef: StateRef): Attachment {
return ledgerInterpreter.services.loadContractAttachment(stateRef)
}
override fun recordUnnotarisedTransaction(txn: SignedTransaction) {} override fun recordUnnotarisedTransaction(txn: SignedTransaction) {}
override fun removeUnnotarisedTransaction(id: SecureHash) {} override fun removeUnnotarisedTransaction(id: SecureHash) {}
@ -169,7 +168,6 @@ data class TestTransactionDSLInterpreter private constructor(
override fun reference(stateRef: StateRef) { override fun reference(stateRef: StateRef) {
val state = ledgerInterpreter.resolveStateRef<ContractState>(stateRef) val state = ledgerInterpreter.resolveStateRef<ContractState>(stateRef)
@Suppress("DEPRECATION") // Will remove when feature finalised.
transactionBuilder.addReferenceState(StateAndRef(state, stateRef).referenced()) 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>