Merge commit '86fb1ed852c69121f989c9eeea92cfb4c27f9d13' into aslemmer-merge-19-Feb

This commit is contained in:
Andras Slemmer
2018-02-19 16:14:43 +00:00
234 changed files with 5046 additions and 1609 deletions

File diff suppressed because it is too large Load Diff

1
.gitignore vendored
View File

@ -6,6 +6,7 @@ tags
.DS_Store .DS_Store
*.log *.log
*.orig *.orig
corda-docs-only-build
# Created by .ignore support plugin (hsz.mobi) # Created by .ignore support plugin (hsz.mobi)

1
.idea/compiler.xml generated
View File

@ -87,6 +87,7 @@
<module name="irs-demo-web_test" target="1.8" /> <module name="irs-demo-web_test" target="1.8" />
<module name="irs-demo_integrationTest" target="1.8" /> <module name="irs-demo_integrationTest" target="1.8" />
<module name="irs-demo_main" target="1.8" /> <module name="irs-demo_main" target="1.8" />
<module name="irs-demo_systemTest" target="1.8" />
<module name="irs-demo_test" target="1.8" /> <module name="irs-demo_test" target="1.8" />
<module name="isolated_main" target="1.8" /> <module name="isolated_main" target="1.8" />
<module name="isolated_test" target="1.8" /> <module name="isolated_test" target="1.8" />

View File

@ -37,7 +37,7 @@ buildscript {
* https://issues.apache.org/jira/browse/ARTEMIS-1559 * https://issues.apache.org/jira/browse/ARTEMIS-1559
*/ */
ext.artemis_version = '2.2.0' ext.artemis_version = '2.2.0'
ext.jackson_version = '2.9.2' ext.jackson_version = '2.9.3'
ext.jetty_version = '9.4.7.v20170914' ext.jetty_version = '9.4.7.v20170914'
ext.jersey_version = '2.25' ext.jersey_version = '2.25'
ext.jolokia_version = '1.3.7' ext.jolokia_version = '1.3.7'
@ -71,6 +71,10 @@ buildscript {
ext.liquibase_version = '3.5.3' ext.liquibase_version = '3.5.3'
ext.shadow_version = '2.0.2' ext.shadow_version = '2.0.2'
ext.hikari_version = '2.5.1' ext.hikari_version = '2.5.1'
ext.snake_yaml_version = constants.getProperty('snakeYamlVersion')
ext.docker_compose_rule_version = '0.33.0'
ext.selenium_version = '3.8.1'
ext.ghostdriver_version = '2.1.0'
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest: // Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
ext.java8_minUpdateVersion = '131' ext.java8_minUpdateVersion = '131'
@ -79,6 +83,7 @@ buildscript {
mavenLocal() mavenLocal()
mavenCentral() mavenCentral()
jcenter() jcenter()
// This repository is needed for Dokka until 0.9.16 is released.
maven { maven {
url 'https://dl.bintray.com/kotlin/kotlin-eap/' url 'https://dl.bintray.com/kotlin/kotlin-eap/'
} }
@ -378,8 +383,12 @@ task generateApi(type: net.corda.plugins.GenerateApi){
} }
// This exists to reduce CI build time when the envvar is set (can save up to 40 minutes) // This exists to reduce CI build time when the envvar is set (can save up to 40 minutes)
if(System.getenv('CORDA_DOCS_ONLY_BUILD') != null) { if(file('corda-docs-only-build').exists() || (System.getenv('CORDA_DOCS_ONLY_BUILD') != null)) {
logger.info("Tests are disabled due to presence of envvar CORDA_DOCS_ONLY_BUILD") if(file('corda-docs-only-build').exists()) {
logger.info("Tests are disabled due to presence of file 'corda-docs-only-build' in the project root")
} else {
logger.info("Tests are disabled due to the presence of envvar CORDA_DOCS_ONLY_BUILD")
}
allprojects { allprojects {
test { test {

View File

@ -8,11 +8,11 @@ dependencies {
compile project(':core') compile project(':core')
testCompile project(':test-utils') testCompile project(':test-utils')
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
// Jackson and its plugins: parsing to/from JSON and other textual formats. // Jackson and its plugins: parsing to/from JSON and other textual formats.
compile "com.fasterxml.jackson.module:jackson-module-kotlin:${jackson_version}" compile "com.fasterxml.jackson.module:jackson-module-kotlin:$jackson_version"
// Yaml is useful for parsing strings to method calls. // Yaml is useful for parsing strings to method calls.
compile "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jackson_version" compile "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jackson_version"
// This adds support for java.time types. // This adds support for java.time types.

View File

@ -2,7 +2,7 @@ package net.corda.client.jfx
import net.corda.client.jfx.model.NodeMonitorModel import net.corda.client.jfx.model.NodeMonitorModel
import net.corda.client.jfx.model.ProgressTrackingEvent import net.corda.client.jfx.model.ProgressTrackingEvent
import net.corda.core.context.Origin import net.corda.core.context.InvocationOrigin
import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount
import net.corda.core.contracts.ContractState import net.corda.core.contracts.ContractState
import net.corda.core.crypto.isFulfilledBy import net.corda.core.crypto.isFulfilledBy
@ -158,8 +158,8 @@ class NodeMonitorModelTest : IntegrationTest() {
// ISSUE // ISSUE
expect { add: StateMachineUpdate.Added -> expect { add: StateMachineUpdate.Added ->
issueSmId = add.id issueSmId = add.id
val context = add.stateMachineInfo.context() val context = add.stateMachineInfo.invocationContext
require(context.origin is Origin.RPC && context.principal().name == "user1") require(context.origin is InvocationOrigin.RPC && context.principal().name == "user1")
}, },
expect { remove: StateMachineUpdate.Removed -> expect { remove: StateMachineUpdate.Removed ->
require(remove.id == issueSmId) require(remove.id == issueSmId)
@ -167,8 +167,8 @@ class NodeMonitorModelTest : IntegrationTest() {
// MOVE - N.B. There are other framework flows that happen in parallel for the remote resolve transactions flow // MOVE - N.B. There are other framework flows that happen in parallel for the remote resolve transactions flow
expect(match = { it.stateMachineInfo.flowLogicClassName == CashPaymentFlow::class.java.name }) { add: StateMachineUpdate.Added -> expect(match = { it.stateMachineInfo.flowLogicClassName == CashPaymentFlow::class.java.name }) { add: StateMachineUpdate.Added ->
moveSmId = add.id moveSmId = add.id
val context = add.stateMachineInfo.context() val context = add.stateMachineInfo.invocationContext
require(context.origin is Origin.RPC && context.principal().name == "user1") require(context.origin is InvocationOrigin.RPC && context.principal().name == "user1")
}, },
expect(match = { it is StateMachineUpdate.Removed && it.id == moveSmId }) { expect(match = { it is StateMachineUpdate.Removed && it.id == moveSmId }) {
} }
@ -179,8 +179,8 @@ class NodeMonitorModelTest : IntegrationTest() {
sequence( sequence(
// MOVE // MOVE
expect { add: StateMachineUpdate.Added -> expect { add: StateMachineUpdate.Added ->
val context = add.stateMachineInfo.context() val context = add.stateMachineInfo.invocationContext
require(context.origin is Origin.Peer && aliceNode.isLegalIdentity(aliceNode.identityFromX500Name((context.origin as Origin.Peer).party))) require(context.origin is InvocationOrigin.Peer && aliceNode.isLegalIdentity(aliceNode.identityFromX500Name((context.origin as InvocationOrigin.Peer).party)))
} }
) )
} }

View File

@ -162,11 +162,11 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
}, },
expect { update: StateMachineUpdate.Added -> expect { update: StateMachineUpdate.Added ->
checkRpcNotification(update.stateMachineInfo, rpcUser.username, historicalIds, externalTrace, impersonatedActor) checkRpcNotification(update.stateMachineInfo, rpcUser.username, historicalIds, externalTrace, impersonatedActor)
sessionId = update.stateMachineInfo.context().trace.sessionId sessionId = update.stateMachineInfo.invocationContext.trace.sessionId
}, },
expect { update: StateMachineUpdate.Added -> expect { update: StateMachineUpdate.Added ->
checkRpcNotification(update.stateMachineInfo, rpcUser.username, historicalIds, externalTrace, impersonatedActor) checkRpcNotification(update.stateMachineInfo, rpcUser.username, historicalIds, externalTrace, impersonatedActor)
assertThat(update.stateMachineInfo.context().trace.sessionId).isEqualTo(sessionId) assertThat(update.stateMachineInfo.invocationContext.trace.sessionId).isEqualTo(sessionId)
} }
) )
} }
@ -174,15 +174,13 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
} }
private fun checkShellNotification(info: StateMachineInfo) { private fun checkShellNotification(info: StateMachineInfo) {
val context = info.invocationContext
val context = info.context() assertThat(context.origin).isInstanceOf(InvocationOrigin.Shell::class.java)
assertThat(context.origin).isInstanceOf(Origin.Shell::class.java)
} }
private fun checkRpcNotification(info: StateMachineInfo, rpcUsername: String, historicalIds: MutableSet<Trace.InvocationId>, externalTrace: Trace?, impersonatedActor: Actor?) { private fun checkRpcNotification(info: StateMachineInfo, rpcUsername: String, historicalIds: MutableSet<Trace.InvocationId>, externalTrace: Trace?, impersonatedActor: Actor?) {
val context = info.invocationContext
val context = info.context() assertThat(context.origin).isInstanceOf(InvocationOrigin.RPC::class.java)
assertThat(context.origin).isInstanceOf(Origin.RPC::class.java)
assertThat(context.externalTrace).isEqualTo(externalTrace) assertThat(context.externalTrace).isEqualTo(externalTrace)
assertThat(context.impersonatedActor).isEqualTo(impersonatedActor) assertThat(context.impersonatedActor).isEqualTo(impersonatedActor)
assertThat(context.actor?.id?.value).isEqualTo(rpcUsername) assertThat(context.actor?.id?.value).isEqualTo(rpcUsername)

View File

@ -2,22 +2,22 @@ package net.corda.client.rpc.internal
import com.esotericsoftware.kryo.pool.KryoPool import com.esotericsoftware.kryo.pool.KryoPool
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal.SerializationEnvironment
import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal.SerializationEnvironmentImpl
import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.core.utilities.ByteSequence
import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT
import net.corda.nodeapi.internal.serialization.KRYO_RPC_CLIENT_CONTEXT import net.corda.nodeapi.internal.serialization.KRYO_RPC_CLIENT_CONTEXT
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme
import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
import net.corda.nodeapi.internal.serialization.kryo.DefaultKryoCustomizer import net.corda.nodeapi.internal.serialization.kryo.DefaultKryoCustomizer
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 import net.corda.nodeapi.internal.serialization.kryo.kryoMagic
import net.corda.nodeapi.internal.serialization.kryo.RPCKryo import net.corda.nodeapi.internal.serialization.kryo.RPCKryo
class KryoClientSerializationScheme : AbstractKryoSerializationScheme() { class KryoClientSerializationScheme : AbstractKryoSerializationScheme() {
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
return byteSequence == KryoHeaderV0_1 && (target == SerializationContext.UseCase.RPCClient || target == SerializationContext.UseCase.P2P) return magic == kryoMagic && (target == SerializationContext.UseCase.RPCClient || target == SerializationContext.UseCase.P2P)
} }
override fun rpcClientKryoPool(context: SerializationContext): KryoPool { override fun rpcClientKryoPool(context: SerializationContext): KryoPool {

View File

@ -58,12 +58,12 @@ class IdentitySyncFlowTests {
val anonymous = true val anonymous = true
val ref = OpaqueBytes.of(0x01) val ref = OpaqueBytes.of(0x01)
val issueFlow = aliceNode.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, alice, anonymous, notary)) val issueFlow = aliceNode.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, alice, anonymous, notary))
val issueTx = issueFlow.resultFuture.getOrThrow().stx val issueTx = issueFlow.getOrThrow().stx
val confidentialIdentity = issueTx.tx.outputs.map { it.data }.filterIsInstance<Cash.State>().single().owner val confidentialIdentity = issueTx.tx.outputs.map { it.data }.filterIsInstance<Cash.State>().single().owner
assertNull(bobNode.database.transaction { bobNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) }) assertNull(bobNode.database.transaction { bobNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) })
// Run the flow to sync up the identities // Run the flow to sync up the identities
aliceNode.services.startFlow(Initiator(bob, issueTx.tx)).resultFuture.getOrThrow() aliceNode.services.startFlow(Initiator(bob, issueTx.tx)).getOrThrow()
val expected = aliceNode.database.transaction { val expected = aliceNode.database.transaction {
aliceNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) aliceNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity)
} }
@ -88,7 +88,7 @@ class IdentitySyncFlowTests {
val anonymous = true val anonymous = true
val ref = OpaqueBytes.of(0x01) val ref = OpaqueBytes.of(0x01)
val issueFlow = charlieNode.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, charlie, anonymous, notary)) val issueFlow = charlieNode.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, charlie, anonymous, notary))
val issueTx = issueFlow.resultFuture.getOrThrow().stx val issueTx = issueFlow.getOrThrow().stx
val confidentialIdentity = issueTx.tx.outputs.map { it.data }.filterIsInstance<Cash.State>().single().owner val confidentialIdentity = issueTx.tx.outputs.map { it.data }.filterIsInstance<Cash.State>().single().owner
val confidentialIdentCert = charlieNode.services.identityService.certificateFromKey(confidentialIdentity.owningKey)!! val confidentialIdentCert = charlieNode.services.identityService.certificateFromKey(confidentialIdentity.owningKey)!!
@ -97,11 +97,11 @@ class IdentitySyncFlowTests {
assertNotNull(aliceNode.database.transaction { aliceNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) }) assertNotNull(aliceNode.database.transaction { aliceNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) })
// Generate a payment from Charlie to Alice, including the confidential state // Generate a payment from Charlie to Alice, including the confidential state
val payTx = charlieNode.services.startFlow(CashPaymentFlow(1000.DOLLARS, alice, anonymous)).resultFuture.getOrThrow().stx val payTx = charlieNode.services.startFlow(CashPaymentFlow(1000.DOLLARS, alice, anonymous)).getOrThrow().stx
// Run the flow to sync up the identities, and confirm Charlie's confidential identity doesn't leak // Run the flow to sync up the identities, and confirm Charlie's confidential identity doesn't leak
assertNull(bobNode.database.transaction { bobNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) }) assertNull(bobNode.database.transaction { bobNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) })
aliceNode.services.startFlow(Initiator(bob, payTx.tx)).resultFuture.getOrThrow() aliceNode.services.startFlow(Initiator(bob, payTx.tx)).getOrThrow()
assertNull(bobNode.database.transaction { bobNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) }) assertNull(bobNode.database.transaction { bobNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) })
} }

View File

@ -30,7 +30,7 @@ class SwapIdentitiesFlowTests {
val requesterFlow = aliceNode.services.startFlow(SwapIdentitiesFlow(bob)) val requesterFlow = aliceNode.services.startFlow(SwapIdentitiesFlow(bob))
// Get the results // Get the results
val actual: Map<Party, AnonymousParty> = requesterFlow.resultFuture.getOrThrow().toMap() val actual: Map<Party, AnonymousParty> = requesterFlow.getOrThrow().toMap()
assertEquals(2, actual.size) assertEquals(2, actual.size)
// Verify that the generated anonymous identities do not match the well known identities // Verify that the generated anonymous identities do not match the well known identities
val aliceAnonymousIdentity = actual[alice] ?: throw IllegalStateException() val aliceAnonymousIdentity = actual[alice] ?: throw IllegalStateException()

View File

@ -1,8 +1,9 @@
gradlePluginsVersion=3.0.5 gradlePluginsVersion=3.0.5
kotlinVersion=1.1.60 kotlinVersion=1.2.20
platformVersion=3 platformVersion=2
guavaVersion=21.0 guavaVersion=21.0
bouncycastleVersion=1.57 bouncycastleVersion=1.57
typesafeConfigVersion=1.3.1 typesafeConfigVersion=1.3.1
jsr305Version=3.0.2 jsr305Version=3.0.2
artifactoryPluginVersion=4.4.18 artifactoryPluginVersion=4.4.18
snakeYamlVersion=1.19

View File

@ -107,7 +107,7 @@ dependencies {
// Apache JEXL: An embeddable expression evaluation library. // Apache JEXL: An embeddable expression evaluation library.
// This may be temporary until we experiment with other ways to do on-the-fly contract specialisation via an API. // This may be temporary until we experiment with other ways to do on-the-fly contract specialisation via an API.
compile "org.apache.commons:commons-jexl3:3.0" compile "org.apache.commons:commons-jexl3:3.0"
compile 'commons-lang:commons-lang:2.6'
// For JSON // For JSON
compile "com.fasterxml.jackson.core:jackson-databind:${jackson_version}" compile "com.fasterxml.jackson.core:jackson-databind:${jackson_version}"

View File

@ -0,0 +1,11 @@
package net.corda.core
/**
* These methods are not part of Corda's API compatibility guarantee and applications should not use them.
*
* These fields are only meant to be used by Corda internally, and are not intended to be part of the public API.
*/
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@MustBeDocumented
annotation class CordaInternal

View File

@ -1,9 +0,0 @@
package net.corda.core.context
import net.corda.core.serialization.CordaSerializable
/**
* Authentication / Authorisation Service ID.
*/
@CordaSerializable
data class AuthServiceId(val value: String)

View File

@ -9,52 +9,50 @@ import java.security.Principal
* Models the information needed to trace an invocation in Corda. * Models the information needed to trace an invocation in Corda.
* Includes initiating actor, origin, trace information, and optional external trace information to correlate clients' IDs. * Includes initiating actor, origin, trace information, and optional external trace information to correlate clients' IDs.
* *
* @param origin origin of the invocation. * @property origin Origin of the invocation.
* @param trace Corda invocation trace. * @property trace Corda invocation trace.
* @param actor acting agent of the invocation, used to derive the security principal. * @property actor Acting agent of the invocation, used to derive the security principal.
* @param externalTrace optional external invocation trace for cross-system logs correlation. * @property externalTrace Optional external invocation trace for cross-system logs correlation.
* @param impersonatedActor optional impersonated actor, used for logging but not for authorisation. * @property impersonatedActor Optional impersonated actor, used for logging but not for authorisation.
*/ */
@CordaSerializable @CordaSerializable
data class InvocationContext(val origin: Origin, val trace: Trace, val actor: Actor?, val externalTrace: Trace? = null, val impersonatedActor: Actor? = null) { data class InvocationContext(val origin: InvocationOrigin, val trace: Trace, val actor: Actor?, val externalTrace: Trace? = null, val impersonatedActor: Actor? = null) {
companion object { companion object {
/** /**
* Creates an [InvocationContext] with a [Trace] that defaults to a [java.util.UUID] as value and [java.time.Instant.now] timestamp. * Creates an [InvocationContext] with a [Trace] that defaults to a [java.util.UUID] as value and [java.time.Instant.now] timestamp.
*/ */
@JvmStatic @JvmStatic
fun newInstance(origin: Origin, trace: Trace = Trace.newInstance(), actor: Actor? = null, externalTrace: Trace? = null, impersonatedActor: Actor? = null) = InvocationContext(origin, trace, actor, externalTrace, impersonatedActor) fun newInstance(origin: InvocationOrigin, trace: Trace = Trace.newInstance(), actor: Actor? = null, externalTrace: Trace? = null, impersonatedActor: Actor? = null) = InvocationContext(origin, trace, actor, externalTrace, impersonatedActor)
/** /**
* Creates an [InvocationContext] with [Origin.RPC] origin. * Creates an [InvocationContext] with [InvocationOrigin.RPC] origin.
*/ */
@JvmStatic @JvmStatic
fun rpc(actor: Actor, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null, impersonatedActor: Actor? = null): InvocationContext = newInstance(Origin.RPC(actor), trace, actor, externalTrace, impersonatedActor) fun rpc(actor: Actor, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null, impersonatedActor: Actor? = null): InvocationContext = newInstance(InvocationOrigin.RPC(actor), trace, actor, externalTrace, impersonatedActor)
/** /**
* Creates an [InvocationContext] with [Origin.Peer] origin. * Creates an [InvocationContext] with [InvocationOrigin.Peer] origin.
*/ */
@JvmStatic @JvmStatic
fun peer(party: CordaX500Name, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null, impersonatedActor: Actor? = null): InvocationContext = newInstance(Origin.Peer(party), trace, null, externalTrace, impersonatedActor) fun peer(party: CordaX500Name, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null, impersonatedActor: Actor? = null): InvocationContext = newInstance(InvocationOrigin.Peer(party), trace, null, externalTrace, impersonatedActor)
/** /**
* Creates an [InvocationContext] with [Origin.Service] origin. * Creates an [InvocationContext] with [InvocationOrigin.Service] origin.
*/ */
@JvmStatic @JvmStatic
fun service(serviceClassName: String, owningLegalIdentity: CordaX500Name, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null): InvocationContext = newInstance(Origin.Service(serviceClassName, owningLegalIdentity), trace, null, externalTrace) fun service(serviceClassName: String, owningLegalIdentity: CordaX500Name, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null): InvocationContext = newInstance(InvocationOrigin.Service(serviceClassName, owningLegalIdentity), trace, null, externalTrace)
/** /**
* Creates an [InvocationContext] with [Origin.Scheduled] origin. * Creates an [InvocationContext] with [InvocationOrigin.Scheduled] origin.
*/ */
@JvmStatic @JvmStatic
fun scheduled(scheduledState: ScheduledStateRef, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null): InvocationContext = newInstance(Origin.Scheduled(scheduledState), trace, null, externalTrace) fun scheduled(scheduledState: ScheduledStateRef, trace: Trace = Trace.newInstance(), externalTrace: Trace? = null): InvocationContext = newInstance(InvocationOrigin.Scheduled(scheduledState), trace, null, externalTrace)
/** /**
* Creates an [InvocationContext] with [Origin.Shell] origin. * Creates an [InvocationContext] with [InvocationOrigin.Shell] origin.
*/ */
@JvmStatic @JvmStatic
fun shell(trace: Trace = Trace.newInstance(), externalTrace: Trace? = null): InvocationContext = InvocationContext(Origin.Shell, trace, null, externalTrace) fun shell(trace: Trace = Trace.newInstance(), externalTrace: Trace? = null): InvocationContext = InvocationContext(InvocationOrigin.Shell, trace, null, externalTrace)
} }
/** /**
@ -83,11 +81,10 @@ data class Actor(val id: Id, val serviceId: AuthServiceId, val owningLegalIdenti
} }
/** /**
* Invocation origin for tracing purposes. * Represents the source of an action such as a flow start, an RPC, a shell command etc.
*/ */
@CordaSerializable @CordaSerializable
sealed class Origin { sealed class InvocationOrigin {
/** /**
* Returns the [Principal] for a given [Actor]. * Returns the [Principal] for a given [Actor].
*/ */
@ -96,32 +93,28 @@ sealed class Origin {
/** /**
* Origin was an RPC call. * Origin was an RPC call.
*/ */
data class RPC(private val actor: Actor) : Origin() { data class RPC(private val actor: Actor) : InvocationOrigin() {
override fun principal() = Principal { actor.id.value } override fun principal() = Principal { actor.id.value }
} }
/** /**
* Origin was a message sent by a [Peer]. * Origin was a message sent by a [Peer].
*/ */
data class Peer(val party: CordaX500Name) : Origin() { data class Peer(val party: CordaX500Name) : InvocationOrigin() {
override fun principal() = Principal { party.toString() } override fun principal() = Principal { party.toString() }
} }
/** /**
* Origin was a Corda Service. * Origin was a Corda Service.
*/ */
data class Service(val serviceClassName: String, val owningLegalIdentity: CordaX500Name) : Origin() { data class Service(val serviceClassName: String, val owningLegalIdentity: CordaX500Name) : InvocationOrigin() {
override fun principal() = Principal { serviceClassName } override fun principal() = Principal { serviceClassName }
} }
/** /**
* Origin was a scheduled activity. * Origin was a scheduled activity.
*/ */
data class Scheduled(val scheduledState: ScheduledStateRef) : Origin() { data class Scheduled(val scheduledState: ScheduledStateRef) : InvocationOrigin() {
override fun principal() = Principal { "Scheduler" } override fun principal() = Principal { "Scheduler" }
} }
@ -129,8 +122,13 @@ sealed class Origin {
/** /**
* Origin was the Shell. * Origin was the Shell.
*/ */
object Shell : Origin() { object Shell : InvocationOrigin() {
override fun principal() = Principal { "Shell User" } override fun principal() = Principal { "Shell User" }
} }
} }
/**
* Authentication / Authorisation Service ID.
*/
@CordaSerializable
data class AuthServiceId(val value: String)

View File

@ -50,4 +50,9 @@ interface Attachment : NamedByHash {
* Can be empty, for example non-contract attachments won't be necessarily be signed. * Can be empty, for example non-contract attachments won't be necessarily be signed.
*/ */
val signers: List<Party> val signers: List<Party>
/**
* Attachment size in bytes.
*/
val size: Int
} }

View File

@ -1,7 +1,6 @@
package net.corda.core.contracts package net.corda.core.contracts
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
// DOCSTART 1 // DOCSTART 1

View File

@ -46,7 +46,7 @@ interface NamedByHash {
@CordaSerializable @CordaSerializable
data class Issued<out P : Any>(val issuer: PartyAndReference, val product: P) { data class Issued<out P : Any>(val issuer: PartyAndReference, val product: P) {
init { init {
require(issuer.reference.bytes.size <= MAX_ISSUER_REF_SIZE) { "Maximum issuer reference size is $MAX_ISSUER_REF_SIZE." } require(issuer.reference.size <= MAX_ISSUER_REF_SIZE) { "Maximum issuer reference size is $MAX_ISSUER_REF_SIZE." }
} }
override fun toString() = "$product issued by $issuer" override fun toString() = "$product issued by $issuer"
} }

View File

@ -1,6 +1,5 @@
package net.corda.core.crypto package net.corda.core.crypto
import net.corda.core.crypto.CompositeKey.NodeAndWeight
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.exactAdd import net.corda.core.utilities.exactAdd
import net.corda.core.utilities.sequence import net.corda.core.utilities.sequence
@ -12,7 +11,7 @@ import java.util.*
/** /**
* A tree data structure that enables the representation of composite public keys, which are used to represent * A tree data structure that enables the representation of composite public keys, which are used to represent
* the signing requirements for multisignature scenarios such as RAFT notary services. A composite key is a list * the signing requirements for multi-signature scenarios such as RAFT notary services. A composite key is a list
* of leaf keys and their contributing weight, and each leaf can be a conventional single key or a composite key. * of leaf keys and their contributing weight, and each leaf can be a conventional single key or a composite key.
* Keys contribute their weight to the total if they are matched by the signature. * Keys contribute their weight to the total if they are matched by the signature.
* *
@ -53,9 +52,19 @@ class CompositeKey private constructor(val threshold: Int, children: List<NodeAn
} }
return builder.build(threshold) return builder.build(threshold)
} }
// Required for sorting [children] list. To ensure a deterministic way of adding children required for equality
// checking, [children] list is sorted during construction. A DESC ordering in the [NodeAndWeight.weight] field
// will improve efficiency, because keys with bigger "weights" are the first to be checked and thus the
// threshold requirement might be met earlier without requiring a full [children] scan.
// TODO: node.encoded.sequence() might be expensive, consider a faster deterministic compareTo implementation
// for public keys in general.
private val descWeightComparator = compareBy<NodeAndWeight>({ -it.weight }, { it.node.encoded.sequence() })
} }
val children: List<NodeAndWeight> = children.sorted() /**
* Τhe order of the children may not be the same to what was provided in the builder.
*/
val children: List<NodeAndWeight> = children.sortedWith(descWeightComparator)
init { init {
// TODO: replace with the more extensive, but slower, checkValidity() test. // TODO: replace with the more extensive, but slower, checkValidity() test.
@ -103,9 +112,9 @@ class CompositeKey private constructor(val threshold: Int, children: List<NodeAn
* requirements are met, while it tests for aggregated-weight integer overflow. * requirements are met, while it tests for aggregated-weight integer overflow.
* In practice, this method should be always invoked on the root [CompositeKey], as it inherently * In practice, this method should be always invoked on the root [CompositeKey], as it inherently
* validates the child nodes (all the way till the leaves). * validates the child nodes (all the way till the leaves).
* TODO: Always call this method when deserialising [CompositeKey]s.
*/ */
fun checkValidity() { fun checkValidity() {
if (validated) return
val visitedMap = IdentityHashMap<CompositeKey, Boolean>() val visitedMap = IdentityHashMap<CompositeKey, Boolean>()
visitedMap.put(this, true) visitedMap.put(this, true)
cycleDetection(visitedMap) // Graph cycle testing on the root node. cycleDetection(visitedMap) // Graph cycle testing on the root node.
@ -143,6 +152,7 @@ class CompositeKey private constructor(val threshold: Int, children: List<NodeAn
override fun compareTo(other: NodeAndWeight): Int { override fun compareTo(other: NodeAndWeight): Int {
return if (weight == other.weight) return if (weight == other.weight)
// TODO: this might be expensive, consider a faster deterministic compareTo implementation when weights are equal.
node.encoded.sequence().compareTo(other.node.encoded.sequence()) node.encoded.sequence().compareTo(other.node.encoded.sequence())
else else
weight.compareTo(other.weight) weight.compareTo(other.weight)
@ -180,17 +190,18 @@ class CompositeKey private constructor(val threshold: Int, children: List<NodeAn
override fun getFormat() = ASN1Encoding.DER override fun getFormat() = ASN1Encoding.DER
// Extracted method from isFulfilledBy. // Return true when and if the threshold requirement is met.
private fun checkFulfilledBy(keysToCheck: Iterable<PublicKey>): Boolean { private fun checkFulfilledBy(keysToCheck: Iterable<PublicKey>): Boolean {
if (keysToCheck.any { it is CompositeKey }) return false var totalWeight = 0
val totalWeight = children.map { (node, weight) -> children.forEach { (node, weight) ->
if (node is CompositeKey) { if (node is CompositeKey) {
if (node.checkFulfilledBy(keysToCheck)) weight else 0 if (node.checkFulfilledBy(keysToCheck)) totalWeight += weight
} else { } else {
if (keysToCheck.contains(node)) weight else 0 if (node in keysToCheck) totalWeight += weight
} }
}.sum() if (totalWeight >= threshold) return true
return totalWeight >= threshold }
return false
} }
/** /**
@ -201,8 +212,8 @@ class CompositeKey private constructor(val threshold: Int, children: List<NodeAn
fun isFulfilledBy(keysToCheck: Iterable<PublicKey>): Boolean { fun isFulfilledBy(keysToCheck: Iterable<PublicKey>): Boolean {
// We validate keys only when checking if they're matched, as this checks subkeys as a result. // We validate keys only when checking if they're matched, as this checks subkeys as a result.
// Doing these checks at deserialization/construction time would result in duplicate checks. // Doing these checks at deserialization/construction time would result in duplicate checks.
if (!validated) checkValidity()
checkValidity() // TODO: remove when checkValidity() will be eventually invoked during/after deserialization. if (keysToCheck.any { it is CompositeKey }) return false
return checkFulfilledBy(keysToCheck) return checkFulfilledBy(keysToCheck)
} }

View File

@ -55,7 +55,7 @@ import javax.crypto.spec.SecretKeySpec
* However, only the schemes returned by {@link #listSupportedSignatureSchemes()} are supported. * However, only the schemes returned by {@link #listSupportedSignatureSchemes()} are supported.
* Note that Corda currently supports the following signature schemes by their code names: * Note that Corda currently supports the following signature schemes by their code names:
* <p><ul> * <p><ul>
* <li>RSA_SHA256 (RSA using SHA256 as hash algorithm and MGF1 (with SHA256) as mask generation function). * <li>RSA_SHA256 (RSA PKCS#1 using SHA256 as hash algorithm).
* <li>ECDSA_SECP256K1_SHA256 (ECDSA using the secp256k1 Koblitz curve and SHA256 as hash algorithm). * <li>ECDSA_SECP256K1_SHA256 (ECDSA using the secp256k1 Koblitz curve and SHA256 as hash algorithm).
* <li>ECDSA_SECP256R1_SHA256 (ECDSA using the secp256r1 (NIST P-256) curve and SHA256 as hash algorithm). * <li>ECDSA_SECP256R1_SHA256 (ECDSA using the secp256r1 (NIST P-256) curve and SHA256 as hash algorithm).
* <li>EDDSA_ED25519_SHA512 (EdDSA using the ed255519 twisted Edwards curve and SHA512 as hash algorithm). * <li>EDDSA_ED25519_SHA512 (EdDSA using the ed255519 twisted Edwards curve and SHA512 as hash algorithm).
@ -64,7 +64,8 @@ import javax.crypto.spec.SecretKeySpec
*/ */
object Crypto { object Crypto {
/** /**
* RSA signature scheme using SHA256 for message hashing. * RSA PKCS#1 signature scheme using SHA256 for message hashing.
* The actual algorithm id is 1.2.840.113549.1.1.1
* Note: Recommended key size >= 3072 bits. * Note: Recommended key size >= 3072 bits.
*/ */
@JvmField @JvmField
@ -75,7 +76,7 @@ object Crypto {
listOf(AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, null)), listOf(AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, null)),
BouncyCastleProvider.PROVIDER_NAME, BouncyCastleProvider.PROVIDER_NAME,
"RSA", "RSA",
"SHA256WITHRSAEncryption", "SHA256WITHRSA",
null, null,
3072, 3072,
"RSA_SHA256 signature scheme using SHA256 as hash algorithm." "RSA_SHA256 signature scheme using SHA256 as hash algorithm."
@ -547,7 +548,7 @@ object Crypto {
/** /**
* Utility to simplify the act of verifying a [TransactionSignature]. * Utility to simplify the act of verifying a [TransactionSignature].
* It returns true if it succeeds, but it always throws an exception if verification fails. * It returns true if it succeeds, but it always throws an exception if verification fails.
* @param txId transaction's id (Merkle root). * @param txId transaction's id.
* @param transactionSignature the signature on the transaction. * @param transactionSignature the signature on the transaction.
* @return true if verification passes or throw exception if verification fails. * @return true if verification passes or throw exception if verification fails.
* @throws InvalidKeyException if the key is invalid. * @throws InvalidKeyException if the key is invalid.
@ -559,7 +560,7 @@ object Crypto {
@JvmStatic @JvmStatic
@Throws(InvalidKeyException::class, SignatureException::class) @Throws(InvalidKeyException::class, SignatureException::class)
fun doVerify(txId: SecureHash, transactionSignature: TransactionSignature): Boolean { fun doVerify(txId: SecureHash, transactionSignature: TransactionSignature): Boolean {
val signableData = SignableData(txId, transactionSignature.signatureMetadata) val signableData = SignableData(originalSignedHash(txId, transactionSignature.partialMerkleTree), transactionSignature.signatureMetadata)
return Crypto.doVerify(transactionSignature.by, transactionSignature.bytes, signableData.serialize().bytes) return Crypto.doVerify(transactionSignature.by, transactionSignature.bytes, signableData.serialize().bytes)
} }
@ -569,7 +570,7 @@ object Crypto {
* It returns true if it succeeds and false if not. In comparison to [doVerify] if the key and signature * It returns true if it succeeds and false if not. In comparison to [doVerify] if the key and signature
* do not match it returns false rather than throwing an exception. Normally you should use the function which throws, * do not match it returns false rather than throwing an exception. Normally you should use the function which throws,
* as it avoids the risk of failing to test the result. * as it avoids the risk of failing to test the result.
* @param txId transaction's id (Merkle root). * @param txId transaction's id.
* @param transactionSignature the signature on the transaction. * @param transactionSignature the signature on the transaction.
* @throws SignatureException if this signatureData object is not initialized properly, * @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type, * the passed-in signatureData is improperly encoded or of the wrong type,
@ -578,7 +579,7 @@ object Crypto {
@JvmStatic @JvmStatic
@Throws(SignatureException::class) @Throws(SignatureException::class)
fun isValid(txId: SecureHash, transactionSignature: TransactionSignature): Boolean { fun isValid(txId: SecureHash, transactionSignature: TransactionSignature): Boolean {
val signableData = SignableData(txId, transactionSignature.signatureMetadata) val signableData = SignableData(originalSignedHash(txId, transactionSignature.partialMerkleTree), transactionSignature.signatureMetadata)
return isValid( return isValid(
findSignatureScheme(transactionSignature.by), findSignatureScheme(transactionSignature.by),
transactionSignature.by, transactionSignature.by,
@ -1011,4 +1012,21 @@ object Crypto {
else -> decodePrivateKey(key.encoded) else -> decodePrivateKey(key.encoded)
} }
} }
/**
* Get the hash value that is actually signed.
* The txId is returned when [partialMerkleTree] is null,
* else the root of the tree is computed and returned.
* Note that the hash of the txId should be a leaf in the tree, not the txId itself.
*/
private fun originalSignedHash(txId: SecureHash, partialMerkleTree: PartialMerkleTree?): SecureHash {
return if (partialMerkleTree != null) {
val usedHashes = mutableListOf<SecureHash>()
val root = PartialMerkleTree.rootAndUsedHashes(partialMerkleTree.root, usedHashes)
require(txId.sha256() in usedHashes) { "Transaction with id:$txId is not a leaf in the provided partial Merkle tree" }
root
} else {
txId
}
}
} }

View File

@ -140,23 +140,24 @@ class PartialMerkleTree(val root: PartialTree) {
is PartialTree.Node -> { is PartialTree.Node -> {
val leftHash = rootAndUsedHashes(node.left, usedHashes) val leftHash = rootAndUsedHashes(node.left, usedHashes)
val rightHash = rootAndUsedHashes(node.right, usedHashes) val rightHash = rootAndUsedHashes(node.right, usedHashes)
return leftHash.hashConcat(rightHash) leftHash.hashConcat(rightHash)
} }
} }
} }
} }
/** /**
* Function to verify a [PartialMerkleTree] against an input Merkle root and a list of leaves.
* The tree should only contain the leaves defined in [hashesToCheck].
* @param merkleRootHash Hash that should be checked for equality with root calculated from this partial tree. * @param merkleRootHash Hash that should be checked for equality with root calculated from this partial tree.
* @param hashesToCheck List of included leaves hashes that should be found in this partial tree. * @param hashesToCheck List of included leaves hashes that should be found in this partial tree.
*/ */
fun verify(merkleRootHash: SecureHash, hashesToCheck: List<SecureHash>): Boolean { fun verify(merkleRootHash: SecureHash, hashesToCheck: List<SecureHash>): Boolean {
val usedHashes = ArrayList<SecureHash>() val usedHashes = ArrayList<SecureHash>()
val verifyRoot = rootAndUsedHashes(root, usedHashes) val verifyRoot = rootAndUsedHashes(root, usedHashes)
// It means that we obtained more/fewer hashes than needed or different sets of hashes. return verifyRoot == merkleRootHash // Tree roots match.
if (hashesToCheck.groupBy { it } != usedHashes.groupBy { it }) && hashesToCheck.size == usedHashes.size // Obtained the same number of hashes (leaves).
return false && hashesToCheck.toSet().containsAll(usedHashes) // Lists contain the same elements.
return (verifyRoot == merkleRootHash)
} }
/** /**

View File

@ -5,8 +5,10 @@ import net.corda.core.serialization.CordaSerializable
/** /**
* A [SignableData] object is the packet actually signed. * A [SignableData] object is the packet actually signed.
* It works as a wrapper over transaction id and signature metadata. * It works as a wrapper over transaction id and signature metadata.
* Note that when multi-transaction signing (signing a block of transactions) is used, the root of the Merkle tree
* (having transaction IDs as leaves) is actually signed and thus [txId] refers to this root and not a specific transaction.
* *
* @param txId transaction's id. * @param txId transaction's id or root of multi-transaction Merkle tree in case of multi-transaction signing.
* @param signatureMetadata meta data required. * @param signatureMetadata meta data required.
*/ */
@CordaSerializable @CordaSerializable

View File

@ -8,13 +8,24 @@ import java.util.*
/** /**
* A wrapper over the signature output accompanied by signer's public key and signature metadata. * A wrapper over the signature output accompanied by signer's public key and signature metadata.
* This is similar to [DigitalSignature.WithKey], but targeted to DLT transaction signatures. * This is similar to [DigitalSignature.WithKey], but targeted to DLT transaction (or block of transactions) signatures.
* @property bytes actual bytes of the cryptographic signature.
* @property by [PublicKey] of the signer.
* @property signatureMetadata attached [SignatureMetadata] for this signature.
* @property partialMerkleTree required when multi-transaction signing is utilised.
*/ */
@CordaSerializable @CordaSerializable
class TransactionSignature(bytes: ByteArray, val by: PublicKey, val signatureMetadata: SignatureMetadata) : DigitalSignature(bytes) { class TransactionSignature(bytes: ByteArray, val by: PublicKey, val signatureMetadata: SignatureMetadata, val partialMerkleTree: PartialMerkleTree?) : DigitalSignature(bytes) {
/**
* Construct a [TransactionSignature] with [partialMerkleTree] set to null.
* This is the recommended constructor when signing over a single transaction.
* */
constructor(bytes: ByteArray, by: PublicKey, signatureMetadata: SignatureMetadata) : this(bytes, by, signatureMetadata, null)
/** /**
* Function to verify a [SignableData] object's signature. * Function to verify a [SignableData] object's signature.
* Note that [SignableData] contains the id of the transaction and extra metadata, such as DLT's platform version. * Note that [SignableData] contains the id of the transaction and extra metadata, such as DLT's platform version.
* A non-null [partialMerkleTree] implies multi-transaction signing and the signature is over the root of this tree.
* *
* @param txId transaction's id (Merkle root), which along with [signatureMetadata] will be used to construct the [SignableData] object to be signed. * @param txId transaction's id (Merkle root), which along with [signatureMetadata] will be used to construct the [SignableData] object to be signed.
* @throws InvalidKeyException if the key is invalid. * @throws InvalidKeyException if the key is invalid.

View File

@ -1,45 +1,69 @@
package net.corda.core.flows package net.corda.core.flows
import net.corda.core.context.Actor
import net.corda.core.context.AuthServiceId
import net.corda.core.context.InvocationContext
import net.corda.core.context.InvocationOrigin
import net.corda.core.contracts.ScheduledStateRef import net.corda.core.contracts.ScheduledStateRef
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import java.security.Principal import java.security.Principal
/** /**
* FlowInitiator holds information on who started the flow. We have different ways of doing that: via RPC [FlowInitiator.RPC], * Please note that [FlowInitiator] has been superceded by [net.corda.core.context.InvocationContext], which offers
* communication started by peer node [FlowInitiator.Peer], scheduled flows [FlowInitiator.Scheduled] * more detail for the same event.
* or via the Corda Shell [FlowInitiator.Shell]. *
* FlowInitiator holds information on who started the flow. We have different ways of doing that: via [FlowInitiator.RPC],
* communication started by peer nodes ([FlowInitiator.Peer]), scheduled flows ([FlowInitiator.Scheduled])
* or via the Corda Shell ([FlowInitiator.Shell]).
*/ */
@Deprecated("Do not use these types. Future releases might remove them.")
@CordaSerializable @CordaSerializable
sealed class FlowInitiator : Principal { sealed class FlowInitiator : Principal {
/** Started using [net.corda.core.messaging.CordaRPCOps.startFlowDynamic]. */ /** Started using [net.corda.core.messaging.CordaRPCOps.startFlowDynamic]. */
@Deprecated("Do not use this type. Future releases might remove it.")
data class RPC(val username: String) : FlowInitiator() { data class RPC(val username: String) : FlowInitiator() {
override fun getName(): String = username override fun getName(): String = username
} }
/** Started when we get new session initiation request. */ /** Started when we get new session initiation request. */
@Deprecated("Do not use this type. Future releases might remove it.")
data class Peer(val party: Party) : FlowInitiator() { data class Peer(val party: Party) : FlowInitiator() {
override fun getName(): String = party.name.toString() override fun getName(): String = party.name.toString()
} }
/** Started by a CordaService. */ /** Started by a CordaService. */
@Deprecated("Do not use this type. Future releases might remove it.")
data class Service(val serviceClassName: String) : FlowInitiator() { data class Service(val serviceClassName: String) : FlowInitiator() {
override fun getName(): String = serviceClassName override fun getName(): String = serviceClassName
} }
/** Started as scheduled activity. */ /** Started as scheduled activity. */
@Deprecated("Do not use this type. Future releases might remove it.")
data class Scheduled(val scheduledState: ScheduledStateRef) : FlowInitiator() { data class Scheduled(val scheduledState: ScheduledStateRef) : FlowInitiator() {
override fun getName(): String = "Scheduler" override fun getName(): String = "Scheduler"
} }
// TODO When proper ssh access enabled, add username/use RPC? // TODO When proper ssh access enabled, add username/use RPC?
@Deprecated("Do not use this type. Future releases might remove it.")
object Shell : FlowInitiator() { object Shell : FlowInitiator() {
override fun getName(): String = "Shell User" override fun getName(): String = "Shell User"
} }
/**
* Returns an [InvocationContext], which is equivalent to this object but expressed using the successor to this
* class hierarchy (which is now deprecated). The returned object has less information than it could have, so
* prefer to use fetch an invocation context directly if you can (e.g. in [net.corda.core.messaging.StateMachineInfo])
*/
val invocationContext: InvocationContext get() {
val unknownName = CordaX500Name("UNKNOWN", "UNKNOWN", "GB")
var actor: Actor? = null
val origin: InvocationOrigin
when (this) {
is FlowInitiator.RPC -> {
actor = Actor(Actor.Id(this.username), AuthServiceId("UNKNOWN"), unknownName)
origin = InvocationOrigin.RPC(actor)
}
is FlowInitiator.Peer -> origin = InvocationOrigin.Peer(this.party.name)
is FlowInitiator.Service -> origin = InvocationOrigin.Service(this.serviceClassName, unknownName)
FlowInitiator.Shell -> origin = InvocationOrigin.Shell
is FlowInitiator.Scheduled -> origin = InvocationOrigin.Scheduled(this.scheduledState)
}
return InvocationContext.newInstance(origin = origin, actor = actor)
}
} }

View File

@ -2,6 +2,7 @@ package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import co.paralleluniverse.strands.Strand import co.paralleluniverse.strands.Strand
import net.corda.core.CordaInternal
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
@ -19,7 +20,6 @@ import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.* import net.corda.core.utilities.*
import org.slf4j.Logger import org.slf4j.Logger
import java.time.Duration import java.time.Duration
import java.time.Instant
/** /**
* A sub-class of [FlowLogic<T>] implements a flow using direct, straight line blocking code. Thus you * A sub-class of [FlowLogic<T>] implements a flow using direct, straight line blocking code. Thus you
@ -43,7 +43,7 @@ import java.time.Instant
* also has a version property to allow you to version your flow and enables a node to restrict support for the flow to * also has a version property to allow you to version your flow and enables a node to restrict support for the flow to
* that particular version. * that particular version.
* *
* Functions that suspend the flow (including all functions on [FlowSession]) accept a [maySkipCheckpoint] parameter * Functions that suspend the flow (including all functions on [FlowSession]) accept a maySkipCheckpoint parameter
* defaulting to false, false meaning a checkpoint should always be created on suspend. This parameter may be set to * defaulting to false, false meaning a checkpoint should always be created on suspend. This parameter may be set to
* true which allows the implementation to potentially optimise away the checkpoint, saving a roundtrip to the database. * true which allows the implementation to potentially optimise away the checkpoint, saving a roundtrip to the database.
* *
@ -53,6 +53,7 @@ import java.time.Instant
* parameter the flow must be prepared for scenarios where a previous running of the flow *already committed its * parameter the flow must be prepared for scenarios where a previous running of the flow *already committed its
* relevant database transactions*. Only set this option to true if you know what you're doing. * relevant database transactions*. Only set this option to true if you know what you're doing.
*/ */
@Suppress("DEPRECATION", "DeprecatedCallableAddReplaceWith")
abstract class FlowLogic<out T> { abstract class FlowLogic<out T> {
/** This is where you should log things to. */ /** This is where you should log things to. */
val logger: Logger get() = stateMachine.logger val logger: Logger get() = stateMachine.logger
@ -61,14 +62,14 @@ abstract class FlowLogic<out T> {
/** /**
* Return the outermost [FlowLogic] instance, or null if not in a flow. * Return the outermost [FlowLogic] instance, or null if not in a flow.
*/ */
@JvmStatic @Suppress("unused") @JvmStatic
val currentTopLevel: FlowLogic<*>? get() = (Strand.currentStrand() as? FlowStateMachine<*>)?.logic val currentTopLevel: FlowLogic<*>? get() = (Strand.currentStrand() as? FlowStateMachine<*>)?.logic
/** /**
* If on a flow, suspends the flow and only wakes it up after at least [duration] time has passed. Otherwise, * If on a flow, suspends the flow and only wakes it up after at least [duration] time has passed. Otherwise,
* just sleep for [duration]. This sleep function is not designed to aid scheduling, for which you should * just sleep for [duration]. This sleep function is not designed to aid scheduling, for which you should
* consider using [SchedulableState]. It is designed to aid with managing contention for which you have not * consider using [net.corda.core.contracts.SchedulableState]. It is designed to aid with managing contention
* managed via another means. * for which you have not managed via another means.
* *
* Warning: long sleeps and in general long running flows are highly discouraged, as there is currently no * Warning: long sleeps and in general long running flows are highly discouraged, as there is currently no
* support for flow migration! This method will throw an exception if you attempt to sleep for longer than * support for flow migration! This method will throw an exception if you attempt to sleep for longer than
@ -79,7 +80,7 @@ abstract class FlowLogic<out T> {
@JvmOverloads @JvmOverloads
@Throws(FlowException::class) @Throws(FlowException::class)
fun sleep(duration: Duration, maySkipCheckpoint: Boolean = false) { fun sleep(duration: Duration, maySkipCheckpoint: Boolean = false) {
if (duration.compareTo(Duration.ofMinutes(5)) > 0) { if (duration > Duration.ofMinutes(5)) {
throw FlowException("Attempt to sleep for longer than 5 minutes is not supported. Consider using SchedulableState.") throw FlowException("Attempt to sleep for longer than 5 minutes is not supported. Consider using SchedulableState.")
} }
val fiber = (Strand.currentStrand() as? FlowStateMachine<*>) val fiber = (Strand.currentStrand() as? FlowStateMachine<*>)
@ -343,7 +344,9 @@ abstract class FlowLogic<out T> {
* is public only because it must be accessed across module boundaries. * is public only because it must be accessed across module boundaries.
*/ */
var stateMachine: FlowStateMachine<*> var stateMachine: FlowStateMachine<*>
@CordaInternal
get() = _stateMachine ?: throw IllegalStateException("This can only be done after the flow has been started.") get() = _stateMachine ?: throw IllegalStateException("This can only be done after the flow has been started.")
@CordaInternal
set(value) { set(value) {
_stateMachine = value _stateMachine = value
} }

View File

@ -50,9 +50,6 @@ data class CordaX500Name(val commonName: String?,
// Legal name checks. // Legal name checks.
LegalNameValidator.validateOrganization(organisation, LegalNameValidator.Validation.MINIMAL) LegalNameValidator.validateOrganization(organisation, LegalNameValidator.Validation.MINIMAL)
// Attribute data width checks.
require(country.length == LENGTH_COUNTRY) { "Invalid country '$country' Country code must be $LENGTH_COUNTRY letters ISO code " }
require(country.toUpperCase() == country) { "Country code should be in upper case." }
require(country in countryCodes) { "Invalid country code $country" } require(country in countryCodes) { "Invalid country code $country" }
require(organisation.length < MAX_LENGTH_ORGANISATION) { require(organisation.length < MAX_LENGTH_ORGANISATION) {
@ -74,6 +71,7 @@ data class CordaX500Name(val commonName: String?,
} }
companion object { companion object {
@Deprecated("Not Used")
const val LENGTH_COUNTRY = 2 const val LENGTH_COUNTRY = 2
const val MAX_LENGTH_ORGANISATION = 128 const val MAX_LENGTH_ORGANISATION = 128
const val MAX_LENGTH_LOCALITY = 64 const val MAX_LENGTH_LOCALITY = 64

View File

@ -27,6 +27,10 @@ abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment {
} }
protected val attachmentData: ByteArray by lazy(dataLoader) protected val attachmentData: ByteArray by lazy(dataLoader)
// TODO: read file size information from metadata instead of loading the data.
override val size: Int get() = attachmentData.size
override fun open(): InputStream = attachmentData.inputStream() override fun open(): InputStream = attachmentData.inputStream()
override val signers by lazy { override val signers by lazy {
// Can't start with empty set if we're doing intersections. Logically the null means "all possible signers": // Can't start with empty set if we're doing intersections. Logically the null means "all possible signers":

View File

@ -0,0 +1,14 @@
package net.corda.core.internal
import net.corda.core.node.NetworkParameters
// TODO: This will cause problems when we run tests in parallel, make each node have its own properties.
object GlobalProperties {
private var _networkParameters: NetworkParameters? = null
var networkParameters: NetworkParameters
get() = checkNotNull(_networkParameters) { "Property 'networkParameters' has not been initialised." }
set(value) {
_networkParameters = value
}
}

View File

@ -3,16 +3,16 @@
package net.corda.core.internal package net.corda.core.internal
import net.corda.core.cordapp.CordappProvider import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.Crypto import net.corda.core.crypto.*
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.OpaqueBytes
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x500.X500NameBuilder import org.bouncycastle.asn1.x500.X500NameBuilder
import org.bouncycastle.asn1.x500.style.BCStyle import org.bouncycastle.asn1.x500.style.BCStyle
@ -26,10 +26,12 @@ import java.lang.reflect.Field
import java.math.BigDecimal import java.math.BigDecimal
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.URL import java.net.URL
import java.nio.ByteBuffer
import java.nio.charset.Charset import java.nio.charset.Charset
import java.nio.charset.StandardCharsets.UTF_8 import java.nio.charset.StandardCharsets.UTF_8
import java.nio.file.* import java.nio.file.*
import java.nio.file.attribute.FileAttribute import java.nio.file.attribute.FileAttribute
import java.security.KeyPair
import java.security.PrivateKey import java.security.PrivateKey
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.time.Duration import java.time.Duration
@ -307,6 +309,16 @@ val KClass<*>.packageName: String get() = java.`package`.name
fun URL.openHttpConnection(): HttpURLConnection = openConnection() as HttpURLConnection fun URL.openHttpConnection(): HttpURLConnection = openConnection() as HttpURLConnection
fun URL.post(serializedData: OpaqueBytes) {
openHttpConnection().apply {
doOutput = true
requestMethod = "POST"
setRequestProperty("Content-Type", "application/octet-stream")
outputStream.use { serializedData.open().copyTo(it) }
checkOkResponse()
}
}
fun HttpURLConnection.checkOkResponse() { fun HttpURLConnection.checkOkResponse() {
if (responseCode != 200) { if (responseCode != 200) {
val message = errorStream.use { it.reader().readText() } val message = errorStream.use { it.reader().readText() }
@ -353,3 +365,13 @@ fun <T : Any> T.signWithCert(privateKey: PrivateKey, certificate: X509Certificat
val signature = Crypto.doSign(privateKey, serialised.bytes) val signature = Crypto.doSign(privateKey, serialised.bytes)
return SignedDataWithCert(serialised, DigitalSignatureWithCert(certificate, signature)) return SignedDataWithCert(serialised, DigitalSignatureWithCert(certificate, signature))
} }
inline fun <T : Any> SerializedBytes<T>.sign(signer: (SerializedBytes<T>) -> DigitalSignature.WithKey): SignedData<T> {
return SignedData(this, signer(this))
}
inline fun <T : Any> SerializedBytes<T>.sign(keyPair: KeyPair): SignedData<T> {
return SignedData(this, keyPair.sign(this.bytes))
}
fun ByteBuffer.copyBytes() = ByteArray(remaining()).also { get(it) }

View File

@ -91,7 +91,8 @@ object LegalNameValidator {
CapitalLetterRule() CapitalLetterRule()
) )
val legalNameRules: List<Rule<String>> = attributeRules + listOf( val legalNameRules: List<Rule<String>> = attributeRules + listOf(
WordRule("node", "server"), // Removal of word restriction was requested in https://github.com/corda/corda/issues/2326
// WordRule("node", "server"),
X500NameRule() X500NameRule()
) )
val legalNameFullRules: List<Rule<String>> = legalNameRules + listOf( val legalNameFullRules: List<Rule<String>> = legalNameRules + listOf(

View File

@ -4,7 +4,7 @@ import net.corda.core.concurrent.CordaFuture
import net.corda.core.context.Actor import net.corda.core.context.Actor
import net.corda.core.context.AuthServiceId import net.corda.core.context.AuthServiceId
import net.corda.core.context.InvocationContext import net.corda.core.context.InvocationContext
import net.corda.core.context.Origin import net.corda.core.context.InvocationOrigin
import net.corda.core.contracts.ContractState import net.corda.core.contracts.ContractState
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowInitiator
@ -13,6 +13,7 @@ import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NetworkMapCache
@ -23,48 +24,45 @@ import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.Try import net.corda.core.utilities.Try
import rx.Observable import rx.Observable
import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.security.PublicKey import java.security.PublicKey
import java.time.Instant import java.time.Instant
private val unknownName = CordaX500Name("UNKNOWN", "UNKNOWN", "GB") /**
* Represents information about a flow (the name "state machine" is legacy, Kotlin users can use the [FlowInfo] type
* alias). You can access progress tracking, information about why the flow was started and so on.
*/
@CordaSerializable @CordaSerializable
data class StateMachineInfo @JvmOverloads constructor( data class StateMachineInfo @JvmOverloads constructor(
/** A univerally unique ID ([java.util.UUID]) representing this particular instance of the named flow. */
val id: StateMachineRunId, val id: StateMachineRunId,
/** The JVM class name of the flow code. */
val flowLogicClassName: String, val flowLogicClassName: String,
val initiator: FlowInitiator, /**
* An object representing information about the initiator of the flow. Note that this field is
* superceded by the [invocationContext] property, which has more detail.
*/
@Deprecated("There is more info available using 'context'") val initiator: FlowInitiator,
/** A [DataFeed] of the current progress step as a human readable string, and updates to that string. */
val progressTrackerStepAndUpdates: DataFeed<String, String>?, val progressTrackerStepAndUpdates: DataFeed<String, String>?,
val context: InvocationContext? = null /** An [InvocationContext] describing why and by whom the flow was started. */
val invocationContext: InvocationContext = initiator.invocationContext
) { ) {
fun context(): InvocationContext = context ?: contextFrom(initiator) @Suppress("DEPRECATION")
private fun contextFrom(initiator: FlowInitiator): InvocationContext {
var actor: Actor? = null
val origin: Origin
when (initiator) {
is FlowInitiator.RPC -> {
actor = Actor(Actor.Id(initiator.username), AuthServiceId("UNKNOWN"), unknownName)
origin = Origin.RPC(actor)
}
is FlowInitiator.Peer -> origin = Origin.Peer(initiator.party.name)
is FlowInitiator.Service -> origin = Origin.Service(initiator.serviceClassName, unknownName)
is FlowInitiator.Shell -> origin = Origin.Shell
is FlowInitiator.Scheduled -> origin = Origin.Scheduled(initiator.scheduledState)
}
return InvocationContext.newInstance(origin = origin, actor = actor)
}
fun copy(id: StateMachineRunId = this.id, fun copy(id: StateMachineRunId = this.id,
flowLogicClassName: String = this.flowLogicClassName, flowLogicClassName: String = this.flowLogicClassName,
initiator: FlowInitiator = this.initiator, initiator: FlowInitiator = this.initiator,
progressTrackerStepAndUpdates: DataFeed<String, String>? = this.progressTrackerStepAndUpdates): StateMachineInfo { progressTrackerStepAndUpdates: DataFeed<String, String>? = this.progressTrackerStepAndUpdates): StateMachineInfo {
return copy(id = id, flowLogicClassName = flowLogicClassName, initiator = initiator, progressTrackerStepAndUpdates = progressTrackerStepAndUpdates, context = context) return copy(id = id, flowLogicClassName = flowLogicClassName, initiator = initiator, progressTrackerStepAndUpdates = progressTrackerStepAndUpdates, invocationContext = invocationContext)
} }
override fun toString(): String = "${javaClass.simpleName}($id, $flowLogicClassName)" override fun toString(): String = "${javaClass.simpleName}($id, $flowLogicClassName)"
} }
/** An alias for [StateMachineInfo] which uses more modern terminology. */
typealias FlowInfo = StateMachineInfo
@CordaSerializable @CordaSerializable
sealed class StateMachineUpdate { sealed class StateMachineUpdate {
abstract val id: StateMachineRunId abstract val id: StateMachineRunId
@ -76,6 +74,24 @@ sealed class StateMachineUpdate {
data class Removed(override val id: StateMachineRunId, val result: Try<*>) : StateMachineUpdate() data class Removed(override val id: StateMachineRunId, val result: Try<*>) : StateMachineUpdate()
} }
// DOCSTART 1
/**
* Data class containing information about the scheduled network parameters update. The info is emitted every time node
* receives network map with [ParametersUpdate] which wasn't seen before. For more information see: [CordaRPCOps.networkParametersFeed] and [CordaRPCOps.acceptNewNetworkParameters].
* @property hash new [NetworkParameters] hash
* @property parameters new [NetworkParameters] data structure
* @property description description of the update
* @property updateDeadline deadline for accepting this update using [CordaRPCOps.acceptNewNetworkParameters]
*/
@CordaSerializable
data class ParametersUpdateInfo(
val hash: SecureHash,
val parameters: NetworkParameters,
val description: String,
val updateDeadline: Instant
)
// DOCEND 1
@CordaSerializable @CordaSerializable
data class StateMachineTransactionMapping(val stateMachineRunId: StateMachineRunId, val transactionId: SecureHash) data class StateMachineTransactionMapping(val stateMachineRunId: StateMachineRunId, val transactionId: SecureHash)
@ -209,6 +225,29 @@ interface CordaRPCOps : RPCOps {
@RPCReturnsObservables @RPCReturnsObservables
fun networkMapFeed(): DataFeed<List<NodeInfo>, NetworkMapCache.MapChange> fun networkMapFeed(): DataFeed<List<NodeInfo>, NetworkMapCache.MapChange>
/**
* Returns [DataFeed] object containing information on currently scheduled parameters update (null if none are currently scheduled)
* and observable with future update events. Any update that occurs before the deadline automatically cancels the current one.
* Only the latest update can be accepted.
* Note: This operation may be restricted only to node administrators.
*/
// TODO This operation should be restricted to just node admins.
@RPCReturnsObservables
fun networkParametersFeed(): DataFeed<ParametersUpdateInfo?, ParametersUpdateInfo>
/**
* Accept network parameters with given hash, hash is obtained through [networkParametersFeed] method.
* Information is sent back to the zone operator that the node accepted the parameters update - this process cannot be
* undone.
* Only parameters that are scheduled for update can be accepted, if different hash is provided this method will fail.
* Note: This operation may be restricted only to node administrators.
* @param parametersHash hash of network parameters to accept
* @throws IllegalArgumentException if network map advertises update with different parameters hash then the one accepted by node's operator.
* @throws IOException if failed to send the approval to network map
*/
// TODO This operation should be restricted to just node admins.
fun acceptNewNetworkParameters(parametersHash: SecureHash)
/** /**
* Start the given flow with the given arguments. [logicType] must be annotated * Start the given flow with the given arguments. [logicType] must be annotated
* with [net.corda.core.flows.StartableByRPC]. * with [net.corda.core.flows.StartableByRPC].

View File

@ -0,0 +1,44 @@
package net.corda.core.node
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import java.time.Instant
/**
* Network parameters are a set of values that every node participating in the zone needs to agree on and use to
* correctly interoperate with each other.
* @property minimumPlatformVersion Minimum version of Corda platform that is required for nodes in the network.
* @property notaries List of well known and trusted notary identities with information on validation type.
* @property maxMessageSize Maximum P2P message sent over the wire in bytes.
* @property maxTransactionSize Maximum permitted transaction size in bytes.
* @property modifiedTime Last modification time of network parameters set.
* @property epoch Version number of the network parameters. Starting from 1, this will always increment on each new set
* of parameters.
*/
// TODO Add eventHorizon - how many days a node can be offline before being automatically ejected from the network.
// It needs separate design.
@CordaSerializable
data class NetworkParameters(
val minimumPlatformVersion: Int,
val notaries: List<NotaryInfo>,
val maxMessageSize: Int,
val maxTransactionSize: Int,
val modifiedTime: Instant,
val epoch: Int
) {
init {
require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" }
require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" }
require(epoch > 0) { "epoch must be at least 1" }
require(maxMessageSize > 0) { "maxMessageSize must be at least 1" }
require(maxTransactionSize > 0) { "maxTransactionSize must be at least 1" }
}
}
/**
* Data class storing information about notaries available in the network.
* @property identity Identity of the notary (note that it can be an identity of the distributed node).
* @property validating Indicates if the notary is validating.
*/
@CordaSerializable
data class NotaryInfo(val identity: Party, val validating: Boolean)

View File

@ -2,7 +2,6 @@ package net.corda.core.node.services
import net.corda.core.DoNotImplement import net.corda.core.DoNotImplement
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party

View File

@ -104,15 +104,19 @@ abstract class TrustedAuthorityNotaryService : NotaryService() {
return NotaryException(NotaryError.Conflict(txId, signedConflict)) return NotaryException(NotaryError.Conflict(txId, signedConflict))
} }
/** Sign a [ByteArray] input. */
fun sign(bits: ByteArray): DigitalSignature.WithKey { fun sign(bits: ByteArray): DigitalSignature.WithKey {
return services.keyManagementService.sign(bits, notaryIdentityKey) return services.keyManagementService.sign(bits, notaryIdentityKey)
} }
/** Sign a single transaction. */
fun sign(txId: SecureHash): TransactionSignature { fun sign(txId: SecureHash): TransactionSignature {
val signableData = SignableData(txId, SignatureMetadata(services.myInfo.platformVersion, Crypto.findSignatureScheme(notaryIdentityKey).schemeNumberID)) val signableData = SignableData(txId, SignatureMetadata(services.myInfo.platformVersion, Crypto.findSignatureScheme(notaryIdentityKey).schemeNumberID))
return services.keyManagementService.sign(signableData, notaryIdentityKey) return services.keyManagementService.sign(signableData, notaryIdentityKey)
} }
// TODO: Sign multiple transactions at once by building their Merkle tree and then signing over its root.
@Deprecated("This property is no longer used") @Suppress("DEPRECATION") @Deprecated("This property is no longer used") @Suppress("DEPRECATION")
protected open val timeWindowChecker: TimeWindowChecker get() = throw UnsupportedOperationException("No default implementation, need to override") protected open val timeWindowChecker: TimeWindowChecker get() = throw UnsupportedOperationException("No default implementation, need to override")
} }

View File

@ -98,9 +98,7 @@ abstract class SerializationFactory {
val currentFactory: SerializationFactory? get() = _currentFactory.get() val currentFactory: SerializationFactory? get() = _currentFactory.get()
} }
} }
typealias SerializationMagic = ByteSequence
typealias VersionHeader = ByteSequence
/** /**
* Parameters to serialization and deserialization. * Parameters to serialization and deserialization.
*/ */
@ -108,7 +106,7 @@ interface SerializationContext {
/** /**
* When serializing, use the format this header sequence represents. * When serializing, use the format this header sequence represents.
*/ */
val preferredSerializationVersion: VersionHeader val preferredSerializationVersion: SerializationMagic
/** /**
* The class loader to use for deserialization. * The class loader to use for deserialization.
*/ */
@ -161,7 +159,7 @@ interface SerializationContext {
/** /**
* Helper method to return a new context based on this context but with serialization using the format this header sequence represents. * Helper method to return a new context based on this context but with serialization using the format this header sequence represents.
*/ */
fun withPreferredSerializationVersion(versionHeader: VersionHeader): SerializationContext fun withPreferredSerializationVersion(magic: SerializationMagic): SerializationContext
/** /**
* The use case that we are serializing for, since it influences the implementations chosen. * The use case that we are serializing for, since it influences the implementations chosen.
@ -225,6 +223,7 @@ fun <T : Any> T.serialize(serializationFactory: SerializationFactory = Serializa
* A type safe wrapper around a byte array that contains a serialised object. You can call [SerializedBytes.deserialize] * A type safe wrapper around a byte array that contains a serialised object. You can call [SerializedBytes.deserialize]
* to get the original object back. * to get the original object back.
*/ */
@Suppress("unused")
class SerializedBytes<T : Any>(bytes: ByteArray) : OpaqueBytes(bytes) { class SerializedBytes<T : Any>(bytes: ByteArray) : OpaqueBytes(bytes) {
// It's OK to use lazy here because SerializedBytes is configured to use the ImmutableClassSerializer. // It's OK to use lazy here because SerializedBytes is configured to use the ImmutableClassSerializer.
val hash: SecureHash by lazy { bytes.sha256() } val hash: SecureHash by lazy { bytes.sha256() }

View File

@ -68,15 +68,15 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
*/ */
fun buildFilteredTransaction(filtering: Predicate<Any>) = tx.buildFilteredTransaction(filtering) fun buildFilteredTransaction(filtering: Predicate<Any>) = tx.buildFilteredTransaction(filtering)
/** Helper to access the inputs of the contained transaction */ /** Helper to access the inputs of the contained transaction. */
val inputs: List<StateRef> get() = transaction.inputs val inputs: List<StateRef> get() = transaction.inputs
/** Helper to access the notary of the contained transaction */ /** Helper to access the notary of the contained transaction. */
val notary: Party? get() = transaction.notary val notary: Party? get() = transaction.notary
override val requiredSigningKeys: Set<PublicKey> get() = tx.requiredSigningKeys override val requiredSigningKeys: Set<PublicKey> get() = tx.requiredSigningKeys
override fun getKeyDescriptions(keys: Set<PublicKey>): ArrayList<String> { override fun getKeyDescriptions(keys: Set<PublicKey>): ArrayList<String> {
// TODO: We need a much better way of structuring this data // TODO: We need a much better way of structuring this data.
val descriptions = ArrayList<String>() val descriptions = ArrayList<String>()
this.tx.commands.forEach { command -> this.tx.commands.forEach { command ->
if (command.signers.any { it in keys }) if (command.signers.any { it in keys })
@ -134,8 +134,18 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
@JvmOverloads @JvmOverloads
@Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class) @Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServiceHub, checkSufficientSignatures: Boolean = true): LedgerTransaction { fun toLedgerTransaction(services: ServiceHub, checkSufficientSignatures: Boolean = true): LedgerTransaction {
// TODO: We could probably optimise the below by
// a) not throwing if threshold is eventually satisfied, but some of the rest of the signatures are failing.
// b) omit verifying signatures when threshold requirement is met.
// c) omit verifying signatures from keys not included in [requiredSigningKeys].
// For the above to work, [checkSignaturesAreValid] should take the [requiredSigningKeys] as input
// and probably combine logic from signature validation and key-fulfilment
// in [TransactionWithSignatures.verifySignaturesExcept].
if (checkSufficientSignatures) {
verifyRequiredSignatures() // It internally invokes checkSignaturesAreValid().
} else {
checkSignaturesAreValid() checkSignaturesAreValid()
if (checkSufficientSignatures) verifyRequiredSignatures() }
return tx.toLedgerTransaction(services) return tx.toLedgerTransaction(services)
} }
@ -153,28 +163,25 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
@Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class) @Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class)
fun verify(services: ServiceHub, checkSufficientSignatures: Boolean = true) { fun verify(services: ServiceHub, checkSufficientSignatures: Boolean = true) {
if (isNotaryChangeTransaction()) { if (isNotaryChangeTransaction()) {
verifyNotaryChangeTransaction(checkSufficientSignatures, services) verifyNotaryChangeTransaction(services, checkSufficientSignatures)
} else { } else {
verifyRegularTransaction(checkSufficientSignatures, services) verifyRegularTransaction(services, checkSufficientSignatures)
} }
} }
/** // TODO: Verify contract constraints here as well as in LedgerTransaction to ensure that anything being deserialised
* TODO: Verify contract constraints here as well as in LedgerTransaction to ensure that anything being deserialised // from the attachment is trusted. This will require some partial serialisation work to not load the ContractState
* from the attachment is trusted. This will require some partial serialisation work to not load the ContractState // objects from the TransactionState.
* objects from the TransactionState. private fun verifyRegularTransaction(services: ServiceHub, checkSufficientSignatures: Boolean) {
*/ val ltx = toLedgerTransaction(services, checkSufficientSignatures)
private fun verifyRegularTransaction(checkSufficientSignatures: Boolean, services: ServiceHub) { // TODO: allow non-blocking verification.
checkSignaturesAreValid()
if (checkSufficientSignatures) verifyRequiredSignatures()
val ltx = tx.toLedgerTransaction(services)
// TODO: allow non-blocking verification
services.transactionVerifierService.verify(ltx).getOrThrow() services.transactionVerifierService.verify(ltx).getOrThrow()
} }
private fun verifyNotaryChangeTransaction(checkSufficientSignatures: Boolean, services: ServiceHub) { private fun verifyNotaryChangeTransaction(services: ServiceHub, checkSufficientSignatures: Boolean) {
val ntx = resolveNotaryChangeTransaction(services) val ntx = resolveNotaryChangeTransaction(services)
if (checkSufficientSignatures) ntx.verifyRequiredSignatures() if (checkSufficientSignatures) ntx.verifyRequiredSignatures()
else checkSignaturesAreValid()
} }
fun isNotaryChangeTransaction() = transaction is NotaryChangeWireTransaction fun isNotaryChangeTransaction() = transaction is NotaryChangeWireTransaction

View File

@ -11,7 +11,7 @@ import java.security.PublicKey
import java.security.SignatureException import java.security.SignatureException
import java.util.* import java.util.*
/** An interface for transactions containing signatures, with logic for signature verification */ /** An interface for transactions containing signatures, with logic for signature verification. */
@DoNotImplement @DoNotImplement
interface TransactionWithSignatures : NamedByHash { interface TransactionWithSignatures : NamedByHash {
/** /**
@ -21,7 +21,7 @@ interface TransactionWithSignatures : NamedByHash {
*/ */
val sigs: List<TransactionSignature> val sigs: List<TransactionSignature>
/** Specifies all the public keys that require signatures for the transaction to be valid */ /** Specifies all the public keys that require signatures for the transaction to be valid. */
val requiredSigningKeys: Set<PublicKey> val requiredSigningKeys: Set<PublicKey>
/** /**
@ -65,11 +65,10 @@ interface TransactionWithSignatures : NamedByHash {
*/ */
@Throws(SignatureException::class) @Throws(SignatureException::class)
fun verifySignaturesExcept(allowedToBeMissing: Collection<PublicKey>) { fun verifySignaturesExcept(allowedToBeMissing: Collection<PublicKey>) {
checkSignaturesAreValid()
val needed = getMissingSigners() - allowedToBeMissing val needed = getMissingSigners() - allowedToBeMissing
if (needed.isNotEmpty()) if (needed.isNotEmpty())
throw SignaturesMissingException(needed.toNonEmptySet(), getKeyDescriptions(needed), id) throw SignaturesMissingException(needed.toNonEmptySet(), getKeyDescriptions(needed), id)
checkSignaturesAreValid()
} }
/** /**

View File

@ -5,6 +5,7 @@ import net.corda.core.contracts.ComponentGroupEnum.*
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.Emoji import net.corda.core.internal.Emoji
import net.corda.core.internal.GlobalProperties
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
@ -118,7 +119,24 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
val contractAttachments = findAttachmentContracts(resolvedInputs, resolveContractAttachment, resolveAttachment) val contractAttachments = findAttachmentContracts(resolvedInputs, resolveContractAttachment, resolveAttachment)
// Order of attachments is important since contracts may refer to indexes so only append automatic attachments // Order of attachments is important since contracts may refer to indexes so only append automatic attachments
val attachments = (attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) } + contractAttachments).distinct() val attachments = (attachments.map { resolveAttachment(it) ?: throw AttachmentResolutionException(it) } + contractAttachments).distinct()
return LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt) val ltx = LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt)
checkTransactionSize(ltx)
return ltx
}
private fun checkTransactionSize(ltx: LedgerTransaction) {
var remainingTransactionSize = GlobalProperties.networkParameters.maxTransactionSize
fun minus(size: Int) {
require(remainingTransactionSize > size) { "Transaction exceeded network's maximum transaction size limit : ${GlobalProperties.networkParameters.maxTransactionSize} bytes." }
remainingTransactionSize -= size
}
// Check attachment size first as they are most likely to go over the limit.
ltx.attachments.forEach { minus(it.size) }
minus(ltx.inputs.serialize().size)
minus(ltx.commands.serialize().size)
minus(ltx.outputs.serialize().size)
} }
/** /**

View File

@ -4,44 +4,27 @@ package net.corda.core.utilities
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.OutputStream
import java.lang.Math.max
import java.lang.Math.min
import java.nio.ByteBuffer
import javax.xml.bind.DatatypeConverter import javax.xml.bind.DatatypeConverter
/** /**
* An abstraction of a byte array, with offset and size that does no copying of bytes unless asked to. * An abstraction of a byte array, with offset and size that does no copying of bytes unless asked to.
* *
* The data of interest typically starts at position [offset] within the [bytes] and is [size] bytes long. * The data of interest typically starts at position [offset] within the [bytes] and is [size] bytes long.
*
* @property offset The start position of the sequence within the byte array.
* @property size The number of bytes this sequence represents.
*/ */
@CordaSerializable @CordaSerializable
sealed class ByteSequence : Comparable<ByteSequence> { sealed class ByteSequence(private val _bytes: ByteArray, val offset: Int, val size: Int) : Comparable<ByteSequence> {
constructor() {
this._bytes = COPY_BYTES
}
/**
* This constructor allows to bypass calls to [bytes] for functions in this class if the implementation
* of [bytes] makes a copy of the underlying [ByteArray] (as [OpaqueBytes] does for safety). This improves
* performance. It is recommended to use this constructor rather than the default constructor.
*/
constructor(uncopiedBytes: ByteArray) {
this._bytes = uncopiedBytes
}
/** /**
* The underlying bytes. Some implementations may choose to make a copy of the underlying [ByteArray] for * The underlying bytes. Some implementations may choose to make a copy of the underlying [ByteArray] for
* security reasons. For example, [OpaqueBytes]. * security reasons. For example, [OpaqueBytes].
*/ */
abstract val bytes: ByteArray abstract val bytes: ByteArray
/**
* The number of bytes this sequence represents.
*/
abstract val size: Int
/**
* The start position of the sequence within the byte array.
*/
abstract val offset: Int
private val _bytes: ByteArray
get() = if (field === COPY_BYTES) bytes else field
/** Returns a [ByteArrayInputStream] of the bytes */ /** Returns a [ByteArrayInputStream] of the bytes */
fun open() = ByteArrayInputStream(_bytes, offset, size) fun open() = ByteArrayInputStream(_bytes, offset, size)
@ -53,6 +36,7 @@ sealed class ByteSequence : Comparable<ByteSequence> {
* @param offset The offset within this sequence to start the new sequence. Note: not the offset within the backing array. * @param offset The offset within this sequence to start the new sequence. Note: not the offset within the backing array.
* @param size The size of the intended sub sequence. * @param size The size of the intended sub sequence.
*/ */
@Suppress("MemberVisibilityCanPrivate")
fun subSequence(offset: Int, size: Int): ByteSequence { fun subSequence(offset: Int, size: Int): ByteSequence {
require(offset >= 0) require(offset >= 0)
require(offset + size <= this.size) require(offset + size <= this.size)
@ -71,23 +55,40 @@ sealed class ByteSequence : Comparable<ByteSequence> {
fun of(bytes: ByteArray, offset: Int = 0, size: Int = bytes.size): ByteSequence { fun of(bytes: ByteArray, offset: Int = 0, size: Int = bytes.size): ByteSequence {
return OpaqueBytesSubSequence(bytes, offset, size) return OpaqueBytesSubSequence(bytes, offset, size)
} }
private val COPY_BYTES: ByteArray = ByteArray(0)
} }
/** /**
* Take the first n bytes of this sequence as a sub-sequence. See [subSequence] for further semantics. * Take the first n bytes of this sequence as a sub-sequence. See [subSequence] for further semantics.
*/ */
fun take(n: Int): ByteSequence { fun take(n: Int): ByteSequence = subSequence(0, n)
require(size >= n)
return subSequence(0, n) /**
* A new read-only [ByteBuffer] view of this sequence or part of it.
* If [start] or [end] are negative then [IllegalArgumentException] is thrown, otherwise they are clamped if necessary.
* This method cannot be used to get bytes before [offset] or after [offset]+[size], and never makes a new array.
*/
fun slice(start: Int = 0, end: Int = size): ByteBuffer {
require(start >= 0)
require(end >= 0)
val clampedStart = min(start, size)
val clampedEnd = min(end, size)
return ByteBuffer.wrap(_bytes, offset + clampedStart, max(0, clampedEnd - clampedStart)).asReadOnlyBuffer()
} }
/** Write this sequence to an [OutputStream]. */
fun writeTo(output: OutputStream) = output.write(_bytes, offset, size)
/** Write this sequence to a [ByteBuffer]. */
fun putTo(buffer: ByteBuffer): ByteBuffer = buffer.put(_bytes, offset, size)
/** /**
* Copy this sequence, complete with new backing array. This can be helpful to break references to potentially * Copy this sequence, complete with new backing array. This can be helpful to break references to potentially
* large backing arrays from small sub-sequences. * large backing arrays from small sub-sequences.
*/ */
fun copy(): ByteSequence = of(_bytes.copyOfRange(offset, offset + size)) fun copy(): ByteSequence = of(copyBytes())
/** Same as [copy] but returns just the new byte array. */
fun copyBytes(): ByteArray = _bytes.copyOfRange(offset, offset + size)
/** /**
* Compare byte arrays byte by byte. Arrays that are shorter are deemed less than longer arrays if all the bytes * Compare byte arrays byte by byte. Arrays that are shorter are deemed less than longer arrays if all the bytes
@ -135,7 +136,7 @@ sealed class ByteSequence : Comparable<ByteSequence> {
return result return result
} }
override fun toString(): String = "[${_bytes.copyOfRange(offset, offset + size).toHexString()}]" override fun toString(): String = "[${copyBytes().toHexString()}]"
} }
/** /**
@ -143,7 +144,7 @@ sealed class ByteSequence : Comparable<ByteSequence> {
* In an ideal JVM this would be a value type and be completely overhead free. Project Valhalla is adding such * In an ideal JVM this would be a value type and be completely overhead free. Project Valhalla is adding such
* functionality to Java, but it won't arrive for a few years yet! * functionality to Java, but it won't arrive for a few years yet!
*/ */
open class OpaqueBytes(bytes: ByteArray) : ByteSequence(bytes) { open class OpaqueBytes(bytes: ByteArray) : ByteSequence(bytes, 0, bytes.size) {
companion object { companion object {
/** /**
* Create [OpaqueBytes] from a sequence of [Byte] values. * Create [OpaqueBytes] from a sequence of [Byte] values.
@ -158,8 +159,8 @@ open class OpaqueBytes(bytes: ByteArray) : ByteSequence(bytes) {
/** /**
* The bytes are always cloned so that this object becomes immutable. This has been done * The bytes are always cloned so that this object becomes immutable. This has been done
* to prevent tampering with entities such as [SecureHash] and [PrivacySalt], as well as * to prevent tampering with entities such as [net.corda.core.crypto.SecureHash] and [net.corda.core.contracts.PrivacySalt], as well as
* preserve the integrity of our hash constants [zeroHash] and [allOnesHash]. * preserve the integrity of our hash constants [net.corda.core.crypto.SecureHash.zeroHash] and [net.corda.core.crypto.SecureHash.allOnesHash].
* *
* Cloning like this may become a performance issue, depending on whether or not the JIT * Cloning like this may become a performance issue, depending on whether or not the JIT
* compiler is ever able to optimise away the clone. In which case we may need to revisit * compiler is ever able to optimise away the clone. In which case we may need to revisit
@ -167,8 +168,6 @@ open class OpaqueBytes(bytes: ByteArray) : ByteSequence(bytes) {
*/ */
override final val bytes: ByteArray = bytes override final val bytes: ByteArray = bytes
get() = field.clone() get() = field.clone()
override val size: Int = bytes.size
override val offset: Int = 0
} }
/** /**
@ -190,7 +189,7 @@ fun String.parseAsHex(): ByteArray = DatatypeConverter.parseHexBinary(this)
/** /**
* Class is public for serialization purposes * Class is public for serialization purposes
*/ */
class OpaqueBytesSubSequence(override val bytes: ByteArray, override val offset: Int, override val size: Int) : ByteSequence(bytes) { class OpaqueBytesSubSequence(override val bytes: ByteArray, offset: Int, size: Int) : ByteSequence(bytes, offset, size) {
init { init {
require(offset >= 0 && offset < bytes.size) require(offset >= 0 && offset < bytes.size)
require(size >= 0 && size <= bytes.size) require(size >= 0 && size <= bytes.size)

View File

@ -40,7 +40,7 @@ public class FlowsInJavaTest {
@Test @Test
public void suspendableActionInsideUnwrap() throws Exception { public void suspendableActionInsideUnwrap() throws Exception {
bobNode.registerInitiatedFlow(SendHelloAndThenReceive.class); bobNode.registerInitiatedFlow(SendHelloAndThenReceive.class);
Future<String> result = startFlow(aliceNode.getServices(), new SendInUnwrapFlow(bob)).getResultFuture(); Future<String> result = startFlow(aliceNode.getServices(), new SendInUnwrapFlow(bob));
mockNet.runNetwork(); mockNet.runNetwork();
assertThat(result.get()).isEqualTo("Hello"); assertThat(result.get()).isEqualTo("Hello");
} }
@ -56,7 +56,7 @@ public class FlowsInJavaTest {
private void primitiveReceiveTypeTest(Class<?> receiveType) throws InterruptedException { private void primitiveReceiveTypeTest(Class<?> receiveType) throws InterruptedException {
PrimitiveReceiveFlow flow = new PrimitiveReceiveFlow(bob, receiveType); PrimitiveReceiveFlow flow = new PrimitiveReceiveFlow(bob, receiveType);
Future<?> result = startFlow(aliceNode.getServices(), flow).getResultFuture(); Future<?> result = startFlow(aliceNode.getServices(), flow);
mockNet.runNetwork(); mockNet.runNetwork();
try { try {
result.get(); result.get();

View File

@ -31,6 +31,7 @@ class AttachmentTest {
override val id get() = throw UnsupportedOperationException() override val id get() = throw UnsupportedOperationException()
override fun open() = inputStream override fun open() = inputStream
override val signers get() = throw UnsupportedOperationException() override val signers get() = throw UnsupportedOperationException()
override val size: Int = 512
} }
try { try {
attachment.openAsJAR() attachment.openAsJAR()

View File

@ -1,5 +1,6 @@
package net.corda.core.crypto package net.corda.core.crypto
import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY
import org.junit.Test import org.junit.Test
import java.math.BigInteger import java.math.BigInteger
import java.util.* import java.util.*
@ -26,7 +27,7 @@ class Base58Test {
assertEquals("1111111", Base58.encode(zeroBytes7)) assertEquals("1111111", Base58.encode(zeroBytes7))
// test empty encode // test empty encode
assertEquals("", Base58.encode(ByteArray(0))) assertEquals("", Base58.encode(EMPTY_BYTE_ARRAY))
} }
@Test @Test

View File

@ -8,6 +8,7 @@ import net.i2p.crypto.eddsa.math.GroupElement
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey
@ -77,7 +78,7 @@ class CryptoUtilsTest {
// test for empty data signing // test for empty data signing
try { try {
Crypto.doSign(privKey, ByteArray(0)) Crypto.doSign(privKey, EMPTY_BYTE_ARRAY)
fail() fail()
} catch (e: Exception) { } catch (e: Exception) {
// expected // expected
@ -85,7 +86,7 @@ class CryptoUtilsTest {
// test for empty source data when verifying // test for empty source data when verifying
try { try {
Crypto.doVerify(pubKey, testBytes, ByteArray(0)) Crypto.doVerify(pubKey, testBytes, EMPTY_BYTE_ARRAY)
fail() fail()
} catch (e: Exception) { } catch (e: Exception) {
// expected // expected
@ -93,7 +94,7 @@ class CryptoUtilsTest {
// test for empty signed data when verifying // test for empty signed data when verifying
try { try {
Crypto.doVerify(pubKey, ByteArray(0), testBytes) Crypto.doVerify(pubKey, EMPTY_BYTE_ARRAY, testBytes)
fail() fail()
} catch (e: Exception) { } catch (e: Exception) {
// expected // expected
@ -132,7 +133,7 @@ class CryptoUtilsTest {
// test for empty data signing // test for empty data signing
try { try {
Crypto.doSign(privKey, ByteArray(0)) Crypto.doSign(privKey, EMPTY_BYTE_ARRAY)
fail() fail()
} catch (e: Exception) { } catch (e: Exception) {
// expected // expected
@ -140,7 +141,7 @@ class CryptoUtilsTest {
// test for empty source data when verifying // test for empty source data when verifying
try { try {
Crypto.doVerify(pubKey, testBytes, ByteArray(0)) Crypto.doVerify(pubKey, testBytes, EMPTY_BYTE_ARRAY)
fail() fail()
} catch (e: Exception) { } catch (e: Exception) {
// expected // expected
@ -148,7 +149,7 @@ class CryptoUtilsTest {
// test for empty signed data when verifying // test for empty signed data when verifying
try { try {
Crypto.doVerify(pubKey, ByteArray(0), testBytes) Crypto.doVerify(pubKey, EMPTY_BYTE_ARRAY, testBytes)
fail() fail()
} catch (e: Exception) { } catch (e: Exception) {
// expected // expected
@ -187,7 +188,7 @@ class CryptoUtilsTest {
// test for empty data signing // test for empty data signing
try { try {
Crypto.doSign(privKey, ByteArray(0)) Crypto.doSign(privKey, EMPTY_BYTE_ARRAY)
fail() fail()
} catch (e: Exception) { } catch (e: Exception) {
// expected // expected
@ -195,7 +196,7 @@ class CryptoUtilsTest {
// test for empty source data when verifying // test for empty source data when verifying
try { try {
Crypto.doVerify(pubKey, testBytes, ByteArray(0)) Crypto.doVerify(pubKey, testBytes, EMPTY_BYTE_ARRAY)
fail() fail()
} catch (e: Exception) { } catch (e: Exception) {
// expected // expected
@ -203,7 +204,7 @@ class CryptoUtilsTest {
// test for empty signed data when verifying // test for empty signed data when verifying
try { try {
Crypto.doVerify(pubKey, ByteArray(0), testBytes) Crypto.doVerify(pubKey, EMPTY_BYTE_ARRAY, testBytes)
fail() fail()
} catch (e: Exception) { } catch (e: Exception) {
// expected // expected
@ -242,7 +243,7 @@ class CryptoUtilsTest {
// test for empty data signing // test for empty data signing
try { try {
Crypto.doSign(privKey, ByteArray(0)) Crypto.doSign(privKey, EMPTY_BYTE_ARRAY)
fail() fail()
} catch (e: Exception) { } catch (e: Exception) {
// expected // expected
@ -250,7 +251,7 @@ class CryptoUtilsTest {
// test for empty source data when verifying // test for empty source data when verifying
try { try {
Crypto.doVerify(pubKey, testBytes, ByteArray(0)) Crypto.doVerify(pubKey, testBytes, EMPTY_BYTE_ARRAY)
fail() fail()
} catch (e: Exception) { } catch (e: Exception) {
// expected // expected
@ -258,7 +259,7 @@ class CryptoUtilsTest {
// test for empty signed data when verifying // test for empty signed data when verifying
try { try {
Crypto.doVerify(pubKey, ByteArray(0), testBytes) Crypto.doVerify(pubKey, EMPTY_BYTE_ARRAY, testBytes)
fail() fail()
} catch (e: Exception) { } catch (e: Exception) {
// expected // expected
@ -297,7 +298,7 @@ class CryptoUtilsTest {
// test for empty data signing // test for empty data signing
try { try {
Crypto.doSign(privKey, ByteArray(0)) Crypto.doSign(privKey, EMPTY_BYTE_ARRAY)
fail() fail()
} catch (e: Exception) { } catch (e: Exception) {
// expected // expected
@ -305,7 +306,7 @@ class CryptoUtilsTest {
// test for empty source data when verifying // test for empty source data when verifying
try { try {
Crypto.doVerify(pubKey, testBytes, ByteArray(0)) Crypto.doVerify(pubKey, testBytes, EMPTY_BYTE_ARRAY)
fail() fail()
} catch (e: Exception) { } catch (e: Exception) {
// expected // expected
@ -313,7 +314,7 @@ class CryptoUtilsTest {
// test for empty signed data when verifying // test for empty signed data when verifying
try { try {
Crypto.doVerify(pubKey, ByteArray(0), testBytes) Crypto.doVerify(pubKey, EMPTY_BYTE_ARRAY, testBytes)
fail() fail()
} catch (e: Exception) { } catch (e: Exception) {
// expected // expected

View File

@ -3,17 +3,21 @@ package net.corda.core.crypto
import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.SerializationEnvironmentRule
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import java.math.BigInteger
import java.security.KeyPair
import java.security.SignatureException import java.security.SignatureException
import kotlin.test.assertFailsWith
import kotlin.test.assertNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
/** /**
* Digital signature MetaData tests. * Transaction signature tests.
*/ */
class TransactionSignatureTest { class TransactionSignatureTest {
@Rule @Rule
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule() val testSerialization = SerializationEnvironmentRule()
val testBytes = "12345678901234567890123456789012".toByteArray() private val testBytes = "12345678901234567890123456789012".toByteArray()
/** Valid sign and verify. */ /** Valid sign and verify. */
@Test @Test
@ -41,4 +45,83 @@ class TransactionSignatureTest {
val transactionSignature = keyPair.sign(signableData) val transactionSignature = keyPair.sign(signableData)
Crypto.doVerify((testBytes + testBytes).sha256(), transactionSignature) Crypto.doVerify((testBytes + testBytes).sha256(), transactionSignature)
} }
@Test
fun `Verify multi-tx signature`() {
val keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger.valueOf(1234567890L))
// Deterministically create 5 txIds.
val txIds: List<SecureHash> = IntRange(0, 4).map { byteArrayOf(it.toByte()).sha256() }
// Multi-tx signature.
val txSignature = signMultipleTx(txIds, keyPair)
// The hash of all txIds are used as leaves.
val merkleTree = MerkleTree.getMerkleTree(txIds.map { it.sha256() })
// We haven't added the partial tree yet.
assertNull(txSignature.partialMerkleTree)
// Because partial tree is still null, but we signed over a block of txs, verifying a single tx will fail.
assertFailsWith<SignatureException> { Crypto.doVerify(txIds[3], txSignature) }
// Create a partial tree for one tx.
val pmt = PartialMerkleTree.build(merkleTree, listOf(txIds[0].sha256()))
// Add the partial Merkle tree to the tx signature.
val txSignatureWithTree = TransactionSignature(txSignature.bytes, txSignature.by, txSignature.signatureMetadata, pmt)
// Verify the corresponding txId with every possible way.
assertTrue(Crypto.doVerify(txIds[0], txSignatureWithTree))
assertTrue(txSignatureWithTree.verify(txIds[0]))
assertTrue(Crypto.isValid(txIds[0], txSignatureWithTree))
assertTrue(txSignatureWithTree.isValid(txIds[0]))
// Verify the rest txs in the block, which are not included in the partial Merkle tree.
txIds.subList(1, txIds.size).forEach {
assertFailsWith<IllegalArgumentException> { Crypto.doVerify(it, txSignatureWithTree) }
}
// Test that the Merkle tree consists of hash(txId), not txId.
assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(merkleTree, listOf(txIds[0])) }
// What if we send the Full tree. This could be used if notaries didn't want to create a per tx partial tree.
// Create a partial tree for all txs, thus all leaves are included.
val pmtFull = PartialMerkleTree.build(merkleTree, txIds.map { it.sha256() })
// Add the partial Merkle tree to the tx.
val txSignatureWithFullTree = TransactionSignature(txSignature.bytes, txSignature.by, txSignature.signatureMetadata, pmtFull)
// All txs can be verified, as they are all included in the provided partial tree.
txIds.forEach {
assertTrue(Crypto.doVerify(it, txSignatureWithFullTree))
}
}
@Test
fun `Verify one-tx signature`() {
val keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger.valueOf(1234567890L))
val txId = "aTransaction".toByteArray().sha256()
// One-tx signature.
val txSignature = signOneTx(txId, keyPair)
// partialMerkleTree should be null.
assertNull(txSignature.partialMerkleTree)
// Verify the corresponding txId with every possible way.
assertTrue(Crypto.doVerify(txId, txSignature))
assertTrue(txSignature.verify(txId))
assertTrue(Crypto.isValid(txId, txSignature))
assertTrue(txSignature.isValid(txId))
// We signed the txId itself, not its hash (because it was a signature over one tx only and no partial tree has been received).
assertFailsWith<SignatureException> { Crypto.doVerify(txId.sha256(), txSignature) }
}
// Returns a TransactionSignature over the Merkle root, but the partial tree is null.
private fun signMultipleTx(txIds: List<SecureHash>, keyPair: KeyPair): TransactionSignature {
val merkleTreeRoot = MerkleTree.getMerkleTree(txIds.map { it.sha256() }).hash
return signOneTx(merkleTreeRoot, keyPair)
}
// Returns a TransactionSignature over one SecureHash.
// Note that if one tx is to be signed, we don't create a Merkle tree and we directly sign over the txId.
private fun signOneTx(txId: SecureHash, keyPair: KeyPair): TransactionSignature {
val signableData = SignableData(txId, SignatureMetadata(3, Crypto.findSignatureScheme(keyPair.public).schemeNumberID))
return keyPair.sign(signableData)
}
} }

View File

@ -65,7 +65,7 @@ class AttachmentTests {
mockNet.runNetwork() mockNet.runNetwork()
val bobFlow = bobNode.startAttachmentFlow(setOf(id), alice) val bobFlow = bobNode.startAttachmentFlow(setOf(id), alice)
mockNet.runNetwork() mockNet.runNetwork()
assertEquals(0, bobFlow.resultFuture.getOrThrow().fromDisk.size) assertEquals(0, bobFlow.getOrThrow().fromDisk.size)
// Verify it was inserted into node one's store. // Verify it was inserted into node one's store.
val attachment = bobNode.database.transaction { val attachment = bobNode.database.transaction {
@ -77,7 +77,7 @@ class AttachmentTests {
// Shut down node zero and ensure node one can still resolve the attachment. // Shut down node zero and ensure node one can still resolve the attachment.
aliceNode.dispose() aliceNode.dispose()
val response: FetchDataFlow.Result<Attachment> = bobNode.startAttachmentFlow(setOf(id), alice).resultFuture.getOrThrow() val response: FetchDataFlow.Result<Attachment> = bobNode.startAttachmentFlow(setOf(id), alice).getOrThrow()
assertEquals(attachment, response.fromDisk[0]) assertEquals(attachment, response.fromDisk[0])
} }
@ -92,7 +92,7 @@ class AttachmentTests {
val alice = aliceNode.info.singleIdentity() val alice = aliceNode.info.singleIdentity()
val bobFlow = bobNode.startAttachmentFlow(setOf(hash), alice) val bobFlow = bobNode.startAttachmentFlow(setOf(hash), alice)
mockNet.runNetwork() mockNet.runNetwork()
val e = assertFailsWith<FetchDataFlow.HashNotFound> { bobFlow.resultFuture.getOrThrow() } val e = assertFailsWith<FetchDataFlow.HashNotFound> { bobFlow.getOrThrow() }
assertEquals(hash, e.requested) assertEquals(hash, e.requested)
} }
@ -127,7 +127,7 @@ class AttachmentTests {
mockNet.runNetwork() mockNet.runNetwork()
val bobFlow = bobNode.startAttachmentFlow(setOf(id), alice) val bobFlow = bobNode.startAttachmentFlow(setOf(id), alice)
mockNet.runNetwork() mockNet.runNetwork()
assertFailsWith<FetchDataFlow.DownloadedVsRequestedDataMismatch> { bobFlow.resultFuture.getOrThrow() } assertFailsWith<FetchDataFlow.DownloadedVsRequestedDataMismatch> { bobFlow.getOrThrow() }
} }
private fun StartedNode<*>.startAttachmentFlow(hashes: Set<SecureHash>, otherSide: Party) = services.startFlow(InitiatingFetchAttachmentsFlow(otherSide, hashes)) private fun StartedNode<*>.startAttachmentFlow(hashes: Set<SecureHash>, otherSide: Party) = services.startFlow(InitiatingFetchAttachmentsFlow(otherSide, hashes))

View File

@ -115,7 +115,7 @@ class CollectSignaturesFlowTests {
val state = DummyContract.MultiOwnerState(magicNumber, parties) val state = DummyContract.MultiOwnerState(magicNumber, parties)
val flow = aliceNode.services.startFlow(TestFlow.Initiator(state, notary)) val flow = aliceNode.services.startFlow(TestFlow.Initiator(state, notary))
mockNet.runNetwork() mockNet.runNetwork()
val result = flow.resultFuture.getOrThrow() val result = flow.getOrThrow()
result.verifyRequiredSignatures() result.verifyRequiredSignatures()
println(result.tx) println(result.tx)
println(result.sigs) println(result.sigs)
@ -127,7 +127,7 @@ class CollectSignaturesFlowTests {
val ptx = aliceNode.services.signInitialTransaction(onePartyDummyContract) val ptx = aliceNode.services.signInitialTransaction(onePartyDummyContract)
val flow = aliceNode.services.startFlow(CollectSignaturesFlow(ptx, emptySet())) val flow = aliceNode.services.startFlow(CollectSignaturesFlow(ptx, emptySet()))
mockNet.runNetwork() mockNet.runNetwork()
val result = flow.resultFuture.getOrThrow() val result = flow.getOrThrow()
result.verifyRequiredSignatures() result.verifyRequiredSignatures()
println(result.tx) println(result.tx)
println(result.sigs) println(result.sigs)
@ -141,7 +141,7 @@ class CollectSignaturesFlowTests {
val flow = aliceNode.services.startFlow(CollectSignaturesFlow(ptx, emptySet())) val flow = aliceNode.services.startFlow(CollectSignaturesFlow(ptx, emptySet()))
mockNet.runNetwork() mockNet.runNetwork()
assertFailsWith<IllegalArgumentException>("The Initiator of CollectSignaturesFlow must have signed the transaction.") { assertFailsWith<IllegalArgumentException>("The Initiator of CollectSignaturesFlow must have signed the transaction.") {
flow.resultFuture.getOrThrow() flow.getOrThrow()
} }
} }
@ -155,7 +155,7 @@ class CollectSignaturesFlowTests {
val signedByBoth = bobNode.services.addSignature(signedByA) val signedByBoth = bobNode.services.addSignature(signedByA)
val flow = aliceNode.services.startFlow(CollectSignaturesFlow(signedByBoth, emptySet())) val flow = aliceNode.services.startFlow(CollectSignaturesFlow(signedByBoth, emptySet()))
mockNet.runNetwork() mockNet.runNetwork()
val result = flow.resultFuture.getOrThrow() val result = flow.getOrThrow()
println(result.tx) println(result.tx)
println(result.sigs) println(result.sigs)
} }

View File

@ -81,24 +81,24 @@ class ContractUpgradeFlowTest {
requireNotNull(btx) requireNotNull(btx)
// The request is expected to be rejected because party B hasn't authorised the upgrade yet. // The request is expected to be rejected because party B hasn't authorised the upgrade yet.
val rejectedFuture = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(atx!!.tx.outRef(0), DummyContractV2::class.java)).resultFuture val rejectedFuture = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(atx!!.tx.outRef(0), DummyContractV2::class.java))
mockNet.runNetwork() mockNet.runNetwork()
assertFailsWith(UnexpectedFlowEndException::class) { rejectedFuture.getOrThrow() } assertFailsWith(UnexpectedFlowEndException::class) { rejectedFuture.getOrThrow() }
// Party B authorise the contract state upgrade, and immediately deauthorise the same. // Party B authorise the contract state upgrade, and immediately deauthorise the same.
bobNode.services.startFlow(ContractUpgradeFlow.Authorise(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)).resultFuture.getOrThrow() bobNode.services.startFlow(ContractUpgradeFlow.Authorise(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)).getOrThrow()
bobNode.services.startFlow(ContractUpgradeFlow.Deauthorise(btx.tx.outRef<ContractState>(0).ref)).resultFuture.getOrThrow() bobNode.services.startFlow(ContractUpgradeFlow.Deauthorise(btx.tx.outRef<ContractState>(0).ref)).getOrThrow()
// The request is expected to be rejected because party B has subsequently deauthorised and a previously authorised upgrade. // The request is expected to be rejected because party B has subsequently deauthorised and a previously authorised upgrade.
val deauthorisedFuture = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture val deauthorisedFuture = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(atx.tx.outRef(0), DummyContractV2::class.java))
mockNet.runNetwork() mockNet.runNetwork()
assertFailsWith(UnexpectedFlowEndException::class) { deauthorisedFuture.getOrThrow() } assertFailsWith(UnexpectedFlowEndException::class) { deauthorisedFuture.getOrThrow() }
// Party B authorise the contract state upgrade // Party B authorise the contract state upgrade
bobNode.services.startFlow(ContractUpgradeFlow.Authorise(btx.tx.outRef<ContractState>(0), DummyContractV2::class.java)).resultFuture.getOrThrow() bobNode.services.startFlow(ContractUpgradeFlow.Authorise(btx.tx.outRef<ContractState>(0), DummyContractV2::class.java)).getOrThrow()
// Party A initiates contract upgrade flow, expected to succeed this time. // Party A initiates contract upgrade flow, expected to succeed this time.
val resultFuture = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture val resultFuture = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(atx.tx.outRef(0), DummyContractV2::class.java))
mockNet.runNetwork() mockNet.runNetwork()
val result = resultFuture.getOrThrow() val result = resultFuture.getOrThrow()
@ -213,7 +213,7 @@ class ContractUpgradeFlowTest {
fun `upgrade Cash to v2`() { fun `upgrade Cash to v2`() {
// Create some cash. // Create some cash.
val chosenIdentity = alice val chosenIdentity = alice
val result = aliceNode.services.startFlow(CashIssueFlow(Amount(1000, USD), OpaqueBytes.of(1), notary)).resultFuture val result = aliceNode.services.startFlow(CashIssueFlow(Amount(1000, USD), OpaqueBytes.of(1), notary))
mockNet.runNetwork() mockNet.runNetwork()
val stx = result.getOrThrow().stx val stx = result.getOrThrow().stx
val anonymisedRecipient = result.get().recipient!! val anonymisedRecipient = result.get().recipient!!
@ -221,7 +221,7 @@ class ContractUpgradeFlowTest {
val baseState = aliceNode.database.transaction { aliceNode.services.vaultService.queryBy<ContractState>().states.single() } val baseState = aliceNode.database.transaction { aliceNode.services.vaultService.queryBy<ContractState>().states.single() }
assertTrue(baseState.state.data is Cash.State, "Contract state is old version.") assertTrue(baseState.state.data is Cash.State, "Contract state is old version.")
// Starts contract upgrade flow. // Starts contract upgrade flow.
val upgradeResult = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(stateAndRef, CashV2::class.java)).resultFuture val upgradeResult = aliceNode.services.startFlow(ContractUpgradeFlow.Initiate(stateAndRef, CashV2::class.java))
mockNet.runNetwork() mockNet.runNetwork()
upgradeResult.getOrThrow() upgradeResult.getOrThrow()
// Get contract state from the vault. // Get contract state from the vault.

View File

@ -53,7 +53,7 @@ class FinalityFlowTests {
val stx = aliceServices.signInitialTransaction(builder) val stx = aliceServices.signInitialTransaction(builder)
val flow = aliceServices.startFlow(FinalityFlow(stx)) val flow = aliceServices.startFlow(FinalityFlow(stx))
mockNet.runNetwork() mockNet.runNetwork()
val notarisedTx = flow.resultFuture.getOrThrow() val notarisedTx = flow.getOrThrow()
notarisedTx.verifyRequiredSignatures() notarisedTx.verifyRequiredSignatures()
val transactionSeenByB = bobServices.database.transaction { val transactionSeenByB = bobServices.database.transaction {
bobServices.validatedTransactions.getTransaction(notarisedTx.id) bobServices.validatedTransactions.getTransaction(notarisedTx.id)
@ -71,7 +71,7 @@ class FinalityFlowTests {
val flow = aliceServices.startFlow(FinalityFlow(stx)) val flow = aliceServices.startFlow(FinalityFlow(stx))
mockNet.runNetwork() mockNet.runNetwork()
assertFailsWith<IllegalArgumentException> { assertFailsWith<IllegalArgumentException> {
flow.resultFuture.getOrThrow() flow.getOrThrow()
} }
} }
} }

View File

@ -52,7 +52,7 @@ class ReceiveMultipleFlowTests {
val flow = nodes[0].services.startFlow(initiatingFlow) val flow = nodes[0].services.startFlow(initiatingFlow)
mockNet.runNetwork() mockNet.runNetwork()
val receivedAnswer = flow.resultFuture.getOrThrow() val receivedAnswer = flow.getOrThrow()
assertThat(receivedAnswer).isEqualTo(answer) assertThat(receivedAnswer).isEqualTo(answer)
} }
@ -64,7 +64,7 @@ class ReceiveMultipleFlowTests {
nodes[2].registerAnswer(AlgorithmDefinition::class, stringValue) nodes[2].registerAnswer(AlgorithmDefinition::class, stringValue)
val flow = nodes[0].services.startFlow(ParallelAlgorithmMap(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity())) val flow = nodes[0].services.startFlow(ParallelAlgorithmMap(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity()))
mockNet.runNetwork() mockNet.runNetwork()
val result = flow.resultFuture.getOrThrow() val result = flow.getOrThrow()
assertThat(result).isEqualTo(doubleValue * stringValue.length) assertThat(result).isEqualTo(doubleValue * stringValue.length)
} }
@ -76,7 +76,7 @@ class ReceiveMultipleFlowTests {
nodes[2].registerAnswer(ParallelAlgorithmList::class, value2) nodes[2].registerAnswer(ParallelAlgorithmList::class, value2)
val flow = nodes[0].services.startFlow(ParallelAlgorithmList(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity())) val flow = nodes[0].services.startFlow(ParallelAlgorithmList(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity()))
mockNet.runNetwork() mockNet.runNetwork()
val data = flow.resultFuture.getOrThrow() val data = flow.getOrThrow()
assertThat(data[0]).isEqualTo(value1) assertThat(data[0]).isEqualTo(value1)
assertThat(data[1]).isEqualTo(value2) assertThat(data[1]).isEqualTo(value2)
assertThat(data.fold(1.0) { a, b -> a * b }).isEqualTo(value1 * value2) assertThat(data.fold(1.0) { a, b -> a * b }).isEqualTo(value1 * value2)

View File

@ -27,13 +27,6 @@ class LegalNameValidatorTest {
} }
} }
@Test
fun `blacklisted words`() {
assertFailsWith(IllegalArgumentException::class) {
LegalNameValidator.validateOrganization("Test Server", LegalNameValidator.Validation.FULL)
}
}
@Test @Test
fun `blacklisted characters`() { fun `blacklisted characters`() {
LegalNameValidator.validateOrganization("Test", LegalNameValidator.Validation.FULL) LegalNameValidator.validateOrganization("Test", LegalNameValidator.Validation.FULL)

View File

@ -52,14 +52,14 @@ class ResolveTransactionsFlowTest {
fun tearDown() { fun tearDown() {
mockNet.stopNodes() mockNet.stopNodes()
} }
// DOCEND 3 // DOCEND 3
// DOCSTART 1 // DOCSTART 1
@Test @Test
fun `resolve from two hashes`() { fun `resolve from two hashes`() {
val (stx1, stx2) = makeTransactions() val (stx1, stx2) = makeTransactions()
val p = TestFlow(setOf(stx2.id), megaCorp) val p = TestFlow(setOf(stx2.id), megaCorp)
val future = miniCorpNode.services.startFlow(p).resultFuture val future = miniCorpNode.services.startFlow(p)
mockNet.runNetwork() mockNet.runNetwork()
val results = future.getOrThrow() val results = future.getOrThrow()
assertEquals(listOf(stx1.id, stx2.id), results.map { it.id }) assertEquals(listOf(stx1.id, stx2.id), results.map { it.id })
@ -74,7 +74,7 @@ class ResolveTransactionsFlowTest {
fun `dependency with an error`() { fun `dependency with an error`() {
val stx = makeTransactions(signFirstTX = false).second val stx = makeTransactions(signFirstTX = false).second
val p = TestFlow(setOf(stx.id), megaCorp) val p = TestFlow(setOf(stx.id), megaCorp)
val future = miniCorpNode.services.startFlow(p).resultFuture val future = miniCorpNode.services.startFlow(p)
mockNet.runNetwork() mockNet.runNetwork()
assertFailsWith(SignedTransaction.SignaturesMissingException::class) { future.getOrThrow() } assertFailsWith(SignedTransaction.SignaturesMissingException::class) { future.getOrThrow() }
} }
@ -83,7 +83,7 @@ class ResolveTransactionsFlowTest {
fun `resolve from a signed transaction`() { fun `resolve from a signed transaction`() {
val (stx1, stx2) = makeTransactions() val (stx1, stx2) = makeTransactions()
val p = TestFlow(stx2, megaCorp) val p = TestFlow(stx2, megaCorp)
val future = miniCorpNode.services.startFlow(p).resultFuture val future = miniCorpNode.services.startFlow(p)
mockNet.runNetwork() mockNet.runNetwork()
future.getOrThrow() future.getOrThrow()
miniCorpNode.database.transaction { miniCorpNode.database.transaction {
@ -108,7 +108,7 @@ class ResolveTransactionsFlowTest {
cursor = stx cursor = stx
} }
val p = TestFlow(setOf(cursor.id), megaCorp, 40) val p = TestFlow(setOf(cursor.id), megaCorp, 40)
val future = miniCorpNode.services.startFlow(p).resultFuture val future = miniCorpNode.services.startFlow(p)
mockNet.runNetwork() mockNet.runNetwork()
assertFailsWith<ResolveTransactionsFlow.ExcessivelyLargeTransactionGraph> { future.getOrThrow() } assertFailsWith<ResolveTransactionsFlow.ExcessivelyLargeTransactionGraph> { future.getOrThrow() }
} }
@ -132,7 +132,7 @@ class ResolveTransactionsFlowTest {
} }
val p = TestFlow(setOf(stx3.id), megaCorp) val p = TestFlow(setOf(stx3.id), megaCorp)
val future = miniCorpNode.services.startFlow(p).resultFuture val future = miniCorpNode.services.startFlow(p)
mockNet.runNetwork() mockNet.runNetwork()
future.getOrThrow() future.getOrThrow()
} }
@ -154,7 +154,7 @@ class ResolveTransactionsFlowTest {
} }
val stx2 = makeTransactions(withAttachment = id).second val stx2 = makeTransactions(withAttachment = id).second
val p = TestFlow(stx2, megaCorp) val p = TestFlow(stx2, megaCorp)
val future = miniCorpNode.services.startFlow(p).resultFuture val future = miniCorpNode.services.startFlow(p)
mockNet.runNetwork() mockNet.runNetwork()
future.getOrThrow() future.getOrThrow()

View File

@ -114,6 +114,7 @@ class AttachmentSerializationTest {
private class CustomAttachment(override val id: SecureHash, internal val customContent: String) : Attachment { private class CustomAttachment(override val id: SecureHash, internal val customContent: String) : Attachment {
override fun open() = throw UnsupportedOperationException("Not implemented.") override fun open() = throw UnsupportedOperationException("Not implemented.")
override val signers get() = throw UnsupportedOperationException() override val signers get() = throw UnsupportedOperationException()
override val size get() = throw UnsupportedOperationException()
} }
private class CustomAttachmentLogic(serverIdentity: Party, private val attachmentId: SecureHash, private val customContent: String) : ClientLogic(serverIdentity) { private class CustomAttachmentLogic(serverIdentity: Party, private val attachmentId: SecureHash, private val customContent: String) : ClientLogic(serverIdentity) {

View File

@ -0,0 +1,45 @@
package net.corda.core.utilities
import net.corda.core.internal.declaredField
import org.assertj.core.api.Assertions.catchThrowable
import org.junit.Assert.assertSame
import org.junit.Test
import java.nio.ByteBuffer
import java.nio.ReadOnlyBufferException
import kotlin.test.assertEquals
class ByteArraysTest {
@Test
fun `slice works`() {
byteArrayOf(9, 9, 0, 1, 2, 3, 4, 9, 9).let {
sliceWorksImpl(it, OpaqueBytesSubSequence(it, 2, 5))
}
byteArrayOf(0, 1, 2, 3, 4).let {
sliceWorksImpl(it, OpaqueBytes(it))
}
}
private fun sliceWorksImpl(array: ByteArray, seq: ByteSequence) {
// Python-style negative indices can be implemented later if needed:
assertSame(IllegalArgumentException::class.java, catchThrowable { seq.slice(-1) }.javaClass)
assertSame(IllegalArgumentException::class.java, catchThrowable { seq.slice(end = -1) }.javaClass)
fun check(expected: ByteArray, actual: ByteBuffer) {
assertEquals(ByteBuffer.wrap(expected), actual)
assertSame(ReadOnlyBufferException::class.java, catchThrowable { actual.array() }.javaClass)
assertSame(array, actual.declaredField<ByteArray>(ByteBuffer::class, "hb").value)
}
check(byteArrayOf(0, 1, 2, 3, 4), seq.slice())
check(byteArrayOf(0, 1, 2, 3, 4), seq.slice(0, 5))
check(byteArrayOf(0, 1, 2, 3, 4), seq.slice(0, 6))
check(byteArrayOf(0, 1, 2, 3), seq.slice(0, 4))
check(byteArrayOf(1, 2, 3), seq.slice(1, 4))
check(byteArrayOf(1, 2, 3, 4), seq.slice(1, 5))
check(byteArrayOf(1, 2, 3, 4), seq.slice(1, 6))
check(byteArrayOf(4), seq.slice(4))
check(byteArrayOf(), seq.slice(5))
check(byteArrayOf(), seq.slice(6))
check(byteArrayOf(2), seq.slice(2, 3))
check(byteArrayOf(), seq.slice(2, 2))
check(byteArrayOf(), seq.slice(2, 1))
}
}

View File

@ -1,6 +1,7 @@
package net.corda.core.utilities package net.corda.core.utilities
import net.corda.core.crypto.AddressFormatException import net.corda.core.crypto.AddressFormatException
import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.fail import kotlin.test.fail
@ -23,10 +24,9 @@ class EncodingUtilsTest {
@Test @Test
fun `empty encoding`() { fun `empty encoding`() {
val emptyByteArray = ByteArray(0) assertEquals("", EMPTY_BYTE_ARRAY.toBase58())
assertEquals("", emptyByteArray.toBase58()) assertEquals("", EMPTY_BYTE_ARRAY.toBase64())
assertEquals("", emptyByteArray.toBase64()) assertEquals("", EMPTY_BYTE_ARRAY.toHex())
assertEquals("", emptyByteArray.toHex())
} }
@Test @Test

View File

@ -411,7 +411,6 @@ Our side of the flow must mirror these calls. We could do this as follows:
Subflows Subflows
-------- --------
Subflows are pieces of reusable flows that may be run by calling ``FlowLogic.subFlow``. There are two broad categories Subflows are pieces of reusable flows that may be run by calling ``FlowLogic.subFlow``. There are two broad categories
of subflows, inlined and initiating ones. The main difference lies in the counter-flow's starting method, initiating of subflows, inlined and initiating ones. The main difference lies in the counter-flow's starting method, initiating
ones initiate counter-flows automatically, while inlined ones expect some parent counter-flow to run the inlined ones initiate counter-flows automatically, while inlined ones expect some parent counter-flow to run the inlined
@ -419,7 +418,6 @@ counter-part.
Inlined subflows Inlined subflows
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
Inlined subflows inherit their calling flow's type when initiating a new session with a counterparty. For example, say Inlined subflows inherit their calling flow's type when initiating a new session with a counterparty. For example, say
we have flow A calling an inlined subflow B, which in turn initiates a session with a party. The FlowLogic type used to we have flow A calling an inlined subflow B, which in turn initiates a session with a party. The FlowLogic type used to
determine which counter-flow should be kicked off will be A, not B. Note that this means that the other side of this determine which counter-flow should be kicked off will be A, not B. Note that this means that the other side of this
@ -437,7 +435,6 @@ In the code inlined subflows appear as regular ``FlowLogic`` instances, `without
Initiating subflows Initiating subflows
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
Initiating subflows are ones annotated with the ``@InitiatingFlow`` annotation. When such a flow initiates a session its Initiating subflows are ones annotated with the ``@InitiatingFlow`` annotation. When such a flow initiates a session its
type will be used to determine which ``@InitiatedBy`` flow to kick off on the counterparty. type will be used to determine which ``@InitiatedBy`` flow to kick off on the counterparty.
@ -446,32 +443,38 @@ An example is the ``@InitiatingFlow InitiatorFlow``/``@InitiatedBy ResponderFlow
.. note:: Initiating flows are versioned separately from their parents. .. note:: Initiating flows are versioned separately from their parents.
Core initiating subflows Core initiating subflows
^^^^^^^^^^^^^^^^^^^^^^^^ ~~~~~~~~~~~~~~~~~~~~~~~~
Corda-provided initiating subflows are a little different to standard ones as they are versioned together with the Corda-provided initiating subflows are a little different to standard ones as they are versioned together with the
platform, and their initiated counter-flows are registered explicitly, so there is no need for the ``InitiatedBy`` platform, and their initiated counter-flows are registered explicitly, so there is no need for the ``InitiatedBy``
annotation. annotation.
An example is the ``FinalityFlow``/``FinalityHandler`` flow pair. Library flows
^^^^^^^^^^^^^
Corda installs four initiating subflow pairs on each node by default:
Built-in subflows * ``FinalityFlow``/``FinalityHandler``, which should be used to notarise and record a transaction and broadcast it to
^^^^^^^^^^^^^^^^^ all relevant parties
* ``NotaryChangeFlow``/``NotaryChangeHandler``, which should be used to change a state's notary
* ``ContractUpgradeFlow.Initiate``/``ContractUpgradeHandler``, which should be used to change a state's contract
* ``SwapIdentitiesFlow``/``SwapIdentitiesHandler``, which is used to exchange confidential identities with a
counterparty
Corda provides a number of built-in flows that should be used for handling common tasks. The most important are: .. warning:: ``SwapIdentitiesFlow``/``SwapIdentitiesHandler`` are only installed if the ``confidential-identities`` module
is included. The ``confidential-identities`` module is still not stabilised, so the
``SwapIdentitiesFlow``/``SwapIdentitiesHandler`` API may change in future releases. See :doc:`corda-api`.
Corda also provides a number of built-in inlined subflows that should be used for handling common tasks. The most
important are:
* ``CollectSignaturesFlow`` (inlined), which should be used to collect a transaction's required signatures * ``CollectSignaturesFlow`` (inlined), which should be used to collect a transaction's required signatures
* ``FinalityFlow`` (initiating), which should be used to notarise and record a transaction as well as to broadcast it to
all relevant parties
* ``SendTransactionFlow`` (inlined), which should be used to send a signed transaction if it needed to be resolved on * ``SendTransactionFlow`` (inlined), which should be used to send a signed transaction if it needed to be resolved on
the other side. the other side.
* ``ReceiveTransactionFlow`` (inlined), which should be used receive a signed transaction * ``ReceiveTransactionFlow`` (inlined), which should be used receive a signed transaction
* ``ContractUpgradeFlow`` (initiating), which should be used to change a state's contract
* ``NotaryChangeFlow`` (initiating), which should be used to change a state's notary
Let's look at three very common examples. Let's look at some of these flows in more detail.
FinalityFlow FinalityFlow
^^^^^^^^^^^^ ~~~~~~~~~~~~
``FinalityFlow`` allows us to notarise the transaction and get it recorded in the vault of the participants of all ``FinalityFlow`` allows us to notarise the transaction and get it recorded in the vault of the participants of all
the transaction's states: the transaction's states:
@ -509,7 +512,7 @@ Only one party has to call ``FinalityFlow`` for a given transaction to be record
**not** need to be called by each participant individually. **not** need to be called by each participant individually.
CollectSignaturesFlow/SignTransactionFlow CollectSignaturesFlow/SignTransactionFlow
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The list of parties who need to sign a transaction is dictated by the transaction's commands. Once we've signed a The list of parties who need to sign a transaction is dictated by the transaction's commands. Once we've signed a
transaction ourselves, we can automatically gather the signatures of the other required signers using transaction ourselves, we can automatically gather the signatures of the other required signers using
``CollectSignaturesFlow``: ``CollectSignaturesFlow``:
@ -546,7 +549,7 @@ transaction and provide their signature if they are satisfied:
:dedent: 12 :dedent: 12
SendTransactionFlow/ReceiveTransactionFlow SendTransactionFlow/ReceiveTransactionFlow
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Verifying a transaction received from a counterparty also requires verification of every transaction in its Verifying a transaction received from a counterparty also requires verification of every transaction in its
dependency chain. This means the receiving party needs to be able to ask the sender all the details of the chain. dependency chain. This means the receiving party needs to be able to ask the sender all the details of the chain.
The sender will use ``SendTransactionFlow`` for sending the transaction and then for processing all subsequent The sender will use ``SendTransactionFlow`` for sending the transaction and then for processing all subsequent
@ -601,7 +604,6 @@ We can also send and receive a ``StateAndRef`` dependency chain and automaticall
Why inlined subflows? Why inlined subflows?
^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
Inlined subflows provide a way to share commonly used flow code `while forcing users to create a parent flow`. Take for Inlined subflows provide a way to share commonly used flow code `while forcing users to create a parent flow`. Take for
example ``CollectSignaturesFlow``. Say we made it an initiating flow that automatically kicks off example ``CollectSignaturesFlow``. Say we made it an initiating flow that automatically kicks off
``SignTransactionFlow`` that signs the transaction. This would mean malicious nodes can just send any old transaction to ``SignTransactionFlow`` that signs the transaction. This would mean malicious nodes can just send any old transaction to

View File

@ -3,6 +3,9 @@ API: Identity
.. note:: Before reading this page, you should be familiar with the key concepts of :doc:`key-concepts-identity`. .. note:: Before reading this page, you should be familiar with the key concepts of :doc:`key-concepts-identity`.
.. warning:: The ``confidential-identities`` module is still not stabilised, so this API may change in future releases.
See :doc:`corda-api`.
.. contents:: .. contents::
Party Party

View File

@ -6,6 +6,8 @@ from previous releases. Please refer to :doc:`upgrade-notes` for detailed instru
UNRELEASED UNRELEASED
---------- ----------
* Removed blacklisted word checks in Corda X.500 name to allow "Server" or "Node" to be use as part of the legal name.
* Separated our pre-existing Artemis broker into an RPC broker and a P2P broker. * Separated our pre-existing Artemis broker into an RPC broker and a P2P broker.
* Refactored ``NodeConfiguration`` to expose ``NodeRpcOptions`` (using top-level "rpcAddress" property still works with warning). * Refactored ``NodeConfiguration`` to expose ``NodeRpcOptions`` (using top-level "rpcAddress" property still works with warning).
@ -84,6 +86,9 @@ R3 Corda 3.0 Developer Preview
* Moved ``NodeInfoSchema`` to internal package as the node info's database schema is not part of the public API. This * Moved ``NodeInfoSchema`` to internal package as the node info's database schema is not part of the public API. This
was needed to allow changes to the schema. was needed to allow changes to the schema.
* Introduced max transaction size limit on transactions. The max transaction size parameter is set by the compatibility zone
operator. The parameter is distributed to Corda nodes by network map service as part of the ``NetworkParameters``.
* Support for external user credentials data source and password encryption [CORDA-827]. * Support for external user credentials data source and password encryption [CORDA-827].
* Integrate database migration tool: http://www.liquibase.org/ : * Integrate database migration tool: http://www.liquibase.org/ :
@ -196,6 +201,14 @@ R3 Corda 3.0 Developer Preview
* Move to a message based control of peer to peer bridge formation to allow for future out of process bridging components. * Move to a message based control of peer to peer bridge formation to allow for future out of process bridging components.
This removes the legacy Artemis bridges completely, so the ``useAMQPBridges`` configuration property has been removed. This removes the legacy Artemis bridges completely, so the ``useAMQPBridges`` configuration property has been removed.
* A ``CordaInternal`` attribute has been added to identify properties that are not intended to form part of the
public api and as such are not intended for public use. This is alongside the existing ``DoNotImplement`` attribute for classes which
provide Corda functionality to user applications, but should not be implemented by consumers, and any classes which
are defined in ``.internal`` packages, which are also not for public use.
* Marked ``stateMachine`` on ``FlowLogic`` as ``CordaInternal`` to make clear that is it not part of the public api and is
only for internal use
.. _changelog_v2: .. _changelog_v2:
Corda 2.0 Corda 2.0

View File

@ -104,6 +104,6 @@ class TutorialMockNetwork {
expectedEx.expect(IllegalArgumentException::class.java) expectedEx.expect(IllegalArgumentException::class.java)
expectedEx.expectMessage("Expected to receive 1") expectedEx.expectMessage("Expected to receive 1")
initiatingReceiveFlow.resultFuture.getOrThrow() initiatingReceiveFlow.getOrThrow()
} }
} }

View File

@ -60,7 +60,7 @@ class CustomVaultQueryTest {
OpaqueBytes.of(0x01), OpaqueBytes.of(0x01),
notary)) notary))
// Wait for the flow to stop and print // Wait for the flow to stop and print
flowHandle1.resultFuture.getOrThrow() flowHandle1.getOrThrow()
} }
private fun topUpCurrencies() { private fun topUpCurrencies() {
@ -69,7 +69,7 @@ class CustomVaultQueryTest {
OpaqueBytes.of(0x01), OpaqueBytes.of(0x01),
nodeA.info.chooseIdentity(), nodeA.info.chooseIdentity(),
notary)) notary))
flowHandle1.resultFuture.getOrThrow() flowHandle1.getOrThrow()
} }
private fun getBalances(): Pair<Map<Currency, Amount<Currency>>, Map<Currency, Amount<Currency>>> { private fun getBalances(): Pair<Map<Currency, Amount<Currency>>, Map<Currency, Amount<Currency>>> {

View File

@ -43,7 +43,7 @@ class FxTransactionBuildTutorialTest {
OpaqueBytes.of(0x01), OpaqueBytes.of(0x01),
notary)) notary))
// Wait for the flow to stop and print // Wait for the flow to stop and print
flowHandle1.resultFuture.getOrThrow() flowHandle1.getOrThrow()
printBalances() printBalances()
// Using NodeB as Issuer create some pounds. // Using NodeB as Issuer create some pounds.
@ -51,7 +51,7 @@ class FxTransactionBuildTutorialTest {
OpaqueBytes.of(0x01), OpaqueBytes.of(0x01),
notary)) notary))
// Wait for flow to come to an end and print // Wait for flow to come to an end and print
flowHandle2.resultFuture.getOrThrow() flowHandle2.getOrThrow()
printBalances() printBalances()
// Setup some futures on the vaults to await the arrival of the exchanged funds at both nodes // Setup some futures on the vaults to await the arrival of the exchanged funds at both nodes
@ -65,7 +65,7 @@ class FxTransactionBuildTutorialTest {
nodeB.info.chooseIdentity(), nodeB.info.chooseIdentity(),
weAreBaseCurrencySeller = false)) weAreBaseCurrencySeller = false))
// wait for the flow to finish and the vault updates to be done // wait for the flow to finish and the vault updates to be done
doIt.resultFuture.getOrThrow() doIt.getOrThrow()
// Get the balances when the vault updates // Get the balances when the vault updates
nodeAVaultUpdate.get() nodeAVaultUpdate.get()
val balancesA = nodeA.database.transaction { val balancesA = nodeA.database.transaction {

View File

@ -56,7 +56,7 @@ class WorkflowTransactionBuildTutorialTest {
// Kick of the proposal flow // Kick of the proposal flow
val flow1 = aliceServices.startFlow(SubmitTradeApprovalFlow("1234", bob)) val flow1 = aliceServices.startFlow(SubmitTradeApprovalFlow("1234", bob))
// Wait for the flow to finish // Wait for the flow to finish
val proposalRef = flow1.resultFuture.getOrThrow() val proposalRef = flow1.getOrThrow()
val proposalLinearId = proposalRef.state.data.linearId val proposalLinearId = proposalRef.state.data.linearId
// Wait for NodeB to include it's copy in the vault // Wait for NodeB to include it's copy in the vault
nodeBVaultUpdate.get() nodeBVaultUpdate.get()
@ -80,7 +80,7 @@ class WorkflowTransactionBuildTutorialTest {
// Run the manual completion flow from NodeB // Run the manual completion flow from NodeB
val flow2 = bobServices.startFlow(SubmitCompletionFlow(latestFromB.ref, WorkflowState.APPROVED)) val flow2 = bobServices.startFlow(SubmitCompletionFlow(latestFromB.ref, WorkflowState.APPROVED))
// wait for the flow to end // wait for the flow to end
val completedRef = flow2.resultFuture.getOrThrow() val completedRef = flow2.getOrThrow()
// wait for the vault updates to stabilise // wait for the vault updates to stabilise
nodeAVaultUpdate.get() nodeAVaultUpdate.get()
secondNodeBVaultUpdate.get() secondNodeBVaultUpdate.get()

View File

@ -63,7 +63,6 @@ The name must also obey the following constraints:
* The organisation field of the name also obeys the following constraints: * The organisation field of the name also obeys the following constraints:
* No double-spacing * No double-spacing
* Does not contain the words "node" or "server"
* This is to avoid right-to-left issues, debugging issues when we can't pronounce names over the phone, and * This is to avoid right-to-left issues, debugging issues when we can't pronounce names over the phone, and
character confusability attacks character confusability attacks

View File

@ -22,6 +22,8 @@ The set of REST end-points for the network map service are as follows.
+================+=========================================+==============================================================================================================================================+ +================+=========================================+==============================================================================================================================================+
| POST | /network-map/publish | For the node to upload its signed ``NodeInfo`` object to the network map. | | POST | /network-map/publish | For the node to upload its signed ``NodeInfo`` object to the network map. |
+----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+ +----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+
| POST | /network-map/ack-parameters | For the node operator to acknowledge network map that new parameters were accepted for future update. |
+----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+
| GET | /network-map | Retrieve the current signed network map object. The entire object is signed with the network map certificate which is also attached. | | GET | /network-map | Retrieve the current signed network map object. The entire object is signed with the network map certificate which is also attached. |
+----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+ +----------------+-----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------+
| GET | /network-map/node-info/{hash} | Retrieve a signed ``NodeInfo`` as specified in the network map object. | | GET | /network-map/node-info/{hash} | Retrieve a signed ``NodeInfo`` as specified in the network map object. |
@ -69,6 +71,7 @@ The current set of network parameters:
:maxMessageSize: Maximum allowed size in bytes of an individual message sent over the wire. Note that attachments are :maxMessageSize: Maximum allowed size in bytes of an individual message sent over the wire. Note that attachments are
a special case and may be fragmented for streaming transfer, however, an individual transaction or flow message a special case and may be fragmented for streaming transfer, however, an individual transaction or flow message
may not be larger than this value. may not be larger than this value.
:maxTransactionSize: Maximum allowed size in bytes of a transaction. This is the size of the transaction object and its attachments.
:modifiedTime: The time when the network parameters were last modified by the compatibility zone operator. :modifiedTime: The time when the network parameters were last modified by the compatibility zone operator.
:epoch: Version number of the network parameters. Starting from 1, this will always increment whenever any of the :epoch: Version number of the network parameters. Starting from 1, this will always increment whenever any of the
parameters change. parameters change.
@ -77,3 +80,35 @@ More parameters will be added in future releases to regulate things like allowed
offline before it is evicted from the zone, whether or not IPv6 connectivity is required for zone members, required offline before it is evicted from the zone, whether or not IPv6 connectivity is required for zone members, required
cryptographic algorithms and rollout schedules (e.g. for moving to post quantum cryptography), parameters related to cryptographic algorithms and rollout schedules (e.g. for moving to post quantum cryptography), parameters related to
SGX and so on. SGX and so on.
Network parameters update process
---------------------------------
In case of the need to change network parameters Corda zone operator will start the update process. There are many reasons
that may lead to this decision: we discovered that some new fields have to be added to enable smooth network interoperability or change
of the existing compatibility constants is required due to upgrade or security reasons.
To synchronize all nodes in the compatibility zone to use the new set of the network parameters two RPC methods exist. The process
requires human interaction and approval of the change.
When the update is about to happen the network map service starts to advertise the additional information with the usual network map
data. It includes new network parameters hash, description of the change and the update deadline. Node queries network map server
for the new set of parameters and emits ``ParametersUpdateInfo`` via ``CordaRPCOps::networkParametersFeed`` method to inform
node operator about the event.
.. container:: codeset
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
:language: kotlin
:start-after: DOCSTART 1
:end-before: DOCEND 1
Node administrator can review the change and decide if is going to accept it. The approval should be done before ``updateDeadline``.
Nodes that don't approve before the deadline will be removed from the network map.
If the network operator starts advertising a different set of new parameters then that new set overrides the previous set. Only the latest update can be accepted.
To send back parameters approval to the zone operator RPC method ``fun acceptNewNetworkParameters(parametersHash: SecureHash)``
has to be called with ``parametersHash`` from update. Notice that the process cannot be undone.
Next time the node polls network map after the deadline the advertised network parameters will be the updated ones. Previous set
of parameters will no longer be valid. At this point the node will automatically shutdown and will require the node operator
to bring it back again.

View File

@ -19,7 +19,7 @@ Start the nodes with ``runnodes`` by running the following command from the root
* Linux/macOS: ``build/nodes/runnodes`` * Linux/macOS: ``build/nodes/runnodes``
* Windows: ``call build\nodes\runnodes.bat`` * Windows: ``call build\nodes\runnodes.bat``
.. warn:: On macOS, do not click/change focus until all the node terminal windows have opened, or some processes may .. warning:: On macOS, do not click/change focus until all the node terminal windows have opened, or some processes may
fail to start. fail to start.
If you receive an ``OutOfMemoryError`` exception when interacting with the nodes, you need to increase the amount of If you receive an ``OutOfMemoryError`` exception when interacting with the nodes, you need to increase the amount of

View File

@ -80,10 +80,14 @@ searching the vault via the ``VaultService`` interface on the
To give a few more specific details consider two simplified real world To give a few more specific details consider two simplified real world
scenarios. First, a basic foreign exchange cash transaction. This scenarios. First, a basic foreign exchange cash transaction. This
transaction needs to locate a set of funds to exchange. A flow transaction needs to locate a set of funds to exchange. A flow
modelling this is implemented in ``FxTransactionBuildTutorial.kt``. modelling this is implemented in ``FxTransactionBuildTutorial.kt``
(see ``docs/source/example-code/src/main/kotlin/net/corda/docs/FxTransactionBuildTutorial.kt`` in the
`main Corda repo <https://github.com/corda/corda>`_).
Second, a simple business model in which parties manually accept or Second, a simple business model in which parties manually accept or
reject each other's trade proposals, which is implemented in reject each other's trade proposals, which is implemented in
``WorkflowTransactionBuildTutorial.kt``. To run and explore these ``WorkflowTransactionBuildTutorial.kt`` (see
``docs/source/example-code/src/main/kotlin/net/corda/docs/WorkflowTransactionBuildTutorial.kt`` in the
`main Corda repo <https://github.com/corda/corda>`_). To run and explore these
examples using the IntelliJ IDE one can run/step through the respective unit examples using the IntelliJ IDE one can run/step through the respective unit
tests in ``FxTransactionBuildTutorialTest.kt`` and tests in ``FxTransactionBuildTutorialTest.kt`` and
``WorkflowTransactionBuildTutorialTest.kt``, which drive the flows as ``WorkflowTransactionBuildTutorialTest.kt``, which drive the flows as

View File

@ -9,13 +9,12 @@ The example CorDapp
.. contents:: .. contents::
The example CorDapp allows nodes to agree IOUs with each other. Nodes will always agree to the creation of a new IOU The example CorDapp allows nodes to agree IOUs with each other, as long as they obey the following contract rules:
if:
* Its value is strictly positive * The IOU's value is strictly positive
* The node is not trying to issue the IOU to itself * A node is not trying to issue an IOU to itself
We will deploy the CorDapp on 4 test nodes: We will deploy and run the CorDapp on four test nodes:
* **NetworkMapAndNotary**, which hosts a validating notary service * **NetworkMapAndNotary**, which hosts a validating notary service
* **PartyA** * **PartyA**
@ -27,7 +26,7 @@ facts" between PartyA and PartyB only. PartyC won't be aware of these IOUs.
Downloading the example CorDapp Downloading the example CorDapp
------------------------------- -------------------------------
We need to download the example CorDapp from GitHub. Start by downloading the example CorDapp from GitHub:
* Set up your machine by following the :doc:`quickstart guide <getting-set-up>` * Set up your machine by following the :doc:`quickstart guide <getting-set-up>`
@ -36,14 +35,11 @@ We need to download the example CorDapp from GitHub.
* Change directories to the freshly cloned repo: ``cd cordapp-example`` * Change directories to the freshly cloned repo: ``cd cordapp-example``
.. note:: If you wish to build off the latest, unstable version of the codebase, follow the instructions in
:doc:`building against Master <building-against-master>` instead.
Opening the example CorDapp in IntelliJ Opening the example CorDapp in IntelliJ
--------------------------------------- ---------------------------------------
Let's open the example CorDapp in IntelliJ IDEA. Let's open the example CorDapp in IntelliJ IDEA:
**If opening a fresh IntelliJ instance** **If opening a fresh IntelliJ instance**:
* Open IntelliJ * Open IntelliJ
* A dialogue box will appear: * A dialogue box will appear:
@ -60,12 +56,12 @@ Let's open the example CorDapp in IntelliJ IDEA.
* Click the 'import gradle project' link. Press OK on the dialogue that pops up * Click the 'import gradle project' link. Press OK on the dialogue that pops up
* Gradle will now download all the project dependencies and perform some indexing. This usually takes a minute or so. * Gradle will now download all the project dependencies and perform some indexing. This usually takes a minute or so
* If the 'import gradle project' pop-up does not appear, click the small green speech bubble at the bottom-right of * If the 'import gradle project' pop-up does not appear, click the small green speech bubble at the bottom-right of
the IDE, or simply close and re-open IntelliJ again to make it reappear. the IDE, or simply close and re-open IntelliJ again to make it reappear
**If you already have IntelliJ open** **If you already have IntelliJ open**:
* Open the ``File`` menu * Open the ``File`` menu
@ -76,8 +72,8 @@ Let's open the example CorDapp in IntelliJ IDEA.
* Click OK * Click OK
Project structure Project structure
----------------- ~~~~~~~~~~~~~~~~~
The example CorDapp has the following directory structure: The example CorDapp has the following structure:
.. sourcecode:: none .. sourcecode:: none
@ -175,11 +171,11 @@ There are two ways to run the example CorDapp:
* Via the terminal * Via the terminal
* Via IntelliJ * Via IntelliJ
In both cases, we will deploy a set of test nodes with our CorDapp installed, then run the nodes. You can read more Both approaches will create a set of test nodes, install the CorDapp on these nodes, and then run the nodes. You can
about how we define the nodes to be deployed :doc:`here <generating-a-node>`. read more about how we generate nodes :doc:`here <generating-a-node>`.
Terminal Running the example CorDapp from the terminal
~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Building the example CorDapp Building the example CorDapp
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -191,29 +187,26 @@ Building the example CorDapp
* Windows: ``gradlew.bat deployNodes`` * Windows: ``gradlew.bat deployNodes``
This will automatically build four pre-configured nodes with our CorDapp installed. These nodes are meant for local This will automatically build four nodes with our CorDapp already installed
testing only
.. note:: CorDapps can be written in any language targeting the JVM. In our case, we've provided the example source in .. note:: CorDapps can be written in any language targeting the JVM. In our case, we've provided the example source in
both Kotlin (``/kotlin-source/src``) and Java (``/java-source/src``) Since both sets of source files are both Kotlin (``/kotlin-source/src``) and Java (``/java-source/src``). Since both sets of source files are
functionally identical, we will refer to the Kotlin build throughout the documentation. functionally identical, we will refer to the Kotlin version throughout the documentation.
* After the build process has finished, you will see the newly-build nodes in the ``kotlin-source/build/nodes`` folder * After the build finishes, you will see the generated nodes in the ``kotlin-source/build/nodes`` folder
* There will be one folder generated for each node you built, plus a ``runnodes`` shell script (or batch file on * There will be a folder for each generated node, plus a ``runnodes`` shell script (or batch file on Windows) to run
Windows) to run all the nodes simultaneously all the nodes simultaneously
* Each node in the ``nodes`` folder has the following structure: * Each node in the ``nodes`` folder has the following structure:
.. sourcecode:: none .. sourcecode:: none
. nodeName . nodeName
├── corda.jar ├── corda.jar // The Corda node runtime.
├── node.conf ├── corda-webserver.jar // The node development webserver.
── cordapps ── node.conf // The node configuration file.
└── cordapps // The node's CorDapps.
``corda.jar`` is the Corda runtime, ``cordapps`` contains our node's CorDapps, and the node's configuration is
given by ``node.conf``
Running the example CorDapp Running the example CorDapp
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -222,7 +215,7 @@ Start the nodes by running the following command from the root of the ``cordapp-
* Unix/Mac OSX: ``kotlin-source/build/nodes/runnodes`` * Unix/Mac OSX: ``kotlin-source/build/nodes/runnodes``
* Windows: ``call kotlin-source\build\nodes\runnodes.bat`` * Windows: ``call kotlin-source\build\nodes\runnodes.bat``
.. warn:: On Unix/Mac OSX, do not click/change focus until all seven additional terminal windows have opened, or some .. warning:: On Unix/Mac OSX, do not click/change focus until all seven additional terminal windows have opened, or some
nodes may fail to start. nodes may fail to start.
For each node, the ``runnodes`` script creates a node tab/window: For each node, the ``runnodes`` script creates a node tab/window:
@ -239,7 +232,7 @@ For each node, the ``runnodes`` script creates a node tab/window:
📚 New! Training now available worldwide, see https://corda.net/corda-training/ 📚 New! Training now available worldwide, see https://corda.net/corda-training/
Logs can be found in : /Users/joeldudley/Desktop/cordapp-example/kotlin-source/build/nodes/PartyA/logs Logs can be found in : /Users/username/Desktop/cordapp-example/kotlin-source/build/nodes/PartyA/logs
Database connection url is : jdbc:h2:tcp://10.163.199.132:54763/node Database connection url is : jdbc:h2:tcp://10.163.199.132:54763/node
Listening on address : 127.0.0.1:10005 Listening on address : 127.0.0.1:10005
RPC service listening on address : localhost:10006 RPC service listening on address : localhost:10006
@ -256,16 +249,16 @@ For every node except the network map/notary, the script also creates a webserve
.. sourcecode:: none .. sourcecode:: none
Logs can be found in /Users/joeldudley/Desktop/cordapp-example/kotlin-source/build/nodes/PartyA/logs/web Logs can be found in /Users/username/Desktop/cordapp-example/kotlin-source/build/nodes/PartyA/logs/web
Starting as webserver: localhost:10007 Starting as webserver: localhost:10007
Webserver started up in 42.02 sec Webserver started up in 42.02 sec
It usually takes around 60 seconds for the nodes to finish starting up. To ensure that all the nodes are running OK, It usually takes around 60 seconds for the nodes to finish starting up. To ensure that all the nodes are running, you
you can query the 'status' end-point located at ``http://localhost:[port]/api/status`` (e.g. can query the 'status' end-point located at ``http://localhost:[port]/api/status`` (e.g.
``http://localhost:10007/api/status`` for ``PartyA``). ``http://localhost:10007/api/status`` for ``PartyA``).
IntelliJ Running the example CorDapp from IntelliJ
~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Select the ``Run Example CorDapp - Kotlin`` run configuration from the drop-down menu at the top right-hand side of * Select the ``Run Example CorDapp - Kotlin`` run configuration from the drop-down menu at the top right-hand side of
the IDE the IDE
@ -274,66 +267,38 @@ IntelliJ
.. image:: resources/run-config-drop-down.png .. image:: resources/run-config-drop-down.png
:width: 400 :width: 400
The node driver defined in ``/src/test/kotlin/com/example/Main.kt`` allows you to specify how many nodes you would like
to run and the configuration settings for each node. For the example CorDapp, the driver starts up four nodes
and adds an RPC user for all but the network map/notary node:
.. sourcecode:: kotlin
fun main(args: Array<String>) {
// No permissions required as we are not invoking flows.
val user = User("user1", "test", permissions = setOf())
driver(isDebug = true, waitForNodesToFinish = true) {
startNode(getX500Name(O="NetworkMapAndNotary",L="London",C='GB"), setOf(ServiceInfo(ValidatingNotaryService.type)))
val (nodeA, nodeB, nodeC) = Futures.allAsList(
startNode(getX500Name(O="PartyA",L="London",C="GB"), rpcUsers = listOf(user)),
startNode(getX500Name(O="PartyB",L="New York",C="US"), rpcUsers = listOf(user)),
startNode(getX500Name(O="PartyC",L="Paris",C="FR"), rpcUsers = listOf(user))).getOrThrow()
startWebserver(nodeA)
startWebserver(nodeB)
startWebserver(nodeC)
}
}
* To stop the nodes, press the red square button at the top right-hand side of the IDE, next to the run configurations * To stop the nodes, press the red square button at the top right-hand side of the IDE, next to the run configurations
Later, we'll look at how the node driver can be useful for `debugging your CorDapp`_.
Interacting with the example CorDapp Interacting with the example CorDapp
------------------------------------ ------------------------------------
Via HTTP Via HTTP
~~~~~~~~ ~~~~~~~~
The CorDapp defines several HTTP API end-points and a web front-end. The end-points allow you to list the IOUs a node The nodes' webservers run locally on the following ports:
is involved in, agree new IOUs, and see who is on the network.
The nodes are running locally on the following ports:
* PartyA: ``localhost:10007`` * PartyA: ``localhost:10007``
* PartyB: ``localhost:10010`` * PartyB: ``localhost:10010``
* PartyC: ``localhost:10013`` * PartyC: ``localhost:10013``
These ports are defined in build.gradle and in each node's node.conf file under ``kotlin-source/build/nodes/NodeX``. These ports are defined in each node's node.conf file under ``kotlin-source/build/nodes/NodeX/node.conf``.
As the nodes start up, they should tell you which port their embedded web server is running on. The available API Each node webserver exposes the following endpoints:
endpoints are:
* ``/api/example/me`` * ``/api/example/me``
* ``/api/example/peers`` * ``/api/example/peers``
* ``/api/example/ious`` * ``/api/example/ious``
* ``/api/example/create-iou`` with parameters ``iouValue`` and ``partyName`` which is CN name of a node * ``/api/example/create-iou`` with parameters ``iouValue`` and ``partyName`` which is CN name of a node
The web front-end is served from ``/web/example``. There is also a web front-end served from ``/web/example``.
An IOU can be created by sending a PUT request to the ``api/example/create-iou`` end-point directly, or by using the
the web form hosted at ``/web/example``.
.. warning:: The content in ``web/example`` is only available for demonstration purposes and does not implement .. warning:: The content in ``web/example`` is only available for demonstration purposes and does not implement
anti-XSS, anti-XSRF or any other security techniques. Do not use this code in production. anti-XSS, anti-XSRF or other security techniques. Do not use this code in production.
Creating an IOU via the endpoint Creating an IOU via the endpoint
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
An IOU can be created by sending a PUT request to the ``api/example/create-iou`` endpoint directly, or by using the
the web form served from ``/web/example``.
To create an IOU between PartyA and PartyB, run the following command from the command line: To create an IOU between PartyA and PartyB, run the following command from the command line:
.. sourcecode:: bash .. sourcecode:: bash
@ -356,8 +321,8 @@ of the page, and enter the IOU details into the web-form. The IOU must have a po
And click submit. Upon clicking submit, the modal dialogue will close, and the nodes will agree the IOU. And click submit. Upon clicking submit, the modal dialogue will close, and the nodes will agree the IOU.
Once an IOU has been submitted Checking the output
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
Assuming all went well, you should see some activity in PartyA's web-server terminal window: Assuming all went well, you should see some activity in PartyA's web-server terminal window:
.. sourcecode:: none .. sourcecode:: none
@ -387,7 +352,7 @@ You can view the newly-created IOU by accessing the vault of PartyA or PartyB:
* PartyA: Navigate to http://localhost:10007/web/example and hit the "refresh" button * PartyA: Navigate to http://localhost:10007/web/example and hit the "refresh" button
* PartyA: Navigate to http://localhost:10010/web/example and hit the "refresh" button * PartyA: Navigate to http://localhost:10010/web/example and hit the "refresh" button
The vault and web front-end of PartyC (on ``localhost:10013``) will not display any IOUs. This is because PartyC was The vault and web front-end of PartyC (at ``localhost:10013``) will not display any IOUs. This is because PartyC was
not involved in this transaction. not involved in this transaction.
Via the interactive shell (terminal only) Via the interactive shell (terminal only)
@ -414,6 +379,8 @@ following list:
net.corda.finance.flows.CashIssueFlow net.corda.finance.flows.CashIssueFlow
net.corda.finance.flows.CashPaymentFlow net.corda.finance.flows.CashPaymentFlow
Creating an IOU via the interactive shell
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
We can create a new IOU using the ``ExampleFlow$Initiator`` flow. For example, from the interactive shell of PartyA, We can create a new IOU using the ``ExampleFlow$Initiator`` flow. For example, from the interactive shell of PartyA,
you can agree an IOU of 50 with PartyB by running you can agree an IOU of 50 with PartyB by running
``flow start ExampleFlow$Initiator iouValue: 50, otherParty: "O=PartyB,L=New York,C=US"``. ``flow start ExampleFlow$Initiator iouValue: 50, otherParty: "O=PartyB,L=New York,C=US"``.
@ -435,9 +402,15 @@ This will print out the following progress steps:
✅ Broadcasting transaction to participants ✅ Broadcasting transaction to participants
✅ Done ✅ Done
Checking the output
^^^^^^^^^^^^^^^^^^^
We can also issue RPC operations to the node via the interactive shell. Type ``run`` to see the full list of available We can also issue RPC operations to the node via the interactive shell. Type ``run`` to see the full list of available
operations. operations.
You can see the newly-created IOU by running ``run vaultQuery contractStateType: com.example.state.IOUState``.
As before, the interactive shell of PartyC will not display any IOUs.
Via the h2 web console Via the h2 web console
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
You can connect directly to your node's database to see its stored states, transactions and attachments. To do so, You can connect directly to your node's database to see its stored states, transactions and attachments. To do so,
@ -445,17 +418,17 @@ please follow the instructions in :doc:`node-database`.
Using the example RPC client Using the example RPC client
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``/src/main/kotlin-source/com/example/client/ExampleClientRPC.kt`` file is a simple utility that uses the client ``/src/main/kotlin-source/com/example/client/ExampleClientRPC.kt`` defines a simple RPC client that connects to a node,
RPC library to connect to a node. It will log any existing IOUs and listen for any future IOUs. If you haven't created logs any existing IOUs and listens for any future IOUs. If you haven't created
any IOUs when you first connect to one of the nodes, the client will simply log any future IOUs that are agreed. any IOUs when you first connect to one of the nodes, the client will simply log any future IOUs that are agreed.
*Running the client via IntelliJ:* Running the client via IntelliJ
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Select the 'Run Example RPC Client' run configuration which, by default, connects to PartyA. Click the green arrow to Run the 'Run Example RPC Client' run configuration. By default, this run configuration is configured to connect to
run the client. You can edit the run configuration to connect on a different port. PartyA. You can edit the run configuration to connect on a different port.
*Running the client via the command line:*
Running the client via the command line
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Run the following gradle task: Run the following gradle task:
``./gradlew runExampleClientRPCKotlin`` ``./gradlew runExampleClientRPCKotlin``
@ -469,7 +442,7 @@ For more information on the client RPC interface and how to build an RPC client
* :doc:`Client RPC documentation <clientrpc>` * :doc:`Client RPC documentation <clientrpc>`
* :doc:`Client RPC tutorial <tutorial-clientrpc-api>` * :doc:`Client RPC tutorial <tutorial-clientrpc-api>`
Running Nodes Across Machines Running nodes across machines
----------------------------- -----------------------------
The nodes can be split across machines and configured to communicate across the network. The nodes can be split across machines and configured to communicate across the network.
@ -490,35 +463,32 @@ and make the following changes:
After starting each node, the nodes will be able to see one another and agree IOUs among themselves. After starting each node, the nodes will be able to see one another and agree IOUs among themselves.
Debugging your CorDapp Testing and debugging
---------------------- ---------------------
Testing a CorDapp
~~~~~~~~~~~~~~~~~
Corda provides several frameworks for writing unit and integration tests for CorDapps.
Contract tests
^^^^^^^^^^^^^^
You can run the CorDapp's contract tests by running the ``Run Contract Tests - Kotlin`` run configuration.
Flow tests
^^^^^^^^^^
You can run the CorDapp's flow tests by running the ``Run Flow Tests - Kotlin`` run configuration.
Integration tests
^^^^^^^^^^^^^^^^^
You can run the CorDapp's integration tests by running the ``Run Integration Tests - Kotlin`` run configuration.
Debugging Corda nodes
~~~~~~~~~~~~~~~~~~~~~
Debugging is done via IntelliJ as follows: Debugging is done via IntelliJ as follows:
1. Edit the node driver code in ``Main.kt`` based on the number of nodes you wish to start, along with any other 1. Start the nodes using the “Run Example CorDapp” run configuration in IntelliJ
configuration options. For example, the code below starts 4 nodes, with one being the network map service and
notary. It also sets up RPC credentials for the three non-notary nodes
.. sourcecode:: kotlin 2. IntelliJ will build and run the CorDapp. The remote debug ports for each node will be automatically generated and
fun main(args: Array<String>) {
// No permissions required as we are not invoking flows.
val user = User("user1", "test", permissions = setOf())
driver(isDebug = true, waitForNodesToFinish = true) {
startNode(getX500Name(O="NetworkMapAndNotary",L="London",C="GB"), setOf(ServiceInfo(ValidatingNotaryService.type)))
val (nodeA, nodeB, nodeC) = Futures.allAsList(
startNode(getX500Name(O="PartyA",L=London,C=GB"), rpcUsers = listOf(user)),
startNode(getX500Name(O="PartyB",L=New York,C=US"), rpcUsers = listOf(user)),
startNode(getX500Name(O="PartyC",L=Paris,C=FR"), rpcUsers = listOf(user))).getOrThrow()
startWebserver(nodeA)
startWebserver(nodeB)
startWebserver(nodeC)
}
}
2. Select and run the “Run Example CorDapp” run configuration in IntelliJ
3. IntelliJ will build and run the CorDapp. The remote debug ports for each node will be automatically generated and
printed to the terminal. For example: printed to the terminal. For example:
.. sourcecode:: none .. sourcecode:: none
@ -526,9 +496,11 @@ Debugging is done via IntelliJ as follows:
[INFO ] 15:27:59.533 [main] Node.logStartupInfo - Working Directory: /Users/joeldudley/cordapp-example/build/20170707142746/PartyA [INFO ] 15:27:59.533 [main] Node.logStartupInfo - Working Directory: /Users/joeldudley/cordapp-example/build/20170707142746/PartyA
[INFO ] 15:27:59.533 [main] Node.logStartupInfo - Debug port: dt_socket:5007 [INFO ] 15:27:59.533 [main] Node.logStartupInfo - Debug port: dt_socket:5007
4. Edit the “Debug CorDapp” run configuration with the port of the node you wish to connect to 3. Edit the “Debug CorDapp” run configuration with the port of the node you wish to connect to
5. Run the “Debug CorDapp” run configuration 4. Run the “Debug CorDapp” run configuration
6. Set your breakpoints and start interacting with the node you wish to connect to. When the node hits a breakpoint, 5. Set your breakpoints and interact with the node you've connected to. When the node hits a breakpoint, execution will
execution will pause pause
* The node webserver runs in a separate process, and is not attached to by the debugger

View File

@ -31,10 +31,10 @@ class CashSelectionH2ImplTest {
// spend operation below. // spend operation below.
// Issuing Integer.MAX_VALUE will not cause an exception since PersistentCashState.pennies is a long // Issuing Integer.MAX_VALUE will not cause an exception since PersistentCashState.pennies is a long
nCopies(2, Integer.MAX_VALUE).map { issueAmount -> nCopies(2, Integer.MAX_VALUE).map { issueAmount ->
node.services.startFlow(CashIssueFlow(issueAmount.POUNDS, OpaqueBytes.of(1), mockNet.defaultNotaryIdentity)).resultFuture node.services.startFlow(CashIssueFlow(issueAmount.POUNDS, OpaqueBytes.of(1), mockNet.defaultNotaryIdentity))
}.transpose().getOrThrow() }.transpose().getOrThrow()
// The spend must be more than the size of a single cash state to force the accumulator onto the second state. // The spend must be more than the size of a single cash state to force the accumulator onto the second state.
node.services.startFlow(CashPaymentFlow((Integer.MAX_VALUE + 1L).POUNDS, node.info.legalIdentities[0])).resultFuture.getOrThrow() node.services.startFlow(CashPaymentFlow((Integer.MAX_VALUE + 1L).POUNDS, node.info.legalIdentities[0])).getOrThrow()
} }
@Test @Test
@ -50,8 +50,8 @@ class CashSelectionH2ImplTest {
val flow2 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notary)) val flow2 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notary))
val flow3 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notary)) val flow3 = bankA.services.startFlow(CashPaymentFlow(amount = 100.DOLLARS, anonymous = false, recipient = notary))
assertThatThrownBy { flow1.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) assertThatThrownBy { flow1.getOrThrow() }.isInstanceOf(CashException::class.java)
assertThatThrownBy { flow2.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) assertThatThrownBy { flow2.getOrThrow() }.isInstanceOf(CashException::class.java)
assertThatThrownBy { flow3.resultFuture.getOrThrow() }.isInstanceOf(CashException::class.java) assertThatThrownBy { flow3.getOrThrow() }.isInstanceOf(CashException::class.java)
} }
} }

View File

@ -33,7 +33,7 @@ class CashExitFlowTests {
bankOfCordaNode = mockNet.createPartyNode(BOC_NAME) bankOfCordaNode = mockNet.createPartyNode(BOC_NAME)
bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME) bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME)
notary = mockNet.defaultNotaryIdentity notary = mockNet.defaultNotaryIdentity
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, notary)).resultFuture val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, notary))
mockNet.runNetwork() mockNet.runNetwork()
future.getOrThrow() future.getOrThrow()
} }
@ -46,7 +46,7 @@ class CashExitFlowTests {
@Test @Test
fun `exit some cash`() { fun `exit some cash`() {
val exitAmount = 500.DOLLARS val exitAmount = 500.DOLLARS
val future = bankOfCordaNode.services.startFlow(CashExitFlow(exitAmount, ref)).resultFuture val future = bankOfCordaNode.services.startFlow(CashExitFlow(exitAmount, ref))
mockNet.runNetwork() mockNet.runNetwork()
val exitTx = future.getOrThrow().stx.tx val exitTx = future.getOrThrow().stx.tx
val expected = (initialBalance - exitAmount).`issued by`(bankOfCorda.ref(ref)) val expected = (initialBalance - exitAmount).`issued by`(bankOfCorda.ref(ref))
@ -59,7 +59,7 @@ class CashExitFlowTests {
@Test @Test
fun `exit zero cash`() { fun `exit zero cash`() {
val expected = 0.DOLLARS val expected = 0.DOLLARS
val future = bankOfCordaNode.services.startFlow(CashExitFlow(expected, ref)).resultFuture val future = bankOfCordaNode.services.startFlow(CashExitFlow(expected, ref))
mockNet.runNetwork() mockNet.runNetwork()
assertFailsWith<CashException> { assertFailsWith<CashException> {
future.getOrThrow() future.getOrThrow()

View File

@ -43,7 +43,7 @@ class CashIssueFlowTests {
fun `issue some cash`() { fun `issue some cash`() {
val expected = 500.DOLLARS val expected = 500.DOLLARS
val ref = OpaqueBytes.of(0x01) val ref = OpaqueBytes.of(0x01)
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(expected, ref, notary)).resultFuture val future = bankOfCordaNode.services.startFlow(CashIssueFlow(expected, ref, notary))
mockNet.runNetwork() mockNet.runNetwork()
val issueTx = future.getOrThrow().stx val issueTx = future.getOrThrow().stx
val output = issueTx.tx.outputsOfType<Cash.State>().single() val output = issueTx.tx.outputsOfType<Cash.State>().single()
@ -54,7 +54,7 @@ class CashIssueFlowTests {
fun `issue zero cash`() { fun `issue zero cash`() {
val expected = 0.DOLLARS val expected = 0.DOLLARS
val ref = OpaqueBytes.of(0x01) val ref = OpaqueBytes.of(0x01)
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(expected, ref, notary)).resultFuture val future = bankOfCordaNode.services.startFlow(CashIssueFlow(expected, ref, notary))
mockNet.runNetwork() mockNet.runNetwork()
assertFailsWith<IllegalArgumentException> { assertFailsWith<IllegalArgumentException> {
future.getOrThrow() future.getOrThrow()

View File

@ -35,7 +35,7 @@ class CashPaymentFlowTests {
bankOfCordaNode = mockNet.createPartyNode(BOC_NAME) bankOfCordaNode = mockNet.createPartyNode(BOC_NAME)
bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME) bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME)
aliceNode = mockNet.createPartyNode(ALICE_NAME) aliceNode = mockNet.createPartyNode(ALICE_NAME)
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, mockNet.defaultNotaryIdentity)).resultFuture val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, mockNet.defaultNotaryIdentity))
future.getOrThrow() future.getOrThrow()
} }
@ -56,8 +56,7 @@ class CashPaymentFlowTests {
val (_, vaultUpdatesBoc) = bankOfCordaNode.services.vaultService.trackBy<Cash.State>(criteria) val (_, vaultUpdatesBoc) = bankOfCordaNode.services.vaultService.trackBy<Cash.State>(criteria)
val (_, vaultUpdatesBankClient) = aliceNode.services.vaultService.trackBy<Cash.State>(criteria) val (_, vaultUpdatesBankClient) = aliceNode.services.vaultService.trackBy<Cash.State>(criteria)
val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expectedPayment, val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expectedPayment, payTo))
payTo)).resultFuture
mockNet.runNetwork() mockNet.runNetwork()
future.getOrThrow() future.getOrThrow()
@ -89,7 +88,7 @@ class CashPaymentFlowTests {
val payTo = aliceNode.info.chooseIdentity() val payTo = aliceNode.info.chooseIdentity()
val expected = 4000.DOLLARS val expected = 4000.DOLLARS
val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expected, val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expected,
payTo)).resultFuture payTo))
mockNet.runNetwork() mockNet.runNetwork()
assertFailsWith<CashException> { assertFailsWith<CashException> {
future.getOrThrow() future.getOrThrow()
@ -101,7 +100,7 @@ class CashPaymentFlowTests {
val payTo = aliceNode.info.chooseIdentity() val payTo = aliceNode.info.chooseIdentity()
val expected = 0.DOLLARS val expected = 0.DOLLARS
val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expected, val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expected,
payTo)).resultFuture payTo))
mockNet.runNetwork() mockNet.runNetwork()
assertFailsWith<IllegalArgumentException> { assertFailsWith<IllegalArgumentException> {
future.getOrThrow() future.getOrThrow()

View File

@ -14,6 +14,7 @@ buildscript {
jsr305_version = constants.getProperty("jsr305Version") jsr305_version = constants.getProperty("jsr305Version")
kotlin_version = constants.getProperty("kotlinVersion") kotlin_version = constants.getProperty("kotlinVersion")
artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion') artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion')
snake_yaml_version = constants.getProperty('snakeYamlVersion')
} }
repositories { repositories {

View File

@ -20,7 +20,8 @@ public class CordformNode implements NodeDefinition {
protected static final String DEFAULT_HOST = "localhost"; protected static final String DEFAULT_HOST = "localhost";
/** /**
* Name of the node. * Name of the node. Node will be placed in directory based on this name - all lowercase with whitespaces removed.
* Actual node name inside node.conf will be as set here.
*/ */
private String name; private String name;
@ -28,6 +29,20 @@ public class CordformNode implements NodeDefinition {
return name; return name;
} }
/**
* p2p Port.
*/
private int p2pPort = 10002;
public int getP2pPort() { return p2pPort; }
/**
* RPC Port.
*/
private int rpcPort = 10003;
public int getRpcPort() { return rpcPort; }
/** /**
* Set the RPC users for this node. This configuration block allows arbitrary configuration. * Set the RPC users for this node. This configuration block allows arbitrary configuration.
* The recommended current structure is: * The recommended current structure is:
@ -79,6 +94,7 @@ public class CordformNode implements NodeDefinition {
*/ */
public void p2pPort(int p2pPort) { public void p2pPort(int p2pPort) {
p2pAddress(DEFAULT_HOST + ':' + p2pPort); p2pAddress(DEFAULT_HOST + ':' + p2pPort);
this.p2pPort = p2pPort;
} }
/** /**
@ -110,6 +126,7 @@ public class CordformNode implements NodeDefinition {
@Deprecated @Deprecated
public void rpcPort(int rpcPort) { public void rpcPort(int rpcPort) {
rpcAddress(DEFAULT_HOST + ':' + rpcPort); rpcAddress(DEFAULT_HOST + ':' + rpcPort);
this.rpcPort = rpcPort;
} }
/** /**

View File

@ -8,6 +8,17 @@ public final class RpcSettings {
private Config config = ConfigFactory.empty(); private Config config = ConfigFactory.empty();
private int port = 10003;
private int adminPort = 10005;
public int getPort() {
return port;
}
public int getAdminPort() {
return adminPort;
}
/** /**
* RPC address for the node. * RPC address for the node.
*/ */
@ -15,6 +26,14 @@ public final class RpcSettings {
setValue("address", value); setValue("address", value);
} }
/**
* RPC Port for the node
*/
public final void port(final int value) {
this.port = value;
setValue("address", "localhost:"+port);
}
/** /**
* RPC admin address for the node (necessary if [useSsl] is false or unset). * RPC admin address for the node (necessary if [useSsl] is false or unset).
*/ */
@ -22,6 +41,11 @@ public final class RpcSettings {
setValue("adminAddress", value); setValue("adminAddress", value);
} }
public final void adminPort(final int value) {
this.adminPort = value;
setValue("adminAddress", "localhost:"+adminPort);
}
/** /**
* Specifies whether the node RPC layer will require SSL from clients. * Specifies whether the node RPC layer will require SSL from clients.
*/ */
@ -43,7 +67,7 @@ public final class RpcSettings {
config = options.addTo("ssl", config); config = options.addTo("ssl", config);
} }
final Config addTo(final String key, final Config config) { public final Config addTo(final String key, final Config config) {
if (this.config.isEmpty()) { if (this.config.isEmpty()) {
return config; return config;
} }

View File

@ -43,7 +43,7 @@ public final class SslOptions {
setValue("trustStoreFile", value); setValue("trustStoreFile", value);
} }
final Config addTo(final String key, final Config config) { public final Config addTo(final String key, final Config config) {
if (this.config.isEmpty()) { if (this.config.isEmpty()) {
return config; return config;
} }

View File

@ -40,6 +40,8 @@ dependencies {
noderunner "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" noderunner "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
compile project(':cordform-common') compile project(':cordform-common')
// Docker-compose file generation
compile "org.yaml:snakeyaml:$snake_yaml_version"
} }
task createNodeRunner(type: Jar, dependsOn: [classes]) { task createNodeRunner(type: Jar, dependsOn: [classes]) {

View File

@ -0,0 +1,174 @@
package net.corda.plugins
import groovy.lang.Closure
import net.corda.cordform.CordformDefinition
import org.apache.tools.ant.filters.FixCrLfFilter
import org.gradle.api.DefaultTask
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
import org.gradle.api.tasks.TaskAction
import java.io.File
import java.lang.reflect.InvocationTargetException
import java.net.URLClassLoader
import java.nio.file.Path
import java.nio.file.Paths
import java.util.jar.JarInputStream
/**
* Creates nodes based on the configuration of this task in the gradle configuration DSL.
*
* See documentation for examples.
*/
@Suppress("unused")
open class Baseform : DefaultTask() {
private companion object {
val nodeJarName = "corda.jar"
private val defaultDirectory: Path = Paths.get("build", "nodes")
}
/**
* Optionally the name of a CordformDefinition subclass to which all configuration will be delegated.
*/
@Suppress("MemberVisibilityCanPrivate")
var definitionClass: String? = null
var directory = defaultDirectory
protected val nodes = mutableListOf<Node>()
/**
* Set the directory to install nodes into.
*
* @param directory The directory the nodes will be installed into.
*/
fun directory(directory: String) {
this.directory = Paths.get(directory)
}
/**
* Add a node configuration.
*
* @param configureClosure A node configuration that will be deployed.
*/
@Suppress("MemberVisibilityCanPrivate")
fun node(configureClosure: Closure<in Node>) {
nodes += project.configure(Node(project), configureClosure) as Node
}
/**
* Add a node configuration
*
* @param configureFunc A node configuration that will be deployed
*/
@Suppress("MemberVisibilityCanPrivate")
fun node(configureFunc: Node.() -> Any?): Node {
val node = Node(project).apply { configureFunc() }
nodes += node
return node
}
/**
* Returns a node by name.
*
* @param name The name of the node as specified in the node configuration DSL.
* @return A node instance.
*/
private fun getNodeByName(name: String): Node? = nodes.firstOrNull { it.name == name }
/**
* The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
*/
private fun loadCordformDefinition(): CordformDefinition {
val plugin = project.convention.getPlugin(JavaPluginConvention::class.java)
val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath
val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray()
return URLClassLoader(urls, CordformDefinition::class.java.classLoader)
.loadClass(definitionClass)
.asSubclass(CordformDefinition::class.java)
.newInstance()
}
/**
* The NetworkBootstrapper needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
*/
private fun loadNetworkBootstrapperClass(): Class<*> {
val plugin = project.convention.getPlugin(JavaPluginConvention::class.java)
val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath
val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray()
return URLClassLoader(urls, javaClass.classLoader).loadClass("net.corda.nodeapi.internal.network.NetworkBootstrapper")
}
/**
* Installs the corda fat JAR to the root directory, for the network bootstrapper to use.
*/
protected fun installCordaJar() {
val cordaJar = Cordformation.verifyAndGetRuntimeJar(project, "corda")
project.copy {
it.apply {
from(cordaJar)
into(directory)
rename(cordaJar.name, nodeJarName)
fileMode = Cordformation.executableFileMode
}
}
}
protected fun initializeConfiguration() {
if (definitionClass != null) {
val cd = loadCordformDefinition()
// If the user has specified their own directory (even if it's the same default path) then let them know
// it's not used and should just rely on the one in CordformDefinition
require(directory === defaultDirectory) {
"'directory' cannot be used when 'definitionClass' is specified. Use CordformDefinition.nodesDirectory instead."
}
directory = cd.nodesDirectory
val cordapps = cd.getMatchingCordapps()
cd.nodeConfigurers.forEach {
val node = node { }
it.accept(node)
node.additionalCordapps.addAll(cordapps)
node.rootDir(directory)
}
cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) }
} else {
nodes.forEach {
it.rootDir(directory)
}
}
}
protected fun bootstrapNetwork() {
val networkBootstrapperClass = loadNetworkBootstrapperClass()
val networkBootstrapper = networkBootstrapperClass.newInstance()
val bootstrapMethod = networkBootstrapperClass.getMethod("bootstrap", Path::class.java).apply { isAccessible = true }
// Call NetworkBootstrapper.bootstrap
try {
val rootDir = project.projectDir.toPath().resolve(directory).toAbsolutePath().normalize()
bootstrapMethod.invoke(networkBootstrapper, rootDir)
} catch (e: InvocationTargetException) {
throw e.cause!!
}
}
private fun CordformDefinition.getMatchingCordapps(): List<File> {
val cordappJars = project.configuration("cordapp").files
return cordappPackages.map { `package` ->
val cordappsWithPackage = cordappJars.filter { it.containsPackage(`package`) }
when (cordappsWithPackage.size) {
0 -> throw IllegalArgumentException("There are no cordapp dependencies containing the package $`package`")
1 -> cordappsWithPackage[0]
else -> throw IllegalArgumentException("More than one cordapp dependency contains the package $`package`: $cordappsWithPackage")
}
}
}
private fun File.containsPackage(`package`: String): Boolean {
JarInputStream(inputStream()).use {
while (true) {
val name = it.nextJarEntry?.name ?: break
if (name.endsWith(".class") && name.replace('/', '.').startsWith(`package`)) {
return true
}
}
return false
}
}
}

View File

@ -1,18 +1,12 @@
package net.corda.plugins package net.corda.plugins
import groovy.lang.Closure
import net.corda.cordform.CordformDefinition
import org.apache.tools.ant.filters.FixCrLfFilter import org.apache.tools.ant.filters.FixCrLfFilter
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.plugins.JavaPluginConvention import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskAction
import java.io.File
import java.lang.reflect.InvocationTargetException
import java.net.URLClassLoader
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import java.util.jar.JarInputStream
/** /**
* Creates nodes based on the configuration of this task in the gradle configuration DSL. * Creates nodes based on the configuration of this task in the gradle configuration DSL.
@ -20,59 +14,12 @@ import java.util.jar.JarInputStream
* See documentation for examples. * See documentation for examples.
*/ */
@Suppress("unused") @Suppress("unused")
open class Cordform : DefaultTask() { open class Cordform : Baseform() {
private companion object { private companion object {
val nodeJarName = "corda.jar" val nodeJarName = "corda.jar"
private val defaultDirectory: Path = Paths.get("build", "nodes") private val defaultDirectory: Path = Paths.get("build", "nodes")
} }
/**
* Optionally the name of a CordformDefinition subclass to which all configuration will be delegated.
*/
@Suppress("MemberVisibilityCanPrivate")
var definitionClass: String? = null
private var directory = defaultDirectory
private val nodes = mutableListOf<Node>()
/**
* Set the directory to install nodes into.
*
* @param directory The directory the nodes will be installed into.
*/
fun directory(directory: String) {
this.directory = Paths.get(directory)
}
/**
* Add a node configuration.
*
* @param configureClosure A node configuration that will be deployed.
*/
@Suppress("MemberVisibilityCanPrivate")
fun node(configureClosure: Closure<in Node>) {
nodes += project.configure(Node(project), configureClosure) as Node
}
/**
* Add a node configuration
*
* @param configureFunc A node configuration that will be deployed
*/
@Suppress("MemberVisibilityCanPrivate")
fun node(configureFunc: Node.() -> Any?): Node {
val node = Node(project).apply { configureFunc() }
nodes += node
return node
}
/**
* Returns a node by name.
*
* @param name The name of the node as specified in the node configuration DSL.
* @return A node instance.
*/
private fun getNodeByName(name: String): Node? = nodes.firstOrNull { it.name == name }
/** /**
* Installs the run script into the nodes directory. * Installs the run script into the nodes directory.
*/ */
@ -103,29 +50,6 @@ open class Cordform : DefaultTask() {
} }
} }
/**
* The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
*/
private fun loadCordformDefinition(): CordformDefinition {
val plugin = project.convention.getPlugin(JavaPluginConvention::class.java)
val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath
val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray()
return URLClassLoader(urls, CordformDefinition::class.java.classLoader)
.loadClass(definitionClass)
.asSubclass(CordformDefinition::class.java)
.newInstance()
}
/**
* The NetworkBootstrapper needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
*/
private fun loadNetworkBootstrapperClass(): Class<*> {
val plugin = project.convention.getPlugin(JavaPluginConvention::class.java)
val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath
val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray()
return URLClassLoader(urls, javaClass.classLoader).loadClass("net.corda.nodeapi.internal.network.NetworkBootstrapper")
}
/** /**
* This task action will create and install the nodes based on the node configurations added. * This task action will create and install the nodes based on the node configurations added.
*/ */
@ -139,80 +63,4 @@ open class Cordform : DefaultTask() {
bootstrapNetwork() bootstrapNetwork()
nodes.forEach(Node::build) nodes.forEach(Node::build)
} }
/**
* Installs the corda fat JAR to the root directory, for the network bootstrapper to use.
*/
private fun installCordaJar() {
val cordaJar = Cordformation.verifyAndGetRuntimeJar(project, "corda")
project.copy {
it.apply {
from(cordaJar)
into(directory)
rename(cordaJar.name, nodeJarName)
fileMode = Cordformation.executableFileMode
}
}
}
private fun initializeConfiguration() {
if (definitionClass != null) {
val cd = loadCordformDefinition()
// If the user has specified their own directory (even if it's the same default path) then let them know
// it's not used and should just rely on the one in CordformDefinition
require(directory === defaultDirectory) {
"'directory' cannot be used when 'definitionClass' is specified. Use CordformDefinition.nodesDirectory instead."
}
directory = cd.nodesDirectory
val cordapps = cd.getMatchingCordapps()
cd.nodeConfigurers.forEach {
val node = node { }
it.accept(node)
node.additionalCordapps.addAll(cordapps)
node.rootDir(directory)
}
cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) }
} else {
nodes.forEach {
it.rootDir(directory)
}
}
}
private fun bootstrapNetwork() {
val networkBootstrapperClass = loadNetworkBootstrapperClass()
val networkBootstrapper = networkBootstrapperClass.newInstance()
val bootstrapMethod = networkBootstrapperClass.getMethod("bootstrap", Path::class.java).apply { isAccessible = true }
// Call NetworkBootstrapper.bootstrap
try {
val rootDir = project.projectDir.toPath().resolve(directory).toAbsolutePath().normalize()
bootstrapMethod.invoke(networkBootstrapper, rootDir)
} catch (e: InvocationTargetException) {
throw e.cause!!
}
}
private fun CordformDefinition.getMatchingCordapps(): List<File> {
val cordappJars = project.configuration("cordapp").files
return cordappPackages.map { `package` ->
val cordappsWithPackage = cordappJars.filter { it.containsPackage(`package`) }
when (cordappsWithPackage.size) {
0 -> throw IllegalArgumentException("There are no cordapp dependencies containing the package $`package`")
1 -> cordappsWithPackage[0]
else -> throw IllegalArgumentException("More than one cordapp dependency contains the package $`package`: $cordappsWithPackage")
}
}
}
private fun File.containsPackage(`package`: String): Boolean {
JarInputStream(inputStream()).use {
while (true) {
val name = it.nextJarEntry?.name ?: break
if (name.endsWith(".class") && name.replace('/', '.').startsWith(`package`)) {
return true
}
}
return false
}
}
} }

View File

@ -0,0 +1,66 @@
package net.corda.plugins
import org.apache.tools.ant.filters.FixCrLfFilter
import org.gradle.api.DefaultTask
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
import org.gradle.api.tasks.TaskAction
import org.yaml.snakeyaml.DumperOptions
import java.nio.file.Path
import java.nio.file.Paths
import org.yaml.snakeyaml.Yaml
import java.nio.charset.StandardCharsets
import java.nio.file.Files
/**
* Creates docker-compose file and image definitions based on the configuration of this task in the gradle configuration DSL.
*
* See documentation for examples.
*/
@Suppress("unused")
open class Dockerform : Baseform() {
private companion object {
val nodeJarName = "corda.jar"
private val defaultDirectory: Path = Paths.get("build", "docker")
private val dockerComposeFileVersion = "3"
private val yamlOptions = DumperOptions().apply {
indent = 2
defaultFlowStyle = DumperOptions.FlowStyle.BLOCK
}
private val yaml = Yaml(yamlOptions)
}
private val directoryPath = project.projectDir.toPath().resolve(directory)
val dockerComposePath = directoryPath.resolve("docker-compose.yml")
/**
* This task action will create and install the nodes based on the node configurations added.
*/
@TaskAction
fun build() {
project.logger.info("Running Cordform task")
initializeConfiguration()
nodes.forEach(Node::installDockerConfig)
installCordaJar()
bootstrapNetwork()
nodes.forEach(Node::buildDocker)
// Transform nodes path the absolute ones
val services = nodes.map { it.containerName to mapOf(
"build" to directoryPath.resolve(it.nodeDir.name).toAbsolutePath().toString(),
"ports" to listOf(it.rpcPort)) }.toMap()
val dockerComposeObject = mapOf(
"version" to dockerComposeFileVersion,
"services" to services)
val dockerComposeContent = yaml.dump(dockerComposeObject)
Files.write(dockerComposePath, dockerComposeContent.toByteArray(StandardCharsets.UTF_8))
}
}

View File

@ -3,8 +3,10 @@ package net.corda.plugins
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigRenderOptions import com.typesafe.config.ConfigRenderOptions
import com.typesafe.config.ConfigValueFactory import com.typesafe.config.ConfigValueFactory
import com.typesafe.config.ConfigObject
import groovy.lang.Closure import groovy.lang.Closure
import net.corda.cordform.CordformNode import net.corda.cordform.CordformNode
import net.corda.cordform.RpcSettings
import org.gradle.api.Project import org.gradle.api.Project
import java.io.File import java.io.File
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
@ -34,6 +36,11 @@ class Node(private val project: Project) : CordformNode() {
private set private set
internal lateinit var rootDir: File internal lateinit var rootDir: File
private set private set
internal lateinit var containerName: String
private set
internal var rpcSettings: RpcSettings = RpcSettings()
private set
/** /**
* Sets whether this node will use HTTPS communication. * Sets whether this node will use HTTPS communication.
@ -59,7 +66,7 @@ class Node(private val project: Project) : CordformNode() {
* Specifies RPC settings for the node. * Specifies RPC settings for the node.
*/ */
fun rpcSettings(configureClosure: Closure<in RpcSettings>) { fun rpcSettings(configureClosure: Closure<in RpcSettings>) {
val rpcSettings = project.configure(RpcSettings(project), configureClosure) as RpcSettings rpcSettings = project.configure(RpcSettings(), configureClosure) as RpcSettings
config = rpcSettings.addTo("rpcSettings", config) config = rpcSettings.addTo("rpcSettings", config)
} }
@ -81,6 +88,19 @@ class Node(private val project: Project) : CordformNode() {
installCordapps() installCordapps()
} }
internal fun buildDocker() {
project.copy {
it.apply {
from(Cordformation.getPluginFile(project, "net/corda/plugins/Dockerfile"))
from(Cordformation.getPluginFile(project, "net/corda/plugins/run-corda.sh"))
into("$nodeDir/")
}
}
installAgentJar()
installBuiltCordapp()
installCordapps()
}
internal fun rootDir(rootDir: Path) { internal fun rootDir(rootDir: Path) {
if (name == null) { if (name == null) {
project.logger.error("Node has a null name - cannot create node") project.logger.error("Node has a null name - cannot create node")
@ -90,6 +110,7 @@ class Node(private val project: Project) : CordformNode() {
// with loading our custom X509EdDSAEngine. // with loading our custom X509EdDSAEngine.
val organizationName = name.trim().split(",").firstOrNull { it.startsWith("O=") }?.substringAfter("=") val organizationName = name.trim().split(",").firstOrNull { it.startsWith("O=") }?.substringAfter("=")
val dirName = organizationName ?: name val dirName = organizationName ?: name
containerName = dirName.replace("\\s+".toRegex(), "-").toLowerCase()
this.rootDir = rootDir.toFile() this.rootDir = rootDir.toFile()
nodeDir = File(this.rootDir, dirName.replace("\\s", "")) nodeDir = File(this.rootDir, dirName.replace("\\s", ""))
Files.createDirectories(nodeDir.toPath()) Files.createDirectories(nodeDir.toPath())
@ -157,14 +178,14 @@ class Node(private val project: Project) : CordformNode() {
} }
} }
private fun createTempConfigFile(): File { private fun createTempConfigFile(configObject: ConfigObject): File {
val options = ConfigRenderOptions val options = ConfigRenderOptions
.defaults() .defaults()
.setOriginComments(false) .setOriginComments(false)
.setComments(false) .setComments(false)
.setFormatted(true) .setFormatted(true)
.setJson(false) .setJson(false)
val configFileText = config.root().render(options).split("\n").toList() val configFileText = configObject.render(options).split("\n").toList()
// Need to write a temporary file first to use the project.copy, which resolves directories correctly. // Need to write a temporary file first to use the project.copy, which resolves directories correctly.
val tmpDir = File(project.buildDir, "tmp") val tmpDir = File(project.buildDir, "tmp")
Files.createDirectories(tmpDir.toPath()) Files.createDirectories(tmpDir.toPath())
@ -179,7 +200,27 @@ class Node(private val project: Project) : CordformNode() {
*/ */
internal fun installConfig() { internal fun installConfig() {
configureProperties() configureProperties()
val tmpConfFile = createTempConfigFile() val tmpConfFile = createTempConfigFile(config.root())
appendOptionalConfig(tmpConfFile)
project.copy {
it.apply {
from(tmpConfFile)
into(rootDir)
}
}
}
/**
* Installs the Dockerized configuration file to the root directory and detokenises it.
*/
internal fun installDockerConfig() {
configureProperties()
val dockerConf = config
.withValue("p2pAddress", ConfigValueFactory.fromAnyRef("$containerName:$p2pPort"))
.withValue("rpcSettings.address", ConfigValueFactory.fromAnyRef("$containerName:${rpcSettings.port}"))
.withValue("rpcSettings.adminAddress", ConfigValueFactory.fromAnyRef("$containerName:${rpcSettings.adminPort}"))
.withValue("detectPublicIp", ConfigValueFactory.fromAnyRef(false))
val tmpConfFile = createTempConfigFile(dockerConf.root())
appendOptionalConfig(tmpConfFile) appendOptionalConfig(tmpConfFile)
project.copy { project.copy {
it.apply { it.apply {

View File

@ -1,59 +0,0 @@
package net.corda.plugins
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigValueFactory
import groovy.lang.Closure
import org.gradle.api.Project
class RpcSettings(private val project: Project) {
private var config: Config = ConfigFactory.empty()
/**
* RPC address for the node.
*/
fun address(value: String) {
config += "address" to value
}
/**
* RPC admin address for the node (necessary if [useSsl] is false or unset).
*/
fun adminAddress(value: String) {
config += "adminAddress" to value
}
/**
* Specifies whether the node RPC layer will require SSL from clients.
*/
fun useSsl(value: Boolean) {
config += "useSsl" to value
}
/**
* Specifies whether the RPC broker is separate from the node.
*/
fun standAloneBroker(value: Boolean) {
config += "standAloneBroker" to value
}
/**
* Specifies SSL certificates options for the RPC layer.
*/
fun ssl(configureClosure: Closure<in SslOptions>) {
val sslOptions = project.configure(SslOptions(), configureClosure) as SslOptions
config = sslOptions.addTo("ssl", config)
}
internal fun addTo(key: String, config: Config): Config {
if (this.config.isEmpty) {
return config
}
return config + (key to this.config.root())
}
}
internal operator fun Config.plus(entry: Pair<String, Any>): Config {
return withValue(entry.first, ConfigValueFactory.fromAnyRef(entry.second))
}

View File

@ -1,50 +0,0 @@
package net.corda.plugins
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
class SslOptions {
private var config: Config = ConfigFactory.empty()
/**
* Password for the keystore.
*/
fun keyStorePassword(value: String) {
config += "keyStorePassword" to value
}
/**
* Password for the truststore.
*/
fun trustStorePassword(value: String) {
config += "trustStorePassword" to value
}
/**
* Directory under which key stores are to be placed.
*/
fun certificatesDirectory(value: String) {
config += "certificatesDirectory" to value
}
/**
* Absolute path to SSL keystore. Default: "[certificatesDirectory]/sslkeystore.jks"
*/
fun sslKeystore(value: String) {
config += "sslKeystore" to value
}
/**
* Absolute path to SSL truststore. Default: "[certificatesDirectory]/truststore.jks"
*/
fun trustStoreFile(value: String) {
config += "trustStoreFile" to value
}
internal fun addTo(key: String, config: Config): Config {
if (this.config.isEmpty) {
return config
}
return config + (key to this.config.root())
}
}

View File

@ -0,0 +1,44 @@
# Base image from (http://phusion.github.io/baseimage-docker)
FROM openjdk:8u151-jre-alpine
ENV CORDA_VERSION=${BUILDTIME_CORDA_VERSION}
ENV JAVA_OPTIONS=${BUILDTIME_JAVA_OPTIONS}
# Set image labels
LABEL net.corda.version = ${CORDA_VERSION} \
maintainer = "<devops@r3.com>" \
vendor = "R3"
RUN apk upgrade --update && \
apk add --update --no-cache bash iputils && \
rm -rf /var/cache/apk/* && \
# Add user to run the app && \
addgroup corda && \
adduser -G corda -D -s /bin/bash corda && \
# Create /opt/corda directory && \
mkdir -p /opt/corda/plugins && \
mkdir -p /opt/corda/logs
# Copy corda files
ADD --chown=corda:corda corda.jar /opt/corda/corda.jar
ADD --chown=corda:corda node.conf /opt/corda/node.conf
ADD --chown=corda:corda network-parameters /opt/corda/
ADD --chown=corda:corda cordapps/ /opt/corda/cordapps
ADD --chown=corda:corda additional-node-infos/ /opt/corda/additional-node-infos
ADD --chown=corda:corda certificates/ /opt/corda/certificates
ADD --chown=corda:corda drivers/ /opt/corda/drivers
ADD --chown=corda:corda persistence* /opt/corda/
COPY run-corda.sh /run-corda.sh
RUN chmod +x /run-corda.sh && \
sync && \
chown -R corda:corda /opt/corda
# Working directory for Corda
WORKDIR /opt/corda
ENV HOME=/opt/corda
USER corda
# Start it
CMD ["/run-corda.sh"]

View File

@ -0,0 +1,10 @@
#!/bin/sh
# If variable not present use default values
: ${CORDA_HOME:=/opt/corda}
: ${JAVA_OPTIONS:=-Xmx512m}
export CORDA_HOME JAVA_OPTIONS
cd ${CORDA_HOME}
java $JAVA_OPTIONS -jar ${CORDA_HOME}/corda.jar 2>&1

View File

@ -30,6 +30,10 @@ class PublishTasks implements Plugin<Project> {
createConfigurations() createConfigurations()
} }
/**
* This call must come at the end of any publish block because it configures the publishing and any
* values set after this call in the DSL will not be configured properly (and will use the default value)
*/
void setPublishName(String publishName) { void setPublishName(String publishName) {
project.logger.info("Changing publishing name from ${project.name} to ${publishName}") project.logger.info("Changing publishing name from ${project.name} to ${publishName}")
this.publishName = publishName this.publishName = publishName
@ -51,12 +55,14 @@ class PublishTasks implements Plugin<Project> {
} }
void configureMavenPublish(BintrayConfigExtension bintrayConfig) { void configureMavenPublish(BintrayConfigExtension bintrayConfig) {
project.logger.info("Configuring maven publish for $publishName")
project.apply([plugin: 'maven-publish']) project.apply([plugin: 'maven-publish'])
project.publishing.publications.create(publishName, MavenPublication) { project.publishing.publications.create(publishName, MavenPublication) {
groupId project.group groupId project.group
artifactId publishName artifactId publishName
if (publishConfig.publishSources) { if (publishConfig.publishSources) {
project.logger.info("Publishing sources for $publishName")
artifact project.tasks.sourceJar artifact project.tasks.sourceJar
} }
artifact project.tasks.javadocJar artifact project.tasks.javadocJar

View File

@ -12,6 +12,7 @@ import net.corda.core.internal.div
import net.corda.core.internal.exists import net.corda.core.internal.exists
import net.corda.core.internal.list import net.corda.core.internal.list
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
import net.corda.core.node.NetworkParameters
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
@ -19,7 +20,6 @@ import net.corda.finance.DOLLARS
import net.corda.finance.flows.CashIssueAndPaymentFlow import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.nodeapi.internal.createDevNetworkMapCa import net.corda.nodeapi.internal.createDevNetworkMapCa
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.SerializationEnvironmentRule

View File

@ -4,7 +4,7 @@ import com.r3.corda.networkmanage.common.utils.SignedNetworkMap
import com.r3.corda.networkmanage.common.utils.SignedNetworkParameters import com.r3.corda.networkmanage.common.utils.SignedNetworkParameters
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.internal.DigitalSignatureWithCert import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.nodeapi.internal.network.NetworkParameters import net.corda.core.node.NetworkParameters
/** /**
* Data access object interface for NetworkMap persistence layer * Data access object interface for NetworkMap persistence layer

View File

@ -6,8 +6,8 @@ import com.r3.corda.networkmanage.common.utils.SignedNetworkParameters
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256 import net.corda.core.crypto.sha256
import net.corda.core.internal.DigitalSignatureWithCert import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
/** /**

View File

@ -3,10 +3,10 @@ package com.r3.corda.networkmanage.common.persistence.entity
import com.r3.corda.networkmanage.common.utils.SignedNetworkParameters import com.r3.corda.networkmanage.common.utils.SignedNetworkParameters
import net.corda.core.internal.DigitalSignatureWithCert import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.internal.SignedDataWithCert import net.corda.core.internal.SignedDataWithCert
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.network.NetworkParameters
import org.hibernate.annotations.CreationTimestamp import org.hibernate.annotations.CreationTimestamp
import java.time.Instant import java.time.Instant
import javax.persistence.* import javax.persistence.*

View File

@ -3,10 +3,10 @@ package com.r3.corda.networkmanage.common.signer
import com.r3.corda.networkmanage.common.persistence.CertificateStatus import com.r3.corda.networkmanage.common.persistence.CertificateStatus
import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage
import net.corda.core.internal.SignedDataWithCert import net.corda.core.internal.SignedDataWithCert
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.network.NetworkMap import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.NetworkParameters
class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private val signer: Signer) { class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private val signer: Signer) {
private companion object { private companion object {
@ -37,7 +37,7 @@ class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private
logger.debug("Fetching node info hashes with VALID certificates...") logger.debug("Fetching node info hashes with VALID certificates...")
val nodeInfoHashes = networkMapStorage.getNodeInfoHashes(CertificateStatus.VALID) val nodeInfoHashes = networkMapStorage.getNodeInfoHashes(CertificateStatus.VALID)
logger.debug("Retrieved node info hashes: $nodeInfoHashes") logger.debug("Retrieved node info hashes: $nodeInfoHashes")
val newNetworkMap = NetworkMap(nodeInfoHashes, latestNetworkParameters.serialize().hash) val newNetworkMap = NetworkMap(nodeInfoHashes, latestNetworkParameters.serialize().hash, null)
val serialisedNetworkMap = newNetworkMap.serialize() val serialisedNetworkMap = newNetworkMap.serialize()
if (serialisedNetworkMap != currentSignedNetworkMap?.raw) { if (serialisedNetworkMap != currentSignedNetworkMap?.raw) {
logger.info("Signing a new network map: $newNetworkMap") logger.info("Signing a new network map: $newNetworkMap")

View File

@ -7,12 +7,12 @@ import joptsimple.ArgumentAcceptingOptionSpec
import joptsimple.OptionParser import joptsimple.OptionParser
import net.corda.core.crypto.sha256 import net.corda.core.crypto.sha256
import net.corda.core.internal.SignedDataWithCert import net.corda.core.internal.SignedDataWithCert
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal.SerializationEnvironmentImpl
import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.network.NetworkMap import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme

View File

@ -5,10 +5,10 @@ import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage
import com.r3.corda.networkmanage.common.persistence.configureDatabase import com.r3.corda.networkmanage.common.persistence.configureDatabase
import com.r3.corda.networkmanage.common.utils.* import com.r3.corda.networkmanage.common.utils.*
import com.r3.corda.networkmanage.doorman.signer.LocalSigner import com.r3.corda.networkmanage.doorman.signer.LocalSigner
import net.corda.core.node.NetworkParameters
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.network.NetworkParameters
import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.PKCS10CertificationRequest
import java.time.Instant import java.time.Instant
import kotlin.concurrent.thread import kotlin.concurrent.thread

View File

@ -12,9 +12,9 @@ import com.r3.corda.networkmanage.doorman.signer.LocalSigner
import com.r3.corda.networkmanage.doorman.webservice.MonitoringWebService import com.r3.corda.networkmanage.doorman.webservice.MonitoringWebService
import com.r3.corda.networkmanage.doorman.webservice.NetworkMapWebService import com.r3.corda.networkmanage.doorman.webservice.NetworkMapWebService
import com.r3.corda.networkmanage.doorman.webservice.RegistrationWebService import com.r3.corda.networkmanage.doorman.webservice.RegistrationWebService
import net.corda.core.node.NetworkParameters
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import java.io.Closeable import java.io.Closeable
import java.net.URI import java.net.URI

View File

@ -1,21 +1,15 @@
package com.r3.corda.networkmanage.doorman package com.r3.corda.networkmanage.doorman
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions import com.typesafe.config.ConfigParseOptions
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.exists import net.corda.core.internal.exists
import net.corda.core.internal.readAll import net.corda.core.internal.readAll
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NotaryInfo
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.utilities.days
import net.corda.core.utilities.parsePublicKeyBase58
import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.config.parseAs import net.corda.nodeapi.internal.config.parseAs
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.network.NotaryInfo
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths
import java.time.Instant import java.time.Instant
/** /**

View File

@ -10,13 +10,13 @@ import com.r3.corda.networkmanage.common.utils.SignedNetworkMap
import com.r3.corda.networkmanage.doorman.NetworkMapConfig import com.r3.corda.networkmanage.doorman.NetworkMapConfig
import com.r3.corda.networkmanage.doorman.webservice.NetworkMapWebService.Companion.NETWORK_MAP_PATH import com.r3.corda.networkmanage.doorman.webservice.NetworkMapWebService.Companion.NETWORK_MAP_PATH
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.trace import net.corda.core.utilities.trace
import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.network.NetworkParameters
import java.io.InputStream import java.io.InputStream
import java.security.InvalidKeyException import java.security.InvalidKeyException
import java.security.SignatureException import java.security.SignatureException

View File

@ -7,6 +7,8 @@ import net.corda.core.node.NodeInfo
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import java.security.PublicKey
import java.security.SignatureException import java.security.SignatureException
/** /**
@ -44,3 +46,11 @@ class SignedNodeInfo(val raw: SerializedBytes<NodeInfo>, val signatures: List<Di
return nodeInfo return nodeInfo
} }
} }
inline fun NodeInfo.sign(signer: (PublicKey, SerializedBytes<NodeInfo>) -> DigitalSignature): SignedNodeInfo {
// For now we exclude any composite identities, see [SignedNodeInfo]
val owningKeys = legalIdentities.map { it.owningKey }.filter { it !is CompositeKey }
val serialised = serialize()
val signatures = owningKeys.map { signer(it, serialised) }
return SignedNodeInfo(serialised, signatures)
}

View File

@ -4,10 +4,7 @@ import net.corda.core.CordaOID
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignatureScheme import net.corda.core.crypto.SignatureScheme
import net.corda.core.crypto.random63BitValue import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.CertRole import net.corda.core.internal.*
import net.corda.core.internal.reader
import net.corda.core.internal.uncheckedCast
import net.corda.core.internal.writer
import net.corda.core.utilities.days import net.corda.core.utilities.days
import net.corda.core.utilities.millis import net.corda.core.utilities.millis
import org.bouncycastle.asn1.* import org.bouncycastle.asn1.*

View File

@ -5,12 +5,14 @@ import net.corda.cordform.CordformNode
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.core.internal.concurrent.fork import net.corda.core.internal.concurrent.fork
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.NotaryInfo
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.nodeapi.internal.serialization.CordaSerializationMagic
import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal.SerializationEnvironmentImpl
import net.corda.core.serialization.internal._contextSerializationEnv import net.corda.core.serialization.internal._contextSerializationEnv
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.SignedNodeInfo
@ -18,7 +20,7 @@ import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 import net.corda.nodeapi.internal.serialization.kryo.kryoMagic
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
@ -167,7 +169,7 @@ class NetworkBootstrapper {
epoch = 1 epoch = 1
), overwriteFile = true) ), overwriteFile = true)
nodeDirs.forEach(copier::install) nodeDirs.forEach { copier.install(it) }
} }
private fun NotaryInfo.prettyPrint(): String = "${identity.name} (${if (validating) "" else "non-"}validating)" private fun NotaryInfo.prettyPrint(): String = "${identity.name} (${if (validating) "" else "non-"}validating)"
@ -196,8 +198,8 @@ class NetworkBootstrapper {
} }
private object KryoParametersSerializationScheme : AbstractKryoSerializationScheme() { private object KryoParametersSerializationScheme : AbstractKryoSerializationScheme() {
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P return magic == kryoMagic && target == SerializationContext.UseCase.P2P
} }
override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException() override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException()

View File

@ -1,55 +1,45 @@
package net.corda.nodeapi.internal.network package net.corda.nodeapi.internal.network
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.internal.CertRole import net.corda.core.internal.CertRole
import net.corda.core.internal.SignedDataWithCert import net.corda.core.internal.SignedDataWithCert
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.time.Instant import java.time.Instant
const val NETWORK_PARAMS_FILE_NAME = "network-parameters" const val NETWORK_PARAMS_FILE_NAME = "network-parameters"
const val NETWORK_PARAMS_UPDATE_FILE_NAME = "network-parameters-update"
/** /**
* Data class containing hash of [NetworkParameters] and network participant's [NodeInfo] hashes. * Data structure representing the network map available from the HTTP network map service as a serialised blob.
* @property nodeInfoHashes list of network participant's [NodeInfo] hashes
* @property networkParameterHash hash of the current active [NetworkParameters]
* @property parametersUpdate if present means that network operator has scheduled an update of the network parameters
*/ */
@CordaSerializable @CordaSerializable
data class NetworkMap(val nodeInfoHashes: List<SecureHash>, val networkParameterHash: SecureHash) data class NetworkMap(
val nodeInfoHashes: List<SecureHash>,
val networkParameterHash: SecureHash,
val parametersUpdate: ParametersUpdate?
)
/** /**
* @property minimumPlatformVersion Minimum version of Corda platform that is required for nodes in the network. * Data class representing scheduled network parameters update.
* @property notaries List of well known and trusted notary identities with information on validation type. * @property newParametersHash Hash of the new [NetworkParameters] which can be requested from the network map
* @property maxMessageSize Maximum P2P message sent over the wire in bytes. * @property description Short description of the update
* @property maxTransactionSize Maximum permitted transaction size in bytes. * @property updateDeadline deadline by which new network parameters need to be accepted, after this date network operator
* @property modifiedTime * can switch to new parameters which will result in getting nodes with old parameters out of the network
* @property epoch Version number of the network parameters. Starting from 1, this will always increment on each new set
* of parameters.
*/ */
// TODO Add eventHorizon - how many days a node can be offline before being automatically ejected from the network.
// It needs separate design.
// TODO Currently maxTransactionSize is not wired.
@CordaSerializable @CordaSerializable
data class NetworkParameters( data class ParametersUpdate(
val minimumPlatformVersion: Int, val newParametersHash: SecureHash,
val notaries: List<NotaryInfo>, val description: String,
val maxMessageSize: Int, val updateDeadline: Instant
val maxTransactionSize: Int, )
val modifiedTime: Instant,
val epoch: Int
) {
init {
require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" }
require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" }
require(epoch > 0) { "epoch must be at least 1" }
require(maxMessageSize > 0) { "maxMessageSize must be at least 1" }
require(maxTransactionSize > 0) { "maxTransactionSize must be at least 1" }
}
}
@CordaSerializable
data class NotaryInfo(val identity: Party, val validating: Boolean)
fun <T : Any> SignedDataWithCert<T>.verifiedNetworkMapCert(rootCert: X509Certificate): T { fun <T : Any> SignedDataWithCert<T>.verifiedNetworkMapCert(rootCert: X509Certificate): T {
require(CertRole.extract(sig.by) == CertRole.NETWORK_MAP) { "Incorrect cert role: ${CertRole.extract(sig.by)}" } require(CertRole.extract(sig.by) == CertRole.NETWORK_MAP) { "Incorrect cert role: ${CertRole.extract(sig.by)}" }

View File

@ -1,8 +1,7 @@
package net.corda.nodeapi.internal.network package net.corda.nodeapi.internal.network
import net.corda.core.internal.copyTo import net.corda.core.internal.*
import net.corda.core.internal.div import net.corda.core.node.NetworkParameters
import net.corda.core.internal.signWithCert
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.nodeapi.internal.createDevNetworkMapCa import net.corda.nodeapi.internal.createDevNetworkMapCa
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
@ -13,7 +12,9 @@ import java.nio.file.StandardCopyOption
class NetworkParametersCopier( class NetworkParametersCopier(
networkParameters: NetworkParameters, networkParameters: NetworkParameters,
networkMapCa: CertificateAndKeyPair = createDevNetworkMapCa(), networkMapCa: CertificateAndKeyPair = createDevNetworkMapCa(),
overwriteFile: Boolean = false overwriteFile: Boolean = false,
@VisibleForTesting
val update: Boolean = false
) { ) {
private val copyOptions = if (overwriteFile) arrayOf(StandardCopyOption.REPLACE_EXISTING) else emptyArray() private val copyOptions = if (overwriteFile) arrayOf(StandardCopyOption.REPLACE_EXISTING) else emptyArray()
private val serialisedSignedNetParams = networkParameters.signWithCert( private val serialisedSignedNetParams = networkParameters.signWithCert(
@ -22,8 +23,9 @@ class NetworkParametersCopier(
).serialize() ).serialize()
fun install(nodeDir: Path) { fun install(nodeDir: Path) {
val fileName = if (update) NETWORK_PARAMS_UPDATE_FILE_NAME else NETWORK_PARAMS_FILE_NAME
try { try {
serialisedSignedNetParams.open().copyTo(nodeDir / NETWORK_PARAMS_FILE_NAME, *copyOptions) serialisedSignedNetParams.open().copyTo(nodeDir / fileName, *copyOptions)
} catch (e: FileAlreadyExistsException) { } catch (e: FileAlreadyExistsException) {
// This is only thrown if the file already exists and we didn't specify to overwrite it. In that case we // This is only thrown if the file already exists and we didn't specify to overwrite it. In that case we
// ignore this exception as we're happy with the existing file. // ignore this exception as we're happy with the existing file.

View File

@ -4,8 +4,8 @@ package net.corda.nodeapi.internal.serialization
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SerializationDefaults
import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0 import net.corda.nodeapi.internal.serialization.amqp.amqpMagic
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1 import net.corda.nodeapi.internal.serialization.kryo.kryoMagic
/* /*
* Serialisation contexts for the client. * Serialisation contexts for the client.
@ -13,14 +13,13 @@ import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
* servers from trying to instantiate any of them. * servers from trying to instantiate any of them.
*/ */
val KRYO_RPC_CLIENT_CONTEXT = SerializationContextImpl(KryoHeaderV0_1, val KRYO_RPC_CLIENT_CONTEXT = SerializationContextImpl(kryoMagic,
SerializationDefaults.javaClass.classLoader, SerializationDefaults.javaClass.classLoader,
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
emptyMap(), emptyMap(),
true, true,
SerializationContext.UseCase.RPCClient) SerializationContext.UseCase.RPCClient)
val AMQP_RPC_CLIENT_CONTEXT = SerializationContextImpl(amqpMagic,
val AMQP_RPC_CLIENT_CONTEXT = SerializationContextImpl(AmqpHeaderV1_0,
SerializationDefaults.javaClass.classLoader, SerializationDefaults.javaClass.classLoader,
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
emptyMap(), emptyMap(),

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