Merge branch 'master' into wn-redo-node-conf-docs

This commit is contained in:
Wawrzyniec 'Wawrzek' Niewodniczanski 2019-01-14 15:31:32 +00:00 committed by GitHub
commit 2f18ce9440
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
329 changed files with 5107 additions and 3365 deletions

View File

@ -6505,10 +6505,6 @@ public final class net.corda.testing.node.NotarySpec extends java.lang.Object
public int hashCode()
public String toString()
##
public interface net.corda.testing.node.ResponderFlowFactory
@NotNull
public abstract F invoke(net.corda.core.flows.FlowSession)
##
public final class net.corda.testing.node.StartedMockNode extends java.lang.Object
@NotNull
public final java.util.List<kotlin.Pair<F, net.corda.core.concurrent.CordaFuture<?>>> findStateMachines(Class<F>)

View File

@ -44,10 +44,12 @@ $newAbstracts
EOF
`
#Get a list of any methods that expose classes in .internal. namespaces, and any classes which extend/implement
#an internal class
# Get a list of any methods that expose internal classes, which includes:
# - classes in .internal. namespaces (excluding the kotlin.jvm.internal namespace)
# - classes which extend/implement an internal class (see above)
# - classes in the net.corda.node. namespace
#TODO: check that only classes in a whitelist are part of the API rather than look for specific invalid cases going forward
newInternalExposures=$(echo "$userDiffContents" | grep "^+" | grep "\.internal\." )
newInternalExposures=$(echo "$userDiffContents" | grep "^+" | grep "(?<!kotlin\.jvm)\.internal\." )
newNodeExposures=$(echo "$userDiffContents" | grep "^+" | grep "net\.corda\.node\.")
internalCount=`grep -v "^$" <<EOF | wc -l

4
.idea/compiler.xml generated
View File

@ -44,6 +44,8 @@
<module name="corda-core_integrationTest" target="1.8" />
<module name="corda-core_smokeTest" target="1.8" />
<module name="corda-finance_integrationTest" target="1.8" />
<module name="corda-isolated_main" target="1.8" />
<module name="corda-isolated_test" target="1.8" />
<module name="corda-project_buildSrc_main" target="1.8" />
<module name="corda-project_buildSrc_test" target="1.8" />
<module name="corda-project_canonicalizer_main" target="1.8" />
@ -158,6 +160,8 @@
<module name="net.corda-blobinspector_test" target="1.8" />
<module name="net.corda-finance-contracts-states_main" target="1.8" />
<module name="net.corda-finance-contracts-states_test" target="1.8" />
<module name="net.corda-isolated_main" target="1.8" />
<module name="net.corda-isolated_test" target="1.8" />
<module name="net.corda-verifier_main" target="1.8" />
<module name="net.corda-verifier_test" target="1.8" />
<module name="net.corda_buildSrc_main" target="1.8" />

View File

@ -14,6 +14,7 @@ see changes to this list.
* Ajitha Thayaharan (BCS Technology International)
* Alberto Arri (R3)
* amiracam
* Amol Pednekar
* Andras Slemmer (R3)
* Andrius Dagys (R3)
* Andrzej Cichocki (R3)
@ -79,6 +80,7 @@ see changes to this list.
* Emanuel Russo (NTT DATA Italy)
* Farzad Pezeshkpour (RBS)
* fracting
* Francisco Spadafora (NTT DATA Italy)
* Frederic Dalibard (Natixis)
* Garrett Macey (Wells Fargo)
* gary-rowe
@ -212,6 +214,7 @@ see changes to this list.
* verymahler
* Viktor Kolomeyko (R3)
* Vipin Bharathan
* Vitalij Reicherdt (Commerzbank AG)
* Wawrzek Niewodniczanski (R3)
* Wei Wu Zhang (Commonwealth Bank of Australia)
* Zabrina Smith (Northern Trust)

View File

@ -1,4 +1,4 @@
Copyright 2016 - 2018, R3 Limited.
Copyright 2016 - 2019, R3 Limited.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -37,7 +37,9 @@ Corda is an open source blockchain project, designed for business from the start
## Contributing
We welcome contributions to Corda! Please see our [CONTRIBUTING.md](./CONTRIBUTING.md).
Corda is an open-source project and contributions are welcome!
To find out how to contribute, please see our [contributing docs](https://docs.corda.net/head/contributing-index.html).
## License

View File

@ -4,7 +4,7 @@ buildscript {
file("$projectDir/constants.properties").withInputStream { constants.load(it) }
// Our version: bump this on release.
ext.corda_release_version = "4.0-SNAPSHOT"
ext.corda_release_version = "5.0-SNAPSHOT"
ext.corda_platform_version = constants.getProperty("platformVersion")
ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion")
@ -383,8 +383,6 @@ bintrayConfig {
'corda-tools-explorer',
'corda-tools-network-bootstrapper',
'corda-tools-cliutils',
'corda-notary-raft',
'corda-notary-bft-smart',
'corda-common-configuration-parsing',
'corda-common-validation'
]
@ -474,4 +472,4 @@ wrapper {
buildScan {
termsOfServiceUrl = 'https://gradle.com/terms-of-service'
termsOfServiceAgree = 'yes'
}
}

View File

@ -10,6 +10,7 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.convertValue
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.client.jackson.internal.childrenAs
import net.corda.client.jackson.internal.valueAs
@ -24,7 +25,8 @@ import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.node.services.NetworkParametersStorage
import net.corda.core.node.services.NetworkParametersService
import net.corda.core.node.services.TransactionStorage
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.serialize
@ -94,10 +96,10 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
services = rigorousMock()
cordappProvider = rigorousMock()
val networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
val networkParametersStorage = rigorousMock<NetworkParametersStorage>().also {
val networkParametersService = rigorousMock<NetworkParametersService>().also {
doReturn(networkParameters.serialize().hash).whenever(it).currentHash
}
doReturn(networkParametersStorage).whenever(services).networkParametersStorage
doReturn(networkParametersService).whenever(services).networkParametersService
doReturn(cordappProvider).whenever(services).cordappProvider
doReturn(networkParameters).whenever(services).networkParameters
doReturn(attachments).whenever(services).attachments
@ -237,6 +239,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
doReturn(attachmentId).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID)
val attachmentStorage = rigorousMock<AttachmentStorage>()
doReturn(attachmentStorage).whenever(services).attachments
doReturn(mock<TransactionStorage>()).whenever(services).validatedTransactions
val attachment = rigorousMock<ContractAttachment>()
doReturn(attachment).whenever(attachmentStorage).openAttachment(attachmentId)
doReturn(attachmentId).whenever(attachment).id

View File

@ -29,6 +29,7 @@ import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.internal.chooseIdentity
import net.corda.testing.node.User
import net.corda.testing.node.internal.FINANCE_CORDAPPS
import org.junit.Test
import rx.Observable
@ -49,7 +50,7 @@ class NodeMonitorModelTest {
private lateinit var newNode: (CordaX500Name) -> NodeInfo
private fun setup(runTest: () -> Unit) {
driver(DriverParameters(extraCordappPackagesToScan = listOf("net.corda.finance"))) {
driver(DriverParameters(cordappsForAllNodes = FINANCE_CORDAPPS)) {
val cashUser = User("user1", "test", permissions = setOf(all()))
val aliceNodeHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(cashUser)).getOrThrow()
aliceNode = aliceNodeHandle.nodeInfo

View File

@ -57,10 +57,13 @@ processSmokeTestResources {
rename 'corda-(.*)', 'corda.jar'
}
from(project(':finance:workflows').tasks['jar']) {
rename 'finance-workflows-(.*)', 'finance-workflows.jar'
rename '.*finance-workflows-.*', 'cordapp-finance-workflows.jar'
}
from(project(':finance:contracts').tasks['jar']) {
rename 'finance-contracts-(.*)', 'finance-contracts.jar'
rename '.*finance-contracts-.*', 'cordapp-finance-contracts.jar'
}
from(project(':confidential-identities').tasks['jar']) {
rename '.*confidential-identities-.*', 'cordapp-confidential-identities.jar'
}
}
@ -87,6 +90,7 @@ dependencies {
smokeTestCompile project(':smoke-test-utils')
smokeTestCompile project(':finance:contracts')
smokeTestCompile project(':finance:workflows')
smokeTestCompile project(':confidential-identities')
smokeTestCompile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
smokeTestCompile "org.apache.logging.log4j:log4j-core:$log4j_version"
smokeTestCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"

View File

@ -3,12 +3,15 @@ package net.corda.client.rpc
import com.github.benmanes.caffeine.cache.Caffeine
import net.corda.client.rpc.internal.RPCClient
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
import net.corda.core.internal.loadClassesImplementing
import net.corda.core.context.Actor
import net.corda.core.context.Trace
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.messaging.ClientRpcSslOptions
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.internal.effectiveSerializationEnv
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.days
@ -19,6 +22,7 @@ import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
import net.corda.serialization.internal.amqp.SerializationFactoryCacheKey
import net.corda.serialization.internal.amqp.SerializerFactory
import java.time.Duration
import java.util.ServiceLoader
/**
* This class is essentially just a wrapper for an RPCConnection<CordaRPCOps> and can be treated identically.
@ -240,6 +244,7 @@ open class CordaRPCClientConfiguration @JvmOverloads constructor(
* @param configuration An optional configuration used to tweak client behaviour.
* @param sslConfiguration An optional [ClientRpcSslOptions] used to enable secure communication with the server.
* @param haAddressPool A list of [NetworkHostAndPort] representing the addresses of servers in HA mode.
* @param classLoader a classloader, which will be used (if provided) to discover available [SerializationCustomSerializer]s and [SerializationWhitelist]s
* The client will attempt to connect to a live server by trying each address in the list. If the servers are not in
* HA mode, the client will round-robin from the beginning of the list and try all servers.
*/
@ -252,8 +257,9 @@ class CordaRPCClient private constructor(
) {
@JvmOverloads
constructor(hostAndPort: NetworkHostAndPort,
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT)
: this(hostAndPort, configuration, null)
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
classLoader: ClassLoader? = null)
: this(hostAndPort, configuration, null, classLoader = classLoader)
/**
* @param haAddressPool A list of [NetworkHostAndPort] representing the addresses of servers in HA mode.
@ -287,7 +293,7 @@ class CordaRPCClient private constructor(
sslConfiguration: ClientRpcSslOptions? = null,
classLoader: ClassLoader? = null
): CordaRPCClient {
return CordaRPCClient(hostAndPort, configuration, sslConfiguration, classLoader)
return CordaRPCClient(hostAndPort, configuration, sslConfiguration, classLoader = classLoader)
}
}
@ -296,7 +302,14 @@ class CordaRPCClient private constructor(
effectiveSerializationEnv
} catch (e: IllegalStateException) {
try {
AMQPClientSerializationScheme.initialiseSerialization(classLoader, Caffeine.newBuilder().maximumSize(128).build<SerializationFactoryCacheKey, SerializerFactory>().asMap())
// If the client has provided a classloader, the associated classpath is checked for available custom serializers and serialization whitelists.
if (classLoader != null) {
val customSerializers = loadClassesImplementing(classLoader, SerializationCustomSerializer::class.java)
val serializationWhitelists = ServiceLoader.load(SerializationWhitelist::class.java, classLoader).toSet()
AMQPClientSerializationScheme.initialiseSerialization(classLoader, customSerializers, serializationWhitelists, Caffeine.newBuilder().maximumSize(128).build<SerializationFactoryCacheKey, SerializerFactory>().asMap())
} else {
AMQPClientSerializationScheme.initialiseSerialization(classLoader, serializerFactoriesForContexts = Caffeine.newBuilder().maximumSize(128).build<SerializationFactoryCacheKey, SerializerFactory>().asMap())
}
} catch (e: IllegalStateException) {
// Race e.g. two of these constructed in parallel, ignore.
}

View File

@ -10,4 +10,4 @@ import net.corda.nodeapi.exceptions.RpcSerializableError
* Thrown to indicate that the calling user does not have permission for something they have requested (for example
* calling a method).
*/
class PermissionException(message: String) : CordaRuntimeException(message), RpcSerializableError, ClientRelevantError
class PermissionException(val msg: String) : CordaRuntimeException(msg), RpcSerializableError, ClientRelevantError

View File

@ -5,6 +5,7 @@ import net.corda.core.internal.toSynchronised
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationContext.UseCase
import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.internal.SerializationEnvironment
import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.serialization.internal.*
@ -17,24 +18,25 @@ import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer
*/
class AMQPClientSerializationScheme(
cordappCustomSerializers: Set<SerializationCustomSerializer<*,*>>,
cordappSerializationWhitelists: Set<SerializationWhitelist>,
serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory>
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised())
constructor(cordapps: List<Cordapp>, serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory>) : this(cordapps.customSerializers, serializerFactoriesForContexts)
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, cordappSerializationWhitelists, serializerFactoriesForContexts) {
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, cordapps.serializationWhitelists, AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised())
constructor(cordapps: List<Cordapp>, serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory>) : this(cordapps.customSerializers, cordapps.serializationWhitelists, serializerFactoriesForContexts)
@Suppress("UNUSED")
constructor() : this(emptySet(), AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised())
constructor() : this(emptySet(), emptySet(), AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised())
companion object {
/** Call from main only. */
fun initialiseSerialization(classLoader: ClassLoader? = null, serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory> = AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised()) {
nodeSerializationEnv = createSerializationEnv(classLoader, serializerFactoriesForContexts)
fun initialiseSerialization(classLoader: ClassLoader? = null, customSerializers: Set<SerializationCustomSerializer<*, *>> = emptySet(), serializationWhitelists: Set<SerializationWhitelist> = emptySet(), serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory> = AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised()) {
nodeSerializationEnv = createSerializationEnv(classLoader, customSerializers, serializationWhitelists, serializerFactoriesForContexts)
}
fun createSerializationEnv(classLoader: ClassLoader? = null, serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory> = AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised()): SerializationEnvironment {
fun createSerializationEnv(classLoader: ClassLoader? = null, customSerializers: Set<SerializationCustomSerializer<*, *>> = emptySet(), serializationWhitelists: Set<SerializationWhitelist> = emptySet(), serializerFactoriesForContexts: MutableMap<SerializationFactoryCacheKey, SerializerFactory> = AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>(128).toSynchronised()): SerializationEnvironment {
return SerializationEnvironment.with(
SerializationFactoryImpl().apply {
registerScheme(AMQPClientSerializationScheme(emptyList(), serializerFactoriesForContexts))
registerScheme(AMQPClientSerializationScheme(customSerializers, serializationWhitelists, serializerFactoriesForContexts))
},
storageContext = AMQP_STORAGE_CONTEXT,
p2pContext = if (classLoader != null) AMQP_P2P_CONTEXT.withClassLoader(classLoader) else AMQP_P2P_CONTEXT,

View File

@ -30,6 +30,27 @@ import static kotlin.test.AssertionsKt.fail;
import static net.corda.finance.contracts.GetBalances.getCashBalance;
public class StandaloneCordaRPCJavaClientTest {
public static void copyCordapps(NodeProcess.Factory factory, NodeConfig notaryConfig) {
Path cordappsDir = (factory.baseDirectory(notaryConfig).resolve(NodeProcess.CORDAPPS_DIR_NAME));
try {
Files.createDirectories(cordappsDir);
} catch (IOException ex) {
fail("Failed to create directories");
}
try (Stream<Path> paths = Files.walk(Paths.get("build", "resources", "smokeTest"))) {
paths.filter(path -> path.toFile().getName().startsWith("cordapp")).forEach(file -> {
try {
Files.copy(file, cordappsDir.resolve(file.getFileName()));
} catch (IOException ex) {
fail("Failed to copy cordapp jar");
}
});
} catch (IOException e) {
fail("Failed to walk files");
}
}
private List<String> perms = Collections.singletonList("ALL");
private Set<String> permSet = new HashSet<>(perms);
private User rpcUser = new User("user1", "test", permSet);
@ -55,7 +76,7 @@ public class StandaloneCordaRPCJavaClientTest {
@Before
public void setUp() {
factory = new NodeProcess.Factory();
copyFinanceCordapp();
copyCordapps(factory, notaryConfig);
notary = factory.create(notaryConfig);
connection = notary.connect();
rpcProxy = connection.getProxy();
@ -73,28 +94,6 @@ public class StandaloneCordaRPCJavaClientTest {
}
}
private void copyFinanceCordapp() {
Path cordappsDir = (factory.baseDirectory(notaryConfig).resolve(NodeProcess.CORDAPPS_DIR_NAME));
try {
Files.createDirectories(cordappsDir);
} catch (IOException ex) {
fail("Failed to create directories");
}
try (Stream<Path> paths = Files.walk(Paths.get("build", "resources", "smokeTest"))) {
paths.forEach(file -> {
if (file.toString().contains("corda-finance")) {
try {
Files.copy(file, cordappsDir.resolve(file.getFileName()));
} catch (IOException ex) {
fail("Failed to copy finance jar");
}
}
});
} catch (IOException e) {
fail("Failed to walk files");
}
}
@Test
public void testCashBalances() throws NoSuchFieldException, ExecutionException, InterruptedException {
Amount<Currency> dollars123 = new Amount<>(123, Currency.getInstance("USD"));

View File

@ -6,12 +6,15 @@ import net.corda.client.rpc.CordaRPCConnection
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.*
import net.corda.core.internal.InputStreamAndHash
import net.corda.core.messaging.*
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.*
import net.corda.core.utilities.*
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.finance.DOLLARS
import net.corda.finance.POUNDS
import net.corda.finance.SWISS_FRANCS
@ -21,6 +24,7 @@ import net.corda.finance.contracts.getCashBalance
import net.corda.finance.contracts.getCashBalances
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.java.rpc.StandaloneCordaRPCJavaClientTest
import net.corda.nodeapi.internal.config.User
import net.corda.smoketesting.NodeConfig
import net.corda.smoketesting.NodeProcess
@ -31,11 +35,9 @@ import org.junit.Ignore
import org.junit.Test
import java.io.FilterInputStream
import java.io.InputStream
import java.nio.file.Paths
import java.util.*
import java.util.concurrent.CountDownLatch
import java.util.concurrent.atomic.AtomicInteger
import kotlin.streams.toList
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
@ -69,7 +71,7 @@ class StandaloneCordaRPClientTest {
@Before
fun setUp() {
factory = NodeProcess.Factory()
copyFinanceCordapp()
StandaloneCordaRPCJavaClientTest.copyCordapps(factory, notaryConfig)
notary = factory.create(notaryConfig)
connection = notary.connect()
rpcProxy = connection.proxy
@ -86,15 +88,6 @@ class StandaloneCordaRPClientTest {
}
}
private fun copyFinanceCordapp() {
val cordappsDir = (factory.baseDirectory(notaryConfig) / NodeProcess.CORDAPPS_DIR_NAME).createDirectories()
// Find the finance jar file for the smoke tests of this module
val financeJars = Paths.get("build", "resources", "smokeTest").list {
it.filter { "corda-finance" in it.toString() }.toList()
}
financeJars.forEach { it.copyToDirectory(cordappsDir) }
}
@Test
fun `test attachments`() {

View File

@ -6,12 +6,13 @@ apply plugin: 'kotlin'
apply plugin: CanonicalizerPlugin
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.cordapp'
apply plugin: 'com.jfrog.artifactory'
description 'Corda Experimental Confidential Identities'
dependencies {
compile project(':core')
cordaCompile project(':core')
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testCompile "junit:junit:$junit_version"
@ -26,6 +27,17 @@ dependencies {
testCompile "org.assertj:assertj-core:$assertj_version"
}
cordapp {
targetPlatformVersion corda_platform_version.toInteger()
minimumPlatformVersion 1
workflow {
name "Corda Experimental Confidential Identities"
versionId 1
vendor "R3"
licence "Open Source (Apache 2)"
}
}
jar {
baseName 'corda-confidential-identities'
}

View File

@ -4,29 +4,49 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.core.CordaInternal
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.verify
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.*
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.VisibleForTesting
import net.corda.core.internal.cordapp.CordappResolver
import net.corda.core.internal.warnOnce
import net.corda.core.node.ServiceHub
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.utilities.ProgressTracker
import net.corda.core.utilities.unwrap
import java.security.PublicKey
import java.security.SignatureException
import java.util.*
/**
* Very basic flow which generates new confidential identities for parties in a transaction and exchanges the transaction
* key and certificate paths between the parties. This is intended for use as a sub-flow of another flow which builds a
* transaction. The flow running on the other side must also call this flow at the correct location.
*
* NOTE: This is an inlined flow but for backwards compatibility is annotated with [InitiatingFlow].
*/
class SwapIdentitiesFlow @JvmOverloads constructor(private val otherSideSession: FlowSession,
override val progressTracker: ProgressTracker = tracker()) : FlowLogic<SwapIdentitiesFlow.AnonymousResult>() {
@InitiatingFlow
class SwapIdentitiesFlow
private constructor(private val otherSideSession: FlowSession?,
private val otherParty: Party?,
override val progressTracker: ProgressTracker) : FlowLogic<LinkedHashMap<Party, AnonymousParty>>() {
@JvmOverloads
constructor(otherSideSession: FlowSession, progressTracker: ProgressTracker = tracker()) : this(otherSideSession, null, progressTracker)
@Deprecated("It is unsafe to use this constructor as it requires nodes to automatically vend anonymous identities without first " +
"checking if they should. Instead, use the constructor that takes in an existing FlowSession.")
constructor(otherParty: Party, revocationEnabled: Boolean, progressTracker: ProgressTracker) : this(null, otherParty, progressTracker)
@Deprecated("It is unsafe to use this constructor as it requires nodes to automatically vend anonymous identities without first " +
"checking if they should. Instead, use the constructor that takes in an existing FlowSession.")
constructor(otherParty: Party) : this(null, otherParty, tracker())
companion object {
object GENERATING_IDENTITY : ProgressTracker.Step("Generating our anonymous identity")
object SIGNING_IDENTITY : ProgressTracker.Step("Signing our anonymous identity")
@ -69,38 +89,49 @@ class SwapIdentitiesFlow @JvmOverloads constructor(private val otherSideSession:
}
@Suspendable
override fun call(): AnonymousResult {
override fun call(): LinkedHashMap<Party, AnonymousParty> {
val session = otherSideSession ?: run {
logger.warnOnce("The current usage of SwapIdentitiesFlow is unsafe. Please consider upgrading your CorDapp to use " +
"SwapIdentitiesFlow with FlowSessions. (${CordappResolver.currentCordapp?.info})")
initiateFlow(otherParty!!)
}
progressTracker.currentStep = GENERATING_IDENTITY
val ourAnonymousIdentity = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, false)
val data = buildDataToSign(ourAnonymousIdentity)
progressTracker.currentStep = SIGNING_IDENTITY
val signature = serviceHub.keyManagementService.sign(data, ourAnonymousIdentity.owningKey).withoutKey()
val ourIdentWithSig = IdentityWithSignature(ourAnonymousIdentity, signature)
val ourIdentWithSig = IdentityWithSignature(ourAnonymousIdentity.serialize(), signature)
progressTracker.currentStep = AWAITING_IDENTITY
val theirAnonymousIdentity = otherSideSession.sendAndReceive<IdentityWithSignature>(ourIdentWithSig).unwrap { theirIdentWithSig ->
val theirAnonymousIdentity = session.sendAndReceive<IdentityWithSignature>(ourIdentWithSig).unwrap { theirIdentWithSig ->
progressTracker.currentStep = VERIFYING_IDENTITY
validateAndRegisterIdentity(serviceHub, otherSideSession.counterparty, theirIdentWithSig.identity, theirIdentWithSig.signature)
validateAndRegisterIdentity(serviceHub, session.counterparty, theirIdentWithSig.identity.deserialize(), theirIdentWithSig.signature)
}
return AnonymousResult(ourAnonymousIdentity.party.anonymise(), theirAnonymousIdentity.party.anonymise())
val identities = LinkedHashMap<Party, AnonymousParty>()
identities[ourIdentity] = ourAnonymousIdentity.party.anonymise()
identities[session.counterparty] = theirAnonymousIdentity.party.anonymise()
return identities
}
/**
* Result class containing the caller's anonymous identity ([ourIdentity]) and the counterparty's ([theirIdentity]).
*/
@CordaSerializable
data class AnonymousResult(val ourIdentity: AnonymousParty, val theirIdentity: AnonymousParty)
@CordaSerializable
private data class IdentityWithSignature(val identity: PartyAndCertificate, val signature: DigitalSignature)
/**
* Data class used only in the context of asserting that the owner of the private key for the listed key wants to use it
* to represent the named entity. This is paired with an X.509 certificate (which asserts the signing identity says
* the key represents the named entity) and protects against a malicious party incorrectly claiming others'
* keys.
*/
@CordaSerializable
private data class CertificateOwnershipAssertion(val name: CordaX500Name, val owningKey: PublicKey)
data class IdentityWithSignature(val identity: SerializedBytes<PartyAndCertificate>, val signature: DigitalSignature)
}
// Data class used only in the context of asserting that the owner of the private key for the listed key wants to use it
// to represent the named entity. This is paired with an X.509 certificate (which asserts the signing identity says
// the key represents the named entity) and protects against a malicious party incorrectly claiming others'
// keys.
@CordaSerializable
data class CertificateOwnershipAssertion(val x500Name: CordaX500Name, val publicKey: PublicKey)
open class SwapIdentitiesException @JvmOverloads constructor(message: String, cause: Throwable? = null) : FlowException(message, cause)
// This only exists for backwards compatibility
@InitiatedBy(SwapIdentitiesFlow::class)
private class SwapIdentitiesHandler(private val otherSide: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
subFlow(SwapIdentitiesFlow(otherSide))
logger.warnOnce("Insecure API to swap anonymous identities was used by ${otherSide.counterparty} (${otherSide.getCounterpartyFlowInfo()})")
}
}

View File

@ -171,10 +171,7 @@ class SwapIdentitiesFlowTests {
@InitiatingFlow
private class SwapIdentitiesInitiator(private val otherSide: Party) : FlowLogic<Map<Party, AnonymousParty>>() {
@Suspendable
override fun call(): Map<Party, AnonymousParty> {
val (anonymousUs, anonymousThem) = subFlow(SwapIdentitiesFlow(initiateFlow(otherSide)))
return mapOf(ourIdentity to anonymousUs, otherSide to anonymousThem)
}
override fun call(): Map<Party, AnonymousParty> = subFlow(SwapIdentitiesFlow(initiateFlow(otherSide)))
}
@InitiatedBy(SwapIdentitiesInitiator::class)

View File

@ -10,27 +10,59 @@
</Properties>
<Appenders>
<Console name="Console-Appender" target="SYSTEM_OUT">
<PatternLayout>
<ScriptPatternSelector defaultPattern="%highlight{[%level{length=5}] %date{HH:mm:ssZ} [%t] %c{2}.%method - %msg%n%throwable{short.message}%n}{INFO=white,WARN=red,FATAL=bright red}">
<Script name="MDCSelector" language="javascript"><![CDATA[
result = null;
if (!logEvent.getContextData().size() == 0) {
result = "WithMDC";
} else {
result = null;
}
result;
]]>
</Script>
<PatternMatch key="WithMDC" pattern="%highlight{[%level{length=5}] %date{HH:mm:ssZ} [%t] %c{2}.%method - %msg %X%n%throwable{short.message}%n}{INFO=white,WARN=red,FATAL=bright red}"/>
</ScriptPatternSelector>
</PatternLayout>
</Console>
<ScriptAppenderSelector name="Console-Selector">
<Script language="nashorn"><![CDATA[
var System = Java.type('java.lang.System');
var level = System.getProperty("consoleLogLevel");
var enabled = System.getProperty("consoleLoggingEnabled");
enabled == "true" && (level == "debug" || level == "trace") ? "Console-Debug-Appender" : "Console-Appender";
]]></Script>
<AppenderSet>
<!-- The default console appender - prints no exception information -->
<Console name="Console-Appender" target="SYSTEM_OUT">
<PatternLayout>
<ScriptPatternSelector
defaultPattern="%highlight{[%level{length=5}] %date{HH:mm:ssZ} [%t] %c{2}.%method - %msg%n%throwable{0}}{INFO=white,WARN=red,FATAL=bright red}">
<Script name="MDCSelector" language="javascript"><![CDATA[
result = null;
if (!logEvent.getContextData().size() == 0) {
result = "WithMDC";
} else {
result = null;
}
result;
]]>
</Script>
<PatternMatch key="WithMDC" pattern="%highlight{[%level{length=5}] %date{HH:mm:ssZ} [%t] %c{2}.%method - %msg %X%n%throwable{0}}{INFO=white,WARN=red,FATAL=bright red}"/>
</ScriptPatternSelector>
</PatternLayout>
</Console>
<!-- The console appender when debug or trace level logging is specified. Prints full stack trace -->
<Console name="Console-Debug-Appender" target="SYSTEM_OUT">
<PatternLayout>
<ScriptPatternSelector defaultPattern="%highlight{[%level{length=5}] %date{HH:mm:ssZ} [%t] %c{2}.%method - %msg%n%throwable{}}{INFO=white,WARN=red,FATAL=bright red}">
<Script name="MDCSelector" language="javascript"><![CDATA[
result = null;
if (!logEvent.getContextData().size() == 0) {
result = "WithMDC";
} else {
result = null;
}
result;
]]>
</Script>
<PatternMatch key="WithMDC" pattern="%highlight{[%level{length=5}] %date{HH:mm:ssZ} [%t] %c{2}.%method - %msg %X%n%throwable{}}{INFO=white,WARN=red,FATAL=bright red}"/>
</ScriptPatternSelector>
</PatternLayout>
</Console>
</AppenderSet>
</ScriptAppenderSelector>
<!-- Required for printBasicInfo -->
<Console name="Console-Appender-Println" target="SYSTEM_OUT">
<PatternLayout pattern="%msg%n%throwable{short.message}" />
<PatternLayout pattern="%msg%n%throwable{0}" />
</Console>
<!-- Will generate up to 100 log files for a given day. During every rollover it will delete
@ -73,8 +105,8 @@
</RollingRandomAccessFile>
<Rewrite name="Console-ErrorCode-Appender">
<AppenderRef ref="Console-Appender"/>
<Rewrite name="Console-ErrorCode-Selector">
<AppenderRef ref="Console-Selector"/>
<ErrorCodeRewritePolicy/>
</Rewrite>
@ -91,7 +123,7 @@
<Loggers>
<Root level="${defaultLogLevel}">
<AppenderRef ref="Console-ErrorCode-Appender" level="${consoleLogLevel}"/>
<AppenderRef ref="Console-ErrorCode-Selector" level="${consoleLogLevel}"/>
<AppenderRef ref="RollingFile-ErrorCode-Appender"/>
</Root>
<Logger name="BasicInfo" additivity="false">
@ -99,11 +131,11 @@
<AppenderRef ref="RollingFile-ErrorCode-Appender"/>
</Logger>
<Logger name="org.hibernate.SQL" level="info" additivity="false">
<AppenderRef ref="Console-ErrorCode-Appender"/>
<AppenderRef ref="Console-ErrorCode-Selector"/>
<AppenderRef ref="RollingFile-ErrorCode-Appender"/>
</Logger>
<Logger name="org.apache.activemq.artemis.core.server" level="error" additivity="false">
<AppenderRef ref="Console-ErrorCode-Appender"/>
<AppenderRef ref="Console-ErrorCode-Selector"/>
<AppenderRef ref="RollingFile-ErrorCode-Appender"/>
</Logger>
<Logger name="org.jolokia" additivity="true" level="warn">

View File

@ -1,10 +1,10 @@
gradlePluginsVersion=4.0.37
gradlePluginsVersion=4.0.38
kotlinVersion=1.2.71
# ***************************************************************#
# When incrementing platformVersion make sure to update #
# net.corda.core.internal.CordaUtilsKt.PLATFORM_VERSION as well. #
# ***************************************************************#
platformVersion=4
platformVersion=5
guavaVersion=25.1-jre
proguardVersion=6.0.3
bouncycastleVersion=1.60

View File

@ -1,6 +1,7 @@
package net.corda.deterministic.data
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.crypto.entropyToKeyPair
import net.corda.core.identity.AnonymousParty
@ -39,7 +40,7 @@ object TransactionGenerator {
private val MEGA_CORP_PUBKEY: PublicKey = megaCorp.keyPair.public
private val MINI_CORP_PUBKEY: PublicKey = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).keyPair.public
private val ledgerServices = MockServices(emptyList(), MEGA_CORP.name, rigorousMock<IdentityServiceInternal>().also {
private val ledgerServices = MockServices(emptyList(), MEGA_CORP.name, mock<IdentityServiceInternal>().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
doReturn(DUMMY_CASH_ISSUER.party).whenever(it).partyFromKey(DUMMY_CASH_ISSUER_KEY.public)
})

View File

@ -0,0 +1,8 @@
package net.corda.core.internal
/**
* Stubbing out non-deterministic method.
*/
fun <T: Any> loadClassesImplementing(classloader: ClassLoader, clazz: Class<T>): Set<T> {
return emptySet()
}

View File

@ -4,6 +4,7 @@ import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationContext.UseCase.P2P
import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.internal.SerializationEnvironment
import net.corda.core.serialization.internal._contextSerializationEnv
import net.corda.serialization.internal.*
@ -57,15 +58,16 @@ class LocalSerializationRule(private val label: String) : TestRule {
private fun createTestSerializationEnv(): SerializationEnvironment {
val factory = SerializationFactoryImpl(mutableMapOf()).apply {
registerScheme(AMQPSerializationScheme(emptySet(), AccessOrderLinkedHashMap(128)))
registerScheme(AMQPSerializationScheme(emptySet(), emptySet(), AccessOrderLinkedHashMap(128)))
}
return SerializationEnvironment.with(factory, AMQP_P2P_CONTEXT)
}
private class AMQPSerializationScheme(
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
cordappSerializationWhitelists: Set<SerializationWhitelist>,
serializerFactoriesForContexts: AccessOrderLinkedHashMap<SerializationFactoryCacheKey, SerializerFactory>
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, cordappSerializationWhitelists, serializerFactoriesForContexts) {
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
throw UnsupportedOperationException()
}

View File

@ -2,8 +2,8 @@ package net.corda.deterministic.verifier
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.ContractClassName
import net.corda.core.internal.DEPLOYED_CORDAPP_UPLOADER
import net.corda.core.internal.toLtxDjvmInternal
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializedBytes
@ -25,13 +25,10 @@ class TransactionVerificationRequest(val wtxToVerify: SerializedBytes<WireTransa
val attachmentMap = attachments
.mapNotNull { it as? MockContractAttachment }
.associateBy(Attachment::id) { ContractAttachment(it, it.contract, uploader = DEPLOYED_CORDAPP_UPLOADER) }
val contractAttachmentMap = emptyMap<ContractClassName, ContractAttachment>()
@Suppress("DEPRECATION")
return wtxToVerify.deserialize().toLedgerTransaction(
resolveIdentity = { null },
return wtxToVerify.deserialize().toLtxDjvmInternal(
resolveAttachment = { attachmentMap[it] },
resolveStateRef = { deps[it.txhash]?.outputs?.get(it.index) },
resolveContractAttachment = { contractAttachmentMap[it.contract]?.id },
resolveParameters = { networkParameters.deserialize() }
)
}

View File

@ -111,6 +111,8 @@ dependencies {
// required to use @Type annotation
compile "org.hibernate:hibernate-core:$hibernate_version"
compile group: "io.github.classgraph", name: "classgraph", version: class_graph_version
}
// TODO Consider moving it to quasar-utils in the future (introduced with PR-1388)

View File

@ -1,6 +1,7 @@
package net.corda.core.contracts
import kotlin.reflect.KClass
import net.corda.core.contracts.TransactionVerificationException.TransactionContractConflictException
/**
* This annotation is required by any [ContractState] which needs to ensure that it is only ever processed as part of a
@ -12,7 +13,8 @@ import kotlin.reflect.KClass
* checked to ensure that their [ContractState]s match with their [Contract]s as specified either by this annotation, or
* by their inner/outer class relationship.
*
* The transaction will write a warning to the log if any mismatch is detected.
* The transaction will write a warning to the log (for corDapps with a target version less than 4) or
* fail with a [TransactionContractConflictException] if any mismatch is detected.
*
* @param value The class of the [Contract] to which states of the annotated [ContractState] belong.
*/

View File

@ -65,6 +65,19 @@ class StaticPointer<T : ContractState>(override val pointer: StateRef, override
override fun resolve(ltx: LedgerTransaction): StateAndRef<T> {
return ltx.referenceInputRefsOfType(type).single { pointer == it.ref }
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is StaticPointer<*>) return false
if (pointer != other.pointer) return false
return true
}
override fun hashCode(): Int {
return pointer.hashCode()
}
}
/**
@ -118,4 +131,17 @@ class LinearPointer<T : LinearState>(override val pointer: UniqueIdentifier, ove
override fun resolve(ltx: LedgerTransaction): StateAndRef<T> {
return ltx.referenceInputRefsOfType(type).single { pointer == it.state.data.linearId }
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is LinearPointer<*>) return false
if (pointer != other.pointer) return false
return true
}
override fun hashCode(): Int {
return pointer.hashCode()
}
}

View File

@ -237,6 +237,7 @@ interface MoveCommand : CommandData {
data class CommandWithParties<out T : CommandData>(
val signers: List<PublicKey>,
/** If any public keys were recognised, the looked up institutions are available here */
@Deprecated("Should not be used in contract verification code as it is non-deterministic, will be disabled for some future target platform version onwards and will take effect only for CorDapps targeting those versions.")
val signingParties: List<Party>,
val value: T
)

View File

@ -7,11 +7,13 @@ import net.corda.core.identity.Party
import net.corda.core.identity.groupAbstractPartyByWellKnownParty
import net.corda.core.internal.cordapp.CordappResolver
import net.corda.core.internal.pushToLoggingContext
import net.corda.core.internal.warnOnce
import net.corda.core.node.StatesToRecord
import net.corda.core.node.StatesToRecord.ONLY_RELEVANT
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.debug
/**
* Verifies the given transaction, then sends it to the named notary. If the notary agrees that the transaction
@ -29,44 +31,74 @@ import net.corda.core.utilities.ProgressTracker
* The flow returns the same transaction but with the additional signatures from the notary.
*
* NOTE: This is an inlined flow but for backwards compatibility is annotated with [InitiatingFlow].
*
* @param transaction What to commit.
* @param sessions A collection of [FlowSession]s who will be given the notarised transaction. This list **must** include
* all participants in the transaction (excluding the local identity).
*/
// To maintain backwards compatibility with the old API, FinalityFlow can act both as an initiating flow and as an inlined flow.
// This is only possible because a flow is only truly initiating when the first call to initiateFlow is made (where the
// presence of @InitiatingFlow is checked). So the new API is inlined simply because that code path doesn't call initiateFlow.
@InitiatingFlow
class FinalityFlow private constructor(val transaction: SignedTransaction,
private val extraRecipients: Set<Party>,
private val oldParticipants: Collection<Party>,
override val progressTracker: ProgressTracker,
private val sessions: Collection<FlowSession>?) : FlowLogic<SignedTransaction>() {
private val sessions: Collection<FlowSession>,
private val newApi: Boolean) : FlowLogic<SignedTransaction>() {
@Deprecated(DEPRECATION_MSG)
constructor(transaction: SignedTransaction, extraRecipients: Set<Party>, progressTracker: ProgressTracker) : this(
transaction, extraRecipients, progressTracker, null
transaction, extraRecipients, progressTracker, emptyList(), false
)
@Deprecated(DEPRECATION_MSG)
constructor(transaction: SignedTransaction, extraRecipients: Set<Party>) : this(transaction, extraRecipients, tracker(), null)
constructor(transaction: SignedTransaction, extraRecipients: Set<Party>) : this(transaction, extraRecipients, tracker(), emptyList(), false)
@Deprecated(DEPRECATION_MSG)
constructor(transaction: SignedTransaction) : this(transaction, emptySet(), tracker(), null)
constructor(transaction: SignedTransaction) : this(transaction, emptySet(), tracker(), emptyList(), false)
@Deprecated(DEPRECATION_MSG)
constructor(transaction: SignedTransaction, progressTracker: ProgressTracker) : this(transaction, emptySet(), progressTracker, null)
constructor(transaction: SignedTransaction, progressTracker: ProgressTracker) : this(transaction, emptySet(), progressTracker, emptyList(), false)
constructor(transaction: SignedTransaction, sessions: Collection<FlowSession>, progressTracker: ProgressTracker) : this(
transaction, emptySet(), progressTracker, sessions
)
constructor(transaction: SignedTransaction, sessions: Collection<FlowSession>) : this(
transaction, emptySet(), tracker(), sessions
)
/**
* Notarise the given transaction and broadcast it to the given [FlowSession]s. This list **must** at least include
* all the non-local participants of the transaction. Sessions to non-participants can also be provided.
*
* @param transaction What to commit.
*/
constructor(transaction: SignedTransaction, firstSession: FlowSession, vararg restSessions: FlowSession) : this(
transaction, emptySet(), tracker(), listOf(firstSession) + restSessions.asList()
transaction, listOf(firstSession) + restSessions.asList()
)
/**
* Notarise the given transaction and broadcast it to all the participants.
*
* @param transaction What to commit.
* @param sessions A collection of [FlowSession]s for each non-local participant of the transaction. Sessions to non-participants can
* also be provided.
*/
@JvmOverloads
constructor(
transaction: SignedTransaction,
sessions: Collection<FlowSession>,
progressTracker: ProgressTracker = tracker()
) : this(transaction, emptyList(), progressTracker, sessions, true)
/**
* Notarise the given transaction and broadcast it to all the participants.
*
* @param transaction What to commit.
* @param sessions A collection of [FlowSession]s for each non-local participant.
* @param oldParticipants An **optional** collection of parties for participants who are still using the old API.
*
* You will only need to use this parameter if you have upgraded your CorDapp from the V3 FinalityFlow API but are required to provide
* backwards compatibility with participants running V3 nodes. If you're writing a new CorDapp then this does not apply and this
* parameter should be ignored.
*/
@Deprecated(DEPRECATION_MSG)
constructor(
transaction: SignedTransaction,
sessions: Collection<FlowSession>,
oldParticipants: Collection<Party>,
progressTracker: ProgressTracker
) : this(transaction, oldParticipants, progressTracker, sessions, true)
companion object {
private const val DEPRECATION_MSG = "It is unsafe to use this constructor as it requires nodes to automatically " +
"accept notarised transactions without first checking their relevancy. Instead, use one of the constructors " +
"that takes in existing FlowSessions."
"that requires only FlowSessions."
object NOTARISING : ProgressTracker.Step("Requesting signature by notary service") {
override fun childProgressTracker() = NotaryFlow.Client.tracker()
@ -81,13 +113,13 @@ class FinalityFlow private constructor(val transaction: SignedTransaction,
@Suspendable
@Throws(NotaryException::class)
override fun call(): SignedTransaction {
if (sessions == null) {
if (!newApi) {
require(CordappResolver.currentTargetVersion < 4) {
"A flow session for each external participant to the transaction must be provided. If you wish to continue " +
"using this insecure API then specify a target platform version of less than 4 for your CorDapp."
}
logger.warn("The current usage of FinalityFlow is unsafe. Please consider upgrading your CorDapp to use " +
"FinalityFlow with FlowSessions.")
logger.warnOnce("The current usage of FinalityFlow is unsafe. Please consider upgrading your CorDapp to use " +
"FinalityFlow with FlowSessions. (${CordappResolver.currentCordapp?.info})")
} else {
require(sessions.none { serviceHub.myInfo.isLegalIdentity(it.counterparty) }) {
"Do not provide flow sessions for the local node. FinalityFlow will record the notarised transaction locally."
@ -103,30 +135,25 @@ class FinalityFlow private constructor(val transaction: SignedTransaction,
transaction.pushToLoggingContext()
logCommandData()
val ledgerTransaction = verifyTx()
val externalParticipants = extractExternalParticipants(ledgerTransaction)
val externalTxParticipants = extractExternalParticipants(ledgerTransaction)
if (sessions != null) {
val missingRecipients = externalParticipants - sessions.map { it.counterparty }
if (newApi) {
val sessionParties = sessions.map { it.counterparty }
val missingRecipients = externalTxParticipants - sessionParties - oldParticipants
require(missingRecipients.isEmpty()) {
"Flow sessions were not provided for the following transaction participants: $missingRecipients"
}
sessionParties.intersect(oldParticipants).let {
require(it.isEmpty()) { "The following parties are specified both in flow sessions and in the oldParticipants list: $it" }
}
}
val notarised = notariseAndRecord()
// Each transaction has its own set of recipients, but extra recipients get them all.
progressTracker.currentStep = BROADCASTING
if (sessions == null) {
val recipients = externalParticipants + (extraRecipients - serviceHub.myInfo.legalIdentities)
logger.info("Broadcasting transaction to parties ${recipients.joinToString(", ", "[", "]")}.")
for (recipient in recipients) {
logger.info("Sending transaction to party ${recipient.name}.")
val session = initiateFlow(recipient)
subFlow(SendTransactionFlow(session, notarised))
logger.info("Party $recipient received the transaction.")
}
} else {
if (newApi) {
oldV3Broadcast(notarised, oldParticipants.toSet())
for (session in sessions) {
try {
subFlow(SendTransactionFlow(session, notarised))
@ -140,6 +167,8 @@ class FinalityFlow private constructor(val transaction: SignedTransaction,
)
}
}
} else {
oldV3Broadcast(notarised, (externalTxParticipants + oldParticipants).toSet())
}
logger.info("All parties received the transaction successfully.")
@ -147,6 +176,18 @@ class FinalityFlow private constructor(val transaction: SignedTransaction,
return notarised
}
@Suspendable
private fun oldV3Broadcast(notarised: SignedTransaction, recipients: Set<Party>) {
for (recipient in recipients) {
if (!serviceHub.myInfo.isLegalIdentity(recipient)) {
logger.debug { "Sending transaction to party $recipient." }
val session = initiateFlow(recipient)
subFlow(SendTransactionFlow(session, notarised))
logger.info("Party $recipient received the transaction.")
}
}
}
private fun logCommandData() {
if (logger.isDebugEnabled) {
val commandDataTypes = transaction.tx.commands.asSequence().mapNotNull { it.value::class.qualifiedName }.distinct()

View File

@ -95,6 +95,8 @@ abstract class FlowLogic<out T> {
fiber.suspend(request, maySkipCheckpoint = maySkipCheckpoint)
}
}
private val DEFAULT_TRACKER = { ProgressTracker() }
}
/**
@ -345,7 +347,7 @@ abstract class FlowLogic<out T> {
* Note that this has to return a tracker before the flow is invoked. You can't change your mind half way
* through.
*/
open val progressTracker: ProgressTracker? = ProgressTracker.DEFAULT_TRACKER()
open val progressTracker: ProgressTracker? = DEFAULT_TRACKER()
/**
* This is where you fill out your business logic.

View File

@ -34,7 +34,7 @@ class NotaryChangeFlow<out T : ContractState>(
inputs.map { it.ref },
originalState.state.notary,
modification,
serviceHub.networkParametersStorage.currentHash
serviceHub.networkParametersService.currentHash
).build()
val participantKeys = inputs.flatMap { it.state.data.participants }.map { it.owningKey }.toSet()

View File

@ -28,7 +28,7 @@ sealed class NotaryError {
/** Specifies which states have already been consumed in another transaction. */
val consumedStates: Map<StateRef, StateConsumptionDetails>
) : NotaryError() {
override fun toString() = "One or more input states have already been used in other transactions. Conflicting state count: ${consumedStates.size}, consumption details:\n" +
override fun toString() = "One or more input states or referenced states have already been used as input states in other transactions. Conflicting state count: ${consumedStates.size}, consumption details:\n" +
"${consumedStates.asSequence().joinToString(",\n", limit = 5) { it.key.toString() + " -> " + it.value }}.\n" +
"To find out if any of the conflicting transactions have been generated by this node you can use the hashLookup Corda shell command."
}
@ -76,7 +76,7 @@ sealed class NotaryError {
*/
// TODO: include notary timestamp?
@CordaSerializable
data class StateConsumptionDetails(
data class StateConsumptionDetails(
val hashOfTransactionId: SecureHash,
val type: ConsumedStateType
) {

View File

@ -10,7 +10,7 @@ import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.Party
import net.corda.core.internal.BackpressureAwareTimedFlow
import net.corda.core.internal.FetchDataFlow
import net.corda.core.internal.notary.HistoricNetworkParameterStorage
import net.corda.core.internal.NetworkParametersServiceInternal
import net.corda.core.internal.notary.generateSignature
import net.corda.core.internal.notary.validateSignatures
import net.corda.core.internal.pushToLoggingContext
@ -107,7 +107,7 @@ class NotaryFlow {
check(stx.coreTransaction is NotaryChangeWireTransaction) {
"Notary $notaryParty is not on the network parameter whitelist. A non-whitelisted notary can only be used for notary change transactions"
}
val historicNotary = (serviceHub.networkParametersStorage as HistoricNetworkParameterStorage).getHistoricNotary(notaryParty)
val historicNotary = (serviceHub.networkParametersService as NetworkParametersServiceInternal).getHistoricNotary(notaryParty)
?: throw IllegalStateException("The notary party $notaryParty specified by transaction ${stx.id}, is not recognised as a current or historic notary.")
historicNotary.validating

View File

@ -43,7 +43,7 @@ open class ReceiveTransactionFlow @JvmOverloads constructor(private val otherSid
it.pushToLoggingContext()
logger.info("Received transaction acknowledgement request from party ${otherSideSession.counterparty}.")
checkParameterHash(it.networkParametersHash)
subFlow(ResolveTransactionsFlow(it, otherSideSession))
subFlow(ResolveTransactionsFlow(it, otherSideSession, statesToRecord))
logger.info("Transaction dependencies resolution completed.")
try {
it.verify(serviceHub, checkSufficientSignatures)

View File

@ -4,11 +4,10 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.StateRef
import net.corda.core.internal.uncheckedCast
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.contextLogger
/**
* Given a flow which uses reference states, the [WithReferencedStatesFlow] will execute the the flow as a subFlow.
* If the flow fails due to a [NotaryError.Conflict] for a reference state, then it will be suspended until the
* Given a flow which uses reference states, the [WithReferencedStatesFlow] will execute the flow as a subFlow.
* If the flow fails due to a [NotaryError.Conflict] for a reference state, then WithReferencedStatesFlow will be suspended until the
* state refs for the reference states are consumed. In this case, a consumption means that:
*
* 1. the owner of the reference state has updated the state with a valid, notarised transaction
@ -21,17 +20,16 @@ import net.corda.core.utilities.contextLogger
* reference states. The flow using reference states should include checks to ensure that the reference data is
* reasonable, especially if some economics transaction depends upon it.
*
* @param flowLogic a flow which uses reference states.
* @param progressTracker a progress tracker instance.
* @param flowLogicProducer a lambda which creates the [FlowLogic] instance using reference states. This will be executed at least once.
* It is recommended a new [FlowLogic] instance be returned each time.
*/
class WithReferencedStatesFlow<T : Any>(
val flowLogic: FlowLogic<T>,
override val progressTracker: ProgressTracker = WithReferencedStatesFlow.tracker()
class WithReferencedStatesFlow<T : Any> @JvmOverloads constructor(
override val progressTracker: ProgressTracker = tracker(),
private val flowLogicProducer: () -> FlowLogic<T>
) : FlowLogic<T>() {
companion object {
val logger = contextLogger()
object ATTEMPT : ProgressTracker.Step("Attempting to run flow which uses reference states.")
object RETRYING : ProgressTracker.Step("Reference states are out of date! Waiting for updated states...")
object SUCCESS : ProgressTracker.Step("Flow ran successfully.")
@ -40,10 +38,10 @@ class WithReferencedStatesFlow<T : Any>(
fun tracker() = ProgressTracker(ATTEMPT, RETRYING, SUCCESS)
}
private sealed class FlowResult {
data class Success<T : Any>(val value: T) : FlowResult()
data class Conflict(val stateRefs: Set<StateRef>) : FlowResult()
}
// This is not a sealed data class as that requires exposing Success and Conflict
private interface FlowResult
private data class Success<T : Any>(val value: T) : FlowResult
private data class Conflict(val stateRefs: Set<StateRef>) : FlowResult
/**
* Process the flow result. We don't care about anything other than NotaryExceptions. If it is a
@ -58,13 +56,13 @@ class WithReferencedStatesFlow<T : Any>(
val conflictingReferenceStateRefs = error.consumedStates.filter {
it.value.type == StateConsumptionDetails.ConsumedStateType.REFERENCE_INPUT_STATE
}.map { it.key }.toSet()
FlowResult.Conflict(conflictingReferenceStateRefs)
Conflict(conflictingReferenceStateRefs)
} else {
throw result
}
}
is FlowException -> throw result
else -> FlowResult.Success(result)
else -> Success(result)
}
}
@ -75,6 +73,7 @@ class WithReferencedStatesFlow<T : Any>(
// Loop until the flow successfully completes. We need to
// do this because there might be consecutive update races.
while (true) {
val flowLogic = flowLogicProducer()
// Return a successful flow result or a FlowException.
logger.info("Attempting to run the supplied flow ${flowLogic.javaClass.canonicalName}.")
val result = try {
@ -91,12 +90,12 @@ class WithReferencedStatesFlow<T : Any>(
// states have been updated.
@Suppress("UNCHECKED_CAST")
when (processedResult) {
is FlowResult.Success<*> -> {
is Success<*> -> {
logger.info("Flow ${flowLogic.javaClass.canonicalName} completed successfully.")
progressTracker.currentStep = SUCCESS
return uncheckedCast(processedResult.value)
}
is FlowResult.Conflict -> {
is Conflict -> {
val conflicts = processedResult.stateRefs
logger.info("Flow ${flowLogic.javaClass.name} failed due to reference state conflicts: $conflicts.")
@ -112,4 +111,4 @@ class WithReferencedStatesFlow<T : Any>(
}
}
}
}
}

View File

@ -0,0 +1,31 @@
package net.corda.core.internal
import io.github.classgraph.ClassGraph
import net.corda.core.CordaInternal
import net.corda.core.DeleteForDJVM
import net.corda.core.StubOutForDJVM
import kotlin.reflect.full.createInstance
/**
* Creates instances of all the classes in the classpath of the provided classloader, which implement the interface of the provided class.
* @param classloader the classloader, which will be searched for the classes.
* @param clazz the class of the interface, which the classes - to be returned - must implement.
*
* @return instances of the identified classes.
* @throws IllegalArgumentException if the classes found do not have proper constructors.
*
* Note: In order to be instantiated, the associated classes must:
* - be non-abstract
* - either be a Kotlin object or have a constructor with no parameters (or only optional ones)
*/
@StubOutForDJVM
fun <T: Any> loadClassesImplementing(classloader: ClassLoader, clazz: Class<T>): Set<T> {
return ClassGraph().addClassLoader(classloader)
.enableAllInfo()
.scan()
.getClassesImplementing(clazz.name)
.filterNot { it.isAbstract }
.mapNotNull { classloader.loadClass(it.name).asSubclass(clazz) }
.map { it.kotlin.objectInstance ?: it.kotlin.createInstance() }
.toSet()
}

View File

@ -21,7 +21,7 @@ object ContractUpgradeUtils {
else -> getContractAttachmentId(stateAndRef.state.contract, services)
}
val upgradedContractAttachmentId = getContractAttachmentId(upgradedContractClass.name, services)
val networkParametersHash = services.networkParametersStorage.currentHash
val networkParametersHash = services.networkParametersService.currentHash
val inputs = listOf(stateAndRef.ref)
return ContractUpgradeTransactionBuilder(

View File

@ -1,6 +1,7 @@
package net.corda.core.internal
import net.corda.core.DeleteForDJVM
import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.ContractClassName
import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappConfig
@ -11,18 +12,28 @@ import net.corda.core.flows.FlowLogic
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.ZoneVersionTooLowException
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.node.services.vault.AttachmentQueryCriteria
import net.corda.core.node.services.vault.AttachmentSort
import net.corda.core.node.services.vault.Builder
import net.corda.core.node.services.vault.Sort
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.MDC
import java.security.PublicKey
import java.util.jar.JarEntry
import java.util.jar.JarInputStream
// *Internal* Corda-specific utilities.
const val PLATFORM_VERSION = 4
const val PLATFORM_VERSION = 5
fun ServicesForResolution.ensureMinimumPlatformVersion(requiredMinPlatformVersion: Int, feature: String) {
checkMinimumPlatformVersion(networkParameters.minimumPlatformVersion, requiredMinPlatformVersion, feature)
@ -116,3 +127,34 @@ internal fun NetworkParameters.getPackageOwnerOf(contractClassNames: Set<Contrac
fun noPackageOverlap(packages: Collection<String>): Boolean {
return packages.all { outer -> packages.none { inner -> inner != outer && inner.startsWith("$outer.") } }
}
/**
* Scans trusted (installed locally) contract 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 contract 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): ContractAttachment?{
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 is ContractAttachment && 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

@ -0,0 +1,20 @@
@file:KeepForDJVM
package net.corda.core.internal
import net.corda.core.KeepForDJVM
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TransactionState
import net.corda.core.crypto.SecureHash
import net.corda.core.node.NetworkParameters
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.WireTransaction
fun WireTransaction.toLtxDjvmInternal(
resolveAttachment: (SecureHash) -> Attachment?,
resolveStateRef: (StateRef) -> TransactionState<*>?,
resolveParameters: (SecureHash?) -> NetworkParameters?
): LedgerTransaction {
return toLtxDjvmInternalBridge(resolveAttachment, resolveStateRef, resolveParameters)
}

View File

@ -1,9 +1,10 @@
package net.corda.core.internal.notary
package net.corda.core.internal
import net.corda.core.identity.Party
import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.NetworkParametersService
interface HistoricNetworkParameterStorage {
interface NetworkParametersServiceInternal : NetworkParametersService {
/**
* Returns the [NotaryInfo] for a notary [party] in the current or any historic network parameter whitelist, or null if not found.
*/

View File

@ -75,6 +75,9 @@ inline val Path.isReadable: Boolean get() = Files.isReadable(this)
/** @see Files.size */
inline val Path.size: Long get() = Files.size(this)
/** @see Files.readAttributes */
fun Path.attributes(vararg options: LinkOption): BasicFileAttributes = Files.readAttributes(this, BasicFileAttributes::class.java, *options)
/** @see Files.getLastModifiedTime */
fun Path.lastModifiedTime(vararg options: LinkOption): FileTime = Files.getLastModifiedTime(this, *options)

View File

@ -20,12 +20,11 @@ import kotlin.math.min
/**
* Resolves transactions for the specified [txHashes] along with their full history (dependency graph) from [otherSide].
* Each retrieved transaction is validated and inserted into the local transaction storage.
*
* @return a list of verified [SignedTransaction] objects, in a depth-first order.
*/
@DeleteForDJVM
class ResolveTransactionsFlow(txHashesArg: Set<SecureHash>,
private val otherSide: FlowSession) : FlowLogic<Unit>() {
private val otherSide: FlowSession,
private val statesToRecord: StatesToRecord = StatesToRecord.NONE) : FlowLogic<Unit>() {
// Need it ordered in terms of iteration. Needs to be a variable for the check-pointing logic to work.
private val txHashes = txHashesArg.toList()
@ -40,6 +39,10 @@ class ResolveTransactionsFlow(txHashesArg: Set<SecureHash>,
this.signedTransaction = signedTransaction
}
constructor(signedTransaction: SignedTransaction, otherSide: FlowSession, statesToRecord: StatesToRecord) : this(dependencyIDs(signedTransaction), otherSide, statesToRecord) {
this.signedTransaction = signedTransaction
}
@DeleteForDJVM
companion object {
private fun dependencyIDs(stx: SignedTransaction) = stx.inputs.map { it.txhash }.toSet() + stx.references.map { it.txhash }.toSet()
@ -87,13 +90,16 @@ class ResolveTransactionsFlow(txHashesArg: Set<SecureHash>,
// Finish fetching data.
val result = topologicalSort(newTxns)
// If transaction resolution is performed for a transaction where some states are relevant, then those should be
// recorded if this has not already occurred.
val usedStatesToRecord = if (statesToRecord == StatesToRecord.NONE) StatesToRecord.ONLY_RELEVANT else statesToRecord
result.forEach {
// For each transaction, verify it and insert it into the database. As we are iterating over them in a
// depth-first order, we should not encounter any verification failures due to missing data. If we fail
// half way through, it's no big deal, although it might result in us attempting to re-download data
// redundantly next time we attempt verification.
it.verify(serviceHub)
serviceHub.recordTransactions(StatesToRecord.NONE, listOf(it))
serviceHub.recordTransactions(usedStatesToRecord, listOf(it))
}
}

View File

@ -1,18 +1,17 @@
package net.corda.core.internal
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.LinearPointer
import net.corda.core.contracts.StatePointer
import net.corda.core.contracts.StaticPointer
import java.lang.reflect.Field
import java.util.*
/**
* Uses reflection to search for instances of [StatePointer] within a [ContractState].
* TODO: Doesn't handle calculated properties. Add support for this.
*/
class StatePointerSearch(val state: ContractState) {
// Classes in these packages should not be part of a search.
private val blackListedPackages = setOf("java.", "javax.")
private val blackListedPackages = setOf("java.", "javax.", "org.bouncycastle.", "net.i2p.crypto.")
// Type required for traversal.
private data class FieldWithObject(val obj: Any, val field: Field)
@ -21,14 +20,26 @@ class StatePointerSearch(val state: ContractState) {
private val statePointers = mutableSetOf<StatePointer<*>>()
// Record seen objects to avoid getting stuck in loops.
private val seenObjects = mutableSetOf<Any>().apply { add(state) }
private val seenObjects = Collections.newSetFromMap(IdentityHashMap<Any, Boolean>()).apply { add(state) }
// Queue of fields to search.
private val fieldQueue = ArrayDeque<FieldWithObject>().apply { addAllFields(state) }
// Get fields of class and all super-classes.
private fun getAllFields(clazz: Class<*>): List<Field> {
val fields = mutableListOf<Field>()
var currentClazz = clazz
while (currentClazz.superclass != null) {
fields.addAll(currentClazz.declaredFields)
currentClazz = currentClazz.superclass
}
return fields
}
// Helper for adding all fields to the queue.
private fun ArrayDeque<FieldWithObject>.addAllFields(obj: Any) {
val fields = obj::class.java.declaredFields
val fields = getAllFields(obj::class.java)
val fieldsWithObjects = fields.mapNotNull { field ->
// Ignore classes which have not been loaded.
// Assumption: all required state classes are already loaded.
@ -36,39 +47,44 @@ class StatePointerSearch(val state: ContractState) {
if (packageName == null) {
null
} else {
// Ignore JDK classes.
val isBlacklistedPackage = blackListedPackages.any { packageName.startsWith(it) }
if (isBlacklistedPackage) {
null
} else {
FieldWithObject(obj, field)
}
FieldWithObject(obj, field)
}
}
addAll(fieldsWithObjects)
}
private fun handleField(obj: Any, field: Field) {
when {
// StatePointer. Handles nullable StatePointers too.
field.type == LinearPointer::class.java -> statePointers.add(field.get(obj) as? LinearPointer<*> ?: return)
field.type == StaticPointer::class.java -> statePointers.add(field.get(obj) as? StaticPointer<*> ?: return)
// Not StatePointer.
private fun handleIterable(iterable: Iterable<*>) {
iterable.forEach { obj -> handleObject(obj) }
}
private fun handleMap(map: Map<*, *>) {
map.forEach { k, v ->
handleObject(k)
handleObject(v)
}
}
private fun handleObject(obj: Any?) {
if (obj == null) return
seenObjects.add(obj)
when (obj) {
is Map<*, *> -> handleMap(obj)
is StatePointer<*> -> statePointers.add(obj)
is Iterable<*> -> handleIterable(obj)
else -> {
val newObj = field.get(obj) ?: return
// Ignore nulls.
if (newObj in seenObjects) {
return
}
// Recurse.
fieldQueue.addAllFields(newObj)
seenObjects.add(obj)
val packageName = obj.javaClass.`package`.name
val isBlackListed = blackListedPackages.any { packageName.startsWith(it) }
if (isBlackListed.not()) fieldQueue.addAllFields(obj)
}
}
}
private fun handleField(obj: Any, field: Field) {
val newObj = field.get(obj) ?: return
if (newObj in seenObjects) return
handleObject(newObj)
}
fun search(): Set<StatePointer<*>> {
while (fieldQueue.isNotEmpty()) {
val (obj, field) = fieldQueue.pop()

View File

@ -70,7 +70,7 @@ fun <T : Any> deserialiseComponentGroup(componentGroups: List<ComponentGroup>,
// If the componentGroup is a [LazyMappedList] it means that the original deserialized version is already available.
val components = group.components
if (!forceDeserialize && components is LazyMappedList<*, OpaqueBytes>) {
return components.originalList as List<T>
return uncheckedCast(components.originalList)
}
return components.lazyMapped { component, internalIndex ->
@ -166,7 +166,7 @@ fun FlowLogic<*>.checkParameterHash(networkParametersHash: SecureHash?) {
if (serviceHub.networkParameters.minimumPlatformVersion < 4) return
else throw IllegalArgumentException("Transaction for notarisation doesn't contain network parameters hash.")
} else {
serviceHub.networkParametersStorage.lookup(networkParametersHash) ?: throw IllegalArgumentException("Transaction for notarisation contains unknown parameters hash: $networkParametersHash")
serviceHub.networkParametersService.lookup(networkParametersHash) ?: throw IllegalArgumentException("Transaction for notarisation contains unknown parameters hash: $networkParametersHash")
}
// TODO: [ENT-2666] Implement network parameters fuzzy checking. By design in Corda network we have propagation time delay.

View File

@ -0,0 +1,408 @@
package net.corda.core.internal
import net.corda.core.DeleteForDJVM
import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.*
import net.corda.core.contracts.TransactionVerificationException.TransactionContractConflictException
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.rules.StateContractValidationEnforcementRule
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.contextLogger
@DeleteForDJVM
interface TransactionVerifierServiceInternal {
/**
* Verifies the [transaction] but adds some [extraAttachments] to the classpath.
* Required for transactions built with Corda 3.x that might miss some dependencies due to a bug in that version.
*/
fun verify(transaction: LedgerTransaction, extraAttachments: List<Attachment> ): CordaFuture<*>
}
/**
* Defined here for visibility reasons.
*/
fun LedgerTransaction.prepareVerify(extraAttachments: List<Attachment>) = this.internalPrepareVerify(extraAttachments)
/**
* Because we create a separate [LedgerTransaction] onto which we need to perform verification, it becomes important we don't verify the
* wrong object instance. This class helps avoid that.
*/
class Verifier(val ltx: LedgerTransaction, val transactionClassLoader: ClassLoader, private val inputStatesContractClassNameToMaxVersion: Map<ContractClassName, Version>) {
private val inputStates: List<TransactionState<*>> = ltx.inputs.map { it.state }
private val allStates: List<TransactionState<*>> = inputStates + ltx.references.map { it.state } + ltx.outputs
private val contractAttachmentsByContract: Map<ContractClassName, Set<ContractAttachment>> = getContractAttachmentsByContract()
companion object {
private val logger = contextLogger()
}
fun verify() {
// checkNoNotaryChange and checkEncumbrancesValid are called here, and not in the c'tor, as they need access to the "outputs"
// list, the contents of which need to be deserialized under the correct classloader.
checkNoNotaryChange()
checkEncumbrancesValid()
validateContractVersions()
validatePackageOwnership()
validateStatesAgainstContract()
val hashToSignatureConstrainedContracts = verifyConstraintsValidity()
verifyConstraints(hashToSignatureConstrainedContracts)
verifyContracts()
}
// TODO: revisit to include contract version information
/**
* This method may return more than one attachment for a given contract class.
* Specifically, this is the case for transactions combining hash and signature constraints where the hash constrained contract jar
* will be unsigned, and the signature constrained counterpart will be signed.
*/
private fun getContractAttachmentsByContract(): Map<ContractClassName, Set<ContractAttachment>> {
val contractClasses = allStates.map { it.contract }.toSet()
val result = mutableMapOf<ContractClassName, Set<ContractAttachment>>()
for (attachment in ltx.attachments) {
if (attachment !is ContractAttachment) continue
for (contract in contractClasses) {
if (contract !in attachment.allContracts) continue
result[contract] = result.getOrDefault(contract, setOf(attachment)).plus(attachment)
}
}
return result
}
/**
* Make sure the notary has stayed the same. As we can't tell how inputs and outputs connect, if there
* are any inputs or reference inputs, all outputs must have the same notary.
*
* TODO: Is that the correct set of restrictions? May need to come back to this, see if we can be more
* flexible on output notaries.
*/
private fun checkNoNotaryChange() {
if (ltx.notary != null && (ltx.inputs.isNotEmpty() || ltx.references.isNotEmpty())) {
ltx.outputs.forEach {
if (it.notary != ltx.notary) {
throw TransactionVerificationException.NotaryChangeInWrongTransactionType(ltx.id, ltx.notary, it.notary)
}
}
}
}
private fun checkEncumbrancesValid() {
// Validate that all encumbrances exist within the set of input states.
ltx.inputs
.filter { it.state.encumbrance != null }
.forEach { (state, ref) -> checkInputEncumbranceStateExists(state, ref) }
// Check that in the outputs,
// a) an encumbered state does not refer to itself as the encumbrance
// b) the number of outputs can contain the encumbrance
// c) the bi-directionality (full cycle) property is satisfied
// d) encumbered output states are assigned to the same notary.
val statesAndEncumbrance = ltx.outputs
.withIndex()
.filter { it.value.encumbrance != null }
.map { Pair(it.index, it.value.encumbrance!!) }
if (!statesAndEncumbrance.isEmpty()) {
checkBidirectionalOutputEncumbrances(statesAndEncumbrance)
checkNotariesOutputEncumbrance(statesAndEncumbrance)
}
}
private fun checkInputEncumbranceStateExists(state: TransactionState<ContractState>, ref: StateRef) {
val encumbranceStateExists = ltx.inputs.any {
it.ref.txhash == ref.txhash && it.ref.index == state.encumbrance
}
if (!encumbranceStateExists) {
throw TransactionVerificationException.TransactionMissingEncumbranceException(
ltx.id,
state.encumbrance!!,
TransactionVerificationException.Direction.INPUT
)
}
}
// Using basic graph theory, a full cycle of encumbered (co-dependent) states should exist to achieve bi-directional
// encumbrances. This property is important to ensure that no states involved in an encumbrance-relationship
// can be spent on their own. Briefly, if any of the states is having more than one encumbrance references by
// other states, a full cycle detection will fail. As a result, all of the encumbered states must be present
// as "from" and "to" only once (or zero times if no encumbrance takes place). For instance,
// a -> b
// c -> b and a -> b
// b -> a b -> c
// do not satisfy the bi-directionality (full cycle) property.
//
// In the first example "b" appears twice in encumbrance ("to") list and "c" exists in the encumbered ("from") list only.
// Due the above, one could consume "a" and "b" in the same transaction and then, because "b" is already consumed, "c" cannot be spent.
//
// Similarly, the second example does not form a full cycle because "a" and "c" exist in one of the lists only.
// As a result, one can consume "b" and "c" in the same transactions, which will make "a" impossible to be spent.
//
// On other hand the following are valid constructions:
// a -> b a -> c
// b -> c and c -> b
// c -> a b -> a
// and form a full cycle, meaning that the bi-directionality property is satisfied.
private fun checkBidirectionalOutputEncumbrances(statesAndEncumbrance: List<Pair<Int, Int>>) {
// [Set] of "from" (encumbered states).
val encumberedSet = mutableSetOf<Int>()
// [Set] of "to" (encumbrance states).
val encumbranceSet = mutableSetOf<Int>()
// Update both [Set]s.
statesAndEncumbrance.forEach { (statePosition, encumbrance) ->
// Check it does not refer to itself.
if (statePosition == encumbrance || encumbrance >= ltx.outputs.size) {
throw TransactionVerificationException.TransactionMissingEncumbranceException(
ltx.id,
encumbrance,
TransactionVerificationException.Direction.OUTPUT
)
} else {
encumberedSet.add(statePosition) // Guaranteed to have unique elements.
if (!encumbranceSet.add(encumbrance)) {
throw TransactionVerificationException.TransactionDuplicateEncumbranceException(ltx.id, encumbrance)
}
}
}
// At this stage we have ensured that "from" and "to" [Set]s are equal in size, but we should check their
// elements do indeed match. If they don't match, we return their symmetric difference (disjunctive union).
val symmetricDifference = (encumberedSet union encumbranceSet).subtract(encumberedSet intersect encumbranceSet)
if (symmetricDifference.isNotEmpty()) {
// At least one encumbered state is not in the [encumbranceSet] and vice versa.
throw TransactionVerificationException.TransactionNonMatchingEncumbranceException(ltx.id, symmetricDifference)
}
}
// Method to check if all encumbered states are assigned to the same notary Party.
// This method should be invoked after [checkBidirectionalOutputEncumbrances], because it assumes that the
// bi-directionality property is already satisfied.
private fun checkNotariesOutputEncumbrance(statesAndEncumbrance: List<Pair<Int, Int>>) {
// We only check for transactions in which notary is null (i.e., issuing transactions).
// Note that if a notary is defined for a transaction, we already check if all outputs are assigned
// to the same notary (transaction's notary) in [checkNoNotaryChange()].
if (ltx.notary == null) {
// indicesAlreadyChecked is used to bypass already checked indices and to avoid cycles.
val indicesAlreadyChecked = HashSet<Int>()
statesAndEncumbrance.forEach {
checkNotary(it.first, indicesAlreadyChecked)
}
}
}
private tailrec fun checkNotary(index: Int, indicesAlreadyChecked: HashSet<Int>) {
if (indicesAlreadyChecked.add(index)) {
val encumbranceIndex = ltx.outputs[index].encumbrance!!
if (ltx.outputs[index].notary != ltx.outputs[encumbranceIndex].notary) {
throw TransactionVerificationException.TransactionNotaryMismatchEncumbranceException(
ltx.id,
index,
encumbranceIndex,
ltx.outputs[index].notary,
ltx.outputs[encumbranceIndex].notary
)
} else {
checkNotary(encumbranceIndex, indicesAlreadyChecked)
}
}
}
/**
* Verify that contract class versions of output states are not lower that versions of relevant input states.
*/
private fun validateContractVersions() {
contractAttachmentsByContract.forEach { contractClassName, attachments ->
val outputVersion = attachments.signed?.version ?: attachments.unsigned?.version ?: CordappImpl.DEFAULT_CORDAPP_VERSION
inputStatesContractClassNameToMaxVersion[contractClassName]?.let {
if (it > outputVersion) {
throw TransactionVerificationException.TransactionVerificationVersionException(ltx.id, contractClassName, "$it", "$outputVersion")
}
}
}
}
/**
* Verify that for each contract the network wide package owner is respected.
*
* TODO - revisit once transaction contains network parameters. - UPDATE: It contains them, but because of the API stability and the fact that
* LedgerTransaction was data class i.e. exposed constructors that shouldn't had been exposed, we still need to keep them nullable :/
*/
private fun validatePackageOwnership() {
val contractsAndOwners = allStates.mapNotNull { transactionState ->
val contractClassName = transactionState.contract
ltx.networkParameters!!.getPackageOwnerOf(contractClassName)?.let { contractClassName to it }
}.toMap()
contractsAndOwners.forEach { contract, owner ->
contractAttachmentsByContract[contract]?.filter { it.isSigned }?.forEach { attachment ->
if (!owner.isFulfilledBy(attachment.signerKeys))
throw TransactionVerificationException.ContractAttachmentNotSignedByPackageOwnerException(ltx.id, attachment.id, contract)
} ?: throw TransactionVerificationException.ContractAttachmentNotSignedByPackageOwnerException(ltx.id, ltx.id, contract)
}
}
/**
* For all input and output [TransactionState]s, validates that the wrapped [ContractState] matches up with the
* wrapped [Contract], as declared by the [BelongsToContract] annotation on the [ContractState]'s class.
*
* If the target platform version of the current CorDapp is lower than 4.0, a warning will be written to the log
* if any mismatch is detected. If it is 4.0 or later, then [TransactionContractConflictException] will be thrown.
*/
private fun validateStatesAgainstContract() = allStates.forEach(::validateStateAgainstContract)
private fun validateStateAgainstContract(state: TransactionState<ContractState>) {
val shouldEnforce = StateContractValidationEnforcementRule.shouldEnforce(state.data)
val requiredContractClassName = state.data.requiredContractClassName
?: if (shouldEnforce) throw TransactionVerificationException.TransactionRequiredContractUnspecifiedException(ltx.id, state) else return
if (state.contract != requiredContractClassName)
if (shouldEnforce) {
throw TransactionContractConflictException(ltx.id, state, requiredContractClassName)
} else {
logger.warnOnce("""
State of class ${state.data::class.java.typeName} belongs to contract $requiredContractClassName, but
is bundled in TransactionState with ${state.contract}.
For details see: https://docs.corda.net/api-contract-constraints.html#contract-state-agreement
""".trimIndent().replace('\n', ' '))
}
}
/**
* Enforces the validity of the actual constraints.
* * Constraints should be one of the valid supported ones.
* * Constraints should propagate correctly if not marked otherwise.
*
* Returns set of contract classes that identify hash -> signature constraint switchover
*/
private fun verifyConstraintsValidity(): MutableSet<ContractClassName> {
// First check that the constraints are valid.
for (state in allStates) {
checkConstraintValidity(state)
}
// Group the inputs and outputs by contract, and for each contract verify the constraints propagation logic.
// This is not required for reference states as there is nothing to propagate.
val inputContractGroups = ltx.inputs.groupBy { it.state.contract }
val outputContractGroups = ltx.outputs.groupBy { it.contract }
// identify any contract classes where input-output pair are transitioning from hash to signature constraints.
val hashToSignatureConstrainedContracts = mutableSetOf<ContractClassName>()
for (contractClassName in (inputContractGroups.keys + outputContractGroups.keys)) {
if (contractClassName.contractHasAutomaticConstraintPropagation(transactionClassLoader)) {
// Verify that the constraints of output states have at least the same level of restriction as the constraints of the
// corresponding input states.
val inputConstraints = inputContractGroups[contractClassName]?.map { it.state.constraint }?.toSet()
val outputConstraints = outputContractGroups[contractClassName]?.map { it.constraint }?.toSet()
outputConstraints?.forEach { outputConstraint ->
inputConstraints?.forEach { inputConstraint ->
val constraintAttachment = resolveAttachment(contractClassName)
if (!(outputConstraint.canBeTransitionedFrom(inputConstraint, constraintAttachment))) {
throw TransactionVerificationException.ConstraintPropagationRejection(
ltx.id,
contractClassName,
inputConstraint,
outputConstraint
)
}
// Hash to signature constraints auto-migration
if (outputConstraint is SignatureAttachmentConstraint && inputConstraint is HashAttachmentConstraint)
hashToSignatureConstrainedContracts.add(contractClassName)
}
}
} else {
contractClassName.warnContractWithoutConstraintPropagation()
}
}
return hashToSignatureConstrainedContracts
}
private fun resolveAttachment(contractClassName: ContractClassName): AttachmentWithContext {
val unsignedAttachment = contractAttachmentsByContract[contractClassName]!!.firstOrNull { !it.isSigned }
val signedAttachment = contractAttachmentsByContract[contractClassName]!!.firstOrNull { it.isSigned }
return when {
(unsignedAttachment != null && signedAttachment != null) -> AttachmentWithContext(signedAttachment, contractClassName, ltx.networkParameters!!)
(unsignedAttachment != null) -> AttachmentWithContext(unsignedAttachment, contractClassName, ltx.networkParameters!!)
(signedAttachment != null) -> AttachmentWithContext(signedAttachment, contractClassName, ltx.networkParameters!!)
else -> throw TransactionVerificationException.ContractConstraintRejection(ltx.id, contractClassName)
}
}
/**
* Verify that all contract constraints are passing before running any contract code.
*
* This check is running the [AttachmentConstraint.isSatisfiedBy] method for each corresponding [ContractAttachment].
*
* @throws TransactionVerificationException if the constraints fail to verify
*/
private fun verifyConstraints(hashToSignatureConstrainedContracts: MutableSet<ContractClassName>) {
for (state in allStates) {
if (state.constraint is SignatureAttachmentConstraint) {
checkMinimumPlatformVersion(ltx.networkParameters!!.minimumPlatformVersion, 4, "Signature constraints")
}
val constraintAttachment = if (state.contract in hashToSignatureConstrainedContracts) {
// hash to to signature constraint migration logic:
// pass the unsigned attachment when verifying the constraint of the input state, and the signed attachment when verifying
// the constraint of the output state.
val unsignedAttachment = contractAttachmentsByContract[state.contract].unsigned
?: throw TransactionVerificationException.MissingAttachmentRejection(ltx.id, state.contract)
val signedAttachment = contractAttachmentsByContract[state.contract].signed
?: throw TransactionVerificationException.MissingAttachmentRejection(ltx.id, state.contract)
when {
// use unsigned attachment if hash-constrained input state
state.data in ltx.inputStates -> AttachmentWithContext(unsignedAttachment, state.contract, ltx.networkParameters!!)
// use signed attachment if signature-constrained output state
state.data in ltx.outputStates -> AttachmentWithContext(signedAttachment, state.contract, ltx.networkParameters!!)
else -> throw IllegalStateException("${state.contract} must use either signed or unsigned attachment in hash to signature constraints migration")
}
} else {
// standard processing logic
val contractAttachment = contractAttachmentsByContract[state.contract]?.firstOrNull()
?: throw TransactionVerificationException.MissingAttachmentRejection(ltx.id, state.contract)
AttachmentWithContext(contractAttachment, state.contract, ltx.networkParameters!!)
}
if (!state.constraint.isSatisfiedBy(constraintAttachment)) {
throw TransactionVerificationException.ContractConstraintRejection(ltx.id, state.contract)
}
}
}
/**
* Check the transaction is contract-valid by running the verify() for each input and output state contract.
* If any contract fails to verify, the whole transaction is considered to be invalid.
*/
private fun verifyContracts() {
val contractClasses = (inputStates + ltx.outputs).toSet()
.map { it.contract to contractClassFor(it.contract, it.data.javaClass.classLoader) }
val contractInstances = contractClasses.map { (contractClassName, contractClass) ->
try {
contractClass.newInstance()
} catch (e: Exception) {
throw TransactionVerificationException.ContractCreationError(ltx.id, contractClassName, e)
}
}
contractInstances.forEach { contract ->
try {
contract.verify(ltx)
} catch (e: Exception) {
throw TransactionVerificationException.ContractRejection(ltx.id, contract, e)
}
}
}
private fun contractClassFor(className: ContractClassName, classLoader: ClassLoader): Class<out Contract> {
return try {
classLoader.loadClass(className).asSubclass(Contract::class.java)
} catch (e: Exception) {
throw TransactionVerificationException.ContractCreationError(ltx.id, className, e)
}
}
private val Set<ContractAttachment>?.unsigned: ContractAttachment? get() = this?.firstOrNull { !it.isSigned }
private val Set<ContractAttachment>?.signed: ContractAttachment? get() = this?.firstOrNull { it.isSigned }
}

View File

@ -32,7 +32,7 @@ data class CordappImpl(
override val jarHash: SecureHash.SHA256,
override val minimumPlatformVersion: Int,
override val targetPlatformVersion: Int,
val notaryService: Class<out NotaryService>?,
val notaryService: Class<out NotaryService>? = null,
/** Indicates whether the CorDapp is loaded from external sources, or generated on node startup (virtual). */
val isLoaded: Boolean = true
) : Cordapp {

View File

@ -38,8 +38,8 @@ interface ServicesForResolution {
/** Provides access to anything relating to cordapps including contract attachment resolution and app context */
val cordappProvider: CordappProvider
/** Provides access to storage of historical network parameters that are used in transaction resolution */
val networkParametersStorage: NetworkParametersStorage
/** Provides access to historical network parameters that are used in transaction resolution. */
val networkParametersService: NetworkParametersService
/** Returns the network parameters the node is operating under. */
val networkParameters: NetworkParameters
@ -359,7 +359,7 @@ interface ServiceHub : ServicesForResolution {
* and thus queryable data will include everything committed as of the last checkpoint.
*
* @throws IllegalStateException if called outside of a transaction.
* @return A new [Connection]
* @return A [Connection]
*/
fun jdbcSession(): Connection

View File

@ -1,7 +1,9 @@
package net.corda.core.node.services
import net.corda.core.CordaInternal
import net.corda.core.DoNotImplement
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractAttachment
import net.corda.core.crypto.SecureHash
import net.corda.core.node.services.vault.AttachmentQueryCriteria
import net.corda.core.node.services.vault.AttachmentSort

View File

@ -2,16 +2,14 @@ package net.corda.core.node.services
import net.corda.core.DoNotImplement
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NotaryInfo
/**
* Interface for handling network parameters storage used for resolving transactions according to parameters that were
* Service for retrieving network parameters used for resolving transactions according to parameters that were
* historically in force in the network.
*/
@DoNotImplement
interface NetworkParametersStorage {
interface NetworkParametersService {
/**
* Hash of the current parameters for the network.
*/
@ -23,8 +21,7 @@ interface NetworkParametersStorage {
val defaultHash: SecureHash
/**
* Return network parameters for the given hash. Null if there are no parameters for this hash in the storage and we are unable to
* get them from network map.
* Return the network parameters with the given hash, or null if it doesn't exist.
*/
fun lookup(hash: SecureHash): NetworkParameters?
}

View File

@ -3,6 +3,7 @@ package net.corda.core.node.services
import net.corda.core.DeleteForDJVM
import net.corda.core.DoNotImplement
import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.Attachment
import net.corda.core.transactions.LedgerTransaction
/**

View File

@ -390,8 +390,11 @@ interface VaultService {
*
* @throws VaultQueryException if the query cannot be executed for any reason.
*
* Notes: the snapshot part of the query adheres to the same behaviour as the [queryBy] function.
* the [QueryCriteria] applies to both snapshot and deltas (streaming updates).
* Notes:
* - The snapshot part of the query adheres to the same behaviour as the [queryBy] function.
* - The update part of the query currently only supports query criteria filtering by contract
* type(s) and state status(es). CID-731 <https://r3-cev.atlassian.net/browse/CID-731> proposes
* adding the complete set of [QueryCriteria] filtering.
*/
@Throws(VaultQueryException::class)
fun <T : ContractState> _trackBy(criteria: QueryCriteria,
@ -410,6 +413,10 @@ interface VaultService {
return _queryBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType)
}
fun <T : ContractState> queryBy(contractStateType: Class<out T>, paging: PageSpecification): Vault.Page<T> {
return _queryBy(QueryCriteria.VaultQueryCriteria(), paging, Sort(emptySet()), contractStateType)
}
fun <T : ContractState> queryBy(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): Vault.Page<T> {
return _queryBy(criteria, paging, Sort(emptySet()), contractStateType)
}
@ -430,6 +437,10 @@ interface VaultService {
return _trackBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType)
}
fun <T : ContractState> trackBy(contractStateType: Class<out T>, paging: PageSpecification): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return _trackBy(QueryCriteria.VaultQueryCriteria(), paging, Sort(emptySet()), contractStateType)
}
fun <T : ContractState> trackBy(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return _trackBy(criteria, paging, Sort(emptySet()), contractStateType)
}
@ -451,6 +462,10 @@ inline fun <reified T : ContractState> VaultService.queryBy(criteria: QueryCrite
return _queryBy(criteria, PageSpecification(), Sort(emptySet()), T::class.java)
}
inline fun <reified T : ContractState> VaultService.queryBy(paging: PageSpecification): Vault.Page<T> {
return _queryBy(QueryCriteria.VaultQueryCriteria(), paging, Sort(emptySet()), T::class.java)
}
inline fun <reified T : ContractState> VaultService.queryBy(criteria: QueryCriteria, paging: PageSpecification): Vault.Page<T> {
return _queryBy(criteria, paging, Sort(emptySet()), T::class.java)
}
@ -467,6 +482,10 @@ inline fun <reified T : ContractState> VaultService.trackBy(): DataFeed<Vault.Pa
return _trackBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), T::class.java)
}
inline fun <reified T : ContractState> VaultService.trackBy(paging: PageSpecification): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return _trackBy(QueryCriteria.VaultQueryCriteria(), paging, Sort(emptySet()), T::class.java)
}
inline fun <reified T : ContractState> VaultService.trackBy(criteria: QueryCriteria): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return _trackBy(criteria, PageSpecification(), Sort(emptySet()), T::class.java)
}

View File

@ -94,12 +94,39 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
val stateRefs: List<StateRef>? = null,
val notary: List<AbstractParty>? = null,
val softLockingCondition: SoftLockingCondition? = null,
val timeCondition: TimeCondition? = null,
override val relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL,
override val constraintTypes: Set<Vault.ConstraintInfo.Type> = emptySet(),
override val constraints: Set<Vault.ConstraintInfo> = emptySet(),
override val participants: List<AbstractParty>? = null
val timeCondition: TimeCondition? = null
) : CommonQueryCriteria() {
// These extra fields are handled this way to preserve Kotlin wire compatibility wrt additional parameters with default values.
constructor(
status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
contractStateTypes: Set<Class<out ContractState>>? = null,
stateRefs: List<StateRef>? = null,
notary: List<AbstractParty>? = null,
softLockingCondition: SoftLockingCondition? = null,
timeCondition: TimeCondition? = null,
relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL,
constraintTypes: Set<Vault.ConstraintInfo.Type> = emptySet(),
constraints: Set<Vault.ConstraintInfo> = emptySet(),
participants: List<AbstractParty>? = null
) : this(status, contractStateTypes, stateRefs, notary, softLockingCondition, timeCondition) {
this.relevancyStatus = relevancyStatus
this.constraintTypes = constraintTypes
this.constraints = constraints
this.participants = participants
}
override var relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
private set
override var constraintTypes: Set<Vault.ConstraintInfo.Type> = emptySet()
private set
override var constraints: Set<Vault.ConstraintInfo> = emptySet()
private set
override var participants: List<AbstractParty>? = null
private set
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
super.visit(parser)
return parser.parseCriteria(this)
@ -111,7 +138,11 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
stateRefs: List<StateRef>? = this.stateRefs,
notary: List<AbstractParty>? = this.notary,
softLockingCondition: SoftLockingCondition? = this.softLockingCondition,
timeCondition: TimeCondition? = this.timeCondition
timeCondition: TimeCondition? = this.timeCondition,
relevancyStatus: Vault.RelevancyStatus = this.relevancyStatus,
constraintTypes: Set<Vault.ConstraintInfo.Type> = this.constraintTypes,
constraints: Set<Vault.ConstraintInfo> = this.constraints,
participants: List<AbstractParty>? = this.participants
): VaultQueryCriteria {
return VaultQueryCriteria(
status,
@ -119,7 +150,11 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
stateRefs,
notary,
softLockingCondition,
timeCondition
timeCondition,
relevancyStatus,
constraintTypes,
constraints,
participants
)
}
}
@ -238,9 +273,21 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
data class VaultCustomQueryCriteria<L : StatePersistable> @JvmOverloads constructor(
val expression: CriteriaExpression<L, Boolean>,
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
override val contractStateTypes: Set<Class<out ContractState>>? = null,
override val relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
override val contractStateTypes: Set<Class<out ContractState>>? = null
) : CommonQueryCriteria() {
// These extra field is handled this way to preserve Kotlin wire compatibility wrt additional parameters with default values.
constructor(
expression: CriteriaExpression<L, Boolean>,
status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
contractStateTypes: Set<Class<out ContractState>>? = null,
relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
) : this(expression, status, contractStateTypes) {
this.relevancyStatus = relevancyStatus
}
override var relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
private set
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
super.visit(parser)
return parser.parseCriteria(this)
@ -249,12 +296,14 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
fun copy(
expression: CriteriaExpression<L, Boolean> = this.expression,
status: Vault.StateStatus = this.status,
contractStateTypes: Set<Class<out ContractState>>? = this.contractStateTypes
contractStateTypes: Set<Class<out ContractState>>? = this.contractStateTypes,
relevancyStatus: Vault.RelevancyStatus = this.relevancyStatus
): VaultCustomQueryCriteria<L> {
return VaultCustomQueryCriteria(
expression,
status,
contractStateTypes
contractStateTypes,
relevancyStatus
)
}
}

View File

@ -212,7 +212,7 @@ data class Sort(val columns: Collection<SortColumn>) : BaseSort() {
data class AttachmentSort(val columns: Collection<AttachmentSortColumn>) : BaseSort() {
enum class AttachmentSortAttribute(val columnName: String) {
INSERTION_DATE("insertion_date"),
INSERTION_DATE("insertionDate"),
UPLOADER("uploader"),
FILENAME("filename"),
VERSION ("version")

View File

@ -1,6 +1,7 @@
@file:KeepForDJVM
package net.corda.core.serialization
import co.paralleluniverse.io.serialization.Serialization
import net.corda.core.CordaInternal
import net.corda.core.DeleteForDJVM
import net.corda.core.DoNotImplement
@ -160,6 +161,10 @@ interface SerializationContext {
* The use case we are serializing or deserializing for. See [UseCase].
*/
val useCase: UseCase
/**
* Additional custom serializers that will be made available during (de)serialization.
*/
val customSerializers: Set<SerializationCustomSerializer<*, *>>
/**
* Helper method to return a new context based on this context with the property added.
@ -200,6 +205,11 @@ interface SerializationContext {
*/
fun withWhitelisted(clazz: Class<*>): SerializationContext
/**
* Helper method to return a new context based on this context with the given serializers added.
*/
fun withCustomSerializers(serializers: Set<SerializationCustomSerializer<*, *>>): SerializationContext
/**
* Helper method to return a new context based on this context but with serialization using the format this header sequence represents.
*/
@ -335,3 +345,15 @@ interface ClassWhitelist {
interface EncodingWhitelist {
fun acceptEncoding(encoding: SerializationEncoding): Boolean
}
/**
* Helper method to return a new context based on this context with the given list of classes specifically whitelisted.
*/
fun SerializationContext.withWhitelist(classes: List<Class<*>>): SerializationContext {
var currentContext = this
classes.forEach {
clazz -> currentContext = currentContext.withWhitelisted(clazz)
}
return currentContext
}

View File

@ -1,17 +1,21 @@
package net.corda.core.serialization.internal
import net.corda.core.CordaException
import net.corda.core.KeepForDJVM
import net.corda.core.internal.loadClassesImplementing
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.TransactionVerificationException.OverlappingAttachmentsException
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.*
import net.corda.core.internal.cordapp.targetPlatformVersion
import net.corda.core.internal.createSimpleCache
import net.corda.core.internal.isUploaderTrusted
import net.corda.core.internal.toSynchronised
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.MissingAttachmentsException
import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.SerializationFactory
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.*
import net.corda.core.serialization.internal.AttachmentURLStreamHandlerFactory.toUrl
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
@ -19,6 +23,7 @@ import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream
import java.net.*
import java.util.*
/**
* A custom ClassLoader that knows how to load classes from a set of attachments. The attachments themselves only
@ -32,7 +37,7 @@ class AttachmentsClassLoader(attachments: List<Attachment>, parent: ClassLoader
init {
val untrusted = attachments.mapNotNull { it as? ContractAttachment }.filterNot { isUploaderTrusted(it.uploader) }.map(ContractAttachment::id)
if(untrusted.isNotEmpty()) {
throw MissingAttachmentsException(untrusted, "Attempting to load Contract Attachments downloaded from the network")
throw UntrustedAttachmentsException(untrusted)
}
requireNoDuplicates(attachments)
}
@ -41,10 +46,11 @@ class AttachmentsClassLoader(attachments: List<Attachment>, parent: ClassLoader
private val log = contextLogger()
init {
// This is required to register the AttachmentURLStreamHandlerFactory.
URL.setURLStreamHandlerFactory(AttachmentURLStreamHandlerFactory)
// Apply our own URLStreamHandlerFactory to resolve attachments
setOrDecorateURLStreamHandlerFactory()
}
// Jolokia and Json-simple are dependencies that were bundled by mistake within contract jars.
// In the AttachmentsClassLoader we just ignore any class in those 2 packages.
private val ignoreDirectories = listOf("org/jolokia/", "org/json/simple/")
@ -115,6 +121,50 @@ class AttachmentsClassLoader(attachments: List<Attachment>, parent: ClassLoader
return it.toByteArray()
}
}
/**
* Apply our custom factory either directly, if `URL.setURLStreamHandlerFactory` has not been called yet,
* or use a decorator and reflection to bypass the single-call-per-JVM restriction otherwise.
*/
private fun setOrDecorateURLStreamHandlerFactory() {
// Retrieve the `URL.factory` field
val factoryField = URL::class.java.getDeclaredField("factory")
// Make it accessible
factoryField.isAccessible = true
// Check for preset factory, set directly if missing
val existingFactory: URLStreamHandlerFactory? = factoryField.get(null) as URLStreamHandlerFactory?
if (existingFactory == null) {
URL.setURLStreamHandlerFactory(AttachmentURLStreamHandlerFactory)
}
// Otherwise, decorate the existing and replace via reflection
// as calling `URL.setURLStreamHandlerFactory` again will throw an error
else {
log.warn("The URLStreamHandlerFactory was already set in the JVM. Please be aware that this is not recommended.")
// Retrieve the field "streamHandlerLock" of the class URL that
// is the lock used to synchronize access to the protocol handlers
val lockField = URL::class.java.getDeclaredField("streamHandlerLock")
// It is a private field so we need to make it accessible
// Note: this will only work as-is in JDK8.
lockField.isAccessible = true
// Use the same lock to reset the factory
synchronized(lockField.get(null)) {
// Reset the value to prevent Error due to a factory already defined
factoryField.set(null, null)
// Set our custom factory and wrap the current one into it
URL.setURLStreamHandlerFactory(
// Set the factory to a decorator
object : URLStreamHandlerFactory {
// route between our own and the pre-existing factory
override fun createURLStreamHandler(protocol: String): URLStreamHandler? {
return AttachmentURLStreamHandlerFactory.createURLStreamHandler(protocol)
?: existingFactory.createURLStreamHandler(protocol)
}
}
)
}
}
}
}
/**
@ -130,34 +180,38 @@ class AttachmentsClassLoader(attachments: List<Attachment>, parent: ClassLoader
}
/**
* This is just a factory that provides a cache to avoid constructing expensive [AttachmentsClassLoader]s.
* This is just a factory that provides caches to optimise expensive construction/loading of classloaders, serializers, whitelisted classes.
*/
@VisibleForTesting
internal object AttachmentsClassLoaderBuilder {
private const val ATTACHMENT_CLASSLOADER_CACHE_SIZE = 1000
private const val CACHE_SIZE = 1000
// This runs in the DJVM so it can't use caffeine.
private val cache: MutableMap<List<SecureHash>, AttachmentsClassLoader> = createSimpleCache<List<SecureHash>, AttachmentsClassLoader>(ATTACHMENT_CLASSLOADER_CACHE_SIZE)
.toSynchronised()
fun build(attachments: List<Attachment>): AttachmentsClassLoader {
return cache.computeIfAbsent(attachments.map { it.id }.sorted()) {
AttachmentsClassLoader(attachments)
}
}
private val cache: MutableMap<Set<SecureHash>, SerializationContext> = createSimpleCache(CACHE_SIZE)
fun <T> withAttachmentsClassloaderContext(attachments: List<Attachment>, block: (ClassLoader) -> T): T {
val attachmentIds = attachments.map { it.id }.toSet()
// Create classloader from the attachments.
val transactionClassLoader = AttachmentsClassLoaderBuilder.build(attachments)
val serializationContext = cache.computeIfAbsent(attachmentIds) {
// Create classloader and load serializers, whitelisted classes
val transactionClassLoader = AttachmentsClassLoader(attachments)
val serializers = loadClassesImplementing(transactionClassLoader, SerializationCustomSerializer::class.java)
val whitelistedClasses = ServiceLoader.load(SerializationWhitelist::class.java, transactionClassLoader)
.flatMap { it.whitelist }
.toList()
// Create a new serializationContext for the current Transaction.
val transactionSerializationContext = SerializationFactory.defaultFactory.defaultContext.withPreventDataLoss().withClassLoader(transactionClassLoader)
// Create a new serializationContext for the current Transaction.
SerializationFactory.defaultFactory.defaultContext
.withPreventDataLoss()
.withClassLoader(transactionClassLoader)
.withWhitelist(whitelistedClasses)
.withCustomSerializers(serializers)
}
// Deserialize all relevant classes in the transaction classloader.
return SerializationFactory.defaultFactory.withCurrentContext(transactionSerializationContext) {
block(transactionClassLoader)
return SerializationFactory.defaultFactory.withCurrentContext(serializationContext) {
block(serializationContext.deserializationClassLoader)
}
}
}
@ -200,3 +254,12 @@ object AttachmentURLStreamHandlerFactory : URLStreamHandlerFactory {
}
}
}
/** Thrown during classloading upon encountering an untrusted attachment (eg. not in the [TRUSTED_UPLOADERS] list) */
@KeepForDJVM
@CordaSerializable
class UntrustedAttachmentsException(val ids: List<SecureHash>) :
CordaException("Attempting to load untrusted Contract Attachments: $ids" +
"These may have been received over the p2p network from a remote node." +
"Please follow the operational steps outlined in https://docs.corda.net/cordapp-build-systems.html#cordapp-contract-attachments to continue."
)

View File

@ -111,8 +111,8 @@ data class ContractUpgradeWireTransaction(
?: throw AttachmentResolutionException(legacyContractAttachmentId)
val upgradedContractAttachment = services.attachments.openAttachment(upgradedContractAttachmentId)
?: throw AttachmentResolutionException(upgradedContractAttachmentId)
val hashToResolve = networkParametersHash ?: services.networkParametersStorage.defaultHash
val resolvedNetworkParameters = services.networkParametersStorage.lookup(hashToResolve) ?: throw TransactionResolutionException(id)
val hashToResolve = networkParametersHash ?: services.networkParametersService.defaultHash
val resolvedNetworkParameters = services.networkParametersService.lookup(hashToResolve) ?: throw TransactionResolutionException(id)
return ContractUpgradeLedgerTransaction(
resolvedInputs,
notary,

View File

@ -3,14 +3,9 @@ package net.corda.core.transactions
import net.corda.core.CordaInternal
import net.corda.core.KeepForDJVM
import net.corda.core.contracts.*
import net.corda.core.contracts.TransactionVerificationException.TransactionContractConflictException
import net.corda.core.contracts.TransactionVerificationException.TransactionRequiredContractUnspecifiedException
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.identity.Party
import net.corda.core.internal.*
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
import net.corda.core.internal.rules.StateContractValidationEnforcementRule
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.ConstructorForDeserialization
import net.corda.core.serialization.CordaSerializable
@ -19,7 +14,6 @@ import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
import net.corda.core.utilities.contextLogger
import java.util.*
import java.util.function.Predicate
import kotlin.collections.HashSet
/**
* A LedgerTransaction is derived from a [WireTransaction]. It is the result of doing the following operations:
@ -54,7 +48,7 @@ private constructor(
/** Network parameters that were in force when the transaction was notarised. */
override val networkParameters: NetworkParameters?,
override val references: List<StateAndRef<ContractState>>,
private val inputStatesContractClassNameToMaxVersion: Map<ContractClassName,Version>
private val inputStatesContractClassNameToMaxVersion: Map<ContractClassName, Version>
//DOCEND 1
) : FullTransaction() {
// These are not part of the c'tor above as that defines LedgerTransaction's serialisation format
@ -66,8 +60,6 @@ private constructor(
checkBaseInvariants()
if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" }
checkNotaryWhitelisted()
checkNoNotaryChange()
checkEncumbrancesValid()
}
companion object {
@ -101,9 +93,6 @@ private constructor(
val inputStates: List<ContractState> get() = inputs.map { it.state.data }
val referenceStates: List<ContractState> get() = references.map { it.state.data }
private val inputAndOutputStates = inputs.map { it.state } + outputs
private val allStates = inputAndOutputStates + references.map { it.state }
/**
* Returns the typed input StateAndRef at the specified index
* @param index The index into the inputs.
@ -129,216 +118,16 @@ private constructor(
logger.warn("Network parameters on the LedgerTransaction with id: $id are null. Please don't use deprecated constructors of the LedgerTransaction. " +
"Use WireTransaction.toLedgerTransaction instead. The result of the verify method might not be accurate.")
}
val contractAttachmentsByContract: Map<ContractClassName, Set<ContractAttachment>> = getContractAttachmentsByContract(allStates.map { it.contract }.toSet())
AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(this.attachments) { transactionClassLoader ->
val internalTx = createLtxForVerification()
validateContractVersions(contractAttachmentsByContract)
validatePackageOwnership(contractAttachmentsByContract)
validateStatesAgainstContract(internalTx)
val hashToSignatureConstrainedContracts = verifyConstraintsValidity(internalTx, contractAttachmentsByContract, transactionClassLoader)
verifyConstraints(internalTx, contractAttachmentsByContract, hashToSignatureConstrainedContracts)
verifyContracts(internalTx)
}
val verifier = internalPrepareVerify(emptyList())
verifier.verify()
}
/**
* Verify that contract class versions of output states are not lower that versions of relevant input states.
* This method has to be called in a context where it has access to the database.
*/
@Throws(TransactionVerificationException::class)
private fun validateContractVersions(contractAttachmentsByContract: Map<ContractClassName, Set<ContractAttachment>>) {
contractAttachmentsByContract.forEach { contractClassName, attachments ->
val outputVersion = attachments.signed?.version ?: attachments.unsigned?.version ?: DEFAULT_CORDAPP_VERSION
inputStatesContractClassNameToMaxVersion[contractClassName]?.let {
if (it > outputVersion) {
throw TransactionVerificationException.TransactionVerificationVersionException(this.id, contractClassName, "$it", "$outputVersion")
}
}
}
}
/**
* For all input and output [TransactionState]s, validates that the wrapped [ContractState] matches up with the
* wrapped [Contract], as declared by the [BelongsToContract] annotation on the [ContractState]'s class.
*
* If the target platform version of the current CorDapp is lower than 4.0, a warning will be written to the log
* if any mismatch is detected. If it is 4.0 or later, then [TransactionContractConflictException] will be thrown.
*/
private fun validateStatesAgainstContract(internalTx: LedgerTransaction) =
internalTx.allStates.forEach(::validateStateAgainstContract)
private fun validateStateAgainstContract(state: TransactionState<ContractState>) {
val shouldEnforce = StateContractValidationEnforcementRule.shouldEnforce(state.data)
val requiredContractClassName = state.data.requiredContractClassName ?:
if (shouldEnforce) throw TransactionRequiredContractUnspecifiedException(id, state)
else return
if (state.contract != requiredContractClassName)
if (shouldEnforce) {
throw TransactionContractConflictException(id, state, requiredContractClassName)
} else {
logger.warnOnce("""
State of class ${state.data::class.java.typeName} belongs to contract $requiredContractClassName, but
is bundled in TransactionState with ${state.contract}.
For details see: https://docs.corda.net/api-contract-constraints.html#contract-state-agreement
""".trimIndent().replace('\n', ' '))
}
}
/**
* Verify that for each contract the network wide package owner is respected.
*
* TODO - revisit once transaction contains network parameters. - UPDATE: It contains them, but because of the API stability and the fact that
* LedgerTransaction was data class i.e. exposed constructors that shouldn't had been exposed, we still need to keep them nullable :/
*/
private fun validatePackageOwnership(contractAttachmentsByContract: Map<ContractClassName, Set<ContractAttachment>>) {
val contractsAndOwners = allStates.mapNotNull { transactionState ->
val contractClassName = transactionState.contract
networkParameters!!.getPackageOwnerOf(contractClassName)?.let { contractClassName to it }
}.toMap()
contractsAndOwners.forEach { contract, owner ->
contractAttachmentsByContract[contract]?.filter { it.isSigned }?.forEach { attachment ->
if (!owner.isFulfilledBy(attachment.signerKeys))
throw TransactionVerificationException.ContractAttachmentNotSignedByPackageOwnerException(this.id, id, contract)
} ?: throw TransactionVerificationException.ContractAttachmentNotSignedByPackageOwnerException(this.id, id, contract)
}
}
/**
* Enforces the validity of the actual constraints.
* * Constraints should be one of the valid supported ones.
* * Constraints should propagate correctly if not marked otherwise.
*
* Returns set of contract classes that identify hash -> signature constraint switchover
*/
private fun verifyConstraintsValidity(internalTx: LedgerTransaction, contractAttachmentsByContract: Map<ContractClassName, Set<ContractAttachment>>, transactionClassLoader: ClassLoader): MutableSet<ContractClassName> {
// First check that the constraints are valid.
for (state in internalTx.allStates) {
checkConstraintValidity(state)
}
// Group the inputs and outputs by contract, and for each contract verify the constraints propagation logic.
// This is not required for reference states as there is nothing to propagate.
val inputContractGroups = internalTx.inputs.groupBy { it.state.contract }
val outputContractGroups = internalTx.outputs.groupBy { it.contract }
// identify any contract classes where input-output pair are transitioning from hash to signature constraints.
val hashToSignatureConstrainedContracts = mutableSetOf<ContractClassName>()
for (contractClassName in (inputContractGroups.keys + outputContractGroups.keys)) {
if (contractClassName.contractHasAutomaticConstraintPropagation(transactionClassLoader)) {
// Verify that the constraints of output states have at least the same level of restriction as the constraints of the corresponding input states.
val inputConstraints = inputContractGroups[contractClassName]?.map { it.state.constraint }?.toSet()
val outputConstraints = outputContractGroups[contractClassName]?.map { it.constraint }?.toSet()
outputConstraints?.forEach { outputConstraint ->
inputConstraints?.forEach { inputConstraint ->
val constraintAttachment = resolveAttachment(contractClassName, contractAttachmentsByContract)
if (!(outputConstraint.canBeTransitionedFrom(inputConstraint, constraintAttachment))) {
throw TransactionVerificationException.ConstraintPropagationRejection(id, contractClassName, inputConstraint, outputConstraint)
}
// Hash to signature constraints auto-migration
if (outputConstraint is SignatureAttachmentConstraint && inputConstraint is HashAttachmentConstraint)
hashToSignatureConstrainedContracts.add(contractClassName)
}
}
} else {
contractClassName.warnContractWithoutConstraintPropagation()
}
}
return hashToSignatureConstrainedContracts
}
private fun resolveAttachment(contractClassName: ContractClassName, contractAttachmentsByContract: Map<ContractClassName, Set<ContractAttachment>>): AttachmentWithContext {
val unsignedAttachment = contractAttachmentsByContract[contractClassName]!!.filter { !it.isSigned }.firstOrNull()
val signedAttachment = contractAttachmentsByContract[contractClassName]!!.filter { it.isSigned }.firstOrNull()
return when {
(unsignedAttachment != null && signedAttachment != null) -> AttachmentWithContext(signedAttachment, contractClassName, networkParameters!!)
(unsignedAttachment != null) -> AttachmentWithContext(unsignedAttachment, contractClassName, networkParameters!!)
(signedAttachment != null) -> AttachmentWithContext(signedAttachment, contractClassName, networkParameters!!)
else -> throw TransactionVerificationException.ContractConstraintRejection(id, contractClassName)
}
}
/**
* Verify that all contract constraints are passing before running any contract code.
*
* This check is running the [AttachmentConstraint.isSatisfiedBy] method for each corresponding [ContractAttachment].
*
* @throws TransactionVerificationException if the constraints fail to verify
*/
private fun verifyConstraints(internalTx: LedgerTransaction, contractAttachmentsByContract: Map<ContractClassName, Set<ContractAttachment>>, hashToSignatureConstrainedContracts: MutableSet<ContractClassName>) {
for (state in internalTx.allStates) {
if (state.constraint is SignatureAttachmentConstraint)
checkMinimumPlatformVersion(networkParameters!!.minimumPlatformVersion, 4, "Signature constraints")
val constraintAttachment =
// hash to to signature constraint migration logic:
// pass the unsigned attachment when verifying the constraint of the input state, and the signed attachment when verifying the constraint of the output state.
if (state.contract in hashToSignatureConstrainedContracts) {
val unsignedAttachment = contractAttachmentsByContract[state.contract].unsigned
?: throw TransactionVerificationException.MissingAttachmentRejection(id, state.contract)
val signedAttachment = contractAttachmentsByContract[state.contract].signed
?: throw TransactionVerificationException.MissingAttachmentRejection(id, state.contract)
when {
// use unsigned attachment if hash-constrained input state
state.data in inputStates -> AttachmentWithContext(unsignedAttachment, state.contract, networkParameters!!)
// use signed attachment if signature-constrained output state
state.data in outputStates -> AttachmentWithContext(signedAttachment, state.contract, networkParameters!!)
else -> throw IllegalStateException("${state.contract} must use either signed or unsigned attachment in hash to signature constraints migration")
}
}
// standard processing logic
else {
val contractAttachment = contractAttachmentsByContract[state.contract]?.firstOrNull()
?: throw TransactionVerificationException.MissingAttachmentRejection(id, state.contract)
AttachmentWithContext(contractAttachment, state.contract, networkParameters!!)
}
if (!state.constraint.isSatisfiedBy(constraintAttachment)) {
throw TransactionVerificationException.ContractConstraintRejection(id, state.contract)
}
}
}
private val Set<ContractAttachment>?.unsigned: ContractAttachment?
get() {
return this?.filter { !it.isSigned }?.firstOrNull()
}
private val Set<ContractAttachment>?.signed: ContractAttachment?
get() {
return this?.filter { it.isSigned }?.firstOrNull()
}
// TODO: revisit to include contract version information
/**
* This method may return more than one attachment for a given contract class.
* Specifically, this is the case for transactions combining hash and signature constraints where the hash constrained contract jar
* will be unsigned, and the signature constrained counterpart will be signed.
*/
private fun getContractAttachmentsByContract(contractClasses: Set<ContractClassName>): Map<ContractClassName, Set<ContractAttachment>> {
val result = mutableMapOf<ContractClassName, Set<ContractAttachment>>()
for (attachment in attachments) {
if (attachment !is ContractAttachment) continue
for (contract in contractClasses) {
if (!attachment.allContracts.contains(contract)) continue
result[contract] = result.getOrDefault(contract, setOf(attachment)).plus(attachment)
}
}
return result
}
private fun contractClassFor(className: ContractClassName, classLoader: ClassLoader): Class<out Contract> = try {
classLoader.loadClass(className).asSubclass(Contract::class.java)
} catch (e: Exception) {
throw TransactionVerificationException.ContractCreationError(id, className, e)
@CordaInternal
internal fun internalPrepareVerify(extraAttachments: List<Attachment>) = AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(this.attachments + extraAttachments) { transactionClassLoader ->
Verifier(createLtxForVerification(), transactionClassLoader, inputStatesContractClassNameToMaxVersion)
}
private fun createLtxForVerification(): LedgerTransaction {
@ -368,7 +157,7 @@ private constructor(
privacySalt = this.privacySalt,
networkParameters = this.networkParameters,
references = deserializedReferences,
inputStatesContractClassNameToMaxVersion = emptyMap()
inputStatesContractClassNameToMaxVersion = this.inputStatesContractClassNameToMaxVersion
)
} else {
// This branch is only present for backwards compatibility.
@ -378,156 +167,6 @@ private constructor(
}
}
/**
* Check the transaction is contract-valid by running the verify() for each input and output state contract.
* If any contract fails to verify, the whole transaction is considered to be invalid.
*/
private fun verifyContracts(internalTx: LedgerTransaction) {
val contractClasses = (internalTx.inputs.map { it.state } + internalTx.outputs).toSet()
.map { it.contract to contractClassFor(it.contract, it.data.javaClass.classLoader) }
val contractInstances = contractClasses.map { (contractClassName, contractClass) ->
try {
contractClass.newInstance()
} catch (e: Exception) {
throw TransactionVerificationException.ContractCreationError(id, contractClassName, e)
}
}
contractInstances.forEach { contract ->
try {
contract.verify(internalTx)
} catch (e: Exception) {
throw TransactionVerificationException.ContractRejection(id, contract, e)
}
}
}
/**
* Make sure the notary has stayed the same. As we can't tell how inputs and outputs connect, if there
* are any inputs or reference inputs, all outputs must have the same notary.
*
* TODO: Is that the correct set of restrictions? May need to come back to this, see if we can be more
* flexible on output notaries.
*/
private fun checkNoNotaryChange() {
if (notary != null && (inputs.isNotEmpty() || references.isNotEmpty())) {
outputs.forEach {
if (it.notary != notary) {
throw TransactionVerificationException.NotaryChangeInWrongTransactionType(id, notary, it.notary)
}
}
}
}
private fun checkEncumbrancesValid() {
// Validate that all encumbrances exist within the set of input states.
inputs.filter { it.state.encumbrance != null }
.forEach { (state, ref) -> checkInputEncumbranceStateExists(state, ref) }
// Check that in the outputs,
// a) an encumbered state does not refer to itself as the encumbrance
// b) the number of outputs can contain the encumbrance
// c) the bi-directionality (full cycle) property is satisfied
// d) encumbered output states are assigned to the same notary.
val statesAndEncumbrance = outputs.withIndex().filter { it.value.encumbrance != null }
.map { Pair(it.index, it.value.encumbrance!!) }
if (!statesAndEncumbrance.isEmpty()) {
checkBidirectionalOutputEncumbrances(statesAndEncumbrance)
checkNotariesOutputEncumbrance(statesAndEncumbrance)
}
}
// Method to check if all encumbered states are assigned to the same notary Party.
// This method should be invoked after [checkBidirectionalOutputEncumbrances], because it assumes that the
// bi-directionality property is already satisfied.
private fun checkNotariesOutputEncumbrance(statesAndEncumbrance: List<Pair<Int, Int>>) {
// We only check for transactions in which notary is null (i.e., issuing transactions).
// Note that if a notary is defined for a transaction, we already check if all outputs are assigned
// to the same notary (transaction's notary) in [checkNoNotaryChange()].
if (notary == null) {
// indicesAlreadyChecked is used to bypass already checked indices and to avoid cycles.
val indicesAlreadyChecked = HashSet<Int>()
statesAndEncumbrance.forEach {
checkNotary(it.first, indicesAlreadyChecked)
}
}
}
private tailrec fun checkNotary(index: Int, indicesAlreadyChecked: HashSet<Int>) {
if (indicesAlreadyChecked.add(index)) {
val encumbranceIndex = outputs[index].encumbrance!!
if (outputs[index].notary != outputs[encumbranceIndex].notary) {
throw TransactionVerificationException.TransactionNotaryMismatchEncumbranceException(id, index, encumbranceIndex, outputs[index].notary, outputs[encumbranceIndex].notary)
} else {
checkNotary(encumbranceIndex, indicesAlreadyChecked)
}
}
}
private fun checkInputEncumbranceStateExists(state: TransactionState<ContractState>, ref: StateRef) {
val encumbranceStateExists = inputs.any {
it.ref.txhash == ref.txhash && it.ref.index == state.encumbrance
}
if (!encumbranceStateExists) {
throw TransactionVerificationException.TransactionMissingEncumbranceException(
id,
state.encumbrance!!,
TransactionVerificationException.Direction.INPUT
)
}
}
// Using basic graph theory, a full cycle of encumbered (co-dependent) states should exist to achieve bi-directional
// encumbrances. This property is important to ensure that no states involved in an encumbrance-relationship
// can be spent on their own. Briefly, if any of the states is having more than one encumbrance references by
// other states, a full cycle detection will fail. As a result, all of the encumbered states must be present
// as "from" and "to" only once (or zero times if no encumbrance takes place). For instance,
// a -> b
// c -> b and a -> b
// b -> a b -> c
// do not satisfy the bi-directionality (full cycle) property.
//
// In the first example "b" appears twice in encumbrance ("to") list and "c" exists in the encumbered ("from") list only.
// Due the above, one could consume "a" and "b" in the same transaction and then, because "b" is already consumed, "c" cannot be spent.
//
// Similarly, the second example does not form a full cycle because "a" and "c" exist in one of the lists only.
// As a result, one can consume "b" and "c" in the same transactions, which will make "a" impossible to be spent.
//
// On other hand the following are valid constructions:
// a -> b a -> c
// b -> c and c -> b
// c -> a b -> a
// and form a full cycle, meaning that the bi-directionality property is satisfied.
private fun checkBidirectionalOutputEncumbrances(statesAndEncumbrance: List<Pair<Int, Int>>) {
// [Set] of "from" (encumbered states).
val encumberedSet = mutableSetOf<Int>()
// [Set] of "to" (encumbrance states).
val encumbranceSet = mutableSetOf<Int>()
// Update both [Set]s.
statesAndEncumbrance.forEach { (statePosition, encumbrance) ->
// Check it does not refer to itself.
if (statePosition == encumbrance || encumbrance >= outputs.size) {
throw TransactionVerificationException.TransactionMissingEncumbranceException(
id,
encumbrance,
TransactionVerificationException.Direction.OUTPUT)
} else {
encumberedSet.add(statePosition) // Guaranteed to have unique elements.
if (!encumbranceSet.add(encumbrance)) {
throw TransactionVerificationException.TransactionDuplicateEncumbranceException(id, encumbrance)
}
}
}
// At this stage we have ensured that "from" and "to" [Set]s are equal in size, but we should check their
// elements do indeed match. If they don't match, we return their symmetric difference (disjunctive union).
val symmetricDifference = (encumberedSet union encumbranceSet).subtract(encumberedSet intersect encumbranceSet)
if (symmetricDifference.isNotEmpty()) {
// At least one encumbered state is not in the [encumbranceSet] and vice versa.
throw TransactionVerificationException.TransactionNonMatchingEncumbranceException(id, symmetricDifference)
}
}
/**
* Given a type and a function that returns a grouping key, associates inputs and outputs together so that they
* can be processed as one. The grouping key is any arbitrary object that can act as a map key (so must implement

View File

@ -78,8 +78,8 @@ data class NotaryChangeWireTransaction(
@DeleteForDJVM
fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>): NotaryChangeLedgerTransaction {
val resolvedInputs = services.loadStates(inputs.toSet()).toList()
val hashToResolve = networkParametersHash ?: services.networkParametersStorage.defaultHash
val resolvedNetworkParameters = services.networkParametersStorage.lookup(hashToResolve)
val hashToResolve = networkParametersHash ?: services.networkParametersService.defaultHash
val resolvedNetworkParameters = services.networkParametersService.lookup(hashToResolve)
?: throw TransactionResolutionException(id)
return NotaryChangeLedgerTransaction.create(resolvedInputs, notary, newNotary, id, sigs, resolvedNetworkParameters)
}

View File

@ -7,13 +7,16 @@ import net.corda.core.KeepForDJVM
import net.corda.core.contracts.*
import net.corda.core.crypto.*
import net.corda.core.identity.Party
import net.corda.core.internal.TransactionVerifierServiceInternal
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.internalFindTrustedAttachmentForClass
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution
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.utilities.contextLogger
import net.corda.core.utilities.getOrThrow
import java.security.KeyPair
import java.security.PublicKey
@ -200,8 +203,29 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
@DeleteForDJVM
private fun verifyRegularTransaction(services: ServiceHub, checkSufficientSignatures: Boolean) {
val ltx = toLedgerTransaction(services, checkSufficientSignatures)
// TODO: allow non-blocking verification.
services.transactionVerifierService.verify(ltx).getOrThrow()
try {
// TODO: allow non-blocking verification.
services.transactionVerifierService.verify(ltx).getOrThrow()
} catch (e: NoClassDefFoundError) {
// Transactions created before Corda 4 can be missing dependencies on other cordapps.
// This code attempts to find the missing dependency in the attachment storage among the trusted contract attachments.
// When it finds one, it instructs the verifier to use it to create the transaction classloader.
// TODO - add check that transaction was created before Corda 4.
// TODO - should this be a [TransactionVerificationException]?
val missingClass = requireNotNull(e.message) { "Transaction $ltx is incorrectly formed." }
val attachment = requireNotNull(services.attachments.internalFindTrustedAttachmentForClass(missingClass)) {
"Transaction $ltx is incorrectly formed. Could not find local dependency for class: $missingClass."
}
log.warn("""Detected that transaction ${this.id} 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 dependency: $attachment.
|Please check with the originator that this is a valid transaction.""".trimMargin())
(services.transactionVerifierService as TransactionVerifierServiceInternal).verify(ltx, listOf(attachment)).getOrThrow()
}
}
/**
@ -219,7 +243,6 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
}
}
/**
* Resolves the underlying transaction with signatures and then returns it, handling any special case transactions
* such as [NotaryChangeWireTransaction].
@ -272,6 +295,8 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
"keys: ${missing.joinToString { it.toStringShort() }}, " +
"by signers: ${descriptions.joinToString()} "
}
private val log = contextLogger()
}
@KeepForDJVM

View File

@ -8,6 +8,7 @@ import net.corda.core.crypto.*
import net.corda.core.identity.Party
import net.corda.core.internal.*
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
import net.corda.core.internal.cordapp.CordappResolver
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution
@ -55,6 +56,8 @@ open class TransactionBuilder @JvmOverloads constructor(
private fun defaultReferencesList(): MutableList<StateRef> = arrayListOf()
private fun defaultServiceHub(): ServiceHub? = (Strand.currentStrand() as? FlowStateMachine<*>)?.serviceHub
private const val CORDA_VERSION_THAT_INTRODUCED_FLATTENED_COMMANDS = 4
}
constructor(
@ -146,7 +149,7 @@ open class TransactionBuilder @JvmOverloads constructor(
checkConstraintValidity(state)
}
return SerializationFactory.defaultFactory.withCurrentContext(serializationContext) {
val wireTx = SerializationFactory.defaultFactory.withCurrentContext(serializationContext) {
WireTransaction(
createComponentGroups(
inputStates(),
@ -156,10 +159,51 @@ open class TransactionBuilder @JvmOverloads constructor(
notary,
window,
referenceStates,
services.networkParametersStorage.currentHash),
services.networkParametersService.currentHash),
privacySalt
)
}
// Check the transaction for missing dependencies, and attempt to add them.
// This is a workaround as the current version of Corda does not support cordapp dependencies.
// It works by running transaction validation and then scan the attachment storage for missing classes.
// TODO - remove once proper support for cordapp dependencies is added.
val addedDependency = addMissingDependency(services, wireTx)
return if (addedDependency)
toWireTransactionWithContext(services, serializationContext)
else
wireTx
}
/**
* @return true if a new dependency was successfully added.
*/
private fun addMissingDependency(services: ServicesForResolution, wireTx: WireTransaction): Boolean {
try {
wireTx.toLedgerTransaction(services).verify()
} catch (e: NoClassDefFoundError) {
val missingClass = e.message
requireNotNull(missingClass) { "Transaction is incorrectly formed." }
val attachment = services.attachments.internalFindTrustedAttachmentForClass(missingClass!!)
?: throw IllegalArgumentException("Attempted to find dependent attachment for class $missingClass, but could not find a suitable candidate.")
log.warnOnce("""The transaction currently built is missing an attachment for class: $missingClass.
Automatically attaching contract dependency $attachment.
It is strongly recommended to check that this is the desired attachment, and to manually add it to the transaction builder.
""".trimIndent())
addAttachment(attachment.id)
return true
// Ignore these exceptions as they will break unit tests.
// The point here is only to detect missing dependencies. The other exceptions are irrelevant.
} catch (tve: TransactionVerificationException) {
} catch (tre: TransactionResolutionException) {
} catch (ise: IllegalStateException) {
} catch (ise: IllegalArgumentException) {
}
return false
}
/**
@ -185,7 +229,8 @@ open class TransactionBuilder @JvmOverloads constructor(
val explicitAttachmentContractsMap: Map<ContractClassName, SecureHash> = explicitAttachmentContracts.toMap()
val inputContractGroups: Map<ContractClassName, List<TransactionState<ContractState>>> = inputsWithTransactionState.map {it.state}.groupBy { it.contract }
val inputContractGroups: Map<ContractClassName, List<TransactionState<ContractState>>> = inputsWithTransactionState.map { it.state }
.groupBy { it.contract }
val outputContractGroups: Map<ContractClassName, List<TransactionState<ContractState>>> = outputs.groupBy { it.contract }
val allContracts: Set<ContractClassName> = inputContractGroups.keys + outputContractGroups.keys
@ -199,7 +244,8 @@ open class TransactionBuilder @JvmOverloads constructor(
selectAttachmentThatSatisfiesConstraints(true, refStateEntry.key, refStateEntry.value, emptySet(), services)
}
val contractClassNameToInputStateRef : Map<ContractClassName, Set<StateRef>> = inputsWithTransactionState.map { Pair(it.state.contract,it.ref) }.groupBy { it.first }.mapValues { it.value.map { e -> e.second }.toSet() }
val contractClassNameToInputStateRef: Map<ContractClassName, Set<StateRef>> = inputsWithTransactionState.map { Pair(it.state.contract, it.ref) }
.groupBy { it.first }.mapValues { it.value.map { e -> e.second }.toSet() }
// For each contract, resolve the AutomaticPlaceholderConstraint, and select the attachment.
val contractAttachmentsAndResolvedOutputStates: List<Pair<Set<AttachmentId>, List<TransactionState<ContractState>>?>> = allContracts.toSet()
@ -255,20 +301,21 @@ open class TransactionBuilder @JvmOverloads constructor(
if (inputsHashConstraints.isNotEmpty() && (outputHashConstraints.isNotEmpty() || outputSignatureConstraints.isNotEmpty())) {
val attachmentIds = services.attachments.getContractAttachments(contractClassName)
// only switchover if we have both signed and unsigned attachments for the given contract class name
if (attachmentIds.isNotEmpty() && attachmentIds.size == 2) {
if (attachmentIds.isNotEmpty() && attachmentIds.size == 2) {
val attachmentsToUse = attachmentIds.map {
services.attachments.openAttachment(it)?.let { it as ContractAttachment }
?: throw IllegalArgumentException("Contract attachment $it for $contractClassName is missing.")
}
val signedAttachment = attachmentsToUse.filter { it.isSigned }.firstOrNull() ?: throw IllegalArgumentException("Signed contract attachment for $contractClassName is missing.")
val signedAttachment = attachmentsToUse.filter { it.isSigned }.firstOrNull()
?: throw IllegalArgumentException("Signed contract attachment for $contractClassName is missing.")
val outputConstraints =
if (outputHashConstraints.isNotEmpty()) {
log.warn("Switching output states from hash to signed constraints using signers in signed contract attachment given by ${signedAttachment.id}")
val outputsSignatureConstraints = outputHashConstraints.map { it.copy(constraint = SignatureAttachmentConstraint(signedAttachment.signerKeys.first())) }
outputs.addAll(outputsSignatureConstraints)
outputs.removeAll(outputHashConstraints)
outputsSignatureConstraints
} else outputSignatureConstraints
if (outputHashConstraints.isNotEmpty()) {
log.warn("Switching output states from hash to signed constraints using signers in signed contract attachment given by ${signedAttachment.id}")
val outputsSignatureConstraints = outputHashConstraints.map { it.copy(constraint = SignatureAttachmentConstraint(signedAttachment.signerKeys.first())) }
outputs.addAll(outputsSignatureConstraints)
outputs.removeAll(outputHashConstraints)
outputsSignatureConstraints
} else outputSignatureConstraints
return Pair(attachmentIds.toSet(), outputConstraints)
}
}
@ -643,8 +690,15 @@ with @BelongsToContract, or supply an explicit contract parameter to addOutputSt
/** Returns an immutable list of output [TransactionState]s. */
fun outputStates(): List<TransactionState<*>> = ArrayList(outputs)
/** Returns an immutable list of [Command]s, grouping by [CommandData] and joining signers. */
fun commands(): List<Command<*>> = commands.groupBy { cmd -> cmd.value }.entries.map { (data, cmds) -> Command(data, cmds.flatMap(Command<*>::signers).toSet().toList()) }
/** Returns an immutable list of [Command]s, grouping by [CommandData] and joining signers (from v4, v3 and below return all commands with duplicates for different signers). */
fun commands(): List<Command<*>> {
return if (CordappResolver.currentTargetVersion >= CORDA_VERSION_THAT_INTRODUCED_FLATTENED_COMMANDS) {
commands.groupBy { cmd -> cmd.value }
.entries.map { (data, cmds) -> Command(data, cmds.flatMap(Command<*>::signers).toSet().toList()) }
} else {
ArrayList(commands)
}
}
/**
* Sign the built transaction and return it. This is an internal function for use by the service hub, please use

View File

@ -105,8 +105,8 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
resolveAttachment = { services.attachments.openAttachment(it) },
resolveStateRefAsSerialized = { resolveStateRefBinaryComponent(it, services) },
resolveParameters = {
val hashToResolve = it ?: services.networkParametersStorage.defaultHash
services.networkParametersStorage.lookup(hashToResolve)
val hashToResolve = it ?: services.networkParametersService.defaultHash
services.networkParametersService.lookup(hashToResolve)
},
resolveContractAttachment = { services.loadContractAttachment(it) }
)
@ -130,18 +130,37 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
*/
@Deprecated("Use toLedgerTransaction(ServicesForTransaction) instead")
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
@JvmOverloads
fun toLedgerTransaction(
resolveIdentity: (PublicKey) -> Party?,
resolveAttachment: (SecureHash) -> Attachment?,
resolveStateRef: (StateRef) -> TransactionState<*>?,
@Suppress("UNUSED_PARAMETER") resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId?,
resolveParameters: (SecureHash?) -> NetworkParameters? = { null } // TODO This { null } is left here only because of API stability. It doesn't make much sense anymore as it will fail on transaction verification.
@Suppress("UNUSED_PARAMETER") resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId?
): LedgerTransaction {
// This reverts to serializing the resolved transaction state.
return toLedgerTransactionInternal(resolveIdentity, resolveAttachment, { stateRef -> resolveStateRef(stateRef)?.serialize() }, resolveParameters,
return toLedgerTransactionInternal(
resolveIdentity,
resolveAttachment,
{ stateRef -> resolveStateRef(stateRef)?.serialize() },
{ null },
// Returning a dummy `missingAttachment` Attachment allows this deprecated method to work and it disables "contract version no downgrade rule" as a dummy Attachment returns version 1
{ it -> resolveAttachment(it.txhash) ?: missingAttachment })
{ it -> resolveAttachment(it.txhash) ?: missingAttachment }
)
}
// Especially crafted for TransactionVerificationRequest
@CordaInternal
internal fun toLtxDjvmInternalBridge(
resolveAttachment: (SecureHash) -> Attachment?,
resolveStateRef: (StateRef) -> TransactionState<*>?,
resolveParameters: (SecureHash?) -> NetworkParameters?
): LedgerTransaction {
return toLedgerTransactionInternal(
{ null },
resolveAttachment,
{ stateRef -> resolveStateRef(stateRef)?.serialize() },
resolveParameters,
{ it -> resolveAttachment(it.txhash) ?: missingAttachment }
)
}
private fun toLedgerTransactionInternal(

View File

@ -287,13 +287,6 @@ class ProgressTracker(vararg inputSteps: Step) {
/** Returns true if the progress tracker has ended, either by reaching the [DONE] step or prematurely with an error */
val hasEnded: Boolean get() = _changes.hasCompleted() || _changes.hasThrowable()
companion object {
val DEFAULT_TRACKER = { ProgressTracker() }
}
}
// TODO: Expose the concept of errors.
// TODO: It'd be helpful if this class was at least partly thread safe.

View File

@ -3,6 +3,7 @@ package net.corda.core.utilities
import net.corda.core.KeepForDJVM
import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.CordaSerializable
import java.util.function.Consumer
/**
* Representation of an operation that has either succeeded with a result (represented by [Success]) or failed with an
@ -12,15 +13,16 @@ import net.corda.core.serialization.CordaSerializable
sealed class Try<out A> {
companion object {
/**
* Executes the given block of code and returns a [Success] capturing the result, or a [Failure] if an exception
* is thrown.
* Executes the given block of code and returns a [Success] capturing the result, or a [Failure] if a [Throwable] is thrown.
*
* It is recommended this be chained with [throwError] to ensure critial [Error]s are thrown and not captured.
*/
@JvmStatic
inline fun <T> on(body: () -> T): Try<T> {
return try {
Success(body())
} catch (e: Exception) {
Failure(e)
} catch (t: Throwable) {
Failure(t)
}
}
}
@ -34,6 +36,15 @@ sealed class Try<out A> {
/** Returns the value if a [Success] otherwise throws the exception if a [Failure]. */
abstract fun getOrThrow(): A
/** If this is a [Failure] wrapping an [Error] then throw it, otherwise return `this` for chaining. */
fun throwError(): Try<A> {
if (this is Failure && exception is Error) {
throw exception
} else {
return this
}
}
/** Maps the given function to the value from this [Success], or returns `this` if this is a [Failure]. */
inline fun <B> map(function: (A) -> B): Try<B> = when (this) {
is Success -> Success(function(value))
@ -59,34 +70,21 @@ sealed class Try<out A> {
}
/** Applies the given action to the value if [Success], or does nothing if [Failure]. Returns `this` for chaining. */
fun doOnSuccess(action: (A) -> Unit): Try<A> {
when (this) {
is Success -> action.invoke(value)
is Failure -> {}
fun doOnSuccess(action: Consumer<in A>): Try<A> {
if (this is Success) {
action.accept(value)
}
return this
}
/** Applies the given action to the error if [Failure], or does nothing if [Success]. Returns `this` for chaining. */
fun doOnFailure(action: (Throwable) -> Unit): Try<A> {
when (this) {
is Success -> {}
is Failure -> action.invoke(exception)
fun doOnFailure(action: Consumer<Throwable>): Try<A> {
if (this is Failure) {
action.accept(exception)
}
return this
}
/** Applies the given action to the exception if [Failure], rethrowing [Error]s. Does nothing if [Success]. Returns `this` for chaining. */
fun doOnException(action: (Exception) -> Unit): Try<A> {
return doOnFailure { error ->
if (error is Exception) {
action.invoke(error)
} else {
throw error
}
}
}
@KeepForDJVM
data class Success<out A>(val value: A) : Try<A>() {
override val isSuccess: Boolean get() = true

View File

@ -1,11 +1,11 @@
package net.corda.core.flows;
import co.paralleluniverse.fibers.Suspendable;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Primitives;
import net.corda.core.identity.Party;
import net.corda.testing.core.TestConstants;
import net.corda.testing.node.MockNetwork;
import net.corda.testing.node.MockNetworkParameters;
import net.corda.testing.node.StartedMockNode;
import org.junit.After;
import org.junit.Before;
@ -15,11 +15,14 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import static net.corda.testing.core.TestUtils.singleIdentity;
import static net.corda.testing.node.internal.InternalTestUtilsKt.cordappsForPackages;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.Assert.fail;
public class FlowsInJavaTest {
private final MockNetwork mockNet = new MockNetwork(ImmutableList.of("net.corda.core.flows"));
private final MockNetwork mockNet = new MockNetwork(
new MockNetworkParameters().withCordappsForAllNodes(cordappsForPackages("net.corda.core.flows"))
);
private StartedMockNode aliceNode;
private StartedMockNode bobNode;
private Party bob;

View File

@ -83,7 +83,7 @@ class ConstraintsPropagationTests {
ledgerServices = object : MockServices(
cordappPackages = listOf("net.corda.finance.contracts.asset"),
initialIdentity = ALICE,
identityService = rigorousMock<IdentityServiceInternal>().also {
identityService = mock<IdentityServiceInternal>().also {
doReturn(ALICE_PARTY).whenever(it).partyFromKey(ALICE_PUBKEY)
doReturn(BOB_PARTY).whenever(it).partyFromKey(BOB_PUBKEY)
},

View File

@ -1,6 +1,7 @@
package net.corda.core.contracts
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
@ -40,7 +41,7 @@ class PackageOwnershipVerificationTests {
private val ledgerServices = MockServices(
cordappPackages = listOf("net.corda.finance.contracts.asset"),
initialIdentity = ALICE,
identityService = rigorousMock<IdentityServiceInternal>().also {
identityService = mock<IdentityServiceInternal>().also {
doReturn(ALICE_PARTY).whenever(it).partyFromKey(ALICE_PUBKEY)
doReturn(BOB_PARTY).whenever(it).partyFromKey(BOB_PUBKEY)
},

View File

@ -1,6 +1,7 @@
package net.corda.core.crypto
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash.Companion.zeroHash
@ -68,7 +69,7 @@ class PartialMerkleTreeTest {
testLedger = MockServices(
cordappPackages = emptyList(),
initialIdentity = TestIdentity(MEGA_CORP.name),
identityService = rigorousMock<IdentityServiceInternal>().also {
identityService = mock<IdentityServiceInternal>().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
},
networkParameters = testNetworkParameters(minimumPlatformVersion = 4, notaries = listOf(NotaryInfo(DUMMY_NOTARY, true)))

View File

@ -3,6 +3,7 @@ package net.corda.core.flows
import com.natpryce.hamkrest.and
import com.natpryce.hamkrest.assertion.assert
import net.corda.core.flows.mixins.WithFinality
import net.corda.core.flows.mixins.WithFinality.FinalityInvoker
import net.corda.core.identity.Party
import net.corda.core.internal.cordapp.CordappResolver
import net.corda.core.transactions.SignedTransaction
@ -75,15 +76,29 @@ class FinalityFlowTests : WithFinality {
@Test
fun `allow use of the old API if the CorDapp target version is 3`() {
// We need Bob to load at least one old CorDapp so that its FinalityHandler is enabled
val bob = createBob(cordapps = listOf(cordappWithPackages("com.template").copy(targetPlatformVersion = 3)))
val stx = aliceNode.issuesCashTo(bob)
val oldBob = createBob(cordapps = listOf(tokenOldCordapp()))
val stx = aliceNode.issuesCashTo(oldBob)
val resultFuture = CordappResolver.withCordapp(targetPlatformVersion = 3) {
@Suppress("DEPRECATION")
aliceNode.startFlowAndRunNetwork(FinalityFlow(stx)).resultFuture
}
resultFuture.getOrThrow()
assertThat(bob.services.validatedTransactions.getTransaction(stx.id)).isNotNull()
assertThat(oldBob.services.validatedTransactions.getTransaction(stx.id)).isNotNull()
}
@Test
fun `broadcasting to both new and old participants`() {
val newCharlie = mockNet.createNode(InternalMockNodeParameters(legalName = CHARLIE_NAME))
val oldBob = createBob(cordapps = listOf(tokenOldCordapp()))
val stx = aliceNode.issuesCashTo(oldBob)
val resultFuture = aliceNode.startFlowAndRunNetwork(FinalityInvoker(
stx,
newRecipients = setOf(newCharlie.info.singleIdentity()),
oldRecipients = setOf(oldBob.info.singleIdentity())
)).resultFuture
resultFuture.getOrThrow()
assertThat(newCharlie.services.validatedTransactions.getTransaction(stx.id)).isNotNull()
assertThat(oldBob.services.validatedTransactions.getTransaction(stx.id)).isNotNull()
}
private fun createBob(cordapps: List<TestCordappInternal> = emptyList()): TestStartedNode {
@ -100,4 +115,7 @@ class FinalityFlowTests : WithFinality {
Cash().generateIssue(builder, amount, other, notary)
return services.signInitialTransaction(builder)
}
/** "Old" CorDapp which will force its node to keep its FinalityHandler enabled */
private fun tokenOldCordapp() = cordappWithPackages("com.template").copy(targetPlatformVersion = 3)
}

View File

@ -149,7 +149,7 @@ class WithReferencedStatesFlowTests {
val updatedRefState = updatedRefTx.tx.outRefsOfType<RefState.State>().single()
// 4. Try to use the old reference state. This will throw a NotaryException.
val useRefTx = nodes[1].services.startFlow(WithReferencedStatesFlow(UseRefState(newRefState.state.data.linearId))).resultFuture
val useRefTx = nodes[1].services.startFlow(WithReferencedStatesFlow { UseRefState(newRefState.state.data.linearId) }).resultFuture
// 5. Share the update reference state.
nodes[0].services.startFlow(ShareRefState.Initiator(updatedRefState)).resultFuture.getOrThrow()

View File

@ -17,7 +17,7 @@ import net.corda.testing.node.internal.TestStartedNode
interface WithFinality : WithMockNet {
//region Operations
fun TestStartedNode.finalise(stx: SignedTransaction, vararg recipients: Party): FlowStateMachine<SignedTransaction> {
return startFlowAndRunNetwork(FinalityInvoker(stx, recipients.toSet()))
return startFlowAndRunNetwork(FinalityInvoker(stx, recipients.toSet(), emptySet()))
}
fun TestStartedNode.getValidatedTransaction(stx: SignedTransaction): SignedTransaction {
@ -25,7 +25,7 @@ interface WithFinality : WithMockNet {
}
fun CordaRPCOps.finalise(stx: SignedTransaction, vararg recipients: Party): FlowHandle<SignedTransaction> {
return startFlow(::FinalityInvoker, stx, recipients.toSet()).andRunNetwork()
return startFlow(::FinalityInvoker, stx, recipients.toSet(), emptySet()).andRunNetwork()
}
//endregion
@ -41,11 +41,13 @@ interface WithFinality : WithMockNet {
@InitiatingFlow
@StartableByRPC
class FinalityInvoker(private val transaction: SignedTransaction,
private val recipients: Set<Party>) : FlowLogic<SignedTransaction>() {
private val newRecipients: Set<Party>,
private val oldRecipients: Set<Party>) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
val sessions = recipients.map(::initiateFlow)
return subFlow(FinalityFlow(transaction, sessions))
val sessions = newRecipients.map(::initiateFlow)
@Suppress("DEPRECATION")
return subFlow(FinalityFlow(transaction, sessions, oldRecipients, FinalityFlow.tracker()))
}
}

View File

@ -0,0 +1,34 @@
package net.corda.core.internal
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.lang.IllegalArgumentException
class ClassLoadingUtilsTest {
interface BaseInterface {}
interface BaseInterface2 {}
class ConcreteClassWithEmptyConstructor: BaseInterface {}
abstract class AbstractClass: BaseInterface
class ConcreteClassWithNonEmptyConstructor(private val someData: Int): BaseInterface2 {}
@Test
fun predicateClassAreLoadedSuccessfully() {
val classes = loadClassesImplementing(BaseInterface::class.java.classLoader, BaseInterface::class.java)
val classNames = classes.map { it.javaClass.name }
assertThat(classNames).contains(ConcreteClassWithEmptyConstructor::class.java.name)
assertThat(classNames).doesNotContain(AbstractClass::class.java.name)
}
@Test(expected = IllegalArgumentException::class)
fun throwsExceptionWhenClassDoesNotContainProperConstructors() {
val classes = loadClassesImplementing(BaseInterface::class.java.classLoader, BaseInterface2::class.java)
}
}

View File

@ -13,7 +13,9 @@ import net.corda.core.utilities.unwrap
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNetworkParameters
import net.corda.testing.node.StartedMockNode
import net.corda.testing.node.internal.cordappsForPackages
import org.junit.After
import org.junit.Before
import org.junit.Test
@ -36,11 +38,9 @@ class ResolveTransactionsFlowTest {
private lateinit var miniCorp: Party
private lateinit var notary: Party
private lateinit var rootTx: SignedTransaction
@Before
fun setup() {
mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts", "net.corda.core.internal"))
mockNet = MockNetwork(MockNetworkParameters(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts", javaClass.packageName)))
notaryNode = mockNet.defaultNotaryNode
megaCorpNode = mockNet.createPartyNode(CordaX500Name("MegaCorp", "London", "GB"))
miniCorpNode = mockNet.createPartyNode(CordaX500Name("MiniCorp", "London", "GB"))

View File

@ -0,0 +1,77 @@
package net.corda.core.internal
import net.corda.core.contracts.*
import net.corda.core.crypto.NullKeys
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.utilities.OpaqueBytes
import org.junit.Test
import kotlin.test.assertEquals
class StatePointerSearchTests {
private val partyAndRef = PartyAndReference(AnonymousParty(NullKeys.NullPublicKey), OpaqueBytes.of(0))
private data class StateWithGeneric(val amount: Amount<Issued<LinearPointer<LinearState>>>) : ContractState {
override val participants: List<AbstractParty> get() = listOf()
}
private data class StateWithList(val pointerList: List<LinearPointer<LinearState>>) : ContractState {
override val participants: List<AbstractParty> get() = listOf()
}
private data class StateWithMap(val pointerMap: Map<Any, Any>) : ContractState {
override val participants: List<AbstractParty> get() = listOf()
}
private data class StateWithSet(val pointerSet: Set<LinearPointer<LinearState>>) : ContractState {
override val participants: List<AbstractParty> get() = listOf()
}
private data class StateWithListOfList(val pointerSet: List<List<LinearPointer<LinearState>>>) : ContractState {
override val participants: List<AbstractParty> get() = listOf()
}
@Test
fun `find pointer in state with generic type`() {
val linearPointer = LinearPointer(UniqueIdentifier(), LinearState::class.java)
val testState = StateWithGeneric(Amount(100L, Issued(partyAndRef, linearPointer)))
val results = StatePointerSearch(testState).search()
assertEquals(results, setOf(linearPointer))
}
@Test
fun `find pointers which are inside a list`() {
val linearPointerOne = LinearPointer(UniqueIdentifier(), LinearState::class.java)
val linearPointerTwo = LinearPointer(UniqueIdentifier(), LinearState::class.java)
val testState = StateWithList(listOf(linearPointerOne, linearPointerTwo))
val results = StatePointerSearch(testState).search()
assertEquals(results, setOf(linearPointerOne, linearPointerTwo))
}
@Test
fun `find pointers which are inside a map`() {
val linearPointerOne = LinearPointer(UniqueIdentifier(), LinearState::class.java)
val linearPointerTwo = LinearPointer(UniqueIdentifier(), LinearState::class.java)
val testState = StateWithMap(mapOf(linearPointerOne to 1, 2 to linearPointerTwo))
val results = StatePointerSearch(testState).search()
assertEquals(results, setOf(linearPointerOne, linearPointerTwo))
}
@Test
fun `find pointers which are inside a set`() {
val linearPointer = LinearPointer(UniqueIdentifier(), LinearState::class.java)
val testState = StateWithSet(setOf(linearPointer))
val results = StatePointerSearch(testState).search()
assertEquals(results, setOf(linearPointer))
}
@Test
fun `find pointers which are inside nested iterables`() {
val linearPointer = LinearPointer(UniqueIdentifier(), LinearState::class.java)
val testState = StateWithListOfList(listOf(listOf(linearPointer)))
val results = StatePointerSearch(testState).search()
assertEquals(results, setOf(linearPointer))
}
}

View File

@ -1,5 +1,7 @@
package net.corda.core.node
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.crypto.generateKeyPair
import net.corda.core.internal.getPackageOwnerOf
import net.corda.core.utilities.OpaqueBytes
@ -7,16 +9,15 @@ import net.corda.core.utilities.days
import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS
import net.corda.finance.flows.CashIssueFlow
import net.corda.node.services.config.NotaryConfig
import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.testing.common.internal.testNetworkParameters
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.singleIdentity
import net.corda.testing.node.MockNetNotaryConfig
import net.corda.testing.node.MockNetworkNotarySpec
import net.corda.testing.node.MockNetworkParameters
import net.corda.testing.node.MockNodeConfigOverrides
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNodeParameters
import net.corda.testing.node.internal.MOCK_VERSION_INFO
@ -66,8 +67,14 @@ class NetworkParametersTest {
// Notaries tests
@Test
fun `choosing notary not specified in network parameters will fail`() {
val fakeNotary = mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME,
configOverrides = MockNodeConfigOverrides(notary = MockNetNotaryConfig(validating = false))))
val fakeNotary = mockNet.createNode(
InternalMockNodeParameters(
legalName = BOB_NAME,
configOverrides = {
doReturn(NotaryConfig(validating = false)).whenever(it).notary
}
)
)
val fakeNotaryId = fakeNotary.info.singleIdentity()
val alice = mockNet.createPartyNode(ALICE_NAME)
assertThat(alice.services.networkMapCache.notaryIdentities).doesNotContain(fakeNotaryId)

View File

@ -1,5 +1,6 @@
package net.corda.core.serialization
import com.nhaarman.mockito_kotlin.mock
import net.corda.core.contracts.*
import net.corda.core.crypto.*
import net.corda.core.identity.AbstractParty
@ -69,7 +70,7 @@ class TransactionSerializationTests {
val outputState = TransactionState(TestCash.State(depositRef, 600.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY)
val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY)
val megaCorpServices = object : MockServices(listOf("net.corda.core.serialization"), MEGA_CORP.name, rigorousMock(), testNetworkParameters(notaries = listOf(NotaryInfo(DUMMY_NOTARY, true))), MEGA_CORP_KEY) {
val megaCorpServices = object : MockServices(listOf("net.corda.core.serialization"), MEGA_CORP.name, mock(), testNetworkParameters(notaries = listOf(NotaryInfo(DUMMY_NOTARY, true))), MEGA_CORP_KEY) {
//override mock implementation with a real one
override fun loadContractAttachment(stateRef: StateRef, forContractClassName: ContractClassName?): Attachment = servicesForResolution.loadContractAttachment(stateRef, forContractClassName)
}

View File

@ -9,7 +9,7 @@ import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
import net.corda.core.serialization.serialize
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.OpaqueBytes
import net.corda.nodeapi.DummyContractBackdoor
import net.corda.isolated.contracts.DummyContractBackdoor
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
@ -26,15 +26,15 @@ import kotlin.test.assertFailsWith
class AttachmentsClassLoaderSerializationTests {
companion object {
val ISOLATED_CONTRACTS_JAR_PATH: URL = AttachmentsClassLoaderSerializationTests::class.java.getResource("isolated.jar")
private const val ISOLATED_CONTRACT_CLASS_NAME = "net.corda.finance.contracts.isolated.AnotherDummyContract"
val ISOLATED_CONTRACTS_JAR_PATH: URL = AttachmentsClassLoaderSerializationTests::class.java.getResource("/isolated.jar")
private const val ISOLATED_CONTRACT_CLASS_NAME = "net.corda.isolated.contracts.AnotherDummyContract"
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
val storage = MockAttachmentStorage()
private val storage = MockAttachmentStorage()
@Test
fun `Can serialize and deserialize with an attachment classloader`() {

View File

@ -4,35 +4,38 @@ import net.corda.core.contracts.Attachment
import net.corda.core.contracts.Contract
import net.corda.core.contracts.TransactionVerificationException
import net.corda.core.internal.declaredField
import net.corda.core.internal.inputStream
import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.internal.AttachmentsClassLoader
import net.corda.testing.core.internal.ContractJarTestUtils.signContractJar
import net.corda.testing.internal.fakeAttachment
import net.corda.testing.node.internal.FINANCE_CONTRACTS_CORDAPP
import net.corda.testing.services.MockAttachmentStorage
import org.apache.commons.io.IOUtils
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.net.URL
import kotlin.test.assertFailsWith
class AttachmentsClassLoaderTests {
companion object {
val ISOLATED_CONTRACTS_JAR_PATH: URL = AttachmentsClassLoaderTests::class.java.getResource("isolated.jar")
// TODO Update this test to use the new isolated.jar
val ISOLATED_CONTRACTS_JAR_PATH: URL = AttachmentsClassLoaderTests::class.java.getResource("old-isolated.jar")
val ISOLATED_CONTRACTS_JAR_PATH_V4: URL = AttachmentsClassLoaderTests::class.java.getResource("isolated-4.0.jar")
val FINANCE_JAR_PATH: URL = AttachmentsClassLoaderTests::class.java.getResource("finance.jar")
private const val ISOLATED_CONTRACT_CLASS_NAME = "net.corda.finance.contracts.isolated.AnotherDummyContract"
private fun readAttachment(attachment: Attachment, filepath: String): ByteArray {
ByteArrayOutputStream().use {
return ByteArrayOutputStream().let {
attachment.extractFile(filepath, it)
return it.toByteArray()
it.toByteArray()
}
}
}
val storage = MockAttachmentStorage()
private val storage = MockAttachmentStorage()
@Test
fun `Loading AnotherDummyContract without using the AttachmentsClassLoader fails`() {
@ -43,7 +46,7 @@ class AttachmentsClassLoaderTests {
@Test
fun `Dynamically load AnotherDummyContract from isolated contracts jar using the AttachmentsClassLoader`() {
val isolatedId = storage.importAttachment(ISOLATED_CONTRACTS_JAR_PATH.openStream(), "app", "isolated.jar")
val isolatedId = importAttachment(ISOLATED_CONTRACTS_JAR_PATH.openStream(), "app", "isolated.jar")
val classloader = AttachmentsClassLoader(listOf(storage.openAttachment(isolatedId)!!))
val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, classloader)
@ -53,8 +56,8 @@ class AttachmentsClassLoaderTests {
@Test
fun `Test non-overlapping contract jar`() {
val att1 = storage.importAttachment(ISOLATED_CONTRACTS_JAR_PATH.openStream(), "app", "isolated.jar")
val att2 = storage.importAttachment(ISOLATED_CONTRACTS_JAR_PATH_V4.openStream(), "app", "isolated-4.0.jar")
val att1 = importAttachment(ISOLATED_CONTRACTS_JAR_PATH.openStream(), "app", "isolated.jar")
val att2 = importAttachment(ISOLATED_CONTRACTS_JAR_PATH_V4.openStream(), "app", "isolated-4.0.jar")
assertFailsWith(TransactionVerificationException.OverlappingAttachmentsException::class) {
AttachmentsClassLoader(arrayOf(att1, att2).map { storage.openAttachment(it)!! })
@ -63,9 +66,9 @@ class AttachmentsClassLoaderTests {
@Test
fun `Test valid overlapping contract jar`() {
val isolatedId = storage.importAttachment(ISOLATED_CONTRACTS_JAR_PATH.openStream(), "app", "isolated.jar")
val isolatedId = importAttachment(ISOLATED_CONTRACTS_JAR_PATH.openStream(), "app", "isolated.jar")
val signedJar = signContractJar(ISOLATED_CONTRACTS_JAR_PATH, copyFirst = true)
val isolatedSignedId = storage.importAttachment(signedJar.first.toUri().toURL().openStream(), "app", "isolated-signed.jar")
val isolatedSignedId = importAttachment(signedJar.first.toUri().toURL().openStream(), "app", "isolated-signed.jar")
// does not throw OverlappingAttachments exception
AttachmentsClassLoader(arrayOf(isolatedId, isolatedSignedId).map { storage.openAttachment(it)!! })
@ -73,8 +76,8 @@ class AttachmentsClassLoaderTests {
@Test
fun `Test non-overlapping different contract jars`() {
val att1 = storage.importAttachment(ISOLATED_CONTRACTS_JAR_PATH.openStream(), "app", "isolated.jar")
val att2 = storage.importAttachment(FINANCE_JAR_PATH.openStream(), "app", "finance.jar")
val att1 = importAttachment(ISOLATED_CONTRACTS_JAR_PATH.openStream(), "app", "isolated.jar")
val att2 = importAttachment(FINANCE_CONTRACTS_CORDAPP.jarFile.inputStream(), "app", "finance.jar")
// does not throw OverlappingAttachments exception
AttachmentsClassLoader(arrayOf(att1, att2).map { storage.openAttachment(it)!! })
@ -82,8 +85,8 @@ class AttachmentsClassLoaderTests {
@Test
fun `Load text resources from AttachmentsClassLoader`() {
val att1 = storage.importAttachment(fakeAttachment("file1.txt", "some data").inputStream(), "app", "file1.jar")
val att2 = storage.importAttachment(fakeAttachment("file2.txt", "some other data").inputStream(), "app", "file2.jar")
val att1 = importAttachment(fakeAttachment("file1.txt", "some data").inputStream(), "app", "file1.jar")
val att2 = importAttachment(fakeAttachment("file2.txt", "some other data").inputStream(), "app", "file2.jar")
val cl = AttachmentsClassLoader(arrayOf(att1, att2).map { storage.openAttachment(it)!! })
val txt = IOUtils.toString(cl.getResourceAsStream("file1.txt"), Charsets.UTF_8.name())
@ -95,8 +98,8 @@ class AttachmentsClassLoaderTests {
@Test
fun `Test valid overlapping file condition`() {
val att1 = storage.importAttachment(fakeAttachment("file1.txt", "same data").inputStream(), "app", "file1.jar")
val att2 = storage.importAttachment(fakeAttachment("file1.txt", "same data").inputStream(), "app", "file2.jar")
val att1 = importAttachment(fakeAttachment("file1.txt", "same data").inputStream(), "app", "file1.jar")
val att2 = importAttachment(fakeAttachment("file1.txt", "same data").inputStream(), "app", "file2.jar")
val cl = AttachmentsClassLoader(arrayOf(att1, att2).map { storage.openAttachment(it)!! })
val txt = IOUtils.toString(cl.getResourceAsStream("file1.txt"), Charsets.UTF_8.name())
@ -106,8 +109,8 @@ class AttachmentsClassLoaderTests {
@Test
fun `No overlapping exception thrown on certain META-INF files`() {
listOf("meta-inf/manifest.mf", "meta-inf/license", "meta-inf/test.dsa", "meta-inf/test.sf").forEach { path ->
val att1 = storage.importAttachment(fakeAttachment(path, "some data").inputStream(), "app", "file1.jar")
val att2 = storage.importAttachment(fakeAttachment(path, "some other data").inputStream(), "app", "file2.jar")
val att1 = importAttachment(fakeAttachment(path, "some data").inputStream(), "app", "file1.jar")
val att2 = importAttachment(fakeAttachment(path, "some other data").inputStream(), "app", "file2.jar")
AttachmentsClassLoader(arrayOf(att1, att2).map { storage.openAttachment(it)!! })
}
@ -115,10 +118,8 @@ class AttachmentsClassLoaderTests {
@Test
fun `Check platform independent path handling in attachment jars`() {
val storage = MockAttachmentStorage()
val att1 = storage.importAttachment(fakeAttachment("/folder1/foldera/file1.txt", "some data").inputStream(), "app", "file1.jar")
val att2 = storage.importAttachment(fakeAttachment("\\folder1\\folderb\\file2.txt", "some other data").inputStream(), "app", "file2.jar")
val att1 = importAttachment(fakeAttachment("/folder1/foldera/file1.txt", "some data").inputStream(), "app", "file1.jar")
val att2 = importAttachment(fakeAttachment("\\folder1\\folderb\\file2.txt", "some other data").inputStream(), "app", "file2.jar")
val data1a = readAttachment(storage.openAttachment(att1)!!, "/folder1/foldera/file1.txt")
assertArrayEquals("some data".toByteArray(), data1a)
@ -132,4 +133,8 @@ class AttachmentsClassLoaderTests {
val data2b = readAttachment(storage.openAttachment(att2)!!, "/folder1/folderb/file2.txt")
assertArrayEquals("some other data".toByteArray(), data2b)
}
private fun importAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId {
return jar.use { storage.importAttachment(jar, uploader, filename) }
}
}

View File

@ -1,6 +1,7 @@
package net.corda.core.transactions
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.*
import net.corda.core.crypto.generateKeyPair
@ -34,7 +35,7 @@ class LedgerTransactionQueryTests {
private val services = MockServices(
listOf("net.corda.testing.contracts"),
TestIdentity(CordaX500Name("MegaCorp", "London", "GB"), keyPair),
rigorousMock<IdentityServiceInternal>().also {
mock<IdentityServiceInternal>().also {
doReturn(null).whenever(it).partyFromKey(keyPair.public)
},
testNetworkParameters(notaries = listOf(NotaryInfo(DUMMY_NOTARY, true))),

View File

@ -1,6 +1,7 @@
package net.corda.core.transactions
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.*
import net.corda.core.contracts.Requirements.using
@ -48,7 +49,7 @@ class ReferenceStateTests {
private val ledgerServices = MockServices(
cordappPackages = listOf("net.corda.core.transactions", "net.corda.finance.contracts.asset"),
initialIdentity = ALICE,
identityService = rigorousMock<IdentityServiceInternal>().also {
identityService = mock<IdentityServiceInternal>().also {
doReturn(ALICE_PARTY).whenever(it).partyFromKey(ALICE_PUBKEY)
doReturn(BOB_PARTY).whenever(it).partyFromKey(BOB_PUBKEY)
},

View File

@ -1,6 +1,7 @@
package net.corda.core.transactions
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.*
import net.corda.core.cordapp.CordappProvider
@ -9,12 +10,11 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.internal.AbstractAttachment
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.RPC_UPLOADER
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.ZoneVersionTooLowException
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.node.services.NetworkParametersStorage
import net.corda.core.node.services.NetworkParametersService
import net.corda.core.serialization.serialize
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract
@ -39,14 +39,14 @@ class TransactionBuilderTest {
private val services = rigorousMock<ServicesForResolution>()
private val contractAttachmentId = SecureHash.randomSHA256()
private val attachments = rigorousMock<AttachmentStorage>()
private val networkParametersStorage = rigorousMock<NetworkParametersStorage>()
private val networkParametersService = mock<NetworkParametersService>()
@Before
fun setup() {
val cordappProvider = rigorousMock<CordappProvider>()
val networkParameters = testNetworkParameters(minimumPlatformVersion = PLATFORM_VERSION)
doReturn(networkParametersStorage).whenever(services).networkParametersStorage
doReturn(networkParameters.serialize().hash).whenever(networkParametersStorage).currentHash
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
@ -77,7 +77,7 @@ class TransactionBuilderTest {
val wtx = builder.toWireTransaction(services)
assertThat(wtx.outputs).containsOnly(outputState)
assertThat(wtx.commands).containsOnly(Command(DummyCommandData, notary.owningKey))
assertThat(wtx.networkParametersHash).isEqualTo(networkParametersStorage.currentHash)
assertThat(wtx.networkParametersHash).isEqualTo(networkParametersService.currentHash)
}
@Test
@ -109,6 +109,7 @@ class TransactionBuilderTest {
.hasMessageContaining("Reference states")
doReturn(testNetworkParameters(minimumPlatformVersion = 4)).whenever(services).networkParameters
doReturn(referenceState).whenever(services).loadState(referenceStateRef)
val wtx = builder.toWireTransaction(services)
assertThat(wtx.references).containsOnly(referenceStateRef)
}

View File

@ -1,6 +1,7 @@
package net.corda.core.transactions
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.*
import net.corda.core.identity.AbstractParty
@ -17,7 +18,7 @@ import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices
import net.corda.testing.node.ledger
import org.assertj.core.api.AssertionsForClassTypes
import org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType
import org.junit.Rule
import org.junit.Test
import java.time.Instant
@ -56,7 +57,7 @@ class TransactionEncumbranceTests {
val ledgerServices = MockServices(
listOf("net.corda.core.transactions", "net.corda.finance.contracts.asset"),
MEGA_CORP.name,
rigorousMock<IdentityServiceInternal>().also {
mock<IdentityServiceInternal>().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
},
testNetworkParameters(notaries = listOf(NotaryInfo(DUMMY_NOTARY, true)))
@ -330,12 +331,13 @@ class TransactionEncumbranceTests {
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY2, 0, AutomaticHashConstraint)
.addCommand(Cash.Commands.Issue(), MEGA_CORP.owningKey)
.toLedgerTransaction(ledgerServices)
.verify()
}
// More complex encumbrance (full cycle of size 4) where one of the encumbered states is assigned to a different notary.
// 0 -> 1, 1 -> 3, 3 -> 2, 2 -> 0
// We expect that state at index 3 cannot be encumbered with the state at index 2, due to mismatched notaries.
AssertionsForClassTypes.assertThatExceptionOfType(TransactionVerificationException.TransactionNotaryMismatchEncumbranceException::class.java)
assertThatExceptionOfType(TransactionVerificationException.TransactionNotaryMismatchEncumbranceException::class.java)
.isThrownBy {
TransactionBuilder()
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 1, AutomaticHashConstraint)
@ -344,13 +346,15 @@ class TransactionEncumbranceTests {
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 2, AutomaticHashConstraint)
.addCommand(Cash.Commands.Issue(), MEGA_CORP.owningKey)
.toLedgerTransaction(ledgerServices)
.verify()
}
.withMessageContaining("index 3 is assigned to notary [O=Notary Service, L=Zurich, C=CH], while its encumbrance with index 2 is assigned to notary [O=Notary Service2, L=Zurich, C=CH]")
.withMessageContaining("index 3 is assigned to notary [O=Notary Service, L=Zurich, C=CH], while its encumbrance with " +
"index 2 is assigned to notary [O=Notary Service2, L=Zurich, C=CH]")
// Two different encumbrance chains, where only one fails due to mismatched notary.
// 0 -> 1, 1 -> 0, 2 -> 3, 3 -> 2 where encumbered states with indices 2 and 3, respectively, are assigned
// to different notaries.
AssertionsForClassTypes.assertThatExceptionOfType(TransactionVerificationException.TransactionNotaryMismatchEncumbranceException::class.java)
assertThatExceptionOfType(TransactionVerificationException.TransactionNotaryMismatchEncumbranceException::class.java)
.isThrownBy {
TransactionBuilder()
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 1, AutomaticHashConstraint)
@ -359,7 +363,9 @@ class TransactionEncumbranceTests {
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY2, 2, AutomaticHashConstraint)
.addCommand(Cash.Commands.Issue(), MEGA_CORP.owningKey)
.toLedgerTransaction(ledgerServices)
.verify()
}
.withMessageContaining("index 2 is assigned to notary [O=Notary Service, L=Zurich, C=CH], while its encumbrance with index 3 is assigned to notary [O=Notary Service2, L=Zurich, C=CH]")
.withMessageContaining("index 2 is assigned to notary [O=Notary Service, L=Zurich, C=CH], while its encumbrance with " +
"index 3 is assigned to notary [O=Notary Service2, L=Zurich, C=CH]")
}
}

View File

@ -170,6 +170,7 @@ class TransactionTests {
val id = SecureHash.randomSHA256()
val timeWindow: TimeWindow? = null
val privacySalt = PrivacySalt()
fun buildTransaction() = LedgerTransaction.create(
inputs,
outputs,
@ -184,7 +185,7 @@ class TransactionTests {
inputStatesContractClassNameToMaxVersion = emptyMap()
)
assertFailsWith<TransactionVerificationException.NotaryChangeInWrongTransactionType> { buildTransaction() }
assertFailsWith<TransactionVerificationException.NotaryChangeInWrongTransactionType> { buildTransaction().verify() }
}
@Test

View File

@ -1,4 +1,4 @@
package net.corda.nodeapi
package net.corda.isolated.contracts
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.PartyAndReference
@ -6,7 +6,7 @@ import net.corda.core.identity.Party
import net.corda.core.transactions.TransactionBuilder
/**
* This interface deliberately mirrors the one in the finance:isolated module.
* This interface deliberately mirrors the one in the isolated module.
* We will actually link [AnotherDummyContract] against this interface rather
* than the one inside isolated.jar, which means we won't need to use reflection
* to execute the contract's generateInitial() method.

Binary file not shown.

View File

@ -4,6 +4,7 @@ import org.assertj.core.api.Assertions.*
import org.junit.Assert.*
import org.junit.Test
import sandbox.java.lang.sandbox
import java.text.DecimalFormatSymbols
class DJVMTest : TestBase() {
@ -41,7 +42,8 @@ class DJVMTest : TestBase() {
stringClass.getMethod("format", stringClass, Array<Any>::class.java)
.invoke(null, stringOf("%9.4f"), arrayOf(doubleOf(1234.5678))).toString()
}
assertEquals("1234.5678", result)
val sep = DecimalFormatSymbols.getInstance().decimalSeparator
assertEquals("1234${sep}5678", result)
}
@Test
@ -50,7 +52,8 @@ class DJVMTest : TestBase() {
stringClass.getMethod("format", stringClass, Array<Any>::class.java)
.invoke(null, stringOf("%7.2f"), arrayOf(floatOf(1234.5678f))).toString()
}
assertEquals("1234.57", result)
val sep = DecimalFormatSymbols.getInstance().decimalSeparator
assertEquals("1234${sep}57", result)
}
@Test

View File

@ -230,6 +230,25 @@ The contract attachment non-downgrade rule is enforced in two locations:
A version number is stored in the manifest information of the enclosing JAR file. This version identifier should be a whole number starting
from 1. This information should be set using the Gradle cordapp plugin, or manually, as described in :doc:`versioning`.
Uniqueness requirement Contract and Version for Signature Constraint
--------------------------------------------------------------------
CorDapps in Corda 4 may be signed (to use new signature constraints functionality) or unsigned, and versioned.
The following controls are enforced for these different types of jars within the attachment store of a node:
- Signed contract JARs must be uniquely versioned per contract class (or group of).
At runtime the node will throw a `DuplicateContractClassException`` exception if this condition is violated.
- Unsigned contract JARs: there should not exist multiple instances of the same contract jar.
When a whitelisted JARs is imported and it doesn't contain a version number, the version will be copied from the position (counting from 1)
of this JAR in the whilelist. The same JAR can be present in many lists (if it contains many contracts),
in such case the version will be equal to the highest position of the JAR in all lists.
The new whitelist needs to be distributed to the node before the JAR is imported, otherwise it will receive default version.
At run-time the node will warn of duplicates encountered.
The most recent version given by insertionDate into the attachment storage will be used upon transaction building/resolution.
Issues when using the HashAttachmentConstraint
----------------------------------------------
@ -311,70 +330,6 @@ For example:
For Contracts that are annotated with ``@NoConstraintPropagation``, the platform requires that the Transaction Builder specifies
an actual constraint for the output states (the ``AutomaticPlaceholderConstraint`` can't be used) .
Testing
-------
Since all tests involving transactions now require attachments it is also required to load the correct attachments
for tests. Unit test environments in JVM ecosystems tend to use class directories rather than JARs, and so CorDapp JARs
typically aren't built for testing. Requiring this would add significant complexity to the build systems of Corda
and CorDapps, so the test suite has a set of convenient functions to generate CorDapps from package names or
to specify JAR URLs in the case that the CorDapp(s) involved in testing already exist. You can also just use
``AlwaysAcceptAttachmentConstraint`` in your tests to disable the constraints mechanism.
MockNetwork/MockNode
********************
The simplest way to ensure that a vanilla instance of a MockNode generates the correct CorDapps is to use the
``cordappPackages`` constructor parameter (Kotlin) or the ``setCordappPackages`` method on ``MockNetworkParameters`` (Java)
when creating the MockNetwork. This will cause the ``AbstractNode`` to use the named packages as sources for CorDapps. All files
within those packages will be zipped into a JAR and added to the attachment store and loaded as CorDapps by the
``CordappLoader``.
An example of this usage would be:
.. sourcecode:: java
class SomeTestClass {
MockNetwork network = null;
@Before
void setup() {
network = new MockNetwork(new MockNetworkParameters().setCordappPackages(Arrays.asList("com.domain.cordapp")))
}
... // Your tests go here
}
MockServices
************
If your test uses a ``MockServices`` directly you can instantiate it using a constructor that takes a list of packages
to use as CorDapps using the ``cordappPackages`` parameter.
.. sourcecode:: java
MockServices mockServices = new MockServices(Arrays.asList("com.domain.cordapp"))
However - there is an easier way! If your unit tests are in the same package as the contract code itself, then you
can use the no-args constructor of ``MockServices``. The package to be scanned for CorDapps will be the same as the
the package of the class that constructed the object. This is a convenient default.
Driver
******
The driver takes a parameter called ``extraCordappPackagesToScan`` which is a list of packages to use as CorDapps.
.. sourcecode:: java
driver(new DriverParameters().setExtraCordappPackagesToScan(Arrays.asList("com.domain.cordapp"))) ...
Full Nodes
**********
When testing against full nodes simply place your CorDapp into the cordapps directory of the node.
Debugging
---------
If an attachment constraint cannot be resolved, a ``MissingContractAttachments`` exception is thrown. There are two

View File

@ -1,3 +1,9 @@
.. highlight:: kotlin
.. raw:: html
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/codesets.js"></script>
API: Core types
===============

View File

@ -1,3 +1,9 @@
.. highlight:: kotlin
.. raw:: html
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/codesets.js"></script>
API: Identity
=============

View File

@ -9,24 +9,26 @@ API: Persistence
.. contents::
Corda offers developers the option to expose all or some part of a contract state to an *Object Relational Mapping*
(ORM) tool to be persisted in a RDBMS. The purpose of this is to assist *vault* development by effectively indexing
persisted contract states held in the vault for the purpose of running queries over them and to allow relational joins
between Corda data and private data local to the organisation owning a node.
Corda offers developers the option to expose all or some parts of a contract state to an *Object Relational Mapping*
(ORM) tool to be persisted in a *Relational Database Management System* (RDBMS).
The ORM mapping is specified using the `Java Persistence API <https://en.wikipedia.org/wiki/Java_Persistence_API>`_
(JPA) as annotations and is converted to database table rows by the node automatically every time a state is recorded
in the node's local vault as part of a transaction.
The purpose of this, is to assist `vault <https://docs.corda.net/vault.html>`_
development and allow for the persistence of state data to a custom database table. Persisted states held in the
vault are indexed for the purposes of executing queries. This also allows for relational joins between Corda tables
and the organization's existing data.
.. note:: Presently the node includes an instance of the H2 database but any database that supports JDBC is a
candidate and the node will in the future support a range of database implementations via their JDBC drivers. Much
of the node internal state is also persisted there. You can access the internal H2 database via JDBC, please see the
info in ":doc:`node-administration`" for details.
The Object Relational Mapping is specified using `Java Persistence API <https://en.wikipedia.org/wiki/Java_Persistence_API>`_
(JPA) annotations. This mapping is persisted to the database as a table row (a single, implicitly structured data item) by the node
automatically every time a state is recorded in the node's local vault as part of a transaction.
.. note:: By default, nodes use an H2 database which is accessed using *Java Database Connectivity* JDBC. Any database
with a JDBC driver is a candidate and several integrations have been contributed to by the community.
Please see the info in ":doc:`node-database`" for details.
Schemas
-------
Every ``ContractState`` can implement the ``QueryableState`` interface if it wishes to be inserted into the node's local
database and accessible using SQL.
Every ``ContractState`` may implement the ``QueryableState`` interface if it wishes to be inserted into a custom table in the node's
database and made accessible using SQL.
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt
:language: kotlin
@ -34,12 +36,12 @@ database and accessible using SQL.
:end-before: DOCEND QueryableState
The ``QueryableState`` interface requires the state to enumerate the different relational schemas it supports, for
instance in cases where the schema has evolved, with each one being represented by a ``MappedSchema`` object return
by the ``supportedSchemas()`` method. Once a schema is selected it must generate that representation when requested
via the ``generateMappedObject()`` method which is then passed to the ORM.
instance in situations where the schema has evolved. Each relational schema is represented as a ``MappedSchema``
object returned by the state's ``supportedSchemas`` method.
Nodes have an internal ``SchemaService`` which decides what to persist and what not by selecting the ``MappedSchema``
to use.
Nodes have an internal ``SchemaService`` which decides what data to persist by selecting the ``MappedSchema`` to use.
Once a ``MappedSchema`` is selected, the ``SchemaService`` will delegate to the ``QueryableState`` to generate a corresponding
representation (mapped object) via the ``generateMappedObject`` method, the output of which is then passed to the *ORM*.
.. literalinclude:: ../../node/src/main/kotlin/net/corda/node/services/api/SchemaService.kt
:language: kotlin
@ -51,13 +53,10 @@ to use.
:start-after: DOCSTART MappedSchema
:end-before: DOCEND MappedSchema
The ``SchemaService`` can be configured by a node administrator to select the schemas used by each app. In this way the
relational view of ledger states can evolve in a controlled fashion in lock-step with internal systems or other
integration points and not necessarily with every upgrade to the contract code. It can select from the
``MappedSchema`` offered by a ``QueryableState``, automatically upgrade to a later version of a schema or even
provide a ``MappedSchema`` not originally offered by the ``QueryableState``.
With this framework, the relational view of ledger states can evolve in a controlled fashion in lock-step with internal systems or other
integration points and is not dependant on changes to the contract code.
It is expected that multiple different contract state implementations might provide mappings within a single schema.
It is expected that multiple contract state implementations might provide mappings within a single schema.
For example an Interest Rate Swap contract and an Equity OTC Option contract might both provide a mapping to
a Derivative contract within the same schema. The schemas should typically not be part of the contract itself and should exist independently
to encourage re-use of a common set within a particular business area or Cordapp.
@ -71,19 +70,19 @@ class name of a *schema family* class that is constant across versions, allowing
preferred version of a schema.
The ``SchemaService`` is also responsible for the ``SchemaOptions`` that can be configured for a particular
``MappedSchema`` which allow the configuration of a database schema or table name prefixes to avoid any clash with
``MappedSchema``. These allow the configuration of database schemas or table name prefixes to avoid clashes with
other ``MappedSchema``.
.. note:: It is intended that there should be plugin support for the ``SchemaService`` to offer the version upgrading
and additional schemas as part of Cordapps, and that the active schemas be configurable. However the present
implementation offers none of this and simply results in all versions of all schemas supported by a
``QueryableState`` being persisted. This will change in due course. Similarly, it does not currently support
.. note:: It is intended that there should be plugin support for the ``SchemaService`` to offer version upgrading, implementation
of additional schemas, and enable active schemas as being configurable. The present implementation does not include these features
and simply results in all versions of all schemas supported by a ``QueryableState`` being persisted.
This will change in due course. Similarly, the service does not currently support
configuring ``SchemaOptions`` but will do so in the future.
Custom schema registration
--------------------------
Custom contract schemas are automatically registered at startup time for CorDapps. The node bootstrap process will scan
for schemas (any class that extends the ``MappedSchema`` interface) in the `plugins` configuration directory in your CorDapp jar.
Custom contract schemas are automatically registered at startup time for CorDapps. The node bootstrap process will scan for states that implement
the Queryable state interface. Tables are then created as specified by the ``MappedSchema``s identified by each states ``supportedSchemas`` method.
For testing purposes it is necessary to manually register the packages containing custom schemas as follows:
@ -94,16 +93,16 @@ For testing purposes it is necessary to manually register the packages containin
Object relational mapping
-------------------------
The persisted representation of a ``QueryableState`` should be an instance of a ``PersistentState`` subclass,
constructed either by the state itself or a plugin to the ``SchemaService``. This allows the ORM layer to always
To facilitate the ORM, the persisted representation of a ``QueryableState`` should be an instance of a ``PersistentState`` subclass,
constructed either by the state itself or a plugin to the ``SchemaService``. This allows the ORM layer to always
associate a ``StateRef`` with a persisted representation of a ``ContractState`` and allows joining with the set of
unconsumed states in the vault.
The ``PersistentState`` subclass should be marked up as a JPA 2.1 *Entity* with a defined table name and having
properties (in Kotlin, getters/setters in Java) annotated to map to the appropriate columns and SQL types. Additional
entities can be included to model these properties where they are more complex, for example collections, so the mapping
does not have to be *flat*. The ``MappedSchema`` must provide a list of all of the JPA entity classes for that schema
in order to initialise the ORM layer.
entities can be included to model these properties where they are more complex, for example collections (:ref:`Persisting Hierarchical Data<persisting-hierarchical-data>`), so
the mapping does not have to be *flat*. The ``MappedSchema`` constructor accepts a list of all JPA entity classes for that schema in
the ``MappedTypes`` parameter. It must provide this list in order to initialise the ORM layer.
Several examples of entities and mappings are provided in the codebase, including ``Cash.State`` and
``CommercialPaper.State``. For example, here's the first version of the cash schema.
@ -116,6 +115,218 @@ Several examples of entities and mappings are provided in the codebase, includin
Ensure that table and column names are compatible with the naming convention of the database vendors for which the Cordapp will be deployed,
e.g. for Oracle database, prior to version 12.2 the maximum length of table/column name is 30 bytes (the exact number of characters depends on the database encoding).
Persisting Hierarchical Data
----------------------------
You may wish to persist hierarchical relationships within states using multiple database tables
You may wish to persist hierarchical relationships within state data using multiple database tables. In order to facillitate this, multiple ``PersistentState``
subclasses may be implemented. The relationship between these classes is defined using JPA annotations. It is important to note that the ``MappedSchema``
constructor requires a list of *all* of these subclasses.
An example Schema implementing hierarchical relationships with JPA annotations has been implemented below. This Schema will cause ``parent_data`` and ``child_data` tables to be
created.
.. container:: codeset
.. sourcecode:: java
@CordaSerializable
public class SchemaV1 extends MappedSchema {
/**
* This class must extend the MappedSchema class. Its name is based on the SchemaFamily name and the associated version number abbreviation (V1, V2... Vn).
* In the constructor, use the super keyword to call the constructor of MappedSchema with the following arguments: a class literal representing the schema family,
* a version number and a collection of mappedTypes (class literals) which represent JPA entity classes that the ORM layer needs to be configured with for this schema.
*/
public SchemaV1() {
super(Schema.class, 1, ImmutableList.of(PersistentParentToken.class, PersistentChildToken.class));
}
/**
* The @entity annotation signifies that the specified POJO class' non-transient fields should be persisted to a relational database using the services
* of an entity manager. The @table annotation specifies properties of the table that will be created to contain the persisted data, in this case we have
* specified a name argument which will be used the table's title.
*/
@Entity
@Table(name = "parent_data")
public static class PersistentParentToken extends PersistentState {
/**
* The @Column annotations specify the columns that will comprise the inserted table and specify the shape of the fields and associated
* data types of each database entry.
*/
@Column(name = "owner") private final String owner;
@Column(name = "issuer") private final String issuer;
@Column(name = "amount") private final int amount;
@Column(name = "linear_id") public final UUID linearId;
/**
* The @OneToMany annotation specifies a one-to-many relationship between this class and a collection included as a field.
* The @JoinColumn and @JoinColumns annotations specify on which columns these tables will be joined on.
*/
@OneToMany(cascade = CascadeType.PERSIST)
@JoinColumns({
@JoinColumn(name = "output_index", referencedColumnName = "output_index"),
@JoinColumn(name = "transaction_id", referencedColumnName = "transaction_id"),
})
private final List<PersistentChildToken> listOfPersistentChildTokens;
public PersistentParentToken(String owner, String issuer, int amount, UUID linearId, List<PersistentChildToken> listOfPersistentChildTokens) {
this.owner = owner;
this.issuer = issuer;
this.amount = amount;
this.linearId = linearId;
this.listOfPersistentChildTokens = listOfPersistentChildTokens;
}
// Default constructor required by hibernate.
public PersistentParentToken() {
this.owner = "";
this.issuer = "";
this.amount = 0;
this.linearId = UUID.randomUUID();
this.listOfPersistentChildTokens = null;
}
public String getOwner() {
return owner;
}
public String getIssuer() {
return issuer;
}
public int getAmount() {
return amount;
}
public UUID getLinearId() {
return linearId;
}
public List<PersistentChildToken> getChildTokens() { return listOfPersistentChildTokens; }
}
@Entity
@CordaSerializable
@Table(name = "child_data")
public static class PersistentChildToken {
// The @Id annotation marks this field as the primary key of the persisted entity.
@Id
private final UUID Id;
@Column(name = "owner")
private final String owner;
@Column(name = "issuer")
private final String issuer;
@Column(name = "amount")
private final int amount;
/**
* The @ManyToOne annotation specifies that this class will be present as a member of a collection on a parent class and that it should
* be persisted with the joining columns specified in the parent class. It is important to note the targetEntity parameter which should correspond
* to a class literal of the parent class.
*/
@ManyToOne(targetEntity = PersistentParentToken.class)
private final TokenState persistentParentToken;
public PersistentChildToken(String owner, String issuer, int amount) {
this.Id = UUID.randomUUID();
this.owner = owner;
this.issuer = issuer;
this.amount = amount;
this.persistentParentToken = null;
}
// Default constructor required by hibernate.
public PersistentChildToken() {
this.Id = UUID.randomUUID();
this.owner = "";
this.issuer = "";
this.amount = 0;
this.persistentParentToken = null;
}
public UUID getId() {
return Id;
}
public String getOwner() {
return owner;
}
public String getIssuer() {
return issuer;
}
public int getAmount() {
return amount;
}
public TokenState getPersistentToken() {
return persistentToken;
}
}
}
.. sourcecode:: kotlin
@CordaSerializable
object SchemaV1 : MappedSchema(schemaFamily = Schema::class.java, version = 1, mappedTypes = listOf(PersistentParentToken::class.java, PersistentChildToken::class.java)) {
@Entity
@Table(name = "parent_data")
class PersistentParentToken(
@Column(name = "owner")
var owner: String,
@Column(name = "issuer")
var issuer: String,
@Column(name = "amount")
var currency: Int,
@Column(name = "linear_id")
var linear_id: UUID,
@JoinColumns(JoinColumn(name = "transaction_id", referencedColumnName = "transaction_id"), JoinColumn(name = "output_index", referencedColumnName = "output_index"))
var listOfPersistentChildTokens: MutableList<PersistentChildToken>
) : PersistentState()
@Entity
@CordaSerializable
@Table(name = "child_data")
class PersistentChildToken(
@Id
var Id: UUID = UUID.randomUUID(),
@Column(name = "owner")
var owner: String,
@Column(name = "issuer")
var issuer: String,
@Column(name = "amount")
var currency: Int,
@Column(name = "linear_id")
var linear_id: UUID,
@ManyToOne(targetEntity = PersistentParentToken::class)
var persistentParentToken: TokenState
) : PersistentState()
Identity mapping
----------------
Schema entity attributes defined by identity types (``AbstractParty``, ``Party``, ``AnonymousParty``) are automatically
@ -161,8 +372,8 @@ In addition to ``jdbcSession``, ``ServiceHub`` also exposes the Java Persistence
method. This method can be used to persist and query entities which inherit from ``MappedSchema``. This is particularly
useful if off-ledger data must be maintained in conjunction with on-ledger state data.
.. note:: Your entity must be included as a mappedType in as part of a MappedSchema for it to be added to Hibernate
as a custom schema. See Samples below.
.. note:: Your entity must be included as a mappedType as part of a ``MappedSchema`` for it to be added to Hibernate
as a custom schema. If it's not included as a mappedType, a corresponding table will not be created. See Samples below.
The code snippet below defines a ``PersistentFoo`` type inside ``FooSchemaV1``. Note that ``PersistentFoo`` is added to
a list of mapped types which is passed to ``MappedSchema``. This is exactly how state schemas are defined, except that
@ -201,7 +412,7 @@ the entity in this case should not subclass ``PersistentState`` (as it is not a
class PersistentFoo(@Id @Column(name = "foo_id") var fooId: String, @Column(name = "foo_data") var fooData: String) : Serializable
}
Instances of ``PersistentFoo`` can be persisted inside a flow as follows:
Instances of ``PersistentFoo`` can be manually persisted inside a flow as follows:
.. container:: codeset

View File

@ -1,3 +1,9 @@
.. highlight:: kotlin
.. raw:: html
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/codesets.js"></script>
API: States
===========

View File

@ -22,108 +22,20 @@ A ``MockNetwork`` is created as follows:
.. container:: codeset
.. sourcecode:: kotlin
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/MockNetworkTestsTutorial.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
class FlowTests {
private lateinit var mockNet: MockNetwork
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/MockNetworkTestsTutorial.java
:language: java
:start-after: DOCSTART 1
:end-before: DOCEND 1
@Before
fun setup() {
network = MockNetwork(listOf("my.cordapp.package", "my.other.cordapp.package"))
}
}
The ``MockNetwork`` requires at a minimum a list of CorDapps to be installed on each ``StartedMockNode``. The CorDapps are looked up on the
classpath by package name, using ``TestCordapp.findCordapp``.
.. sourcecode:: java
public class IOUFlowTests {
private MockNetwork network;
@Before
public void setup() {
network = new MockNetwork(ImmutableList.of("my.cordapp.package", "my.other.cordapp.package"));
}
}
The ``MockNetwork`` requires at a minimum a list of packages. Each package is packaged into a CorDapp JAR and installed
as a CorDapp on each ``StartedMockNode``.
Configuring the ``MockNetwork``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``MockNetwork`` is configured automatically. You can tweak its configuration using a ``MockNetworkParameters``
object, or by using named parameters in Kotlin:
.. container:: codeset
.. sourcecode:: kotlin
val network = MockNetwork(
// A list of packages to scan. Any contracts, flows and Corda services within these
// packages will be automatically available to any nodes within the mock network
cordappPackages = listOf("my.cordapp.package", "my.other.cordapp.package"),
// If true then each node will be run in its own thread. This can result in race conditions in your
// code if not carefully written, but is more realistic and may help if you have flows in your app that
// do long blocking operations.
threadPerNode = false,
// The notaries to use on the mock network. By default you get one mock notary and that is usually
// sufficient.
notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME)),
// If true then messages will not be routed from sender to receiver until you use the
// [MockNetwork.runNetwork] method. This is useful for writing single-threaded unit test code that can
// examine the state of the mock network before and after a message is sent, without races and without
// the receiving node immediately sending a response.
networkSendManuallyPumped = false,
// How traffic is allocated in the case where multiple nodes share a single identity, which happens for
// notaries in a cluster. You don't normally ever need to change this: it is mostly useful for testing
// notary implementations.
servicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random())
val network2 = MockNetwork(
// A list of packages to scan. Any contracts, flows and Corda services within these
// packages will be automatically available to any nodes within the mock network
listOf("my.cordapp.package", "my.other.cordapp.package"), MockNetworkParameters(
// If true then each node will be run in its own thread. This can result in race conditions in your
// code if not carefully written, but is more realistic and may help if you have flows in your app that
// do long blocking operations.
threadPerNode = false,
// The notaries to use on the mock network. By default you get one mock notary and that is usually
// sufficient.
notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME)),
// If true then messages will not be routed from sender to receiver until you use the
// [MockNetwork.runNetwork] method. This is useful for writing single-threaded unit test code that can
// examine the state of the mock network before and after a message is sent, without races and without
// the receiving node immediately sending a response.
networkSendManuallyPumped = false,
// How traffic is allocated in the case where multiple nodes share a single identity, which happens for
// notaries in a cluster. You don't normally ever need to change this: it is mostly useful for testing
// notary implementations.
servicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random())
)
.. sourcecode:: java
MockNetwork network = MockNetwork(
// A list of packages to scan. Any contracts, flows and Corda services within these
// packages will be automatically available to any nodes within the mock network
ImmutableList.of("my.cordapp.package", "my.other.cordapp.package"),
new MockNetworkParameters()
// If true then each node will be run in its own thread. This can result in race conditions in
// your code if not carefully written, but is more realistic and may help if you have flows in
// your app that do long blocking operations.
.setThreadPerNode(false)
// The notaries to use on the mock network. By default you get one mock notary and that is
// usually sufficient.
.setNotarySpecs(ImmutableList.of(new MockNetworkNotarySpec(DUMMY_NOTARY_NAME)))
// If true then messages will not be routed from sender to receiver until you use the
// [MockNetwork.runNetwork] method. This is useful for writing single-threaded unit test code
// that can examine the state of the mock network before and after a message is sent, without
// races and without the receiving node immediately sending a response.
.setNetworkSendManuallyPumped(false)
// How traffic is allocated in the case where multiple nodes share a single identity, which
// happens for notaries in a cluster. You don't normally ever need to change this: it is mostly
// useful for testing notary implementations.
.setServicePeerAllocationStrategy(new InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random()));
``MockNetworkParameters`` provides other properties for the network which can be tweaked. They default to sensible values if not specified.
Adding nodes to the network
^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -132,73 +44,21 @@ Nodes are created on the ``MockNetwork`` using:
.. container:: codeset
.. sourcecode:: kotlin
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/MockNetworkTestsTutorial.kt
:language: kotlin
:start-after: DOCSTART 2
:end-before: DOCEND 2
class FlowTests {
private lateinit var mockNet: MockNetwork
lateinit var nodeA: StartedMockNode
lateinit var nodeB: StartedMockNode
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/MockNetworkTestsTutorial.java
:language: java
:start-after: DOCSTART 2
:end-before: DOCEND 2
@Before
fun setup() {
network = MockNetwork(listOf("my.cordapp.package", "my.other.cordapp.package"))
nodeA = network.createPartyNode()
// We can optionally give the node a name.
nodeB = network.createPartyNode(CordaX500Name("Bank B", "London", "GB"))
}
}
.. sourcecode:: java
public class IOUFlowTests {
private MockNetwork network;
private StartedMockNode a;
private StartedMockNode b;
@Before
public void setup() {
network = new MockNetwork(ImmutableList.of("my.cordapp.package", "my.other.cordapp.package"));
nodeA = network.createPartyNode(null);
// We can optionally give the node a name.
nodeB = network.createPartyNode(new CordaX500Name("Bank B", "London", "GB"));
}
}
Nodes added using ``createPartyNode`` are provided a default set of node parameters. However, it is also possible to
provide different parameters to each node using the following methods on ``MockNetwork``:
.. container:: codeset
.. sourcecode:: kotlin
/**
* Create a started node with the given parameters.
*
* @param legalName The node's legal name.
* @param forcedID A unique identifier for the node.
* @param entropyRoot The initial entropy value to use when generating keys. Defaults to an (insecure) random value,
* but can be overridden to cause nodes to have stable or colliding identity/service keys.
* @param configOverrides Add/override the default configuration/behaviour of the node
* @param extraCordappPackages Extra CorDapp packages to add for this node.
*/
@JvmOverloads
fun createNode(legalName: CordaX500Name? = null,
forcedID: Int? = null,
entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()),
configOverrides: MockNodeConfigOverrides? = null,
extraCordappPackages: List<String> = emptyList()
): StartedMockNode
/** Create a started node with the given parameters. **/
fun createNode(parameters: MockNodeParameters = MockNodeParameters()): StartedMockNode
As you can see above, parameters can be added individually or encapsulated within a ``MockNodeParameters`` object. Of
particular interest are ``configOverrides`` which allow you to override some of the default node
configuration options. Please refer to the ``MockNodeConfigOverrides`` class for details what can currently be overridden.
Also, the ``extraCordappPackages`` parameter allows you to add extra CorDapps to a
specific node. This is useful when you wish for all nodes to load a common CorDapp but for a subset of nodes to load
CorDapps specific to their role in the network.
Nodes added using ``createNode`` are provided a default set of node parameters. However, it is also possible to
provide different parameters to each node using ``MockNodeParameters``. Of particular interest are ``configOverrides`` which allow you to
override some of the default node configuration options. Please refer to the ``MockNodeConfigOverrides`` class for details what can currently
be overridden. Also, the ``additionalCordapps`` parameter allows you to add extra CorDapps to a specific node. This is useful when you wish
for all nodes to load a common CorDapp but for a subset of nodes to load CorDapps specific to their role in the network.
Running the network
^^^^^^^^^^^^^^^^^^^

View File

@ -94,25 +94,24 @@ to "walk the chain" and verify that each input was generated through a valid seq
Reference input states
~~~~~~~~~~~~~~~~~~~~~~
A reference input state is added to a transaction as a ``ReferencedStateAndRef``. A ``ReferencedStateAndRef`` can be
obtained from a ``StateAndRef`` by calling the ``StateAndRef.referenced()`` method which returns a
``ReferencedStateAndRef``.
.. warning:: Reference states are only available on Corda networks with a minimum platform version >= 4.
A reference input state is added to a transaction as a ``ReferencedStateAndRef``. A ``ReferencedStateAndRef`` can be
obtained from a ``StateAndRef`` by calling the ``StateAndRef.referenced()`` method which returns a ``ReferencedStateAndRef``.
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/FlowCookbook.kt
:language: kotlin
:start-after: DOCSTART 55
:end-before: DOCEND 55
:dedent: 8
:language: kotlin
:start-after: DOCSTART 55
:end-before: DOCEND 55
:dedent: 8
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
:language: java
:start-after: DOCSTART 55
:end-before: DOCEND 55
:dedent: 12
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/java/FlowCookbook.java
:language: java
:start-after: DOCSTART 55
:end-before: DOCEND 55
:dedent: 12
**Handling of update races:**

View File

@ -64,7 +64,8 @@ filter criteria:
- Use ``queryBy`` to obtain a current snapshot of data (for a given ``QueryCriteria``)
- Use ``trackBy`` to obtain both a current snapshot and a future stream of updates (for a given ``QueryCriteria``)
.. note:: Streaming updates are only filtered based on contract type and state status (UNCONSUMED, CONSUMED, ALL)
.. note:: Streaming updates are only filtered based on contract type and state status (UNCONSUMED, CONSUMED, ALL).
They will not respect any other criteria that the initial query has been filtered by.
Simple pagination (page number and size) and sorting (directional ordering using standard or custom property
attributes) is also specifiable. Defaults are defined for paging (pageNumber = 1, pageSize = 200) and sorting

View File

@ -28,7 +28,7 @@ Alter the versions you depend on in your Gradle file like so:
.. sourcecode:: groovy
ext.corda_release_version = '4.0'
ext.corda_gradle_plugins_version = '4.0.37'
ext.corda_gradle_plugins_version = '4.0.38'
ext.kotlin_version = '1.2.71'
ext.quasar_version = '0.7.10'
@ -121,10 +121,13 @@ away to the new API, as otherwise things like business network membership checks
This is a three step process:
1. Change the flow that calls ``FinalityFlow``
1. Change the flow that calls ``FinalityFlow``.
2. Change or create the flow that will receive the finalised transaction.
3. Make sure your application's minimum and target version numbers are both set to 4 (see step 2).
Upgrading a non-initiating flow
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
As an example, let's take a very simple flow that finalises a transaction without the involvement of a counterpart flow:
.. container:: codeset
@ -140,7 +143,7 @@ As an example, let's take a very simple flow that finalises a transaction withou
:end-before: DOCEND SimpleFlowUsingOldApi
:dedent: 4
To use the new API, this flow needs to be annotated with ``InitiatingFlow`` and a ``FlowSession`` to the participant of the transaction must be
To use the new API, this flow needs to be annotated with ``InitiatingFlow`` and a ``FlowSession`` to the participant(s) of the transaction must be
passed to ``FinalityFlow`` :
.. container:: codeset
@ -175,10 +178,17 @@ to record the finalised transaction:
:end-before: DOCEND SimpleNewResponderFlow
:dedent: 4
For flows which are already initiating counterpart flows then it's a simple matter of using the existing flow session.
.. note:: All the nodes in your business network will need the new CorDapp, otherwise they won't know how to receive the transaction. **This
includes nodes which previously didn't have the old CorDapp.** If a node is sent a transaction and it doesn't have the new CorDapp loaded
then simply restart it with the CorDapp and the transaction will be recorded.
Upgrading an initiating flow
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
For flows which are already initiating counterpart flows then it's a matter of using the existing flow session.
Note however, the new ``FinalityFlow`` is inlined and so the sequence of sends and receives between the two flows will
change and will be incompatible with your current flows. You can use the flow version API to write your flows in a
backwards compatible way.
backwards compatible manner.
Here's what an upgraded initiating flow may look like:
@ -212,8 +222,9 @@ finalised transaction. If the initiator is written in a backwards compatible way
:end-before: DOCEND ExistingResponderFlow
:dedent: 12
The responder flow may be waiting for the finalised transaction to appear in the local node's vault using ``waitForLedgerCommit``.
This is no longer necessary with ``ReceiveFinalityFlow`` and the call to ``waitForLedgerCommit`` can be removed.
You may already be using ``waitForLedgerCommit`` in your responder flow for the finalised transaction to appear in the local node's vault.
Now that it's calling ``ReceiveFinalityFlow``, which effectively does the same thing, this is no longer necessary. The call to
``waitForLedgerCommit`` should be removed.
Step 4. Security: Upgrade your use of SwapIdentitiesFlow
--------------------------------------------------------

View File

@ -52,18 +52,8 @@ Version 4.0
* Fixed a problem that was preventing `Cash.generateSpend` to be used more than once per transaction (https://github.com/corda/corda/issues/4110).
* ``SwapIdentitiesFlow``, from the experimental confidential-identities module, is now an inlined flow. Instead of passing in a ``Party`` with
whom to exchange the anonymous identity, a ``FlowSession`` to that party is required instead. The flow running on the other side must
also call ``SwapIdentitiesFlow``. This change was required as the previous API allowed any counterparty to generate anonoymous identities
with a node at will with no checks.
The result type has changed to a simple wrapper class, instead of a Map, to make extracting the identities easier. Also, the wire protocol
of the flow has slightly changed.
.. note:: V3 and V4 of confidential-identities are not compatible and confidential-identities V3 will not work with a V4 Corda node. CorDapps
in such scenarios using confidential-identities must be updated.
The ``confidential-identities`` dependency in your CorDapp must now be ``compile`` and not ``cordaCompile``.
* The experimental confidential-identities is now a separate CorDapp and must now be loaded onto the node alongside any CorDapp that needs it.
This also means your gradle dependency for it should be ``cordapp`` and not ``cordaCompile``.
* Fixed a bug resulting in poor vault query performance and incorrect results when sorting.
@ -86,6 +76,9 @@ Version 4.0
* Introduced new optional network bootstrapper command line options (--register-package-owner, --unregister-package-owner)
to register/unregister a java package namespace with an associated owner in the network parameter packageOwnership whitelist.
* BFT-Smart and Raft notary implementations have been move to the ``net.corda.notary.experimental`` package to emphasise
their experimental nature.
* New "validate-configuration" sub-command to `corda.jar`, allowing to validate the actual node configuration without starting the node.
* CorDapps now have the ability to specify a minimum platform version in their MANIFEST.MF to prevent old nodes from loading them.
@ -99,39 +92,16 @@ Version 4.0
* ``FinalityFlow`` is now an inlined flow and requires ``FlowSession`` s to each party intended to receive the transaction. This is to fix the
security problem with the old API that required every node to accept any transaction it received without any checks. Existing CorDapp
binaries relying on this old behaviour will continue to function as previously. However, it is strongly recommended that CorDapps switch to
binaries relying on this old behaviour will continue to function as previously. However, it is strongly recommended CorDapps switch to
this new API. See :doc:`app-upgrade-notes` for further details.
* For similar reasons, ``SwapIdentitiesFlow``, from confidential-identities, is also now an inlined flow. The old API has been preserved but
it is strongly recommended CorDapps switch to this new API. See :doc:`app-upgrade-notes` for further details.
* Introduced new optional network bootstrapper command line option (--minimum-platform-version) to set as a network parameter
* BFT-Smart and Raft notary implementations have been extracted out of node into ``experimental`` CorDapps to emphasise
their experimental nature. Moreover, the BFT-Smart notary will only work in dev mode due to its use of Java serialization.
* Vault storage of contract state constraints metadata and associated vault query functions to retrieve and sort by constraint type.
* UPGRADE REQUIRED: changes have been made to how notary implementations are configured and loaded.
No upgrade steps are required for the single-node notary (both validating and non-validating variants).
Other notary implementations have been moved out of the Corda node into individual Cordapps, and require configuration
file updates.
To run a notary you will now need to include the appropriate notary CorDapp in the ``cordapps/`` directory:
* ``corda-notary-raft`` for the Raft notary.
* ``corda-notary-bft-smart`` for the BFT-Smart notary.
It is now required to specify the fully qualified notary service class name, ``className``, and the legal name of
the notary service in case of distributed notaries: ``serviceLegalName``.
Implementation-specific configuration values have been moved to the ``extraConfig`` configuration block.
Example configuration changes for the Raft notary:
.. image:: resources/notary-config-update.png
Example configuration changes for the BFT-Smart notary:
.. image:: resources/notary-config-update-bft.png
* New overload for ``CordaRPCClient.start()`` method allowing to specify target legal identity to use for RPC call.
* Case insensitive vault queries can be specified via a boolean on applicable SQL criteria builder operators. By default
@ -145,7 +115,8 @@ Version 4.0
* Removed experimental feature ``CordformDefinition``
* Added ``registerResponderFlow`` method to ``StartedMockNode``, to support isolated testing of responder flow behaviour.
* Added new overload of ``StartedMockNode.registerInitiatedFlow`` which allows registering custom initiating-responder flow pairs, which
can be useful for testing error cases.
* "app", "rpc", "p2p" and "unknown" are no longer allowed as uploader values when importing attachments. These are used
internally in security sensitive code.
@ -340,6 +311,9 @@ Version 4.0
* Finance CorDapp is now build as a sealed and signed JAR file.
Custom classes can no longer be placed in the packages defined in Finance Cordapp or access it's non-public members.
* Finance CorDapp was split into two separate apps: ``corda-finance-contracts`` and ``corda-finance-workflows``,
``corda-finance`` is kept for backward compatibility, it is recommended to use separated jars.
* The format of the shell commands' output can now be customized via the node shell, using the ``output-format`` command.
* The ``node_transaction_mapping`` database table has been folded into the ``node_transactions`` database table as an additional column.

View File

@ -21,6 +21,8 @@ you.
`CordaRPCClient`_ class. You can find an example of how to do this using the popular Spring Boot server
`here <https://github.com/corda/spring-webserver>`_.
.. _clientrpc_connect_ref:
Connecting to a node via RPC
----------------------------
To use `CordaRPCClient`_, you must add ``net.corda:corda-rpc:$corda_release_version`` as a ``cordaCompile`` dependency

View File

@ -50,7 +50,7 @@ Corda incubating modules
The following modules don't yet have a completely stable API, but we will do our best to minimise disruption to
developers using them until we are able to graduate them into the public API:
* **net.corda.confidential.identities**: experimental support for confidential identities on the ledger
* **net.corda.confidential**: experimental support for confidential identities on the ledger
* **net.corda.finance**: a range of elementary contracts (and associated schemas) and protocols, such as abstract fungible assets, cash, obligation and commercial paper
* **net.corda.client.jfx**: support for Java FX UI
* **net.corda.client.mock**: client mock utilities

View File

@ -1,545 +0,0 @@
Corda Network Foundation : Governance Guidelines
================================================
23 October 2018
Version 0.95
DRAFT for discussion
This is a set of governance guidelines for the Corda Network Foundation. It provides a framework for the Foundations
Board, to steer and govern Corda Network effectively to realise its potential. It is not a set of binding legal obligations.
1 Background to Corda and the Network
-------------------------------------
Corda allows multiple independent applications and private networks to coexist, each with their own business models and
membership criteria, yet linked by the same underlying network (Corda Network). This Network enables interoperability,
the exchange of data or assets via a secure, efficient internet layer, in a way that isnt possible with competing
permissioned distributed ledger technologies or legacy systems.
Corda Network operates the protocol of Corda currently, and is always expected to. The protocol is currently
specified in the Corda Open Source Project codebase, but later may be formalised in a protocol specification document,
which then will become canonical.
1.1 Reason for a Corda Network Foundation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
R3 has set up and governs by default Corda Network currently (along with Corda). This includes making key decisions
around establishing, maintaining and updating standards, policies, and procedures for participation in, and use of,
Corda Network.
However, it is critically important that a commercial entity should not control Corda Network going forwards. It should
be governed transparently (to its Participants), with a fair and stable structure. Analysis and feedback show
that Corda Network will be most effectively governed via a Foundation, a not-for-profit, independent entity in which
Network Participants elect, and can be elected to, a governing Board.
A Foundation will enable Network Participants to be involved with, and also understand, how decisions are made (including
around issues of identity and permission), building trust and engagement from a wide range of stakeholders. This will
bring about the best decisions and outcomes for the Networks long-term success.
In other words, to achieve the community's objective of Corda ubiquity, it is necessary to put in place a governance
structure which explicitly limits R3s control of Corda Network, and enables this ubiquity.
2 The Corda Network Foundation
------------------------------
2.1 Mission and Values
^^^^^^^^^^^^^^^^^^^^^^
Following on from the Corda introduction and technical white papers, we see the mission of the Corda Network Foundation
to achieve the vision of Corda - whereby the state of transactions and agreements of business partners can be recorded
in a single global database, ending the need for costly reconciliation and error correction, while maintaining privacy.
Further details of the vision are laid out in the
[Corda Introductory White Paper](https://www.corda.net/content/corda-platform-whitepaper.pdf).
Achieving this vision in its full ubiquity will involve running and maintaining a stable and secure Network with open
and fair governance, while also promoting the Network so as to ensure its more widespread use.
Following on this, the Corda Network Foundation shall embody the following qualities in executing its mission:
* Fairness and openness Participants can join the Network and make up the Networks governing board, elected through a
straightforward voting process.
* Democracy and transparency Key decisions and rationale are shared openly with Participants.
* Stability (with a long-term view) with flexibility Board directors terms will last three years with a staggered
board, and the governance model will be flexible to adapt where required.
* Efficiency Staying a lean organisation, sufficient to commission and monitor an Operator to run any services,
physical infrastructure, or operational processes that may be needed to achieve the vision of Corda. Provide adequate
support, through advisory committees.
* Cost effectiveness - Funding received through participation fees pays for an Operator to run the Network securely, and
the Foundation shall not be a profit-making entity.
* Independence Corda Network Foundation makes its own decisions (within the law), and is not following another
entitys rules.
More specifically, the Foundation shall focus on the following commitments over the long-term:
* Maintain the long-term standards, services and open governance model of the Network, ensuring it continues to be
updated to the current Corda protocol version.
* Hold the Trust Root for the Network, used for creation of operational certificates, independently of the Operator.
* Commission the provision and operation of infrastructure and services for the Network, both of technical services, and
infrastructure needed for meetings, events and collaborative discussions, and provide structure around the business and
technical governance of the Network.
* Facilitate a diverse and vibrant community of industry experts, Corda contributors, users and services, including
developers, service and solution providers and end users.
* Set minimum standards for the external provision of notary and oracle services.
* Enable the ubiquity and utility of Corda throughout all applicable industries and commercial use cases.
* Balance the divergent interests of a wide range of stakeholders, including business network operators, Corda
customers, open source developers, and R3s shareholders.
2.2 Structure of the Foundation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The Foundation shall be a not-for-profit entity created exclusively to execute the mission set out in this Constitution.
With the advice of international lawyers, this is a Stichting domiciled in Holland a legal entity suited for
governance activities, able to act commercially, with limited liability but no shareholders, capital or dividends.
The Foundation is defined in a set of Articles of Association and By-laws.
The Foundation governance bodies shall include:
1. A **Governing Board** (the Board) of 11 representatives (Directors) with privileges and responsibilities as set out
in section 3.
2. A **Technical Advisory Committee** (the TAC), comprised of representatives of Participant organisations with
responsibilities set out in section 6.2.
3. A **Governance Advisory Committee**, comprised of representatives of Participant organisations with responsibilities
set out in section 6.3.
4. A **Network Operator** (the Operator), charging the Foundation reasonable costs for providing network and
administration services, paid by the Foundation through membership funds, and accountable directly to the Board, set
out in section 7.
Operating on behalf of:
* A **General Membership** (the Participant Community), which is open to any organisation participating in Corda Network,
and with privileges and responsibilities as set out in section 6.
Any change to the structure of the Foundation is a constitutional change, described in section 5.1.
3 Governing Board
-----------------
3.1 Role of the Board
^^^^^^^^^^^^^^^^^^^^^
The goal of the Board is primarily to ensure the stable and secure operation of the Network, as well as to achieve the
vision of Corda laid out in section 2.1. The fundamental responsibility of directors appointed to the Board is to
exercise their business judgement to act in what they believe to be the best interests of the Network, taking account
of the interests of the Network community as a whole (rather than any one individual interest).
Directors are expected to comply with the Conflict of Interest policy, which includes a responsibility to disclose
promptly any conflicts that may arise, and meet the expected standards specified in the Code of Conduct Guidelines for
ethical conduct and breach reporting.
The Board is the formal decision-making authority of the Foundation, and actions of the Board reflect its collective
decision making.
3.2 Relationship of the Board with the Operator
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
It is the duty of the Board to monitor the Operators performance to ensure that the Network operates in an effective,
efficient and ethical manner. The Board will also be responsible for overseeing the Operator in the development of the
Networks strategic and tactical plans, ensuring that they will result in broad and open adoption of Corda. The Operator
is responsible to the Board for the execution of day to day operations, and the implementation of strategic and tactical
change.
3.3 Composition and Establishment of the Board
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
### 3.3.1 Size
The Board shall consist of 11 voting members (Directors) in total, to allow broad representation but maintain an agile
decision-making ability. The selection process (using the Participant Community) is intended to ensure that the Board is
composed of members who display diversity in geography, culture, skills, experience and perspective, and that the
abilities and interests of Directors are aligned with those of Corda.
R3 shall have the ongoing right to appoint two Directors, as the firm which developed Corda and funded the initial
construction of the Network. It represents the interests of its large and diverse alliance of commercial organisations,
financial institutions, and regulatory bodies. Similarly to the rest of the board, the Directors will have three-year
terms (unless the director resigns or leaves for another reason) and can be re-appointed without limit. Appointment will
be effective immediately.
The Chair of the board will be elected for a one year term by a vote of the Directors of the Board, at the first Board
meeting following the Board election.
#### 3.3.2 Participation Criteria
Participants shall be directed to vote to ensure that the Board is composed of Directors who in the aggregate produce
the broadest diversity on the Board, consistent with meeting the other criteria. In addition, the Board is to be
comprised of individuals who can demonstrate to Participants they meet the following requirements:
* Hold an understanding and appreciation of the Corda protocol and community purpose.
* Have an awareness of cultural and geographic perspectives.
* Demonstrate integrity, intelligence and objectivity.
* Can work and communicate in written and spoken English.
To promote diversity, the following guidelines are adopted, in particular for steady-state governance (recognising that
these may not be possible to fulfil during the transition period):
* No corporate group of participants may have more than one Director. In the event of votes for two different candidates
representing the same overall corporate group, the candidate with the most votes shall be considered.
* Of the nine Directors, there should not be more than three Directors from any broad industry classification, according to
company classification data.
* Of the nine Directors, there should not be more than three Directors from any continent (one must be based in the Americas,
Europe/Africa and Asia, to ensure geographic diversity.
* Of the nine Directors, there should not be more than three Directors representing any Corporate Group with more than
100,000 employees.
* There is no restriction of re-election of Directors or the Chair of the Board.
### 3.3.3 Establishment of the Board
#### 3.3.3.1 Pre-Foundation
Initially R3 shall govern the Network to ensure rapid progress on its implementation. Once the Foundation is set-up and
at least three business networks are conducting live transactions using the network with at least three Participants each, the
'transition period' of one year will commence.
#### 3.3.3.2 Transition: Initial set-up of Foundation Board:
For the transition year, the first three business networks shall have the right to choose three Participants, to
represent the interests of the business network. One of each of these must be based in the Americas, Europe/Africa and
Asia, to ensure geographic diversity, if the pool Participants allows. Each selected Participant will appoint a
Director, to sit on the Board, making nine Directors in addition to the two Directors from R3.
After this start-up period, there will be a vote for Board Directors.
For the first election only, of the nine vacant seats, three will be for a duration of one year, three for two years, and
three for three years. This will introduce a staggered board, so there is greater continuity at the end of each term.
Candidates with the most votes will fill the three-year seats first, followed by two-year and then one-year seats. In all other
respects, the first election will follow the steady state process.
#### 3.3.3.3 Steady-State
1. Participants may nominate candidates for Director election. Appointments to the nine rotating seats of the Board will be
by vote of the Participants, with three seats up for election each year. Any seats vacated mid-term will also be
elected at the same time. R3 may not put forward candidates for the nine rotating seats, and these may not be held by
R3 employees.
2. Candidates will create a personal statement and short biography, which will be shared with all Participants.
3. Participants may each cast up to three votes for three separate candidates.
4. Subject to meeting certain criteria (including diversity of geography and industry), the most popular candidates
will be appointed as Directors.
5. Candidates will be considered in sequence from most popular to least, and if a seat is vacant according to the
diversity criteria in section 3.3.2, the candidate will be allocated to it. This may mean that occasionally a less
popular candidate fills a seat instead of a more popular one.
6. R3 shall appoint Directors to the two remaining seats, when appropriate.
#### 3.3.4 Removal from the Board and Re-election.
Apart from the three-year expiry, Directors can otherwise leave the Board by resignation, prolonged non-attendance of
board meetings of more than six months, death, or if necessary, removal by a Mandatory Governance Event. In any case, a
vacant seat will be contested at the next annual election.
3.4 Conduct of Board Meetings
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Attendance may be in person or by video-conference.
The Board shall meet not less than every six months, and may meet on the request of any Director, but not more than every
two months. At least two weeks notice shall be given to Directors. By exception, the Chair may convene an emergency
meeting with minimal notice, appropriate to the situation in the Chair's judgement.
The Board shall consider all Governance Events proposed since the previous meeting, except for an emergency convening.
Board meetings shall be limited to the Board representatives, and shall follow the requirements for quorum and voting
outlined in this Constitution.
The Board may decide whether to allow one named representative to attend as an alternate, and typically these shall be
allowed.
The Board meetings shall be conducted in private, but in the interest of transparency, public minutes shall be
published within two weeks following their approval by the Board.
Participants who do not have representation on the Board may request an observer to be present at a Board meeting.
This is subject to a lottery held one week prior to the meeting, a limit of 20 observer places, and a limit of one
observer per unrepresented Participant. Observers may participate in discussions but shall not participate in any Board
vote, and may be asked to join by video-conference if there are logistical constraints.
4 Relation of the Foundation to Business Networks
---------------------------------------------------
The global Network shall support the operation of any business networks which may be formed by industry-specific
operators on top of the Network. The Board shall ensure that there is a clear separation between areas of governance
for the Foundation and Network outlined in this document, and for individual business networks.
Additionally, the structure and control processes defined for the Foundation shall be documented and made available
under a Creative Commons license, both for reuse by business network operators if business networks need a similar
governance structure, and so that such governance layers are complementary and not contradictory.
5 Governance Events
---------------------
All formal changes to the Network and the Foundation shall be controlled through a formal Governance Event process, and
the right to initiate this shall be held by all Directors and Participants. In the event of disruptive behaviour by an
individual Participant or group of Participants, this right may be curtailed, as described in 5.2.5.
5.1 Types of Governance Events
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There are three types of change that affect the Network and the Foundation, which require a controlled change process
and a vote described in 5.5, and are defined as Governance Events:
1. All changes to the Stichting Articles of Association and By-laws are defined as Constitutional Governance Events.
2. All changes to Network participation criteria, charges, budgets, change management process and other business areas not
defined in Articles of Association or By-laws are defined as Mandatory Governance Events in section 5.2. The Board shall
vote to accept or reject all such Mandatory Governance Events, and the outcomes are binding on Participants and the
Operator for implementation.
3. All changes to technical parameters and notary criteria, which affect the nodes of participants, are defined as
Advisory Governance Events in section 5.3. While the Operator can implement these without Board approval, it may ask the
Board to provide an advisory (non-binding) vote. Conversely, the Board may require that it is given the opportunity to
provide an advisory vote.
Any other changes in the day to day internal implementation of network services by the Operator, which do not require
changes to be implemented on the nodes of participants, are out of scope as Governance Events.
All Constitutional, Mandatory and Advisory Governance Events shall be supported by a formal proposal, using standard
structured documents and containing all relevant background information, to create an efficient process both for the
submitter and the Board.
Depending on the content of the Governance Event proposal, the Board or Operator may rely on the Governance or Technical
Advisory Committee to provide due diligence and make a recommendation for implementation.
For all Governance Events, decisions and the rationale for the decision shall be published transparently.
5.2 Mandatory Governance Events
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
### 5.2.1 Access Standards
The Corda system can be accessed by using software which implements the set of technical protocols which define
compatibility (see 5.3.1) above). The reference implementation of this software is open source and freely accessible at
[www.corda.net](https://www.corda.net).
To join the Network, a participant running Corda compatible software also needs a unique and real-world identity. The
Foundation shall enforce this access requirement through the issuance of PKI certificates. Corda has a primary objective
to facilitate automation of real-world contracts between real-world parties, and has a particular requirement to ensure
that identities on the Network are unique and that all participants understand the basis on which they have been issued.
The Foundation shall govern the operation of the technical infrastructure to enable a good level of service for identity
issuance.
The access criteria for proving real world identity shall be defined by the Board and shall be transparent and objective.
Any party which can demonstrate they meet these criteria will be issued a certificate without prejudice. The goal to
ensure that the Operator which manages the issuance of the network certificates cannot act arbitrarily or
discriminatory. But lawful requests from regulatory authorities of the Foundations jurisdiction shall be accepted.
These criteria may be subject to change over time to deal with changing circumstances, like regulatory requirements.
However, the changes shall be subject to Mandatory Governance Events. In this way the Network is able to provide its
participants with a strong and fair identity framework.
Arbitration, suspension, and in extreme circumstances, revocation (for example for illegal behaviour or when a
participant no longer meets the standards set forth) shall be managed through an Emergency Governance Event, set out in
5.4.
#### 5.2.2 Budget, Expenditure and Participation Fees
The Board shall annually prepare and approve a budget for the operations of the Foundation, taking into account the
not-for-profit status of the Foundation and the mission to promote the Corda Ecosystem.
The Foundation shall charge a fee for Membership, as described in section 6.1.
The Operator shall charge the Foundation for services that the Operator provides under the requirements of the contract
with the Foundation, including management of Participants, Network participation and access services, Network map
and Operator-provided notary services. The Operator may also provide fee-based services that are supplementary to those
needed to participate on the Network.
#### 5.2.3 Change of Network Operator
For three years upon establishment of the Foundation, R3 will undertake the role of Operator. Annually thereafter, the
Board will approve the appointment of the Operator, which may be changed with a Mandatory Governance Event and vote.
As noted, the Foundation shall hold the Trust Root, and the Operator and any services they operate shall be provisioned
with intermediate certificates that derive from this. The Operator must enter into an escrow arrangement upon
appointment, so that existing certificates continue to work, certificate revocation lists continue to be published, and
there is no disruption to Participants if the Operator is changed.
#### 5.2.4 Change Management Process
The Network will periodically require participating nodes to implement change. A change notification and management
process shall be defined and communicated; and any change to the change management process shall be the subject of a
Mandatory Governance Event.
#### 5.2.5 Other Mandatory Governance Events
Restrictions on individual Participants or a group to initiate Governance Events; in the event of disruptive behaviour.
Audit: the Board may request an audit of the activities and services provided by the Operator, no more frequently than
every year, unless an emergency audit is authorised through a Mandatory Governance Event.
Marketing, Trademark and Branding: R3 shall commit to license the Corda trademark to the Foundation. The Foundation
shall manage its own brand and any trademarks created.
Certifications: Where the Foundation provides standards for certification of organisations, individuals or technologies,
the Board shall approve the standards and processes for certification.
Change to the arbitration and dispute resolution process shall be the subject of a Mandatory Governance Event.
Policies covering areas of operation not covered by the Constitution (e.g. code of conduct for Board Directors).
5.3 Advisory Governance Events
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#### 5.3.1 Technical Standards
There is a set of technical standards, such as network parameters, which all Corda Network nodes need to comply with
in order to guarantee technical compatibility to other nodes and services within the Network. While Corda has stability
and backwards compatibility as key design goals, there may be circumstances under which these standards will need to
change. Where these changes require participants in the Network to update to remain compatible, these changes will be
subject to Governance Events.
Changes to technical standards, such as some network parameters, shall require formal design processes, and the Operator may
choose to delegate technical due diligence to the Technical Advisory Committee prior to formally accepting a change to
the technical standards.
The Corda open source software is the reference implementation for the core technical standards adopted for the Network.
Corda implementations and distributions can vary in their internal details, but their core interfaces and Corda protocol
implementation must conform to this standard to be compatible with the Network.
#### 5.3.2 Consensus Standards
The Foundation shall set minimum standards for notary clusters, to allow their use across different business
applications. The Operator shall ensure that standards are followed by notary service providers, and shall operate a
framework of audit and assessment, review, feedback, and certification, covering the following:
1. Technical standards, such as meeting strict requirements for high-availability and data replication/security and
performance.
2. Compliance with necessary laws and regulations (for example privacy and data retention regulations) in the
jurisdictions in which they operate.
3. Availability for independent audits upon request by the Board.
Additionally, the Operator shall manage a reference distributed notary service for the Network, using a Board-approved
crash or Byzantine fault tolerant (BFT) consensus algorithm, with nodes provided by a minimum number of identified and
independent entities.
#### 5.3.3 Dispute Resolution Process
Disputes between Participants arising from the operation of a Corda application are anticipated to be resolved by the
business network operator, or directly if no business network is involved. If necessary, Participants may escalate to
the Board by creating an Advisory Governance Event.
5.4 Emergency Governance Events
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Certain Network incidents, which could impact one or more Network participants and that would be the subject of
Mandatory or Advisory Governance Events, shall require immediate resolution. In these cases, the Operator may make
emergency changes, but these shall be subject to post-event evaluation and standard Governance Event processing. Areas
of control that are the subject of Mandatory Governance Events are not expected to require emergency remediation, but
the Operator shall be entitled to make emergency changes to preserve the stability and integrity of the Network.
5.5 Voting
^^^^^^^^^^
All Constitutional, Mandatory and Advisory Governance Events outlined in sections 5.2 and 5.3 shall be presented to the
Board for voting. The representatives of the Board shall vote on a one vote per Director basis to approve or reject the
Governance Event.
Quorum for the Board shall require two thirds of the Directors to vote. Abstention is not a vote. The Board may continue
to meet if quorum is not met, but shall be prevented from making any decisions at the meeting. Decisions by electronic
vote without a meeting shall require a vote by two thirds majority of all Directors.
Provided quorum is met, Constitutional Governance Events shall require a three quarters majority vote, and Mandatory
Governance Events shall require a two thirds majority vote.
In the event of a tied vote (the odd number of representatives is intended to avoid tied votes) the vote of the Chair
shall carry the vote. If the Chair does not vote in the case of a tied vote, the Event will not be passed.
All Governance Events proposed for consideration by the Board at a meeting shall be circulated in draft form to the
members of the Board at least one week prior to the date of the meeting, and the text of such draft events may be altered
at the meeting.
The Foundation may chose to implement the tracking and voting for Governance Events using an on-ledger Corda application
in an attempt to simplify governance, provide transparency and lower costs, provided the application has been tested
thoroughly and has sufficient manual override controls.
6 Participation
-----------------
6.1 General Membership
^^^^^^^^^^^^^^^^^^^^^^
Participation is open to any potential participant on the Network, subject to meeting normal Network access conditions
described in section 5.2.1, and paying a nominal annual participation fee to cover both the operational costs of Network
services and the Foundation, and to ensure that its activities are sufficiently resourced.
The Participant Community have the right to:
1. Propose a formal Governance Event to the Board for voting. This must meet the appropriate standards and formats.
2. Request observer representation at a board meeting subject to logistical constraints.
3. Utilise any brand and marketing materials that may be provided by the Foundation to Participants.
4. Identify themselves as participants of the Foundation.
5. Vote in the periodic election of a new Board.
6. Participate in conferences, projects and initiatives of the Foundation. Numbers of participants and any additional
costs will depend on the individual event.
7. Receive an identity necessary to operate a Corda node on the Network.
8. Use the Network for live business activities running 'in production'.
6.2 Technical Advisory Committee
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The Technical Advisory Committee shall have limited participants appointed directly by the Board. Its mandate and
charter will be set by the Board. It shall act directly on the instructions of the Board or the Operator, which shall
set expected deliverables and timelines. It shall focus on specific technical topics and may have responsibility for
the following:
1. Advise on technical decisions for the Operator.
2. Advising the Board in technical matters.
3. Provide feedback on the technical roadmap for Corda, from real-world and practical experience gained from observing
the operation of the Network.
4. Conducting open design reviews and soliciting public input for technical proposals.
5. Contributing to the Corda open source community from a Network perspective, to ensure that Corda retains a coherent,
elegant and practical system design
6.3 Governance Advisory Committee
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The Governance Advisory Committee shall have limited participants appointed directly by the board. Its purpose is to
recommend actions to the Board for approval on non-technical matters, where additional support is helpful. This may
include decisions on:
1. Operator Due Diligence
2. Identity and Permissions
3. Risks and Escalations
4. Interacting with Regulators
5. Complaints and Whistle-blowing
7 The Corda Network Operator
----------------------------
In order to pursue the mission of the Foundation as set out in section 1, there will need to be a set of operational
activities, including technical activities such as hosting services, marketing activities, community management and
promotion. These activities shall be funded through the participation fees and overseen by the Board, and they will
require operational staffing by the Operator. It is not envisaged (at least during the first year) that the Corda
Network Foundation will need separate staff. Administrative operations and meeting facilities will be provided by the
Operator.
The Operator shall invoice the Foundation for the costs of operating the Network and minor administrative expenses,
initially on a cost-plus basis, and subject to annual review. Corda Network identity and map technical services
have been designed to be highly cacheable, and low-cost in operation.
For the first three years, R3 shall act as the Operator.
8 Costs and Participation Fees
------------------------------
8.1 Costs
^^^^^^^^^
In line with the mission and values of the Foundation, the Network Foundation is not a profit seeking entity. But the
Foundation needs to provide governance and technical services, and these will incur costs. The Foundation maintains these
cost principles, as ideals but not contractual standards:
1. Costs for all services should be borne by all users of those services.
2. One group of participants should not subsidise another.
3. The costs shall be tightly managed, and the Foundation shall seek to provide the most cost-effective implementation
of all of its own administration, governance and technical services.
4. Costs of one service should not be subsidised by another.
5. The Foundation's cost model should be public, to demonstrate that the costs could not reasonably be lower.
8.2 Participation Fee
^^^^^^^^^^^^^^^^^^^^^
The Foundation shall meet costs by levying a participation fee and notary fee for all Participants. The participation
fee will be independent of organisation size and number of transactions on Corda, to reflect the underlying cost of
identity issuance.
The fee shall be based on the number of Participants divided by an estimate of the cost of running the Foundation,
which is set out in section 7. There may be variance in the fee depending on whether the Participant is indirectly using a
Corda Network-powered application, and therefore the services which the Participant is able to access.
Such fees shall be detailed in a separate schedule to be updated annually and approved by the Board by a Mandatory
Governance Event.
The Operator may agree to provide the Foundation with a start-up commercial loan, in order to allow the Foundation to
cap fees for Participants initially. This will allow early widespread adoption, when early participant numbers will not
offset fixed operating costs. In this case, the fees will not fall to steady-state levels until the loan has been repaid.
Subsidiaries of large organisations shall apply for membership separately, since the model for Corda usage is for one
identity per legal entity, unless varied by Mandatory Governance Event. The fee and voting right shall apply to each
subsidiary individually.
The fee applies even if the Participants chooses not to operate a Corda node on the Network. Therefore, Participants
can be potential or active participants.
8.3 Notary Fee
^^^^^^^^^^^^^^
Transaction notary fees will be charged separately, on a per-use basis. This reflects the variable cost of providing
notary services, with a wide orders-of-magnitude disparity between frequent and infrequent participant transaction
volumes. As a principle, notary fees shall not subsidise participation fees, nor vice versa.
9 Community
-----------
Corda is a collaborative effort, and part of the Foundations mission is to help create and foster a technical community
that will benefit all Corda solution providers and users. As such, the Foundation will work to encourage further
participation of leading Participants of the ecosystem, including developers, service and solution providers and end
users. This community shall work towards furthering the adoption of Corda, and contribute to the specific capabilities
identified in the technical white paper.
The Corda technical community should be broad and open, encouraging participation and active conversations on the
technology and applications, but this cannot be mandated by the Foundation.
9.1 Non-Discrimination
^^^^^^^^^^^^^^^^^^^^^^
The Foundation will welcome any organization able to meet the Participation criteria, regardless of competitive
interests with other Participants. The Board shall not seek to exclude any Participant for any reasons other than those
that are reasonable, explicit and applied on a non-discriminatory basis to all Participants.
END

Some files were not shown because too many files have changed in this diff Show More