mirror of
https://github.com/corda/corda.git
synced 2025-06-16 14:18:20 +00:00
Merge commit '86fb1ed852c69121f989c9eeea92cfb4c27f9d13' into aslemmer-merge-19-Feb
This commit is contained in:
1251
.ci/api-current.txt
1251
.ci/api-current.txt
File diff suppressed because it is too large
Load Diff
1
.gitignore
vendored
1
.gitignore
vendored
@ -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
1
.idea/compiler.xml
generated
@ -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" />
|
||||||
|
15
build.gradle
15
build.gradle
@ -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 {
|
||||||
|
@ -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.
|
||||||
|
@ -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)))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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 {
|
||||||
|
@ -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) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
@ -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}"
|
||||||
|
|
||||||
|
11
core/src/main/kotlin/net/corda/core/CordaInternal.kt
Normal file
11
core/src/main/kotlin/net/corda/core/CordaInternal.kt
Normal 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
|
@ -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)
|
|
@ -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)
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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":
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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) }
|
||||||
|
@ -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(
|
||||||
|
@ -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].
|
||||||
|
@ -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)
|
@ -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
|
||||||
|
@ -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")
|
||||||
}
|
}
|
@ -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() }
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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)
|
||||||
|
@ -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();
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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>>> {
|
||||||
|
@ -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 {
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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]) {
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
@ -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 {
|
||||||
|
@ -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))
|
|
||||||
}
|
|
@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
@ -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"]
|
@ -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
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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.*
|
||||||
|
@ -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")
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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.*
|
||||||
|
@ -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()
|
||||||
|
@ -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)}" }
|
||||||
|
@ -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.
|
||||||
|
@ -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
Reference in New Issue
Block a user