Merge pull request #466 from corda/aslemmer-merge-19-Feb

Aslemmer merge 19 feb
This commit is contained in:
Andras Slemmer 2018-02-23 13:20:07 +00:00 committed by GitHub
commit 089916d350
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
563 changed files with 12528 additions and 6021 deletions

File diff suppressed because it is too large Load Diff

View File

@ -31,8 +31,7 @@ if [ $removalCount -gt 0 ]; then
fi
# Adding new abstract methods could also break the API.
# However, first exclude anything with the @DoNotImplement annotation.
# However, first exclude classes marked with the @DoNotImplement annotation
function forUserImpl() {
awk '/DoNotImplement/,/^##/{ next }{ print }' $1
}
@ -45,13 +44,28 @@ $newAbstracts
EOF
`
#Get a list of any methods that expose classes in .internal. namespaces, and any classes which extend/implement
#an internal class
newInternalExposures=$(echo "$userDiffContents" | grep "^+" | grep "\.internal\." )
internalCount=`grep -v "^$" <<EOF | wc -l
$newInternalExposures
EOF
`
echo "Number of new internal class exposures: "$internalCount
if [ $internalCount -gt 0 ]; then
echo "$newInternalExposures"
echo
fi
echo "Number of new abstract APIs: "$abstractCount
if [ $abstractCount -gt 0 ]; then
echo "$newAbstracts"
echo
fi
badChanges=$(($removalCount + $abstractCount))
badChanges=$(($removalCount + $abstractCount + $internalCount))
if [ $badChanges -gt 255 ]; then
echo "OVERFLOW! Number of bad API changes: $badChanges"
badChanges=255

1
.gitignore vendored
View File

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

34
.idea/compiler.xml generated
View File

@ -25,11 +25,6 @@
<module name="client_test" target="1.8" />
<module name="confidential-identities_main" target="1.8" />
<module name="confidential-identities_test" target="1.8" />
<module name="corda-cordform-common_main" target="1.8" />
<module name="corda-cordform-common_test" target="1.8" />
<module name="corda-core_integrationTest" target="1.8" />
<module name="corda-core_smokeTest" target="1.8" />
<module name="corda-finance_integrationTest" target="1.8" />
<module name="corda-project_main" target="1.8" />
<module name="corda-project_test" target="1.8" />
<module name="cordapp_integrationTest" target="1.8" />
@ -37,9 +32,6 @@
<module name="cordapp_test" target="1.8" />
<module name="cordform-common_main" target="1.8" />
<module name="cordform-common_test" target="1.8" />
<module name="cordformation_main" target="1.8" />
<module name="cordformation_runnodes" target="1.8" />
<module name="cordformation_test" target="1.8" />
<module name="core_integrationTest" target="1.8" />
<module name="core_main" target="1.8" />
<module name="core_smokeTest" target="1.8" />
@ -50,9 +42,6 @@
<module name="demobench_main" target="1.8" />
<module name="demobench_test" target="1.8" />
<module name="docs_main" target="1.8" />
<module name="docs_source_example-code_integrationTest" target="1.8" />
<module name="docs_source_example-code_main" target="1.8" />
<module name="docs_source_example-code_test" target="1.8" />
<module name="docs_test" target="1.8" />
<module name="example-code_integrationTest" target="1.8" />
<module name="example-code_main" target="1.8" />
@ -70,21 +59,10 @@
<module name="finance_test" target="1.8" />
<module name="flow-hook_main" target="1.8" />
<module name="flow-hook_test" target="1.8" />
<module name="gradle-plugins-cordapp_main" target="1.8" />
<module name="gradle-plugins-cordapp_test" target="1.8" />
<module name="gradle-plugins-cordform-common_main" target="1.8" />
<module name="gradle-plugins-cordform-common_test" target="1.8" />
<module name="graphs_main" target="1.8" />
<module name="graphs_test" target="1.8" />
<module name="intellij-plugin_main" target="1.8" />
<module name="intellij-plugin_test" target="1.8" />
<module name="irs-demo-cordapp_integrationTest" target="1.8" />
<module name="irs-demo-cordapp_main" target="1.8" />
<module name="irs-demo-cordapp_main~1" target="1.8" />
<module name="irs-demo-cordapp_test" target="1.8" />
<module name="irs-demo-cordapp_test~1" target="1.8" />
<module name="irs-demo-web_main" target="1.8" />
<module name="irs-demo-web_test" target="1.8" />
<module name="irs-demo_integrationTest" target="1.8" />
<module name="irs-demo_main" target="1.8" />
<module name="irs-demo_test" target="1.8" />
@ -128,8 +106,6 @@
<module name="perftestcordapp_integrationTest" target="1.8" />
<module name="perftestcordapp_main" target="1.8" />
<module name="perftestcordapp_test" target="1.8" />
<module name="publish-utils_main" target="1.8" />
<module name="publish-utils_test" target="1.8" />
<module name="quasar-hook_main" target="1.8" />
<module name="quasar-hook_test" target="1.8" />
<module name="quasar-utils_main" target="1.8" />
@ -154,16 +130,6 @@
<module name="simm-valuation-demo_integrationTest" target="1.8" />
<module name="simm-valuation-demo_main" target="1.8" />
<module name="simm-valuation-demo_test" target="1.8" />
<module name="smoke-test-utils_main" target="1.8" />
<module name="smoke-test-utils_test" target="1.8" />
<module name="smoke-test-utils_testDriver" target="1.8" />
<module name="source-example-code_integrationTest" target="1.8" />
<module name="source-example-code_main" target="1.8" />
<module name="source-example-code_test" target="1.8" />
<module name="test-common_main" target="1.8" />
<module name="test-common_test" target="1.8" />
<module name="test-utils_main" target="1.8" />
<module name="test-utils_test" target="1.8" />
<module name="testing-smoke-test-utils_main" target="1.8" />
<module name="testing-smoke-test-utils_test" target="1.8" />
<module name="testing-test-common_main" target="1.8" />

View File

@ -15,7 +15,9 @@ buildscript {
//
// TODO: Sort this alphabetically.
ext.kotlin_version = constants.getProperty("kotlinVersion")
ext.quasar_version = '0.7.9'
// use our fork of quasar
ext.quasar_group = 'com.github.corda.quasar'
ext.quasar_version = '7629695563deae6cc95adcfbebcbc8322fd0241a'
// gradle-capsule-plugin:1.0.2 contains capsule:1.0.1
// TODO: Upgrade gradle-capsule-plugin to a version with capsule:1.0.3
@ -37,10 +39,9 @@ buildscript {
* https://issues.apache.org/jira/browse/ARTEMIS-1559
*/
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.jersey_version = '2.25'
ext.jolokia_version = '1.3.7'
ext.assertj_version = '3.8.0'
ext.slf4j_version = '1.7.25'
ext.log4j_version = '2.9.1'
@ -71,6 +72,11 @@ buildscript {
ext.liquibase_version = '3.5.3'
ext.shadow_version = '2.0.2'
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'
ext.eaagentloader_version = '1.0.3'
ext.curator_version = '4.0.0'
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
@ -80,6 +86,7 @@ buildscript {
mavenLocal()
mavenCentral()
jcenter()
// This repository is needed for Dokka until 0.9.16 is released.
maven {
url 'https://dl.bintray.com/kotlin/kotlin-eap/'
}
@ -197,6 +204,10 @@ allprojects {
systemProperty(it, property)
}
}
if (System.getProperty("test.maxParallelForks") != null) {
maxParallelForks = Integer.valueOf(System.getProperty("test.maxParallelForks"))
}
}
group 'com.r3.corda'
@ -379,8 +390,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)
if(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() || (System.getenv('CORDA_DOCS_ONLY_BUILD') != null)) {
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 {
test {

View File

@ -8,11 +8,11 @@ dependencies {
compile project(':core')
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"
// 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.
compile "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jackson_version"
// This adds support for java.time types.

View File

@ -2,7 +2,7 @@ package net.corda.client.jfx
import net.corda.client.jfx.model.NodeMonitorModel
import net.corda.client.jfx.model.ProgressTrackingEvent
import net.corda.core.context.Origin
import net.corda.core.context.InvocationOrigin
import net.corda.core.contracts.Amount
import net.corda.core.contracts.ContractState
import net.corda.core.crypto.isFulfilledBy
@ -29,11 +29,12 @@ import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.Permissions.Companion.invokeRpc
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.testing.core.*
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.node.User
import net.corda.testing.internal.IntegrationTest
import net.corda.testing.internal.IntegrationTestSchemas
import net.corda.testing.internal.toDatabaseSchemaName
import net.corda.testing.node.User
import org.junit.ClassRule
import org.junit.Test
import rx.Observable
@ -61,7 +62,7 @@ class NodeMonitorModelTest : IntegrationTest() {
}
private fun setup(runTest: () -> Unit) {
driver(extraCordappPackagesToScan = listOf("net.corda.finance")) {
driver(DriverParameters(extraCordappPackagesToScan = listOf("net.corda.finance"))) {
val cashUser = User("user1", "test", permissions = setOf(
startFlow<CashIssueFlow>(),
startFlow<CashPaymentFlow>(),
@ -85,7 +86,7 @@ class NodeMonitorModelTest : IntegrationTest() {
vaultUpdates = monitor.vaultUpdates.bufferUntilSubscribed()
networkMapUpdates = monitor.networkMap.bufferUntilSubscribed()
monitor.register(aliceNodeHandle.configuration.rpcOptions.address!!, cashUser.username, cashUser.password)
monitor.register(aliceNodeHandle.rpcAddress, cashUser.username, cashUser.password)
rpc = monitor.proxyObservable.value!!
notaryParty = defaultNotaryIdentity
@ -93,7 +94,7 @@ class NodeMonitorModelTest : IntegrationTest() {
bobNode = bobNodeHandle.nodeInfo
val monitorBob = NodeMonitorModel()
stateMachineUpdatesBob = monitorBob.stateMachineUpdates.bufferUntilSubscribed()
monitorBob.register(bobNodeHandle.configuration.rpcOptions.address!!, cashUser.username, cashUser.password)
monitorBob.register(bobNodeHandle.rpcAddress, cashUser.username, cashUser.password)
rpcBob = monitorBob.proxyObservable.value!!
runTest()
}
@ -158,8 +159,8 @@ class NodeMonitorModelTest : IntegrationTest() {
// ISSUE
expect { add: StateMachineUpdate.Added ->
issueSmId = add.id
val context = add.stateMachineInfo.context()
require(context.origin is Origin.RPC && context.principal().name == "user1")
val context = add.stateMachineInfo.invocationContext
require(context.origin is InvocationOrigin.RPC && context.principal().name == "user1")
},
expect { remove: StateMachineUpdate.Removed ->
require(remove.id == issueSmId)
@ -167,8 +168,8 @@ class NodeMonitorModelTest : IntegrationTest() {
// 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 ->
moveSmId = add.id
val context = add.stateMachineInfo.context()
require(context.origin is Origin.RPC && context.principal().name == "user1")
val context = add.stateMachineInfo.invocationContext
require(context.origin is InvocationOrigin.RPC && context.principal().name == "user1")
},
expect(match = { it is StateMachineUpdate.Removed && it.id == moveSmId }) {
}
@ -179,8 +180,8 @@ class NodeMonitorModelTest : IntegrationTest() {
sequence(
// MOVE
expect { add: StateMachineUpdate.Added ->
val context = add.stateMachineInfo.context()
require(context.origin is Origin.Peer && aliceNode.isLegalIdentity(aliceNode.identityFromX500Name((context.origin as Origin.Peer).party)))
val context = add.stateMachineInfo.invocationContext
require(context.origin is InvocationOrigin.Peer && aliceNode.isLegalIdentity(aliceNode.identityFromX500Name((context.origin as InvocationOrigin.Peer).party)))
}
)
}

View File

@ -15,6 +15,14 @@ configurations {
smokeTestRuntime.extendsFrom runtime
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
sourceSets {
integrationTest {
kotlin {

View File

@ -9,6 +9,7 @@ import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.getOrThrow
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.internal.IntegrationTest
import net.corda.testing.internal.IntegrationTestSchemas
@ -36,7 +37,7 @@ class BlacklistKotlinClosureTest : IntegrationTest() {
@Test
fun `closure sent via RPC`() {
driver(startNodesInProcess = true) {
driver(DriverParameters(startNodesInProcess = true)) {
val rpc = startNode(providedName = ALICE_NAME).getOrThrow().rpc
val packet = Packet { EVIL }
assertThatExceptionOfType(KryoException::class.java)

View File

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

View File

@ -0,0 +1,110 @@
package net.corda.client.rpc
import net.corda.core.context.Actor
import net.corda.core.context.Trace
import net.corda.core.internal.packageName
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.utilities.getOrThrow
import net.corda.finance.schemas.CashSchemaV1
import net.corda.node.internal.Node
import net.corda.node.internal.StartedNode
import net.corda.node.services.Permissions
import net.corda.node.services.Permissions.Companion.invokeRpc
import net.corda.testing.core.*
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver
import net.corda.testing.node.User
import net.corda.testing.node.internal.NodeBasedTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.Test
class FlowsExecutionModeRpcTest {
@Test
fun `persistent state survives node restart`() {
// Temporary disable this test when executed on Windows. It is known to be sporadically failing.
// More investigation is needed to establish why.
assumeFalse(System.getProperty("os.name").toLowerCase().startsWith("win"))
val user = User("mark", "dadada", setOf(invokeRpc("setFlowsDrainingModeEnabled"), invokeRpc("isFlowsDrainingModeEnabled")))
driver(DriverParameters(isDebug = true, startNodesInProcess = true)) {
val nodeName = {
val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow()
val nodeName = nodeHandle.nodeInfo.chooseIdentity().name
nodeHandle.rpc.setFlowsDrainingModeEnabled(true)
nodeHandle.stop()
nodeName
}()
val nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user)).getOrThrow()
assertThat(nodeHandle.rpc.isFlowsDrainingModeEnabled()).isEqualTo(true)
nodeHandle.stop()
}
}
}
class FlowsExecutionModeTests : NodeBasedTest(listOf("net.corda.finance.contracts", CashSchemaV1::class.packageName)) {
private val rpcUser = User("user1", "test", permissions = setOf(Permissions.all()))
private lateinit var node: StartedNode<Node>
private lateinit var client: CordaRPCClient
@Before
fun setup() {
node = startNode(ALICE_NAME, rpcUsers = listOf(rpcUser))
client = CordaRPCClient(node.internals.configuration.rpcOptions.address!!)
}
@Test
fun `flows draining mode can be enabled and queried`() {
asALoggerUser { rpcOps ->
val newValue = true
rpcOps.setFlowsDrainingModeEnabled(true)
val flowsExecutionMode = rpcOps.isFlowsDrainingModeEnabled()
assertThat(flowsExecutionMode).isEqualTo(newValue)
}
}
@Test
fun `flows draining mode can be disabled and queried`() {
asALoggerUser { rpcOps ->
rpcOps.setFlowsDrainingModeEnabled(true)
val newValue = false
rpcOps.setFlowsDrainingModeEnabled(newValue)
val flowsExecutionMode = rpcOps.isFlowsDrainingModeEnabled()
assertThat(flowsExecutionMode).isEqualTo(newValue)
}
}
@Test
fun `node starts with flows draining mode disabled`() {
asALoggerUser { rpcOps ->
val defaultStartingMode = rpcOps.isFlowsDrainingModeEnabled()
assertThat(defaultStartingMode).isEqualTo(false)
}
}
private fun login(username: String, password: String, externalTrace: Trace? = null, impersonatedActor: Actor? = null): CordaRPCConnection {
return client.start(username, password, externalTrace, impersonatedActor)
}
private fun asALoggerUser(action: (CordaRPCOps) -> Unit) {
login(rpcUser.username, rpcUser.password).use {
action(it.proxy)
}
}
}

View File

@ -30,6 +30,7 @@ import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong
class RPCStabilityTests {
@Rule
@ -127,7 +128,7 @@ class RPCStabilityTests {
rpcDriver {
fun startAndCloseServer(broker: RpcBrokerHandle) {
startRpcServerWithBrokerRunning(
configuration = RPCServerConfiguration.default.copy(consumerPoolSize = 1, producerPoolBound = 1),
configuration = RPCServerConfiguration.default,
ops = DummyOps,
brokerHandle = broker
).rpcServer.close()
@ -148,7 +149,7 @@ class RPCStabilityTests {
@Test
fun `rpc client close doesnt leak broker resources`() {
rpcDriver {
val server = startRpcServer(configuration = RPCServerConfiguration.default.copy(consumerPoolSize = 1, producerPoolBound = 1), ops = DummyOps).get()
val server = startRpcServer(configuration = RPCServerConfiguration.default, ops = DummyOps).get()
RPCClient<RPCOps>(server.broker.hostAndPort!!).start(RPCOps::class.java, rpcTestUser.username, rpcTestUser.password).close()
val initial = server.broker.getStats()
repeat(100) {
@ -337,11 +338,12 @@ class RPCStabilityTests {
val request = RPCApi.ClientToServer.RpcRequest(
clientAddress = SimpleString(myQueue),
methodName = SlowConsumerRPCOps::streamAtInterval.name,
serialisedArguments = listOf(10.millis, 123456).serialize(context = SerializationDefaults.RPC_SERVER_CONTEXT).bytes,
serialisedArguments = listOf(10.millis, 123456).serialize(context = SerializationDefaults.RPC_SERVER_CONTEXT),
replyId = Trace.InvocationId.newInstance(),
sessionId = Trace.SessionId.newInstance()
)
request.writeToClientMessage(message)
message.putLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME, 0)
producer.send(message)
session.commit()
@ -350,6 +352,79 @@ class RPCStabilityTests {
}
}
@Test
fun `deduplication in the server`() {
rpcDriver {
val server = startRpcServer(ops = SlowConsumerRPCOpsImpl()).getOrThrow()
// Construct an RPC client session manually
val myQueue = "${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.test.${random63BitValue()}"
val session = startArtemisSession(server.broker.hostAndPort!!)
session.createTemporaryQueue(myQueue, myQueue)
val consumer = session.createConsumer(myQueue, null, -1, -1, false)
val replies = ArrayList<Any>()
consumer.setMessageHandler {
replies.add(it)
it.acknowledge()
}
val producer = session.createProducer(RPCApi.RPC_SERVER_QUEUE_NAME)
session.start()
pollUntilClientNumber(server, 1)
val message = session.createMessage(false)
val request = RPCApi.ClientToServer.RpcRequest(
clientAddress = SimpleString(myQueue),
methodName = DummyOps::protocolVersion.name,
serialisedArguments = emptyList<Any>().serialize(context = SerializationDefaults.RPC_SERVER_CONTEXT),
replyId = Trace.InvocationId.newInstance(),
sessionId = Trace.SessionId.newInstance()
)
request.writeToClientMessage(message)
message.putLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME, 0)
producer.send(message)
// duplicate the message
producer.send(message)
pollUntilTrue("Number of replies is 1") {
replies.size == 1
}.getOrThrow()
}
}
@Test
fun `deduplication in the client`() {
rpcDriver {
val broker = startRpcBroker().getOrThrow()
// Construct an RPC server session manually
val session = startArtemisSession(broker.hostAndPort!!)
val consumer = session.createConsumer(RPCApi.RPC_SERVER_QUEUE_NAME)
val producer = session.createProducer()
val dedupeId = AtomicLong(0)
consumer.setMessageHandler {
it.acknowledge()
val request = RPCApi.ClientToServer.fromClientMessage(it)
when (request) {
is RPCApi.ClientToServer.RpcRequest -> {
val reply = RPCApi.ServerToClient.RpcReply(request.replyId, Try.Success(0), "server")
val message = session.createMessage(false)
reply.writeToClientMessage(SerializationDefaults.RPC_SERVER_CONTEXT, message)
message.putLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME, dedupeId.getAndIncrement())
producer.send(request.clientAddress, message)
// duplicate the reply
producer.send(request.clientAddress, message)
}
is RPCApi.ClientToServer.ObservablesClosed -> {
}
}
}
session.start()
startRpcClient<RPCOps>(broker.hostAndPort!!).getOrThrow()
}
}
}
fun RPCDriverDSL.pollUntilClientNumber(server: RpcServerHandle, expected: Int) {

View File

@ -70,11 +70,24 @@ data class CordaRPCClientConfiguration(val connectionMaxRetryInterval: Duration)
* @param configuration An optional configuration used to tweak client behaviour.
* @param sslConfiguration An optional [SSLConfiguration] used to enable secure communication with the server.
*/
class CordaRPCClient @JvmOverloads constructor(
class CordaRPCClient private constructor(
hostAndPort: NetworkHostAndPort,
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
sslConfiguration: SSLConfiguration? = null
) {
@JvmOverloads
constructor(hostAndPort: NetworkHostAndPort, configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT) : this(hostAndPort, configuration, null)
companion object {
internal fun createWithSsl(
hostAndPort: NetworkHostAndPort,
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
sslConfiguration: SSLConfiguration? = null
): CordaRPCClient {
return CordaRPCClient(hostAndPort, configuration, sslConfiguration)
}
}
init {
try {
effectiveSerializationEnv

View File

@ -0,0 +1,13 @@
package net.corda.client.rpc.internal
import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.CordaRPCClientConfiguration
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.internal.config.SSLConfiguration
/** Utility which exposes the internal Corda RPC constructor to other internal Corda components */
fun createCordaRPCClientWithSsl(
hostAndPort: NetworkHostAndPort,
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.DEFAULT,
sslConfiguration: SSLConfiguration? = null
) = CordaRPCClient.createWithSsl(hostAndPort, configuration, sslConfiguration)

View File

@ -2,22 +2,22 @@ package net.corda.client.rpc.internal
import com.esotericsoftware.kryo.pool.KryoPool
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.SerializationEnvironmentImpl
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_RPC_CLIENT_CONTEXT
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme
import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
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
class KryoClientSerializationScheme : AbstractKryoSerializationScheme() {
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean {
return byteSequence == KryoHeaderV0_1 && (target == SerializationContext.UseCase.RPCClient || target == SerializationContext.UseCase.P2P)
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
return magic == kryoMagic && (target == SerializationContext.UseCase.RPCClient || target == SerializationContext.UseCase.P2P)
}
override fun rpcClientKryoPool(context: SerializationContext): KryoPool {

View File

@ -1,5 +1,7 @@
package net.corda.client.rpc.internal
import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.CordaRPCClientConfiguration
import net.corda.client.rpc.RPCConnection
import net.corda.client.rpc.RPCException
import net.corda.core.context.Actor
@ -42,8 +44,6 @@ data class RPCClientConfiguration(
val reapInterval: Duration,
/** The number of threads to use for observations (for executing [Observable.onNext]) */
val observationExecutorPoolSize: Int,
/** The maximum number of producers to create to handle outgoing messages */
val producerPoolBound: Int,
/**
* Determines the concurrency level of the Observable Cache. This is exposed because it implicitly determines
* the limit on the number of leaked observables reaped because of garbage collection per reaping.
@ -56,9 +56,12 @@ data class RPCClientConfiguration(
val connectionRetryIntervalMultiplier: Double,
/** Maximum retry interval */
val connectionMaxRetryInterval: Duration,
/** Maximum reconnect attempts on failover */
val maxReconnectAttempts: Int,
/** Maximum file size */
val maxFileSize: Int
val maxFileSize: Int,
/** The cache expiry of a deduplication watermark per client. */
val deduplicationCacheExpiry: Duration
) {
companion object {
val unlimitedReconnectAttempts = -1
@ -68,14 +71,14 @@ data class RPCClientConfiguration(
trackRpcCallSites = false,
reapInterval = 1.seconds,
observationExecutorPoolSize = 4,
producerPoolBound = 1,
cacheConcurrencyLevel = 8,
connectionRetryInterval = 5.seconds,
connectionRetryIntervalMultiplier = 1.5,
connectionMaxRetryInterval = 3.minutes,
maxReconnectAttempts = unlimitedReconnectAttempts,
/** 10 MiB maximum allowed file size for attachments, including message headers. TODO: acquire this value from Network Map when supported. */
maxFileSize = 10485760
maxFileSize = 10485760,
deduplicationCacheExpiry = 1.days
)
}
}

View File

@ -15,7 +15,6 @@ import net.corda.client.rpc.RPCSinceVersion
import net.corda.core.context.Actor
import net.corda.core.context.Trace
import net.corda.core.context.Trace.InvocationId
import net.corda.core.internal.LazyPool
import net.corda.core.internal.LazyStickyPool
import net.corda.core.internal.LifeCycle
import net.corda.core.internal.ThreadBox
@ -26,14 +25,12 @@ import net.corda.core.utilities.Try
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.core.utilities.getOrThrow
import net.corda.nodeapi.ArtemisConsumer
import net.corda.nodeapi.ArtemisProducer
import net.corda.nodeapi.RPCApi
import net.corda.nodeapi.internal.DeduplicationChecker
import org.apache.activemq.artemis.api.core.RoutingType
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.client.*
import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE
import org.apache.activemq.artemis.api.core.client.ClientMessage
import org.apache.activemq.artemis.api.core.client.ServerLocator
import rx.Notification
import rx.Observable
import rx.subjects.UnicastSubject
@ -43,6 +40,7 @@ import java.time.Instant
import java.util.*
import java.util.concurrent.*
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong
import kotlin.reflect.jvm.javaMethod
/**
@ -111,6 +109,8 @@ class RPCClientProxyHandler(
// Used for reaping
private var reaperExecutor: ScheduledExecutorService? = null
// Used for sending
private var sendExecutor: ExecutorService? = null
// A sticky pool for running Observable.onNext()s. We need the stickiness to preserve the observation ordering.
private val observationExecutorThreadFactory = ThreadFactoryBuilder().setNameFormat("rpc-client-observation-pool-%d").setDaemon(true).build()
@ -161,22 +161,14 @@ class RPCClientProxyHandler(
build()
}
// We cannot pool consumers as we need to preserve the original muxed message order.
// TODO We may need to pool these somehow anyway, otherwise if the server sends many big messages in parallel a
// single consumer may be starved for flow control credits. Recheck this once Artemis's large message streaming is
// integrated properly.
private var sessionAndConsumer: ArtemisConsumer? = null
// Pool producers to reduce contention on the client side.
private val sessionAndProducerPool = LazyPool(bound = rpcConfiguration.producerPoolBound) {
// Note how we create new sessions *and* session factories per producer.
// We cannot simply pool producers on one session because sessions are single threaded.
// We cannot simply pool sessions on one session factory because flow control credits are tied to factories, so
// sessions tend to starve each other when used concurrently.
val sessionFactory = serverLocator.createSessionFactory()
val session = sessionFactory.createSession(rpcUsername, rpcPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE)
session.start()
ArtemisProducer(sessionFactory, session, session.createProducer(RPCApi.RPC_SERVER_QUEUE_NAME))
}
private var sessionFactory: ClientSessionFactory? = null
private var producerSession: ClientSession? = null
private var consumerSession: ClientSession? = null
private var rpcProducer: ClientProducer? = null
private var rpcConsumer: ClientConsumer? = null
private val deduplicationChecker = DeduplicationChecker(rpcConfiguration.deduplicationCacheExpiry)
private val deduplicationSequenceNumber = AtomicLong(0)
/**
* Start the client. This creates the per-client queue, starts the consumer session and the reaper.
@ -187,22 +179,25 @@ class RPCClientProxyHandler(
1,
ThreadFactoryBuilder().setNameFormat("rpc-client-reaper-%d").setDaemon(true).build()
)
sendExecutor = Executors.newSingleThreadExecutor(
ThreadFactoryBuilder().setNameFormat("rpc-client-sender-%d").build()
)
reaperScheduledFuture = reaperExecutor!!.scheduleAtFixedRate(
this::reapObservablesAndNotify,
rpcConfiguration.reapInterval.toMillis(),
rpcConfiguration.reapInterval.toMillis(),
TimeUnit.MILLISECONDS
)
sessionAndProducerPool.run {
it.session.createTemporaryQueue(clientAddress, RoutingType.ANYCAST, clientAddress)
}
val sessionFactory = serverLocator.createSessionFactory()
val session = sessionFactory.createSession(rpcUsername, rpcPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE)
val consumer = session.createConsumer(clientAddress)
consumer.setMessageHandler(this@RPCClientProxyHandler::artemisMessageHandler)
sessionAndConsumer = ArtemisConsumer(sessionFactory, session, consumer)
sessionFactory = serverLocator.createSessionFactory()
producerSession = sessionFactory!!.createSession(rpcUsername, rpcPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE)
rpcProducer = producerSession!!.createProducer(RPCApi.RPC_SERVER_QUEUE_NAME)
consumerSession = sessionFactory!!.createSession(rpcUsername, rpcPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE)
consumerSession!!.createTemporaryQueue(clientAddress, RoutingType.ANYCAST, clientAddress)
rpcConsumer = consumerSession!!.createConsumer(clientAddress)
rpcConsumer!!.setMessageHandler(this::artemisMessageHandler)
lifeCycle.transition(State.UNSTARTED, State.SERVER_VERSION_NOT_SET)
session.start()
consumerSession!!.start()
producerSession!!.start()
}
// This is the general function that transforms a client side RPC to internal Artemis messages.
@ -212,7 +207,7 @@ class RPCClientProxyHandler(
if (method == toStringMethod) {
return "Client RPC proxy for $rpcOpsClass"
}
if (sessionAndConsumer!!.session.isClosed) {
if (consumerSession!!.isClosed) {
throw RPCException("RPC Proxy is closed")
}
@ -220,23 +215,20 @@ class RPCClientProxyHandler(
callSiteMap?.set(replyId, Throwable("<Call site of root RPC '${method.name}'>"))
try {
val serialisedArguments = (arguments?.toList() ?: emptyList()).serialize(context = serializationContextWithObservableContext)
val request = RPCApi.ClientToServer.RpcRequest(clientAddress, method.name, serialisedArguments.bytes, replyId, sessionId, externalTrace, impersonatedActor)
val request = RPCApi.ClientToServer.RpcRequest(
clientAddress,
method.name,
serialisedArguments,
replyId,
sessionId,
externalTrace,
impersonatedActor
)
val replyFuture = SettableFuture.create<Any>()
sessionAndProducerPool.run {
val message = it.session.createMessage(false)
request.writeToClientMessage(message)
log.debug {
val argumentsString = arguments?.joinToString() ?: ""
"-> RPC(${replyId.value}) -> ${method.name}($argumentsString): ${method.returnType}"
}
require(rpcReplyMap.put(replyId, replyFuture) == null) {
"Generated several RPC requests with same ID $replyId"
}
it.producer.send(message)
it.session.commit()
require(rpcReplyMap.put(replyId, replyFuture) == null) {
"Generated several RPC requests with same ID $replyId"
}
sendMessage(request)
return replyFuture.getOrThrow()
} catch (e: RuntimeException) {
// Already an unchecked exception, so just rethrow it
@ -249,9 +241,24 @@ class RPCClientProxyHandler(
}
}
private fun sendMessage(message: RPCApi.ClientToServer) {
val artemisMessage = producerSession!!.createMessage(false)
message.writeToClientMessage(artemisMessage)
sendExecutor!!.submit {
artemisMessage.putLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME, deduplicationSequenceNumber.getAndIncrement())
log.debug { "-> RPC -> $message" }
rpcProducer!!.send(artemisMessage)
}
}
// The handler for Artemis messages.
private fun artemisMessageHandler(message: ClientMessage) {
val serverToClient = RPCApi.ServerToClient.fromClientMessage(serializationContextWithObservableContext, message)
val deduplicationSequenceNumber = message.getLongProperty(RPCApi.DEDUPLICATION_SEQUENCE_NUMBER_FIELD_NAME)
if (deduplicationChecker.checkDuplicateMessageId(serverToClient.deduplicationIdentity, deduplicationSequenceNumber)) {
log.info("Message duplication detected, discarding message")
return
}
log.debug { "Got message from RPC server $serverToClient" }
when (serverToClient) {
is RPCApi.ServerToClient.RpcReply -> {
@ -325,14 +332,12 @@ class RPCClientProxyHandler(
* @param notify whether to notify observables or not.
*/
private fun close(notify: Boolean = true) {
sessionAndConsumer?.sessionFactory?.close()
sessionFactory?.close()
reaperScheduledFuture?.cancel(false)
observableContext.observableMap.invalidateAll()
reapObservables(notify)
reaperExecutor?.shutdownNow()
sessionAndProducerPool.close().forEach {
it.sessionFactory.close()
}
sendExecutor?.shutdownNow()
// Note the ordering is important, we shut down the consumer *before* the observation executor, otherwise we may
// leak borrowed executors.
val observationExecutors = observationExecutorPool.close()
@ -385,11 +390,7 @@ class RPCClientProxyHandler(
}
if (observableIds != null) {
log.debug { "Reaping ${observableIds.size} observables" }
sessionAndProducerPool.run {
val message = it.session.createMessage(false)
RPCApi.ClientToServer.ObservablesClosed(observableIds).writeToClientMessage(message)
it.producer.send(message)
}
sendMessage(RPCApi.ClientToServer.ObservablesClosed(observableIds))
}
}
}

View File

@ -4,6 +4,7 @@ import com.google.common.base.Stopwatch
import net.corda.client.rpc.internal.RPCClientConfiguration
import net.corda.core.internal.concurrent.doneFuture
import net.corda.core.messaging.RPCOps
import net.corda.core.utilities.days
import net.corda.core.utilities.minutes
import net.corda.core.utilities.seconds
import net.corda.node.services.messaging.RPCServerConfiguration
@ -88,13 +89,10 @@ class RPCPerformanceTests : AbstractRPCTest() {
val proxy = testProxy(
RPCClientConfiguration.default.copy(
cacheConcurrencyLevel = 16,
observationExecutorPoolSize = 2,
producerPoolBound = 2
observationExecutorPoolSize = 2
),
RPCServerConfiguration.default.copy(
rpcThreadPoolSize = 8,
consumerPoolSize = 2,
producerPoolBound = 8
rpcThreadPoolSize = 8
)
)
@ -131,13 +129,10 @@ class RPCPerformanceTests : AbstractRPCTest() {
val proxy = testProxy(
RPCClientConfiguration.default.copy(
reapInterval = 1.seconds,
cacheConcurrencyLevel = 16,
producerPoolBound = 8
cacheConcurrencyLevel = 16
),
RPCServerConfiguration.default.copy(
rpcThreadPoolSize = 8,
consumerPoolSize = 1,
producerPoolBound = 8
rpcThreadPoolSize = 8
)
)
startPublishingFixedRateInjector(
@ -167,9 +162,7 @@ class RPCPerformanceTests : AbstractRPCTest() {
rpcDriver {
val proxy = testProxy(
RPCClientConfiguration.default,
RPCServerConfiguration.default.copy(
consumerPoolSize = 1
)
RPCServerConfiguration.default
)
val numberOfMessages = 1000
val bigSize = 10_000_000

View File

@ -14,7 +14,7 @@ dependencies {
compile project(':core')
// Quasar, for suspendable fibres.
compileOnly "co.paralleluniverse:quasar-core:$quasar_version:jdk8"
compileOnly "$quasar_group:quasar-core:$quasar_version:jdk8"
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testCompile "junit:junit:$junit_version"

View File

@ -18,7 +18,7 @@ import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.CHARLIE_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.startFlow
import org.junit.After
import org.junit.Before
@ -28,15 +28,15 @@ import kotlin.test.assertNotNull
import kotlin.test.assertNull
class IdentitySyncFlowTests {
private lateinit var mockNet: MockNetwork
private lateinit var mockNet: InternalMockNetwork
@Before
fun before() {
// We run this in parallel threads to help catch any race conditions that may exist.
mockNet = MockNetwork(
mockNet = InternalMockNetwork(
cordappPackages = listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas"),
networkSendManuallyPumped = false,
threadPerNode = true,
cordappPackages = listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas")
threadPerNode = true
)
}
@ -58,12 +58,12 @@ class IdentitySyncFlowTests {
val anonymous = true
val ref = OpaqueBytes.of(0x01)
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
assertNull(bobNode.database.transaction { bobNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) })
// 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 {
aliceNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity)
}
@ -88,7 +88,7 @@ class IdentitySyncFlowTests {
val anonymous = true
val ref = OpaqueBytes.of(0x01)
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 confidentialIdentCert = charlieNode.services.identityService.certificateFromKey(confidentialIdentity.owningKey)!!
@ -97,11 +97,11 @@ class IdentitySyncFlowTests {
assertNotNull(aliceNode.database.transaction { aliceNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity) })
// 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
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) })
}

View File

@ -3,19 +3,19 @@ package net.corda.confidential
import net.corda.core.identity.*
import net.corda.core.utilities.getOrThrow
import net.corda.testing.core.*
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.internal.InternalMockNetwork
import org.junit.Before
import net.corda.testing.node.startFlow
import org.junit.Test
import kotlin.test.*
class SwapIdentitiesFlowTests {
private lateinit var mockNet: MockNetwork
private lateinit var mockNet: InternalMockNetwork
@Before
fun setup() {
// We run this in parallel threads to help catch any race conditions that may exist.
mockNet = MockNetwork(emptyList(), networkSendManuallyPumped = false, threadPerNode = true)
mockNet = InternalMockNetwork(emptyList(), networkSendManuallyPumped = false, threadPerNode = true)
}
@Test
@ -30,7 +30,7 @@ class SwapIdentitiesFlowTests {
val requesterFlow = aliceNode.services.startFlow(SwapIdentitiesFlow(bob))
// Get the results
val actual: Map<Party, AnonymousParty> = requesterFlow.resultFuture.getOrThrow().toMap()
val actual: Map<Party, AnonymousParty> = requesterFlow.getOrThrow().toMap()
assertEquals(2, actual.size)
// Verify that the generated anonymous identities do not match the well known identities
val aliceAnonymousIdentity = actual[alice] ?: throw IllegalStateException()

View File

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

View File

@ -78,7 +78,7 @@ dependencies {
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
// Quasar, for suspendable fibres.
compileOnly "co.paralleluniverse:quasar-core:$quasar_version:jdk8"
compileOnly "$quasar_group:quasar-core:$quasar_version:jdk8"
// Thread safety annotations
compile "com.google.code.findbugs:jsr305:$jsr305_version"
@ -107,7 +107,7 @@ dependencies {
// 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.
compile "org.apache.commons:commons-jexl3:3.0"
compile 'commons-lang:commons-lang:2.6'
// For JSON
compile "com.fasterxml.jackson.core:jackson-databind:${jackson_version}"

View File

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

View File

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

View File

@ -9,52 +9,50 @@ import java.security.Principal
* Models the information needed to trace an invocation in Corda.
* Includes initiating actor, origin, trace information, and optional external trace information to correlate clients' IDs.
*
* @param origin origin of the invocation.
* @param trace Corda invocation trace.
* @param actor acting agent of the invocation, used to derive the security principal.
* @param externalTrace optional external invocation trace for cross-system logs correlation.
* @param impersonatedActor optional impersonated actor, used for logging but not for authorisation.
* @property origin Origin of the invocation.
* @property trace Corda invocation trace.
* @property actor Acting agent of the invocation, used to derive the security principal.
* @property externalTrace Optional external invocation trace for cross-system logs correlation.
* @property impersonatedActor Optional impersonated actor, used for logging but not for authorisation.
*/
@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 {
/**
* Creates an [InvocationContext] with a [Trace] that defaults to a [java.util.UUID] as value and [java.time.Instant.now] timestamp.
*/
@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
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
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
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
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
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
sealed class Origin {
sealed class InvocationOrigin {
/**
* Returns the [Principal] for a given [Actor].
*/
@ -96,32 +93,28 @@ sealed class Origin {
/**
* 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 }
}
/**
* 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() }
}
/**
* 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 }
}
/**
* Origin was a scheduled activity.
*/
data class Scheduled(val scheduledState: ScheduledStateRef) : Origin() {
data class Scheduled(val scheduledState: ScheduledStateRef) : InvocationOrigin() {
override fun principal() = Principal { "Scheduler" }
}
@ -129,8 +122,13 @@ sealed class Origin {
/**
* Origin was the Shell.
*/
object Shell : Origin() {
object Shell : InvocationOrigin() {
override fun principal() = Principal { "Shell User" }
}
}
}
/**
* Authentication / Authorisation Service ID.
*/
@CordaSerializable
data class AuthServiceId(val value: String)

View File

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

View File

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

View File

@ -46,7 +46,7 @@ interface NamedByHash {
@CordaSerializable
data class Issued<out P : Any>(val issuer: PartyAndReference, val product: P) {
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"
}

View File

@ -0,0 +1,6 @@
package net.corda.core.cordapp
/**
* Thrown if an exception occurs in accessing or parsing cordapp configuration
*/
class CordappConfigException(msg: String, e: Throwable) : Exception(msg, e)

View File

@ -0,0 +1,70 @@
package net.corda.core.cordapp
import net.corda.core.DoNotImplement
/**
* Provides access to cordapp configuration independent of the configuration provider.
*/
@DoNotImplement
interface CordappConfig {
/**
* Check if a config exists at path
*/
fun exists(path: String): Boolean
/**
* Get the value of the configuration at "path".
*
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
*/
fun get(path: String): Any
/**
* Get the int value of the configuration at "path".
*
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
*/
fun getInt(path: String): Int
/**
* Get the long value of the configuration at "path".
*
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
*/
fun getLong(path: String): Long
/**
* Get the float value of the configuration at "path".
*
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
*/
fun getFloat(path: String): Float
/**
* Get the double value of the configuration at "path".
*
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
*/
fun getDouble(path: String): Double
/**
* Get the number value of the configuration at "path".
*
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
*/
fun getNumber(path: String): Number
/**
* Get the string value of the configuration at "path".
*
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
*/
fun getString(path: String): String
/**
* Get the boolean value of the configuration at "path".
*
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
*/
fun getBoolean(path: String): Boolean
}

View File

@ -2,8 +2,6 @@ package net.corda.core.cordapp
import net.corda.core.crypto.SecureHash
// TODO: Add per app config
/**
* An app context provides information about where an app was loaded from, access to its classloader,
* and (in the included [Cordapp] object) lists of annotated classes discovered via scanning the JAR.
@ -15,5 +13,11 @@ import net.corda.core.crypto.SecureHash
* @property attachmentId For CorDapps containing [Contract] or [UpgradedContract] implementations this will be populated
* with the attachment containing those class files
* @property classLoader the classloader used to load this cordapp's classes
* @property config Configuration for this CorDapp
*/
class CordappContext(val cordapp: Cordapp, val attachmentId: SecureHash?, val classLoader: ClassLoader)
class CordappContext internal constructor(
val cordapp: Cordapp,
val attachmentId: SecureHash?,
val classLoader: ClassLoader,
val config: CordappConfig
)

View File

@ -1,6 +1,5 @@
package net.corda.core.crypto
import net.corda.core.crypto.CompositeKey.NodeAndWeight
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.exactAdd
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
* 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.
* 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)
}
// 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 {
// 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.
* 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).
* TODO: Always call this method when deserialising [CompositeKey]s.
*/
fun checkValidity() {
if (validated) return
val visitedMap = IdentityHashMap<CompositeKey, Boolean>()
visitedMap.put(this, true)
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 {
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())
else
weight.compareTo(other.weight)
@ -180,17 +190,18 @@ class CompositeKey private constructor(val threshold: Int, children: List<NodeAn
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 {
if (keysToCheck.any { it is CompositeKey }) return false
val totalWeight = children.map { (node, weight) ->
var totalWeight = 0
children.forEach { (node, weight) ->
if (node is CompositeKey) {
if (node.checkFulfilledBy(keysToCheck)) weight else 0
if (node.checkFulfilledBy(keysToCheck)) totalWeight += weight
} else {
if (keysToCheck.contains(node)) weight else 0
if (node in keysToCheck) totalWeight += weight
}
}.sum()
return totalWeight >= threshold
if (totalWeight >= threshold) return true
}
return false
}
/**
@ -201,8 +212,8 @@ class CompositeKey private constructor(val threshold: Int, children: List<NodeAn
fun isFulfilledBy(keysToCheck: Iterable<PublicKey>): Boolean {
// 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.
if (!validated)
checkValidity() // TODO: remove when checkValidity() will be eventually invoked during/after deserialization.
checkValidity()
if (keysToCheck.any { it is CompositeKey }) return false
return checkFulfilledBy(keysToCheck)
}

View File

@ -55,7 +55,7 @@ import javax.crypto.spec.SecretKeySpec
* However, only the schemes returned by {@link #listSupportedSignatureSchemes()} are supported.
* Note that Corda currently supports the following signature schemes by their code names:
* <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_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).
@ -64,7 +64,8 @@ import javax.crypto.spec.SecretKeySpec
*/
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.
*/
@JvmField
@ -75,7 +76,7 @@ object Crypto {
listOf(AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, null)),
BouncyCastleProvider.PROVIDER_NAME,
"RSA",
"SHA256WITHRSAEncryption",
"SHA256WITHRSA",
null,
3072,
"RSA_SHA256 signature scheme using SHA256 as hash algorithm."
@ -547,7 +548,7 @@ object Crypto {
/**
* Utility to simplify the act of verifying a [TransactionSignature].
* 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.
* @return true if verification passes or throw exception if verification fails.
* @throws InvalidKeyException if the key is invalid.
@ -559,7 +560,7 @@ object Crypto {
@JvmStatic
@Throws(InvalidKeyException::class, SignatureException::class)
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)
}
@ -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
* 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.
* @param txId transaction's id (Merkle root).
* @param txId transaction's id.
* @param transactionSignature the signature on the transaction.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
@ -578,7 +579,7 @@ object Crypto {
@JvmStatic
@Throws(SignatureException::class)
fun isValid(txId: SecureHash, transactionSignature: TransactionSignature): Boolean {
val signableData = SignableData(txId, transactionSignature.signatureMetadata)
val signableData = SignableData(originalSignedHash(txId, transactionSignature.partialMerkleTree), transactionSignature.signatureMetadata)
return isValid(
findSignatureScheme(transactionSignature.by),
transactionSignature.by,
@ -1011,4 +1012,21 @@ object Crypto {
else -> decodePrivateKey(key.encoded)
}
}
/**
* Get the hash value that is actually signed.
* The txId is returned when [partialMerkleTree] is null,
* else the root of the tree is computed and returned.
* Note that the hash of the txId should be a leaf in the tree, not the txId itself.
*/
private fun originalSignedHash(txId: SecureHash, partialMerkleTree: PartialMerkleTree?): SecureHash {
return if (partialMerkleTree != null) {
val usedHashes = mutableListOf<SecureHash>()
val root = PartialMerkleTree.rootAndUsedHashes(partialMerkleTree.root, usedHashes)
require(txId.sha256() in usedHashes) { "Transaction with id:$txId is not a leaf in the provided partial Merkle tree" }
root
} else {
txId
}
}
}

View File

@ -140,23 +140,24 @@ class PartialMerkleTree(val root: PartialTree) {
is PartialTree.Node -> {
val leftHash = rootAndUsedHashes(node.left, 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 hashesToCheck List of included leaves hashes that should be found in this partial tree.
*/
fun verify(merkleRootHash: SecureHash, hashesToCheck: List<SecureHash>): Boolean {
val usedHashes = ArrayList<SecureHash>()
val verifyRoot = rootAndUsedHashes(root, usedHashes)
// It means that we obtained more/fewer hashes than needed or different sets of hashes.
if (hashesToCheck.groupBy { it } != usedHashes.groupBy { it })
return false
return (verifyRoot == merkleRootHash)
return verifyRoot == merkleRootHash // Tree roots match.
&& hashesToCheck.size == usedHashes.size // Obtained the same number of hashes (leaves).
&& hashesToCheck.toSet().containsAll(usedHashes) // Lists contain the same elements.
}
/**

View File

@ -5,8 +5,10 @@ import net.corda.core.serialization.CordaSerializable
/**
* A [SignableData] object is the packet actually signed.
* 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.
*/
@CordaSerializable

View File

@ -8,13 +8,24 @@ import java.util.*
/**
* A wrapper over the signature output accompanied by signer's public key and signature metadata.
* 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
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.
* 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.
* @throws InvalidKeyException if the key is invalid.

View File

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

View File

@ -2,6 +2,7 @@ package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import co.paralleluniverse.strands.Strand
import net.corda.core.CordaInternal
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
@ -19,7 +20,6 @@ import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.*
import org.slf4j.Logger
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
@ -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
* 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
* 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
* relevant database transactions*. Only set this option to true if you know what you're doing.
*/
@Suppress("DEPRECATION", "DeprecatedCallableAddReplaceWith")
abstract class FlowLogic<out T> {
/** This is where you should log things to. */
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.
*/
@JvmStatic
@Suppress("unused") @JvmStatic
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,
* 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
* managed via another means.
* consider using [net.corda.core.contracts.SchedulableState]. It is designed to aid with managing contention
* 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
* 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
@Throws(FlowException::class)
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.")
}
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.
*/
var stateMachine: FlowStateMachine<*>
@CordaInternal
get() = _stateMachine ?: throw IllegalStateException("This can only be done after the flow has been started.")
@CordaInternal
set(value) {
_stateMachine = value
}

View File

@ -0,0 +1,101 @@
package net.corda.core.flows
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.serialize
import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.SignedTransaction
import java.security.InvalidKeyException
import java.security.SignatureException
/**
* A notarisation request specifies a list of states to consume and the id of the consuming transaction. Its primary
* purpose is for notarisation traceability a signature over the notarisation request, [NotarisationRequestSignature],
* allows a notary to prove that a certain party requested the consumption of a particular state.
*
* While the signature must be retained, the notarisation request does not need to be transferred or stored anywhere - it
* can be built from a [SignedTransaction] or a [CoreTransaction]. The notary can recompute it from the committed states index.
*
* In case there is a need to prove that a party spent a particular state, the notary will:
* 1) Locate the consuming transaction id in the index, along with all other states consumed in the same transaction.
* 2) Build a [NotarisationRequest].
* 3) Locate the [NotarisationRequestSignature] for the transaction id. The signature will contain the signing public key.
* 4) Demonstrate the signature verifies against the serialized request. The provided states are always sorted internally,
* to ensure the serialization does not get affected by the order.
*/
@CordaSerializable
class NotarisationRequest(statesToConsume: List<StateRef>, val transactionId: SecureHash) {
companion object {
/** Sorts in ascending order first by transaction hash, then by output index. */
private val stateRefComparator = compareBy<StateRef>({ it.txhash }, { it.index })
}
private val _statesToConsumeSorted = statesToConsume.sortedWith(stateRefComparator)
/** States this request specifies to be consumed. Sorted to ensure the serialized form does not get affected by the state order. */
val statesToConsume: List<StateRef> get() = _statesToConsumeSorted // Getter required for AMQP serialization
/** Verifies the signature against this notarisation request. Checks that the signature is issued by the right party. */
fun verifySignature(requestSignature: NotarisationRequestSignature, intendedSigner: Party) {
val signature = requestSignature.digitalSignature
if (intendedSigner.owningKey != signature.by) {
val errorMessage = "Expected a signature by ${intendedSigner.owningKey}, but received by ${signature.by}}"
throw NotaryException(NotaryError.RequestSignatureInvalid(IllegalArgumentException(errorMessage)))
}
// TODO: if requestSignature was generated over an old version of NotarisationRequest, we need to be able to
// reserialize it in that version to get the exact same bytes. Modify the serialization logic once that's
// available.
val expectedSignedBytes = this.serialize().bytes
verifyCorrectBytesSigned(signature, expectedSignedBytes)
}
private fun verifyCorrectBytesSigned(signature: DigitalSignature.WithKey, bytes: ByteArray) {
try {
signature.verify(bytes)
} catch (e: Exception) {
when (e) {
is InvalidKeyException, is SignatureException -> {
val error = NotaryError.RequestSignatureInvalid(e)
throw NotaryException(error)
}
else -> throw e
}
}
}
}
/**
* A wrapper around a digital signature used for notarisation requests.
*
* The [platformVersion] is required so the notary can verify the signature against the right version of serialized
* bytes of the [NotarisationRequest]. Otherwise, the request may be rejected.
*/
@CordaSerializable
data class NotarisationRequestSignature(val digitalSignature: DigitalSignature.WithKey, val platformVersion: Int)
/**
* Container for the transaction and notarisation request signature.
* This is the payload that gets sent by a client to a notary service for committing the input states of the [transaction].
*/
@CordaSerializable
data class NotarisationPayload(val transaction: Any, val requestSignature: NotarisationRequestSignature) {
init {
require(transaction is SignedTransaction || transaction is CoreTransaction) {
"Unsupported transaction type in the notarisation payload: ${transaction.javaClass.simpleName}"
}
}
/**
* A helper for automatically casting the underlying [transaction] payload to a [SignedTransaction].
* Should only be used by validating notaries.
*/
val signedTransaction get() = transaction as SignedTransaction
/**
* A helper for automatically casting the underlying [transaction] payload to a [CoreTransaction].
* Should only be used by non-validating notaries.
*/
val coreTransaction get() = transaction as CoreTransaction
}

View File

@ -9,10 +9,12 @@ import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.keys
import net.corda.core.identity.Party
import net.corda.core.internal.FetchDataFlow
import net.corda.core.internal.generateSignature
import net.corda.core.node.services.NotaryService
import net.corda.core.node.services.TrustedAuthorityNotaryService
import net.corda.core.node.services.UniquenessProvider
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.UntrustworthyData
@ -73,15 +75,17 @@ class NotaryFlow {
return notaryParty
}
/** Notarises the transaction with the [notaryParty], obtains the notary's signature(s). */
@Throws(NotaryException::class)
@Suspendable
protected fun notarise(notaryParty: Party): UntrustworthyData<List<TransactionSignature>> {
return try {
val session = initiateFlow(notaryParty)
val requestSignature = NotarisationRequest(stx.inputs, stx.id).generateSignature(serviceHub)
if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) {
sendAndReceiveValidating(session)
sendAndReceiveValidating(session, requestSignature)
} else {
sendAndReceiveNonValidating(notaryParty, session)
sendAndReceiveNonValidating(notaryParty, session, requestSignature)
}
} catch (e: NotaryException) {
if (e.error is NotaryError.Conflict) {
@ -92,21 +96,23 @@ class NotaryFlow {
}
@Suspendable
protected open fun sendAndReceiveValidating(session: FlowSession): UntrustworthyData<List<TransactionSignature>> {
subFlow(SendTransactionWithRetry(session, stx))
private fun sendAndReceiveValidating(session: FlowSession, signature: NotarisationRequestSignature): UntrustworthyData<List<TransactionSignature>> {
val payload = NotarisationPayload(stx, signature)
subFlow(NotarySendTransactionFlow(session, payload))
return session.receive()
}
@Suspendable
protected open fun sendAndReceiveNonValidating(notaryParty: Party, session: FlowSession): UntrustworthyData<List<TransactionSignature>> {
val tx: Any = if (stx.isNotaryChangeTransaction()) {
private fun sendAndReceiveNonValidating(notaryParty: Party, session: FlowSession, signature: NotarisationRequestSignature): UntrustworthyData<List<TransactionSignature>> {
val tx: CoreTransaction = if (stx.isNotaryChangeTransaction()) {
stx.notaryChangeTx // Notary change transactions do not support filtering
} else {
stx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow || it == notaryParty })
}
return session.sendAndReceiveWithRetry(tx)
return session.sendAndReceiveWithRetry(NotarisationPayload(tx, signature))
}
/** Checks that the notary's signature(s) is/are valid. */
protected fun validateResponse(response: UntrustworthyData<List<TransactionSignature>>, notaryParty: Party): List<TransactionSignature> {
return response.unwrap { signatures ->
signatures.forEach { validateSignature(it, stx.id, notaryParty) }
@ -118,16 +124,16 @@ class NotaryFlow {
check(sig.by in notaryParty.owningKey.keys) { "Invalid signer for the notary result" }
sig.verify(txId)
}
}
/**
* The [SendTransactionWithRetry] flow is equivalent to [SendTransactionFlow] but using [sendAndReceiveWithRetry]
* instead of [sendAndReceive], [SendTransactionWithRetry] is intended to be use by the notary client only.
*/
private class SendTransactionWithRetry(otherSideSession: FlowSession, stx: SignedTransaction) : SendTransactionFlow(otherSideSession, stx) {
@Suspendable
override fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any): UntrustworthyData<FetchDataFlow.Request> {
return otherSideSession.sendAndReceiveWithRetry(payload)
/**
* The [NotarySendTransactionFlow] flow is similar to [SendTransactionFlow], but uses [NotarisationPayload] as the
* initial message, and retries message delivery.
*/
private class NotarySendTransactionFlow(otherSide: FlowSession, payload: NotarisationPayload) : DataVendingFlow(otherSide, payload) {
@Suspendable
override fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any): UntrustworthyData<FetchDataFlow.Request> {
return otherSideSession.sendAndReceiveWithRetry(payload)
}
}
}
@ -186,10 +192,16 @@ class NotaryFlow {
*/
data class TransactionParts(val id: SecureHash, val inputs: List<StateRef>, val timestamp: TimeWindow?, val notary: Party?)
/**
* Exception thrown by the notary service if any issues are encountered while trying to commit a transaction. The
* underlying [error] specifies the cause of failure.
*/
class NotaryException(val error: NotaryError) : FlowException("Unable to notarise: $error")
/** Specifies the cause for notarisation request failure. */
@CordaSerializable
sealed class NotaryError {
/** Occurs when one or more input states of transaction with [txId] have already been consumed by another transaction. */
data class Conflict(val txId: SecureHash, val conflict: SignedData<UniquenessProvider.Conflict>) : NotaryError() {
override fun toString() = "One or more input states for transaction $txId have been used in another transaction"
}
@ -199,18 +211,27 @@ sealed class NotaryError {
override fun toString() = "Current time $currentTime is outside the time bounds specified by the transaction: $txTimeWindow"
companion object {
@JvmField @Deprecated("Here only for binary compatibility purposes, do not use.")
@JvmField
@Deprecated("Here only for binary compatibility purposes, do not use.")
val INSTANCE = TimeWindowInvalid(Instant.EPOCH, TimeWindow.fromOnly(Instant.EPOCH))
}
}
/** Occurs when the provided transaction fails to verify. */
data class TransactionInvalid(val cause: Throwable) : NotaryError() {
override fun toString() = cause.toString()
}
/** Occurs when the transaction sent for notarisation is assigned to a different notary identity. */
object WrongNotary : NotaryError()
data class General(val cause: String): NotaryError() {
override fun toString() = cause
/** Occurs when the notarisation request signature does not verify for the provided transaction. */
data class RequestSignatureInvalid(val cause: Throwable) : NotaryError() {
override fun toString() = "Request signature invalid: $cause"
}
/** Occurs when the notary service encounters an unexpected issue or becomes temporarily unavailable. */
data class General(val cause: Throwable) : NotaryError() {
override fun toString() = cause.toString()
}
}

View File

@ -28,7 +28,7 @@ open class SendTransactionFlow(otherSide: FlowSession, stx: SignedTransaction) :
*/
open class SendStateAndRefFlow(otherSideSession: FlowSession, stateAndRefs: List<StateAndRef<*>>) : DataVendingFlow(otherSideSession, stateAndRefs)
sealed class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any) : FlowLogic<Void?>() {
open class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any) : FlowLogic<Void?>() {
@Suspendable
protected open fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any) = otherSideSession.sendAndReceive<FetchDataFlow.Request>(payload)

View File

@ -50,9 +50,6 @@ data class CordaX500Name(val commonName: String?,
// Legal name checks.
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(organisation.length < MAX_LENGTH_ORGANISATION) {
@ -74,6 +71,7 @@ data class CordaX500Name(val commonName: String?,
}
companion object {
@Deprecated("Not Used")
const val LENGTH_COUNTRY = 2
const val MAX_LENGTH_ORGANISATION = 128
const val MAX_LENGTH_LOCALITY = 64

View File

@ -27,6 +27,10 @@ abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment {
}
protected val attachmentData: ByteArray by lazy(dataLoader)
// 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 val signers by lazy {
// Can't start with empty set if we're doing intersections. Logically the null means "all possible signers":

View File

@ -2,17 +2,24 @@
package net.corda.core.internal
import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappConfig
import net.corda.core.cordapp.CordappContext
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.crypto.*
import net.corda.core.flows.NotarisationRequest
import net.corda.core.flows.NotarisationRequestSignature
import net.corda.core.flows.NotaryFlow
import net.corda.core.identity.CordaX500Name
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.OpaqueBytes
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x500.X500NameBuilder
import org.bouncycastle.asn1.x500.style.BCStyle
@ -26,10 +33,12 @@ import java.lang.reflect.Field
import java.math.BigDecimal
import java.net.HttpURLConnection
import java.net.URL
import java.nio.ByteBuffer
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets.UTF_8
import java.nio.file.*
import java.nio.file.attribute.FileAttribute
import java.security.KeyPair
import java.security.PrivateKey
import java.security.cert.X509Certificate
import java.time.Duration
@ -307,6 +316,16 @@ val KClass<*>.packageName: String get() = java.`package`.name
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() {
if (responseCode != 200) {
val message = errorStream.use { it.reader().readText() }
@ -353,3 +372,33 @@ fun <T : Any> T.signWithCert(privateKey: PrivateKey, certificate: X509Certificat
val signature = Crypto.doSign(privateKey, serialised.bytes)
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) }
fun createCordappContext(cordapp: Cordapp, attachmentId: SecureHash?, classLoader: ClassLoader, config: CordappConfig): CordappContext {
return CordappContext(cordapp, attachmentId, classLoader, config)
}
/** Verifies that the correct notarisation request was signed by the counterparty. */
fun NotaryFlow.Service.validateRequest(request: NotarisationRequest, signature: NotarisationRequestSignature) {
val requestingParty = otherSideSession.counterparty
request.verifySignature(signature, requestingParty)
// TODO: persist the signature for traceability. Do we need to persist the request as well?
}
/** Creates a signature over the notarisation request using the legal identity key. */
fun NotarisationRequest.generateSignature(serviceHub: ServiceHub): NotarisationRequestSignature {
val serializedRequest = this.serialize().bytes
val signature = with(serviceHub) {
val myLegalIdentity = myInfo.legalIdentitiesAndCerts.first().owningKey
keyManagementService.sign(serializedRequest, myLegalIdentity)
}
return NotarisationRequestSignature(signature, serviceHub.myInfo.platformVersion)
}

View File

@ -91,7 +91,8 @@ object LegalNameValidator {
CapitalLetterRule()
)
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()
)
val legalNameFullRules: List<Rule<String>> = legalNameRules + listOf(

View File

@ -27,12 +27,11 @@ class X509EdDSAEngine : Signature {
override fun engineInitSign(privateKey: PrivateKey, random: SecureRandom) = engine.initSign(privateKey, random)
override fun engineInitVerify(publicKey: PublicKey) {
val parsedKey = if (publicKey is sun.security.x509.X509Key) {
EdDSAPublicKey(X509EncodedKeySpec(publicKey.encoded))
} else {
publicKey
val parsedKey = try {
publicKey as? EdDSAPublicKey ?: EdDSAPublicKey(X509EncodedKeySpec(publicKey.encoded))
} catch(e: Exception) {
throw (InvalidKeyException(e.message))
}
engine.initVerify(parsedKey)
}

View File

@ -1,10 +1,7 @@
package net.corda.core.messaging
import net.corda.core.concurrent.CordaFuture
import net.corda.core.context.Actor
import net.corda.core.context.AuthServiceId
import net.corda.core.context.InvocationContext
import net.corda.core.context.Origin
import net.corda.core.contracts.ContractState
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowInitiator
@ -13,6 +10,7 @@ import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.NetworkMapCache
@ -23,48 +21,45 @@ import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.Try
import rx.Observable
import java.io.IOException
import java.io.InputStream
import java.security.PublicKey
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
data class StateMachineInfo @JvmOverloads constructor(
/** A univerally unique ID ([java.util.UUID]) representing this particular instance of the named flow. */
val id: StateMachineRunId,
/** The JVM class name of the flow code. */
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 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)
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)
}
@Suppress("DEPRECATION")
fun copy(id: StateMachineRunId = this.id,
flowLogicClassName: String = this.flowLogicClassName,
initiator: FlowInitiator = this.initiator,
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)"
}
/** An alias for [StateMachineInfo] which uses more modern terminology. */
typealias FlowInfo = StateMachineInfo
@CordaSerializable
sealed class StateMachineUpdate {
abstract val id: StateMachineRunId
@ -76,6 +71,24 @@ sealed class 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
data class StateMachineTransactionMapping(val stateMachineRunId: StateMachineRunId, val transactionId: SecureHash)
@ -209,6 +222,29 @@ interface CordaRPCOps : RPCOps {
@RPCReturnsObservables
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
* with [net.corda.core.flows.StartableByRPC].
@ -321,6 +357,21 @@ interface CordaRPCOps : RPCOps {
/** Clear all network map data from local node cache. */
fun clearNetworkMapCache()
/** Sets the value of the node's flows draining mode.
* If this mode is [enabled], the node will reject new flows through RPC, ignore scheduled flows, and do not process
* initial session messages, meaning that P2P counter-parties will not be able to initiate new flows involving the node.
*
* @param enabled whether the flows draining mode will be enabled.
* */
fun setFlowsDrainingModeEnabled(enabled: Boolean)
/**
* Returns whether the flows draining mode is enabled.
*
* @see setFlowsDrainingModeEnabled
*/
fun isFlowsDrainingModeEnabled(): Boolean
}
inline fun <reified T : ContractState> CordaRPCOps.vaultQueryBy(criteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(),

View File

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

View File

@ -26,7 +26,9 @@ data class NodeInfo(val addresses: List<NetworkHostAndPort>,
) {
// TODO We currently don't support multi-IP/multi-identity nodes, we only left slots in the data structures.
init {
require(legalIdentitiesAndCerts.isNotEmpty()) { "Node should have at least one legal identity" }
require(addresses.isNotEmpty()) { "Node must have at least one address" }
require(legalIdentitiesAndCerts.isNotEmpty()) { "Node must have at least one legal identity" }
require(platformVersion > 0) { "Platform version must be at least 1" }
}
@Transient private var _legalIdentities: List<Party>? = null

View File

@ -2,6 +2,7 @@ package net.corda.core.node
import net.corda.core.DoNotImplement
import net.corda.core.contracts.*
import net.corda.core.cordapp.CordappContext
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignableData
@ -60,6 +61,9 @@ interface ServicesForResolution : StateLoader {
/** Provides access to anything relating to cordapps including contract attachment resolution and app context */
val cordappProvider: CordappProvider
/** Returns the network parameters the node is operating under. */
val networkParameters: NetworkParameters
}
/**
@ -369,4 +373,9 @@ interface ServiceHub : ServicesForResolution {
* node starts.
*/
fun registerUnloadHandler(runOnStop: () -> Unit)
/**
* See [CordappProvider.getAppContext]
*/
fun getAppContext(): CordappContext = cordappProvider.getAppContext()
}

View File

@ -2,7 +2,6 @@ package net.corda.core.node.services
import net.corda.core.DoNotImplement
import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
@ -68,12 +67,22 @@ interface NetworkMapCacheBase {
fun track(): DataFeed<List<NodeInfo>, NetworkMapCache.MapChange>
/**
* Look up the node info for a legal name.
* Notice that when there are more than one node for a given name (in case of distributed services) first service node
* found will be returned.
* Return a [NodeInfo] which has the given legal name for one of its identities, or null if no such node is found.
*
* @throws IllegalArgumentException If more than one matching node is found, in the case of a distributed service identity
* (such as with a notary cluster). For such a scenerio use [getNodesByLegalName] instead.
*/
fun getNodeByLegalName(name: CordaX500Name): NodeInfo?
/**
* Return a list of [NodeInfo]s which have the given legal name for one of their identities, or an empty list if no
* such nodes are found.
*
* Normally there is at most one node for a legal name, but for distributed service identities (such as with a notary
* cluster) there can be multiple nodes sharing the same identity.
*/
fun getNodesByLegalName(name: CordaX500Name): List<NodeInfo>
/** Look up the node info for a host and port. */
fun getNodeByAddress(address: NetworkHostAndPort): NodeInfo?
@ -101,13 +110,6 @@ interface NetworkMapCacheBase {
*/
fun getNodesByLegalIdentityKey(identityKey: PublicKey): List<NodeInfo>
/**
* Look up the node information entries for a legal name.
* Note that normally there will be only one node for a legal name, but for clusters of nodes or distributed services there
* can be multiple nodes.
*/
fun getNodesByLegalName(name: CordaX500Name): List<NodeInfo>
/** Returns information about the party, which may be a specific node or a service */
fun getPartyInfo(party: Party): PartyInfo?

View File

@ -94,7 +94,7 @@ abstract class TrustedAuthorityNotaryService : NotaryService() {
}
} catch (e: Exception) {
log.error("Internal error", e)
throw NotaryException(NotaryError.General("Service unavailable, please try again later"))
throw NotaryException(NotaryError.General(Exception("Service unavailable, please try again later")))
}
}
@ -104,15 +104,19 @@ abstract class TrustedAuthorityNotaryService : NotaryService() {
return NotaryException(NotaryError.Conflict(txId, signedConflict))
}
/** Sign a [ByteArray] input. */
fun sign(bits: ByteArray): DigitalSignature.WithKey {
return services.keyManagementService.sign(bits, notaryIdentityKey)
}
/** Sign a single transaction. */
fun sign(txId: SecureHash): TransactionSignature {
val signableData = SignableData(txId, SignatureMetadata(services.myInfo.platformVersion, Crypto.findSignatureScheme(notaryIdentityKey).schemeNumberID))
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")
protected open val timeWindowChecker: TimeWindowChecker get() = throw UnsupportedOperationException("No default implementation, need to override")
}

View File

@ -98,9 +98,7 @@ abstract class SerializationFactory {
val currentFactory: SerializationFactory? get() = _currentFactory.get()
}
}
typealias VersionHeader = ByteSequence
typealias SerializationMagic = ByteSequence
/**
* Parameters to serialization and deserialization.
*/
@ -108,7 +106,7 @@ interface SerializationContext {
/**
* When serializing, use the format this header sequence represents.
*/
val preferredSerializationVersion: VersionHeader
val preferredSerializationVersion: SerializationMagic
/**
* 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.
*/
fun withPreferredSerializationVersion(versionHeader: VersionHeader): SerializationContext
fun withPreferredSerializationVersion(magic: SerializationMagic): SerializationContext
/**
* 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]
* to get the original object back.
*/
@Suppress("unused")
class SerializedBytes<T : Any>(bytes: ByteArray) : OpaqueBytes(bytes) {
// It's OK to use lazy here because SerializedBytes is configured to use the ImmutableClassSerializer.
val hash: SecureHash by lazy { bytes.sha256() }

View File

@ -3,12 +3,14 @@ package net.corda.core.transactions
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.serialization.CordaSerializable
/**
* A transaction with the minimal amount of information required to compute the unique transaction [id], and
* resolve a [FullTransaction]. This type of transaction, wrapped in [SignedTransaction], gets transferred across the
* wire and recorded to storage.
*/
@CordaSerializable
abstract class CoreTransaction : BaseTransaction() {
/** The inputs of this transaction, containing state references only **/
abstract override val inputs: List<StateRef>

View File

@ -68,15 +68,15 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
*/
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
/** Helper to access the notary of the contained transaction */
/** Helper to access the notary of the contained transaction. */
val notary: Party? get() = transaction.notary
override val requiredSigningKeys: Set<PublicKey> get() = tx.requiredSigningKeys
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>()
this.tx.commands.forEach { command ->
if (command.signers.any { it in keys })
@ -134,8 +134,18 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
@JvmOverloads
@Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServiceHub, checkSufficientSignatures: Boolean = true): LedgerTransaction {
checkSignaturesAreValid()
if (checkSufficientSignatures) verifyRequiredSignatures()
// 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()
}
return tx.toLedgerTransaction(services)
}
@ -153,28 +163,25 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
@Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class)
fun verify(services: ServiceHub, checkSufficientSignatures: Boolean = true) {
if (isNotaryChangeTransaction()) {
verifyNotaryChangeTransaction(checkSufficientSignatures, services)
verifyNotaryChangeTransaction(services, checkSufficientSignatures)
} else {
verifyRegularTransaction(checkSufficientSignatures, services)
verifyRegularTransaction(services, checkSufficientSignatures)
}
}
/**
* TODO: Verify contract constraints here as well as in LedgerTransaction to ensure that anything being deserialised
* from the attachment is trusted. This will require some partial serialisation work to not load the ContractState
* objects from the TransactionState.
*/
private fun verifyRegularTransaction(checkSufficientSignatures: Boolean, services: ServiceHub) {
checkSignaturesAreValid()
if (checkSufficientSignatures) verifyRequiredSignatures()
val ltx = tx.toLedgerTransaction(services)
// TODO: allow non-blocking verification
// TODO: Verify contract constraints here as well as in LedgerTransaction to ensure that anything being deserialised
// from the attachment is trusted. This will require some partial serialisation work to not load the ContractState
// objects from the TransactionState.
private fun verifyRegularTransaction(services: ServiceHub, checkSufficientSignatures: Boolean) {
val ltx = toLedgerTransaction(services, checkSufficientSignatures)
// TODO: allow non-blocking verification.
services.transactionVerifierService.verify(ltx).getOrThrow()
}
private fun verifyNotaryChangeTransaction(checkSufficientSignatures: Boolean, services: ServiceHub) {
private fun verifyNotaryChangeTransaction(services: ServiceHub, checkSufficientSignatures: Boolean) {
val ntx = resolveNotaryChangeTransaction(services)
if (checkSufficientSignatures) ntx.verifyRequiredSignatures()
else checkSignaturesAreValid()
}
fun isNotaryChangeTransaction() = transaction is NotaryChangeWireTransaction

View File

@ -11,7 +11,7 @@ import java.security.PublicKey
import java.security.SignatureException
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
interface TransactionWithSignatures : NamedByHash {
/**
@ -21,7 +21,7 @@ interface TransactionWithSignatures : NamedByHash {
*/
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>
/**
@ -65,11 +65,10 @@ interface TransactionWithSignatures : NamedByHash {
*/
@Throws(SignatureException::class)
fun verifySignaturesExcept(allowedToBeMissing: Collection<PublicKey>) {
checkSignaturesAreValid()
val needed = getMissingSigners() - allowedToBeMissing
if (needed.isNotEmpty())
throw SignaturesMissingException(needed.toNonEmptySet(), getKeyDescriptions(needed), id)
checkSignaturesAreValid()
}
/**

View File

@ -84,11 +84,12 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
*/
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServicesForResolution): LedgerTransaction {
return toLedgerTransaction(
return toLedgerTransactionInternal(
resolveIdentity = { services.identityService.partyFromKey(it) },
resolveAttachment = { services.attachments.openAttachment(it) },
resolveStateRef = { services.loadState(it) },
resolveContractAttachment = { services.cordappProvider.getContractAttachmentID(it.contract) }
resolveContractAttachment = { services.cordappProvider.getContractAttachmentID(it.contract) },
maxTransactionSize = services.networkParameters.maxTransactionSize
)
}
@ -99,12 +100,23 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
* @throws AttachmentResolutionException if a required attachment was not found using [resolveAttachment].
* @throws TransactionResolutionException if an input was not found not using [resolveStateRef].
*/
@Deprecated("Use toLedgerTransaction(ServicesForTransaction) instead")
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(
resolveIdentity: (PublicKey) -> Party?,
resolveAttachment: (SecureHash) -> Attachment?,
resolveStateRef: (StateRef) -> TransactionState<*>?,
resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId?
): LedgerTransaction {
return toLedgerTransactionInternal(resolveIdentity, resolveAttachment, resolveStateRef, resolveContractAttachment, 10485760)
}
private fun toLedgerTransactionInternal(
resolveIdentity: (PublicKey) -> Party?,
resolveAttachment: (SecureHash) -> Attachment?,
resolveStateRef: (StateRef) -> TransactionState<*>?,
resolveContractAttachment: (TransactionState<ContractState>) -> AttachmentId?,
maxTransactionSize: Int
): LedgerTransaction {
// Look up public keys to authenticated identities. This is just a stub placeholder and will all change in future.
val authenticatedArgs = commands.map {
@ -118,7 +130,24 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
val contractAttachments = findAttachmentContracts(resolvedInputs, resolveContractAttachment, resolveAttachment)
// 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()
return LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt)
val ltx = LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, timeWindow, privacySalt)
checkTransactionSize(ltx, maxTransactionSize)
return ltx
}
private fun checkTransactionSize(ltx: LedgerTransaction, maxTransactionSize: Int) {
var remainingTransactionSize = maxTransactionSize
fun minus(size: Int) {
require(remainingTransactionSize > size) { "Transaction exceeded network's maximum transaction size limit : $maxTransactionSize bytes." }
remainingTransactionSize -= size
}
// Check attachment size first as they are most likely to go over the limit.
ltx.attachments.associateBy(Attachment::id).values.forEach { minus(it.size) }
minus(ltx.inputs.serialize().size)
minus(ltx.commands.serialize().size)
minus(ltx.outputs.serialize().size)
}
/**

View File

@ -4,44 +4,27 @@ package net.corda.core.utilities
import net.corda.core.serialization.CordaSerializable
import 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
/**
* 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.
*
* @property offset The start position of the sequence within the byte array.
* @property size The number of bytes this sequence represents.
*/
@CordaSerializable
sealed class ByteSequence : 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
}
sealed class ByteSequence(private val _bytes: ByteArray, val offset: Int, val size: Int) : Comparable<ByteSequence> {
/**
* The underlying bytes. Some implementations may choose to make a copy of the underlying [ByteArray] for
* security reasons. For example, [OpaqueBytes].
*/
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 */
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 size The size of the intended sub sequence.
*/
@Suppress("MemberVisibilityCanPrivate")
fun subSequence(offset: Int, size: Int): ByteSequence {
require(offset >= 0)
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 {
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.
*/
fun take(n: Int): ByteSequence {
require(size >= n)
return subSequence(0, n)
fun take(n: Int): ByteSequence = 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
* 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
@ -135,7 +136,7 @@ sealed class ByteSequence : Comparable<ByteSequence> {
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
* 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 {
/**
* 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
* to prevent tampering with entities such as [SecureHash] and [PrivacySalt], as well as
* preserve the integrity of our hash constants [zeroHash] and [allOnesHash].
* 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 [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
* 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
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 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 {
require(offset >= 0 && offset < bytes.size)
require(size >= 0 && size <= bytes.size)

View File

@ -5,6 +5,7 @@ import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.CordaSerializable
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import rx.Observable
import java.time.Duration
import java.util.concurrent.ExecutionException
import java.util.concurrent.Future

View File

@ -3,9 +3,9 @@ package net.corda.core.flows;
import co.paralleluniverse.fibers.Suspendable;
import com.google.common.primitives.Primitives;
import net.corda.core.identity.Party;
import net.corda.node.internal.StartedNode;
import net.corda.testing.core.TestConstants;
import net.corda.testing.node.MockNetwork;
import net.corda.testing.node.StartedMockNode;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@ -21,8 +21,8 @@ import static org.junit.Assert.fail;
public class FlowsInJavaTest {
private final MockNetwork mockNet = new MockNetwork(emptyList());
private StartedNode<MockNetwork.MockNode> aliceNode;
private StartedNode<MockNetwork.MockNode> bobNode;
private StartedMockNode aliceNode;
private StartedMockNode bobNode;
private Party bob;
@Before
@ -40,7 +40,7 @@ public class FlowsInJavaTest {
@Test
public void suspendableActionInsideUnwrap() throws Exception {
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();
assertThat(result.get()).isEqualTo("Hello");
}
@ -56,7 +56,7 @@ public class FlowsInJavaTest {
private void primitiveReceiveTypeTest(Class<?> receiveType) throws InterruptedException {
PrimitiveReceiveFlow flow = new PrimitiveReceiveFlow(bob, receiveType);
Future<?> result = startFlow(aliceNode.getServices(), flow).getResultFuture();
Future<?> result = startFlow(aliceNode.getServices(), flow);
mockNet.runNetwork();
try {
result.get();

View File

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

View File

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

View File

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

View File

@ -58,19 +58,19 @@ class PartialMerkleTreeTest {
hashed = nodes.map { it.serialize().sha256() }
expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash)).hash
merkleTree = MerkleTree.getMerkleTree(hashed)
testLedger = MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also {
testLedger = MockServices(emptyList(), MEGA_CORP.name, rigorousMock<IdentityServiceInternal>().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
}, MEGA_CORP.name).ledger(DUMMY_NOTARY) {
}).ledger(DUMMY_NOTARY) {
unverifiedTransaction {
attachments(Cash.PROGRAM_ID)
output(Cash.PROGRAM_ID, "MEGA_CORP cash",
Cash.State(
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
owner = MEGA_CORP))
Cash.State(
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
owner = MEGA_CORP))
output(Cash.PROGRAM_ID, "dummy cash 1",
Cash.State(
amount = 900.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
owner = MINI_CORP))
Cash.State(
amount = 900.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
owner = MINI_CORP))
}
transaction {
attachments(Cash.PROGRAM_ID)

View File

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

View File

@ -13,8 +13,8 @@ import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNodeParameters
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.startFlow
import org.junit.After
import org.junit.Before
@ -27,11 +27,11 @@ import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class AttachmentTests {
lateinit var mockNet: MockNetwork
lateinit var mockNet: InternalMockNetwork
@Before
fun setUp() {
mockNet = MockNetwork(emptyList())
mockNet = InternalMockNetwork(emptyList())
}
@After
@ -65,7 +65,7 @@ class AttachmentTests {
mockNet.runNetwork()
val bobFlow = bobNode.startAttachmentFlow(setOf(id), alice)
mockNet.runNetwork()
assertEquals(0, bobFlow.resultFuture.getOrThrow().fromDisk.size)
assertEquals(0, bobFlow.getOrThrow().fromDisk.size)
// Verify it was inserted into node one's store.
val attachment = bobNode.database.transaction {
@ -77,7 +77,7 @@ class AttachmentTests {
// Shut down node zero and ensure node one can still resolve the attachment.
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])
}
@ -92,7 +92,7 @@ class AttachmentTests {
val alice = aliceNode.info.singleIdentity()
val bobFlow = bobNode.startAttachmentFlow(setOf(hash), alice)
mockNet.runNetwork()
val e = assertFailsWith<FetchDataFlow.HashNotFound> { bobFlow.resultFuture.getOrThrow() }
val e = assertFailsWith<FetchDataFlow.HashNotFound> { bobFlow.getOrThrow() }
assertEquals(hash, e.requested)
}
@ -100,7 +100,7 @@ class AttachmentTests {
fun maliciousResponse() {
// Make a node that doesn't do sanity checking at load time.
val aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME), nodeFactory = { args ->
object : MockNetwork.MockNode(args) {
object : InternalMockNetwork.MockNode(args) {
override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = false }
}
})
@ -127,7 +127,7 @@ class AttachmentTests {
mockNet.runNetwork()
val bobFlow = bobNode.startAttachmentFlow(setOf(id), alice)
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))

View File

@ -15,8 +15,9 @@ import net.corda.node.internal.StartedNode
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.*
import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockServices
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
import net.corda.testing.node.startFlow
import org.junit.After
import org.junit.Before
@ -29,10 +30,10 @@ class CollectSignaturesFlowTests {
private val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
}
private lateinit var mockNet: MockNetwork
private lateinit var aliceNode: StartedNode<MockNetwork.MockNode>
private lateinit var bobNode: StartedNode<MockNetwork.MockNode>
private lateinit var charlieNode: StartedNode<MockNetwork.MockNode>
private lateinit var mockNet: InternalMockNetwork
private lateinit var aliceNode: StartedNode<MockNode>
private lateinit var bobNode: StartedNode<MockNode>
private lateinit var charlieNode: StartedNode<MockNode>
private lateinit var alice: Party
private lateinit var bob: Party
private lateinit var charlie: Party
@ -40,7 +41,7 @@ class CollectSignaturesFlowTests {
@Before
fun setup() {
mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
aliceNode = mockNet.createPartyNode(ALICE_NAME)
bobNode = mockNet.createPartyNode(BOB_NAME)
charlieNode = mockNet.createPartyNode(CHARLIE_NAME)
@ -115,7 +116,7 @@ class CollectSignaturesFlowTests {
val state = DummyContract.MultiOwnerState(magicNumber, parties)
val flow = aliceNode.services.startFlow(TestFlow.Initiator(state, notary))
mockNet.runNetwork()
val result = flow.resultFuture.getOrThrow()
val result = flow.getOrThrow()
result.verifyRequiredSignatures()
println(result.tx)
println(result.sigs)
@ -127,7 +128,7 @@ class CollectSignaturesFlowTests {
val ptx = aliceNode.services.signInitialTransaction(onePartyDummyContract)
val flow = aliceNode.services.startFlow(CollectSignaturesFlow(ptx, emptySet()))
mockNet.runNetwork()
val result = flow.resultFuture.getOrThrow()
val result = flow.getOrThrow()
result.verifyRequiredSignatures()
println(result.tx)
println(result.sigs)
@ -136,12 +137,12 @@ class CollectSignaturesFlowTests {
@Test
fun `fails when not signed by initiator`() {
val onePartyDummyContract = DummyContract.generateInitial(1337, notary, alice.ref(1))
val miniCorpServices = MockServices(listOf("net.corda.testing.contracts"), rigorousMock(), miniCorp)
val miniCorpServices = MockServices(listOf("net.corda.testing.contracts"), miniCorp, rigorousMock())
val ptx = miniCorpServices.signInitialTransaction(onePartyDummyContract)
val flow = aliceNode.services.startFlow(CollectSignaturesFlow(ptx, emptySet()))
mockNet.runNetwork()
assertFailsWith<IllegalArgumentException>("The Initiator of CollectSignaturesFlow must have signed the transaction.") {
flow.resultFuture.getOrThrow()
flow.getOrThrow()
}
}
@ -155,7 +156,7 @@ class CollectSignaturesFlowTests {
val signedByBoth = bobNode.services.addSignature(signedByA)
val flow = aliceNode.services.startFlow(CollectSignaturesFlow(signedByBoth, emptySet()))
mockNet.runNetwork()
val result = flow.resultFuture.getOrThrow()
val result = flow.getOrThrow()
println(result.tx)
println(result.sigs)
}

View File

@ -19,17 +19,14 @@ import net.corda.finance.flows.CashIssueFlow
import net.corda.node.internal.SecureCordaRPCOps
import net.corda.node.internal.StartedNode
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.node.User
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyContractV2
import net.corda.testing.node.internal.RPCDriverDSL
import net.corda.testing.node.internal.rpcDriver
import net.corda.testing.node.internal.rpcTestUser
import net.corda.testing.node.internal.startRpcClient
import net.corda.testing.node.MockNetwork
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.User
import net.corda.testing.node.internal.*
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
import net.corda.testing.node.startFlow
import org.junit.After
import org.junit.Before
@ -40,16 +37,16 @@ import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class ContractUpgradeFlowTest {
private lateinit var mockNet: MockNetwork
private lateinit var aliceNode: StartedNode<MockNetwork.MockNode>
private lateinit var bobNode: StartedNode<MockNetwork.MockNode>
private lateinit var mockNet: InternalMockNetwork
private lateinit var aliceNode: StartedNode<MockNode>
private lateinit var bobNode: StartedNode<MockNode>
private lateinit var notary: Party
private lateinit var alice: Party
private lateinit var bob: Party
@Before
fun setup() {
mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts", "net.corda.finance.contracts.asset", "net.corda.core.flows", "net.corda.finance.schemas"))
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts", "net.corda.finance.contracts.asset", "net.corda.core.flows", "net.corda.finance.schemas"))
aliceNode = mockNet.createPartyNode(ALICE_NAME)
bobNode = mockNet.createPartyNode(BOB_NAME)
notary = mockNet.defaultNotaryIdentity
@ -81,29 +78,29 @@ class ContractUpgradeFlowTest {
requireNotNull(btx)
// 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()
assertFailsWith(UnexpectedFlowEndException::class) { rejectedFuture.getOrThrow() }
// 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.Deauthorise(btx.tx.outRef<ContractState>(0).ref)).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)).getOrThrow()
// 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()
assertFailsWith(UnexpectedFlowEndException::class) { deauthorisedFuture.getOrThrow() }
// 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.
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()
val result = resultFuture.getOrThrow()
fun check(node: StartedNode<*>) {
fun check(node: StartedNode<MockNode>) {
val nodeStx = node.database.transaction {
node.services.validatedTransactions.getTransaction(result.ref.txhash)
}
@ -123,7 +120,7 @@ class ContractUpgradeFlowTest {
check(bobNode)
}
private fun RPCDriverDSL.startProxy(node: StartedNode<*>, user: User): CordaRPCOps {
private fun RPCDriverDSL.startProxy(node: StartedNode<MockNode>, user: User): CordaRPCOps {
return startRpcClient<CordaRPCOps>(
rpcAddress = startRpcServer(
rpcUser = user,
@ -213,7 +210,7 @@ class ContractUpgradeFlowTest {
fun `upgrade Cash to v2`() {
// Create some cash.
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()
val stx = result.getOrThrow().stx
val anonymisedRecipient = result.get().recipient!!
@ -221,7 +218,7 @@ class ContractUpgradeFlowTest {
val baseState = aliceNode.database.transaction { aliceNode.services.vaultService.queryBy<ContractState>().states.single() }
assertTrue(baseState.state.data is Cash.State, "Contract state is old version.")
// 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()
upgradeResult.getOrThrow()
// Get contract state from the vault.

View File

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

View File

@ -5,6 +5,8 @@ import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.unwrap
import net.corda.node.internal.InitiatedFlowFactory
import net.corda.node.internal.StartedNode
import net.corda.testing.node.StartedMockNode
import net.corda.testing.node.internal.InternalMockNetwork
import kotlin.reflect.KClass
/**
@ -37,14 +39,14 @@ class NoAnswer(private val closure: () -> Unit = {}) : FlowLogic<Unit>() {
/**
* Allows to register a flow of type [R] against an initiating flow of type [I].
*/
inline fun <I : FlowLogic<*>, reified R : FlowLogic<*>> StartedNode<*>.registerInitiatedFlow(initiatingFlowType: KClass<I>, crossinline construct: (session: FlowSession) -> R) {
inline fun <I : FlowLogic<*>, reified R : FlowLogic<*>> StartedNode<InternalMockNetwork.MockNode>.registerInitiatedFlow(initiatingFlowType: KClass<I>, crossinline construct: (session: FlowSession) -> R) {
internalRegisterFlowFactory(initiatingFlowType.java, InitiatedFlowFactory.Core { session -> construct(session) }, R::class.javaObjectType, true)
}
/**
* Allows to register a flow of type [Answer] against an initiating flow of type [I], returning a valure of type [R].
*/
inline fun <I : FlowLogic<*>, reified R : Any> StartedNode<*>.registerAnswer(initiatingFlowType: KClass<I>, value: R) {
inline fun <I : FlowLogic<*>, reified R : Any> StartedNode<InternalMockNetwork.MockNode>.registerAnswer(initiatingFlowType: KClass<I>, value: R) {
internalRegisterFlowFactory(initiatingFlowType.java, InitiatedFlowFactory.Core { session -> Answer(session, value) }, Answer::class.javaObjectType, true)
}

View File

@ -5,15 +5,15 @@ import net.corda.core.identity.Party
import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap
import net.corda.testing.node.MockNetwork
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.startFlow
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Test
class ReceiveMultipleFlowTests {
private val mockNet = MockNetwork(emptyList())
private val mockNet = InternalMockNetwork(emptyList())
private val nodes = (0..2).map { mockNet.createPartyNode() }
@After
fun stopNodes() {
@ -52,7 +52,7 @@ class ReceiveMultipleFlowTests {
val flow = nodes[0].services.startFlow(initiatingFlow)
mockNet.runNetwork()
val receivedAnswer = flow.resultFuture.getOrThrow()
val receivedAnswer = flow.getOrThrow()
assertThat(receivedAnswer).isEqualTo(answer)
}
@ -64,7 +64,7 @@ class ReceiveMultipleFlowTests {
nodes[2].registerAnswer(AlgorithmDefinition::class, stringValue)
val flow = nodes[0].services.startFlow(ParallelAlgorithmMap(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity()))
mockNet.runNetwork()
val result = flow.resultFuture.getOrThrow()
val result = flow.getOrThrow()
assertThat(result).isEqualTo(doubleValue * stringValue.length)
}
@ -76,7 +76,7 @@ class ReceiveMultipleFlowTests {
nodes[2].registerAnswer(ParallelAlgorithmList::class, value2)
val flow = nodes[0].services.startFlow(ParallelAlgorithmList(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity()))
mockNet.runNetwork()
val data = flow.resultFuture.getOrThrow()
val data = flow.getOrThrow()
assertThat(data[0]).isEqualTo(value1)
assertThat(data[1]).isEqualTo(value2)
assertThat(data.fold(1.0) { a, b -> a * b }).isEqualTo(value1 * value2)

View File

@ -7,9 +7,9 @@ import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.testing.core.DEV_ROOT_CA
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.getTestPartyAndCertificate
import net.corda.testing.internal.DEV_ROOT_CA
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test

View File

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

View File

@ -10,8 +10,9 @@ import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.sequence
import net.corda.node.internal.StartedNode
import net.corda.testing.contracts.DummyContract
import net.corda.testing.node.MockNetwork
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.InternalMockNetwork.MockNode
import net.corda.testing.node.startFlow
import org.junit.After
import org.junit.Before
@ -27,17 +28,17 @@ import kotlin.test.assertNull
// DOCSTART 3
class ResolveTransactionsFlowTest {
private lateinit var mockNet: MockNetwork
private lateinit var notaryNode: StartedNode<MockNetwork.MockNode>
private lateinit var megaCorpNode: StartedNode<MockNetwork.MockNode>
private lateinit var miniCorpNode: StartedNode<MockNetwork.MockNode>
private lateinit var mockNet: InternalMockNetwork
private lateinit var notaryNode: StartedNode<MockNode>
private lateinit var megaCorpNode: StartedNode<MockNode>
private lateinit var miniCorpNode: StartedNode<MockNode>
private lateinit var megaCorp: Party
private lateinit var miniCorp: Party
private lateinit var notary: Party
@Before
fun setup() {
mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
mockNet = InternalMockNetwork(cordappPackages = listOf("net.corda.testing.contracts"))
notaryNode = mockNet.defaultNotaryNode
megaCorpNode = mockNet.createPartyNode(CordaX500Name("MegaCorp", "London", "GB"))
miniCorpNode = mockNet.createPartyNode(CordaX500Name("MiniCorp", "London", "GB"))
@ -52,14 +53,14 @@ class ResolveTransactionsFlowTest {
fun tearDown() {
mockNet.stopNodes()
}
// DOCEND 3
// DOCEND 3
// DOCSTART 1
@Test
fun `resolve from two hashes`() {
val (stx1, stx2) = makeTransactions()
val p = TestFlow(setOf(stx2.id), megaCorp)
val future = miniCorpNode.services.startFlow(p).resultFuture
val future = miniCorpNode.services.startFlow(p)
mockNet.runNetwork()
val results = future.getOrThrow()
assertEquals(listOf(stx1.id, stx2.id), results.map { it.id })
@ -74,7 +75,7 @@ class ResolveTransactionsFlowTest {
fun `dependency with an error`() {
val stx = makeTransactions(signFirstTX = false).second
val p = TestFlow(setOf(stx.id), megaCorp)
val future = miniCorpNode.services.startFlow(p).resultFuture
val future = miniCorpNode.services.startFlow(p)
mockNet.runNetwork()
assertFailsWith(SignedTransaction.SignaturesMissingException::class) { future.getOrThrow() }
}
@ -83,7 +84,7 @@ class ResolveTransactionsFlowTest {
fun `resolve from a signed transaction`() {
val (stx1, stx2) = makeTransactions()
val p = TestFlow(stx2, megaCorp)
val future = miniCorpNode.services.startFlow(p).resultFuture
val future = miniCorpNode.services.startFlow(p)
mockNet.runNetwork()
future.getOrThrow()
miniCorpNode.database.transaction {
@ -108,7 +109,7 @@ class ResolveTransactionsFlowTest {
cursor = stx
}
val p = TestFlow(setOf(cursor.id), megaCorp, 40)
val future = miniCorpNode.services.startFlow(p).resultFuture
val future = miniCorpNode.services.startFlow(p)
mockNet.runNetwork()
assertFailsWith<ResolveTransactionsFlow.ExcessivelyLargeTransactionGraph> { future.getOrThrow() }
}
@ -132,7 +133,7 @@ class ResolveTransactionsFlowTest {
}
val p = TestFlow(setOf(stx3.id), megaCorp)
val future = miniCorpNode.services.startFlow(p).resultFuture
val future = miniCorpNode.services.startFlow(p)
mockNet.runNetwork()
future.getOrThrow()
}
@ -154,7 +155,7 @@ class ResolveTransactionsFlowTest {
}
val stx2 = makeTransactions(withAttachment = id).second
val p = TestFlow(stx2, megaCorp)
val future = miniCorpNode.services.startFlow(p).resultFuture
val future = miniCorpNode.services.startFlow(p)
mockNet.runNetwork()
future.getOrThrow()

View File

@ -114,4 +114,12 @@ class X509EdDSAEngineTest {
engine.verify(signature)
}
}
/** Verify will fail if the input public key cannot be converted to EdDSA public key. */
@Test
fun `verify with non-supported key type fails`() {
val engine = EdDSAEngine()
val keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.ECDSA_SECP256K1_SHA256, BigInteger.valueOf(SEED))
assertFailsWith<InvalidKeyException> { engine.initVerify(keyPair.public) }
}
}

View File

@ -18,9 +18,9 @@ import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.nodeapi.internal.persistence.currentDBSession
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNodeParameters
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.startFlow
import org.junit.After
import org.junit.Before
@ -63,14 +63,14 @@ private fun updateAttachment(attachmentId: SecureHash, data: ByteArray) {
}
class AttachmentSerializationTest {
private lateinit var mockNet: MockNetwork
private lateinit var server: StartedNode<MockNetwork.MockNode>
private lateinit var client: StartedNode<MockNetwork.MockNode>
private lateinit var mockNet: InternalMockNetwork
private lateinit var server: StartedNode<InternalMockNetwork.MockNode>
private lateinit var client: StartedNode<InternalMockNetwork.MockNode>
private lateinit var serverIdentity: Party
@Before
fun setUp() {
mockNet = MockNetwork(emptyList())
mockNet = InternalMockNetwork(emptyList())
server = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME))
client = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME))
client.internals.disableDBCloseOnStop() // Otherwise the in-memory database may disappear (taking the checkpoint with it) while we reboot the client.
@ -114,6 +114,7 @@ class AttachmentSerializationTest {
private class CustomAttachment(override val id: SecureHash, internal val customContent: String) : Attachment {
override fun open() = throw UnsupportedOperationException("Not implemented.")
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) {
@ -160,7 +161,7 @@ class AttachmentSerializationTest {
private fun rebootClientAndGetAttachmentContent(checkAttachmentsOnLoad: Boolean = true): String {
client.dispose()
client = mockNet.createNode(MockNodeParameters(client.internals.id, client.internals.configuration.myLegalName), { args ->
object : MockNetwork.MockNode(args) {
object : InternalMockNetwork.MockNode(args) {
override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = checkAttachmentsOnLoad }
}
})

View File

@ -62,8 +62,8 @@ class TransactionSerializationTests {
val inputState = StateAndRef(TransactionState(TestCash.State(depositRef, 100.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY), fakeStateRef)
val outputState = TransactionState(TestCash.State(depositRef, 600.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY)
val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, MEGA_CORP), TEST_CASH_PROGRAM_ID, DUMMY_NOTARY)
val megaCorpServices = MockServices(listOf("net.corda.core.serialization"), rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY)
val notaryServices = MockServices(listOf("net.corda.core.serialization"), rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
val megaCorpServices = MockServices(listOf("net.corda.core.serialization"), MEGA_CORP.name, rigorousMock(), MEGA_CORP_KEY)
val notaryServices = MockServices(listOf("net.corda.core.serialization"), DUMMY_NOTARY.name, rigorousMock(), DUMMY_NOTARY_KEY)
lateinit var tx: TransactionBuilder
@Before
@ -107,7 +107,7 @@ class TransactionSerializationTests {
Command(TestCash.Commands.Move(), DUMMY_KEY_2.public))
val ptx2 = notaryServices.signInitialTransaction(tx2)
val dummyServices = MockServices(emptyList(), rigorousMock(), MEGA_CORP.name, DUMMY_KEY_2)
val dummyServices = MockServices(emptyList(), MEGA_CORP.name, rigorousMock(), DUMMY_KEY_2)
val stx2 = dummyServices.addSignature(ptx2)
stx.copy(sigs = stx2.sigs).verifyRequiredSignatures()

View File

@ -10,6 +10,7 @@ import net.corda.core.identity.Party
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.*
import net.corda.testing.internal.MockCordappProvider
import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices
import org.junit.Before
@ -29,14 +30,15 @@ class LedgerTransactionQueryTests {
@JvmField
val testSerialization = SerializationEnvironmentRule()
private val keyPair = generateKeyPair()
private val services = MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also {
doReturn(null).whenever(it).partyFromKey(keyPair.public)
}, CordaX500Name("MegaCorp", "London", "GB"), keyPair)
private val services = MockServices(emptyList(), CordaX500Name("MegaCorp", "London", "GB"),
rigorousMock<IdentityServiceInternal>().also {
doReturn(null).whenever(it).partyFromKey(keyPair.public)
}, keyPair)
private val identity: Party = services.myInfo.singleIdentity()
@Before
fun setup() {
services.mockCordappProvider.addMockCordapp(DummyContract.PROGRAM_ID, services.attachments)
services.addMockCordapp(DummyContract.PROGRAM_ID)
}
interface Commands {

View File

@ -51,7 +51,8 @@ class TransactionEncumbranceTests {
class DummyTimeLock : Contract {
override fun verify(tx: LedgerTransaction) {
val timeLockInput = tx.inputsOfType<State>().singleOrNull() ?: return
val time = tx.timeWindow?.untilTime ?: throw IllegalArgumentException("Transactions containing time-locks must have a time-window")
val time = tx.timeWindow?.untilTime
?: throw IllegalArgumentException("Transactions containing time-locks must have a time-window")
requireThat {
"the time specified in the time-lock has passed" using (time >= timeLockInput.validFrom)
}
@ -64,9 +65,10 @@ class TransactionEncumbranceTests {
}
}
private val ledgerServices = MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
}, MEGA_CORP.name)
private val ledgerServices = MockServices(emptyList(), MEGA_CORP.name,
rigorousMock<IdentityServiceInternal>().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
})
@Test
fun `state can be encumbered`() {

View File

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

View File

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

View File

@ -7,55 +7,135 @@ API: Contract Constraints
Contract constraints
--------------------
Transaction states specify a constraint over the contract that will be used to verify it. For a transaction to be
valid, the ``verify`` function associated with each state must run successfully. However, for this to be secure, it is
not sufficient to specify the ``verify`` function by name as there may exist multiple different implementations with
the same method signature and enclosing class. Contract constraints solve this problem by allowing a contract developer
to constrain which ``verify`` functions out of the universe of implementations can be used (i.e. the universe is
everything that matches the signature and contract constraints restrict this universe to a subset).
A typical constraint is the hash of the CorDapp JAR that contains the contract and states but will in future releases
include constraints that require specific signers of the JAR, or both the signer and the hash. Constraints can be
specified when constructing a transaction; if unspecified, an automatic constraint is used.
Corda separates verification of states from their definition. Whilst you might have expected the ``ContractState``
interface to define a verify method, or perhaps to do verification logic in the constructor, instead it is primarily
done by a method on a ``Contract`` class. This is because what we're actually checking is the
validity of a *transaction*, which is more than just whether the individual states are internally consistent.
The transition between two valid states may be invalid, if the rules of the application are not being respected.
For instance, two cash states of $100 and $200 may both be internally valid, but replacing the first with the second
isn't allowed unless you're a cash issuer - otherwise you could print money for free.
For a transaction to be valid, the ``verify`` function associated with each state must run successfully. However,
for this to be secure, it is not sufficient to specify the ``verify`` function by name as there may exist multiple
different implementations with the same method signature and enclosing class. This normally will happen as applications
evolve, but could also happen maliciously.
Contract constraints solve this problem by allowing a contract developer to constrain which ``verify`` functions out of
the universe of implementations can be used (i.e. the universe is everything that matches the signature and contract
constraints restrict this universe to a subset). Constraints are satisfied by attachments (JARs). You are not allowed to
attach two JARs that both define the same application due to the *no overlap rule*. This rule specifies that two
attachment JARs may not provide the same file path. If they do, the transaction is considered invalid. Because each
state specifies both a constraint over attachments *and* a Contract class name to use, the specified class must appear
in only one attachment.
So who picks the attachment to use? It is chosen by the creator of the transaction that has to satisfy input constraints.
The transaction creator also gets to pick the constraints used by any output states, but the contract logic itself may
have opinions about what those constraints are - a typical contract would require that the constraints are propagated,
that is, the contract will not just enforce the validity of the next transaction that uses a state, but *all successive
transactions as well*. The constraints mechanism creates a balance of power between the creator of data on
the ledger and the user who is trying to edit it, which can be very useful when managing upgrades to Corda applications.
There are two ways of handling upgrades to a smart contract in Corda:
1. *Implicit:* By allowing multiple implementations of the contract ahead of time, using constraints.
2. *Explicit:* By creating a special *contract upgrade transaction* and getting all participants of a state to sign it using the
contract upgrade flows.
This article focuses on the first approach. To learn about the second please see :doc:`upgrading-cordapps`.
The advantage of pre-authorising upgrades using constraints is that you don't need the heavyweight process of creating
upgrade transactions for every state on the ledger. The disadvantage is that you place more faith in third parties,
who could potentially change the app in ways you did not expect or agree with. The advantage of using the explicit
upgrade approach is that you can upgrade states regardless of their constraint, including in cases where you didn't
anticipate a need to do so. But it requires everyone to sign, requires everyone to manually authorise the upgrade,
consumes notary and ledger resources, and is just in general more complex.
How constraints work
--------------------
Starting from Corda 3 there are two types of constraint that can be used: hash and zone whitelist. In future
releases a third type will be added, the signature constraint.
**Hash constraints.** The behaviour provided by public blockchain systems like Bitcoin and Ethereum is that once data is placed on the ledger,
the program that controls it is fixed and cannot be changed. There is no support for upgrades at all. This implements a
form of "code is law", assuming you trust the community of that blockchain to not release a new version of the platform
that invalidates or changes the meaning of your program.
This is supported by Corda using a hash constraint. This specifies exactly one hash of a CorDapp JAR that contains the
contract and states any consuming transaction is allowed to use. Once such a state is created, other nodes will only
accept a transaction if it uses that exact JAR file as an attachment. By implication, any bugs in the contract code
or state definitions cannot be fixed except by using an explicit upgrade process via ``ContractUpgradeFlow``.
.. note:: Corda does not support any way to create states that can never be upgraded at all, but the same effect can be
obtained by using a hash constraint and then simply refusing to agree to any explicit upgrades. Hash
constraints put you in control by requiring an explicit agreement to any upgrade.
**Zone constraints.** Often a hash constraint will be too restrictive. You do want the ability to upgrade an app,
and you don't mind the upgrade taking effect "just in time" when a transaction happens to be required for other business
reasons. In this case you can use a zone constraint. This specifies that the network parameters of a compatibility zone
(see :doc:`network-map`) is expected to contain a map of class name to hashes of JARs that are allowed to provide that
class. The process for upgrading an app then involves asking the zone operator to add the hash of your new JAR to the
parameters file, and trigger the network parameters upgrade process. This involves each node operator running a shell
command to accept the new parameters file and then restarting the node. Node owners who do not restart their node in
time effectively stop being a part of the network.
**Signature constraints.** These are not yet supported, but once implemented they will allow a state to require a JAR
signed by a specified identity, via the regular Java jarsigner tool. This will be the most flexible type
and the smoothest to deploy: no restarts or contract upgrade transactions are needed.
**Defaults.** The default constraint type is either a zone constraint, if the network parameters in effect when the
transaction is built contain an entry for that contract class, or a hash constraint if not.
A ``TransactionState`` has a ``constraint`` field that represents that state's attachment constraint. When a party
constructs a ``TransactionState`` without specifying the constraint parameter a default value
(``AutomaticHashConstraint``) is used. This default will be automatically resolved to a specific
``HashAttachmentConstraint`` that contains the hash of the attachment which contains the contract of that
``TransactionState``. This automatic resolution occurs when a ``TransactionBuilder`` is converted to a
``WireTransaction``. This reduces the boilerplate involved in finding a specific hash constraint when building a
transaction.
constructs a ``TransactionState``, or adds a state using ``TransactionBuilder.addOutput(ContractState)`` without
specifying the constraint parameter, a default value (``AutomaticHashConstraint``) is used. This default will be
automatically resolved to a specific ``HashAttachmentConstraint`` or a ``WhitelistedByZoneAttachmentConstraint``.
This automatic resolution occurs when a ``TransactionBuilder`` is converted to a ``WireTransaction``. This reduces
the boilerplate that would otherwise be involved.
It is possible to specify the constraint explicitly with any other class that implements the ``AttachmentConstraint``
interface. To specify a hash manually the ``HashAttachmentConstraint`` can be used and to not provide any constraint
the ``AlwaysAcceptAttachmentConstraint`` can be used - though this is intended for testing only. An example below
shows how to construct a ``TransactionState`` with an explicitly specified hash constraint from within a flow:
Finally, an ``AlwaysAcceptAttachmentConstraint`` can be used which accepts anything, though this is intended for
testing only.
Please note that the ``AttachmentConstraint`` interface is marked as ``@DoNotImplement``. You are not allowed to write
new constraint types. Only the platform may implement this interface. If you tried, other nodes would not understand
your constraint type and your transaction would not verify.
.. warning:: An AlwaysAccept constraint is effectively the same as disabling security for those states entirely.
Nothing stops you using this constraint in production, but that degrades Corda to being effectively a form
of distributed messaging with optional contract logic being useful only to catch mistakes, rather than potentially
malicious action. If you are deploying an app for which malicious actors aren't in your threat model, using an
AlwaysAccept constraint might simplify things operationally.
An example below shows how to construct a ``TransactionState`` with an explicitly specified hash constraint from within
a flow:
.. sourcecode:: java
// Constructing a transaction with a custom hash constraint on a state
TransactionBuilder tx = new TransactionBuilder()
// Constructing a transaction with a custom hash constraint on a state
TransactionBuilder tx = new TransactionBuilder();
Party notaryParty = ... // a notary party
DummyState contractState = new DummyState()
SecureHash myAttachmentsHash = serviceHub.cordappProvider.getContractAttachmentID(DummyContract.PROGRAM_ID)
TransactionState transactionState = new TransactionState(contractState, DummyContract.Companion.getPROGRAMID(), notaryParty, new AttachmentHashConstraint(myAttachmentsHash))
Party notaryParty = ... // a notary party
DummyState contractState = new DummyState();
tx.addOutputState(transactionState)
WireTransaction wtx = tx.toWireTransaction(serviceHub) // This is where an automatic constraint would be resolved
LedgerTransaction ltx = wtx.toLedgerTransaction(serviceHub)
ltx.verify() // Verifies both the attachment constraints and contracts
SecureHash myAttachmentHash = SecureHash.parse("2b4042aed7e0e39d312c4c477dca1d96ec5a878ddcfd5583251a8367edbd4a5f");
TransactionState transactionState = new TransactionState(contractState, DummyContract.Companion.getPROGRAMID(), notaryParty, new AttachmentHashConstraint(myAttachmentHash));
This mechanism exists both for integrity and security reasons. It is important not to verify against the wrong contract,
which could happen if the wrong version of the contract is attached. More importantly when resolving transaction chains
there will, in a future release, be attachments loaded from the network into the attachment sandbox that are used
to verify the transaction chain. Ensuring the attachment used is the correct one ensures that the verification is
tamper-proof by providing a fake contract.
tx.addOutputState(transactionState);
WireTransaction wtx = tx.toWireTransaction(serviceHub); // This is where an automatic constraint would be resolved.
LedgerTransaction ltx = wtx.toLedgerTransaction(serviceHub);
ltx.verify(); // Verifies both the attachment constraints and contracts
Hard-coding the hash of your app in the code itself can be pretty awkward, so the API also offers the ``AutomaticHashConstraint``.
This isn't a real constraint that will appear in a transaction: it acts as a marker to the ``TransactionBuilder`` that
you require the hash of the node's installed app which supplies the specified contract to be used. In practice, when using
hash constraints, you almost always want "whatever the current code is" and not a hard-coded hash. So this automatic
constraint placeholder is useful.
CorDapps as attachments
-----------------------
CorDapp JARs (:doc:`cordapp-overview`) that are installed to the node and contain classes implementing the ``Contract``
CorDapp JARs (see :doc:`cordapp-overview`) that are installed to the node and contain classes implementing the ``Contract``
interface are automatically loaded into the ``AttachmentStorage`` of a node at startup.
After CorDapps are loaded into the attachment store the node creates a link between contract classes and the attachment
@ -63,28 +143,12 @@ that they were loaded from. This makes it possible to find the attachment for an
automatic resolution of attachments is done by the ``TransactionBuilder`` and how, when verifying the constraints and
contracts, attachments are associated with their respective contracts.
Implementations of AttachmentConstraint
---------------------------------------
There are three implementations of ``AttachmentConstraint`` with more planned in the future.
``AlwaysAcceptAttachmentConstraint``: Any attachment (except a missing one) will satisfy this constraint.
``AutomaticHashConstraint``: This will be resolved to a ``HashAttachmentConstraint`` when a ``TransactionBuilder`` is
converted to a ``WireTransaction``. The ``HashAttachmentConstraint`` will include the attachment hash of the CorDapp
that contains the ``ContractState`` on the ``TransactionState.contract`` field.
``HashAttachmentConstraint``: Will require that the hash of the attachment containing the contract matches the hash
stored in the constraint.
We plan to add a future ``AttachmentConstraint`` that will only be satisfied by the presence of signatures on the
attachment JAR. This allows for trusting of attachments from trusted entities.
Limitations
-----------
An ``AttachmentConstraint`` is verified by running the ``AttachmentConstraint.isSatisfiedBy`` method. When this is called
it is provided only the relevant attachment by the transaction that is verifying it.
.. note:: The obvious way to write a CorDapp is to put all you states, contracts, flows and support code into a single
Java module. This will work but it will effectively publish your entire app onto the ledger. That has two problems:
(1) it is inefficient, and (2) it means changes to your flows or other parts of the app will be seen by the ledger
as a "new app", which may end up requiring essentially unnecessary upgrade procedures. It's better to split your
app into multiple modules: one which contains just states, contracts and core data types. And another which contains
the rest of the app. See :ref:`cordapp-structure`.
Testing
-------
@ -93,7 +157,8 @@ Since all tests involving transactions now require attachments it is also requir
for tests. Unit test environments in JVM ecosystems tend to use class directories rather than JARs, and so CorDapp JARs
typically aren't built for testing. Requiring this would add significant complexity to the build systems of Corda
and CorDapps, so the test suite has a set of convenient functions to generate CorDapps from package names or
to specify JAR URLs in the case that the CorDapp(s) involved in testing already exist.
to specify JAR URLs in the case that the CorDapp(s) involved in testing already exist. You can also just use
``AlwaysAcceptAttachmentConstraint`` in your tests to disable the constraints mechanism.
MockNetwork/MockNode
********************
@ -102,12 +167,14 @@ The simplest way to ensure that a vanilla instance of a MockNode generates the c
``cordappPackages`` constructor parameter (Kotlin) or the ``setCordappPackages`` method on ``MockNetworkParameters`` (Java)
when creating the MockNetwork. This will cause the ``AbstractNode`` to use the named packages as sources for CorDapps. All files
within those packages will be zipped into a JAR and added to the attachment store and loaded as CorDapps by the
``CordappLoader``. An example of this usage would be:
``CordappLoader``.
An example of this usage would be:
.. sourcecode:: java
class SomeTestClass {
MockNetwork network = null
MockNetwork network = null;
@Before
void setup() {
@ -117,6 +184,7 @@ within those packages will be zipped into a JAR and added to the attachment stor
... // Your tests go here
}
MockServices
************
@ -127,6 +195,10 @@ to use as CorDapps using the ``cordappPackages`` parameter.
MockServices mockServices = new MockServices(Arrays.asList("com.domain.cordapp"))
However - there is an easier way! If your unit tests are in the same package as the contract code itself, then you
can use the no-args constructor of ``MockServices``. The package to be scanned for CorDapps will be the same as the
the package of the class that constructed the object. This is a convenient default.
Driver
******

View File

@ -411,7 +411,6 @@ Our side of the flow must mirror these calls. We could do this as follows:
Subflows
--------
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
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 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
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 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.
@ -446,32 +443,38 @@ An example is the ``@InitiatingFlow InitiatorFlow``/``@InitiatedBy ResponderFlow
.. note:: Initiating flows are versioned separately from their parents.
Core initiating subflows
^^^^^^^^^^^^^^^^^^^^^^^^
~~~~~~~~~~~~~~~~~~~~~~~~
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``
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
* ``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
the other side.
* ``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`` allows us to notarise the transaction and get it recorded in the vault of the participants of all
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.
CollectSignaturesFlow/SignTransactionFlow
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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
``CollectSignaturesFlow``:
@ -546,7 +549,7 @@ transaction and provide their signature if they are satisfied:
:dedent: 12
SendTransactionFlow/ReceiveTransactionFlow
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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.
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?
^^^^^^^^^^^^^^^^^^^^^
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
``SignTransactionFlow`` that signs the transaction. This would mean malicious nodes can just send any old transaction to

View File

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

View File

@ -615,6 +615,24 @@ which the signatures are allowed to be missing:
:end-before: DOCEND 36
:dedent: 16
There is also an overload of ``SignedTransaction.verifySignaturesExcept``, which takes a ``Collection`` of the
public keys for which the signatures are allowed to be missing:
.. container:: codeset
.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/FlowCookbook.kt
:language: kotlin
:start-after: DOCSTART 54
:end-before: DOCEND 54
:dedent: 8
.. literalinclude:: ../../docs/source/example-code/src/main/java/net/corda/docs/FlowCookbookJava.java
:language: java
:start-after: DOCSTART 54
:end-before: DOCEND 54
:dedent: 16
If the transaction is missing any signatures without the corresponding public keys being passed in, a
``SignaturesMissingException`` is thrown.

View File

@ -6,6 +6,22 @@ from previous releases. Please refer to :doc:`upgrade-notes` for detailed instru
UNRELEASED
----------
* Added ``NetworkMapCache.getNodesByLegalName`` for querying nodes belonging to a distributed service such as a notary cluster
where they all share a common identity. ``NetworkMapCache.getNodeByLegalName`` has been tightened to throw if more than
one node with the legal name is found.
* Per CorDapp configuration is now exposed. ``CordappContext`` now exposes a ``CordappConfig`` object that is populated
at CorDapp context creation time from a file source during runtime.
* Introduced Flow Draining mode, in which a node continues executing existing flows, but does not start new. This is to support graceful node shutdown/restarts.
In particular, when this mode is on, new flows through RPC will be rejected, scheduled flows will be ignored, and initial session messages will not be consumed.
This will ensure that the number of checkpoints will strictly diminish with time, allowing for a clean shutdown.
* Make the serialisation finger-printer a pluggable entity rather than hard wiring into the factory
* 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.
* Refactored ``NodeConfiguration`` to expose ``NodeRpcOptions`` (using top-level "rpcAddress" property still works with warning).
@ -71,7 +87,10 @@ R3 Corda 3.0 Developer Preview
:doc:`corda-configuration-file` for more details.
* Introducing the concept of network parameters which are a set of constants which all nodes on a network must agree on
to correctly interop.
to correctly interop. These can be retrieved from ``ServiceHub.networkParameters``.
* One of these parameters, ``maxTransactionSize``, limits the size of a transaction, including its attachments, so that
all nodes have sufficient memory to validate transactions.
* The set of valid notaries has been moved to the network parameters. Notaries are no longer identified by the CN in
their X.500 name.
@ -196,6 +215,26 @@ 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.
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
* Provided experimental support for specifying your own webserver to be used instead of the default development
webserver in ``Cordform`` using the ``webserverJar`` argument
* Created new ``StartedMockNode`` and ``UnstartedMockNode`` classes which are wrappers around our MockNode implementation
that expose relevant methods for testing without exposing internals, create these using a ``MockNetwork``.
* The test utils in ``Expect.kt``, ``SerializationTestHelpers.kt``, ``TestConstants.kt`` and ``TestUtils.kt`` have moved
from the ``net.corda.testing`` package to the ``net.corda.testing.core`` package, and ``FlowStackSnapshot.kt`` has moved to the
``net.corda.testing.services`` package. Moving existing classes out of the ``net.corda.testing.*`` package
will help make it clearer which parts of the api are stable. Scripts have been provided to smooth the upgrade
process for existing projects in the ``tools\scripts`` directory of the Corda repo.
.. _changelog_v2:
Corda 2.0

View File

@ -203,8 +203,8 @@ path to the node's base directory.
:publicKeyFile: Path to the public key file for SSH authentication.
:sshPort: Port to be used for SSH connection, default ``22``.
:exportJMXTo: If set to ``http``, will enable JMX metrics reporting via the Jolokia HTTP/JSON agent.
Default Jolokia access url is http://127.0.0.1:7005/jolokia/
:jmxMonitoringHttpPort: If set, will enable JMX metrics reporting via the Jolokia HTTP/JSON agent on the corresponding port.
Default Jolokia access url is http://127.0.0.1:port/jolokia/
:transactionCacheSizeMegaBytes: Optionally specify how much memory should be used for caching of ledger transactions in memory.
Otherwise defaults to 8MB plus 5% of all heap memory above 300MB.

View File

@ -157,3 +157,20 @@ Installing the CorDapp JAR
At runtime, nodes will load any CorDapps present in their ``cordapps`` folder. Therefore in order to install a CorDapp on
a node, the CorDapp JAR must be added to the ``<node_dir>/cordapps/`` folder, where ``node_dir`` is the folder in which
the node's JAR and configuration files are stored.
CorDapp configuration files
---------------------------
CorDapp configuration files should be placed in ``<node_dir>/cordapps/config``. The name of the file should match the
name of the JAR of the CorDapp (eg; if your CorDapp is called ``hello-0.1.jar`` the config should be ``config/hello-0.1.conf``).
Config files are currently only available in the `Typesafe/Lightbend <https://github.com/lightbend/config>`_ config format.
These files are loaded when a CorDapp context is created and so can change during runtime.
CorDapp configuration can be accessed from ``CordappContext::config`` whenever a ``CordappContext`` is available.
There is an example project that demonstrates in ``samples` called ``cordapp-configuration`` and API documentation in
<api/kotlin/corda/net.corda.core.cordapp/index.html>`_.

View File

@ -30,8 +30,8 @@ handling, and ensures the Corda service is run at boot.
4. (Optional) Download the `Corda webserver jar <http://r3.bintray.com/corda/net/corda/corda-webserver/>`_
(under ``/VERSION_NUMBER/corda-VERSION_NUMBER.jar``) and place it in ``/opt/corda``
5. Create a directory called ``plugins`` in ``/opt/corda`` and save your CorDapp jar file to it. Alternatively, download one of
our `sample CorDapps <https://www.corda.net/samples/>`_ to the ``plugins`` directory
5. Create a directory called ``cordapps`` in ``/opt/corda`` and save your CorDapp jar file to it. Alternatively, download one of
our `sample CorDapps <https://www.corda.net/samples/>`_ to the ``cordapps`` directory
6. Save the below as ``/opt/corda/node.conf``. See :doc:`corda-configuration-file` for a description of these options
@ -199,8 +199,8 @@ at boot, and means the Corda service stays running with no users connected to th
mkdir C:\Corda
wget http://jcenter.bintray.com/net/corda/corda/VERSION_NUMBER/corda-VERSION_NUMBER.jar -OutFile C:\Corda\corda.jar
2. Create a directory called ``plugins`` in ``/opt/corda`` and save your CorDapp jar file to it. Alternatively,
download one of our `sample CorDapps <https://www.corda.net/samples/>`_ to the ``plugins`` directory
2. Create a directory called ``cordapps`` in ``/opt/corda`` and save your CorDapp jar file to it. Alternatively,
download one of our `sample CorDapps <https://www.corda.net/samples/>`_ to the ``cordapps`` directory
3. Save the below as ``C:\Corda\node.conf``. See :doc:`corda-configuration-file` for a description of these options
@ -282,4 +282,4 @@ You can verify Corda is running by connecting to your RPC port from another host
``telnet your-hostname.example.com 10002``
If you receive the message "Escape character is ^]", Corda is running and accessible. Press Ctrl-] and Ctrl-D to exit
telnet.
telnet.

View File

@ -1,5 +1,6 @@
package net.corda.docs
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.internal.concurrent.transpose
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow
@ -15,11 +16,12 @@ import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.Permissions.Companion.invokeRpc
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.testing.core.*
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.node.User
import net.corda.testing.internal.IntegrationTest
import net.corda.testing.internal.IntegrationTestSchemas
import net.corda.testing.internal.toDatabaseSchemaName
import net.corda.testing.node.User
import org.junit.ClassRule
import org.junit.Test
import kotlin.test.assertEquals
@ -34,8 +36,10 @@ class IntegrationTestingTutorial : IntegrationTest() {
@Test
fun `alice bob cash exchange example`() {
// START 1
driver(startNodesInProcess = true,
extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset","net.corda.finance.schemas")) {
driver(DriverParameters(
startNodesInProcess = true,
extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset","net.corda.finance.schemas")
)) {
val aliceUser = User("aliceUser", "testPassword1", permissions = setOf(
startFlow<CashIssueFlow>(),
startFlow<CashPaymentFlow>(),
@ -56,10 +60,10 @@ class IntegrationTestingTutorial : IntegrationTest() {
// END 1
// START 2
val aliceClient = alice.rpcClientToNode()
val aliceClient = CordaRPCClient(alice.rpcAddress)
val aliceProxy = aliceClient.start("aliceUser", "testPassword1").proxy
val bobClient = bob.rpcClientToNode()
val bobClient = CordaRPCClient(bob.rpcAddress)
val bobProxy = bobClient.start("bobUser", "testPassword2").proxy
// END 2

View File

@ -540,12 +540,21 @@ public class FlowCookbookJava {
// DOCEND 35
// If the transaction is only partially signed, we have to pass in
// a list of the public keys corresponding to the missing
// a vararg of the public keys corresponding to the missing
// signatures, explicitly telling the system not to check them.
// DOCSTART 36
onceSignedTx.verifySignaturesExcept(counterpartyPubKey);
// DOCEND 36
// There is also an overload of ``verifySignaturesExcept`` which accepts
// a ``Collection`` of the public keys corresponding to the missing
// signatures. In the example below, we could also use
// ``Arrays.asList(counterpartyPubKey)`` instead of
// ``Collections.singletonList(counterpartyPubKey)``.
// DOCSTART 54
onceSignedTx.verifySignaturesExcept(Collections.singletonList(counterpartyPubKey));
// DOCEND 54
// We can also choose to only check the signatures that are
// present. BE VERY CAREFUL - this function provides no guarantees
// that the signatures are correct, or that none are missing.

View File

@ -2,6 +2,7 @@
package net.corda.docs
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.contracts.Amount
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow
@ -18,6 +19,7 @@ import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.Permissions.Companion.invokeRpc
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.driver.DriverParameters
import net.corda.testing.node.User
import net.corda.testing.driver.driver
import org.graphstream.graph.Edge
@ -48,12 +50,12 @@ fun main(args: Array<String>) {
startFlow<CashExitFlow>(),
invokeRpc(CordaRPCOps::nodeInfo)
))
driver(driverDirectory = baseDirectory, extraCordappPackagesToScan = listOf("net.corda.finance"), waitForAllNodesToFinish = true) {
driver(DriverParameters(driverDirectory = baseDirectory, extraCordappPackagesToScan = listOf("net.corda.finance"), waitForAllNodesToFinish = true)) {
val node = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).get()
// END 1
// START 2
val client = node.rpcClientToNode()
val client = CordaRPCClient(node.rpcAddress)
val proxy = client.start("user", "password").proxy
thread {

View File

@ -522,12 +522,19 @@ class InitiatorFlow(val arg1: Boolean, val arg2: Int, private val counterparty:
// DOCEND 35
// If the transaction is only partially signed, we have to pass in
// a list of the public keys corresponding to the missing
// a vararg of the public keys corresponding to the missing
// signatures, explicitly telling the system not to check them.
// DOCSTART 36
onceSignedTx.verifySignaturesExcept(counterpartyPubKey)
// DOCEND 36
// There is also an overload of ``verifySignaturesExcept`` which accepts
// a ``Collection`` of the public keys corresponding to the missing
// signatures.
// DOCSTART 54
onceSignedTx.verifySignaturesExcept(listOf(counterpartyPubKey))
// DOCEND 54
// We can also choose to only check the signatures that are
// present. BE VERY CAREFUL - this function provides no guarantees
// that the signatures are correct, or that none are missing.

View File

@ -13,15 +13,10 @@ import net.corda.core.serialization.serialize
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap
import net.corda.node.internal.StartedNode
import net.corda.node.services.messaging.Message
import net.corda.node.services.statemachine.DataSessionMessage
import net.corda.node.services.statemachine.ExistingSessionMessage
import net.corda.testing.node.InMemoryMessagingNetwork
import net.corda.testing.node.MessagingServiceSpy
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.setMessagingServiceSpy
import net.corda.testing.node.startFlow
import net.corda.testing.node.*
import org.junit.After
import org.junit.Before
import org.junit.Rule
@ -58,8 +53,8 @@ class TutorialMockNetwork {
}
lateinit private var mockNet: MockNetwork
lateinit private var nodeA: StartedNode<MockNetwork.MockNode>
lateinit private var nodeB: StartedNode<MockNetwork.MockNode>
lateinit private var nodeA: StartedMockNode
lateinit private var nodeB: StartedMockNode
@Rule
@JvmField
@ -104,6 +99,6 @@ class TutorialMockNetwork {
expectedEx.expect(IllegalArgumentException::class.java)
expectedEx.expectMessage("Expected to receive 1")
initiatingReceiveFlow.resultFuture.getOrThrow()
initiatingReceiveFlow.getOrThrow()
}
}

View File

@ -7,9 +7,9 @@ import net.corda.core.utilities.getOrThrow
import net.corda.finance.*
import net.corda.finance.contracts.getCashBalances
import net.corda.finance.flows.CashIssueFlow
import net.corda.node.internal.StartedNode
import net.corda.testing.core.chooseIdentity
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.StartedMockNode
import net.corda.testing.node.startFlow
import org.junit.After
import org.junit.Assert
@ -19,8 +19,8 @@ import java.util.*
class CustomVaultQueryTest {
private lateinit var mockNet: MockNetwork
private lateinit var nodeA: StartedNode<MockNetwork.MockNode>
private lateinit var nodeB: StartedNode<MockNetwork.MockNode>
private lateinit var nodeA: StartedMockNode
private lateinit var nodeB: StartedMockNode
private lateinit var notary: Party
@Before
@ -60,7 +60,7 @@ class CustomVaultQueryTest {
OpaqueBytes.of(0x01),
notary))
// Wait for the flow to stop and print
flowHandle1.resultFuture.getOrThrow()
flowHandle1.getOrThrow()
}
private fun topUpCurrencies() {
@ -69,21 +69,19 @@ class CustomVaultQueryTest {
OpaqueBytes.of(0x01),
nodeA.info.chooseIdentity(),
notary))
flowHandle1.resultFuture.getOrThrow()
flowHandle1.getOrThrow()
}
private fun getBalances(): Pair<Map<Currency, Amount<Currency>>, Map<Currency, Amount<Currency>>> {
// Print out the balances
val balancesNodesA =
nodeA.database.transaction {
nodeA.services.getCashBalances()
}
val balancesNodesA = nodeA.transaction {
nodeA.services.getCashBalances()
}
println("BalanceA\n" + balancesNodesA)
val balancesNodesB =
nodeB.database.transaction {
nodeB.services.getCashBalances()
}
val balancesNodesB = nodeB.transaction {
nodeB.services.getCashBalances()
}
println("BalanceB\n" + balancesNodesB)
return Pair(balancesNodesA, balancesNodesB)

View File

@ -7,19 +7,20 @@ import net.corda.core.utilities.getOrThrow
import net.corda.finance.*
import net.corda.finance.contracts.getCashBalances
import net.corda.finance.flows.CashIssueFlow
import net.corda.node.internal.StartedNode
import net.corda.testing.core.chooseIdentity
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.StartedMockNode
import net.corda.testing.node.startFlow
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import kotlin.test.assertEquals
class FxTransactionBuildTutorialTest {
private lateinit var mockNet: MockNetwork
private lateinit var nodeA: StartedNode<MockNetwork.MockNode>
private lateinit var nodeB: StartedNode<MockNetwork.MockNode>
private lateinit var nodeA: StartedMockNode
private lateinit var nodeB: StartedMockNode
private lateinit var notary: Party
@Before
@ -36,6 +37,7 @@ class FxTransactionBuildTutorialTest {
mockNet.stopNodes()
}
@Ignore("Pending fix from corda")
@Test
fun `Run ForeignExchangeFlow to completion`() {
// Use NodeA as issuer and create some dollars
@ -43,7 +45,7 @@ class FxTransactionBuildTutorialTest {
OpaqueBytes.of(0x01),
notary))
// Wait for the flow to stop and print
flowHandle1.resultFuture.getOrThrow()
flowHandle1.getOrThrow()
printBalances()
// Using NodeB as Issuer create some pounds.
@ -51,7 +53,7 @@ class FxTransactionBuildTutorialTest {
OpaqueBytes.of(0x01),
notary))
// Wait for flow to come to an end and print
flowHandle2.resultFuture.getOrThrow()
flowHandle2.getOrThrow()
printBalances()
// Setup some futures on the vaults to await the arrival of the exchanged funds at both nodes
@ -65,16 +67,17 @@ class FxTransactionBuildTutorialTest {
nodeB.info.chooseIdentity(),
weAreBaseCurrencySeller = false))
// 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
nodeAVaultUpdate.get()
val balancesA = nodeA.database.transaction {
val balancesA = nodeA.transaction {
nodeA.services.getCashBalances()
}
nodeBVaultUpdate.get()
val balancesB = nodeB.database.transaction {
val balancesB = nodeB.transaction {
nodeB.services.getCashBalances()
}
println("BalanceA\n" + balancesA)
println("BalanceB\n" + balancesB)
// Verify the transfers occurred as expected
@ -86,10 +89,10 @@ class FxTransactionBuildTutorialTest {
private fun printBalances() {
// Print out the balances
nodeA.database.transaction {
nodeA.transaction {
println("BalanceA\n" + nodeA.services.getCashBalances())
}
nodeB.database.transaction {
nodeB.transaction {
println("BalanceB\n" + nodeB.services.getCashBalances())
}
}

View File

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

View File

@ -5,6 +5,7 @@ import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.TransactionVerificationException
import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.CordaX500Name
import net.corda.core.node.services.IdentityService
import net.corda.core.utilities.days
import net.corda.finance.DOLLARS
import net.corda.finance.`issued by`
@ -13,7 +14,6 @@ import net.corda.finance.contracts.CommercialPaper
import net.corda.finance.contracts.ICommercialPaperState
import net.corda.finance.contracts.asset.CASH
import net.corda.finance.contracts.asset.Cash
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.core.*
import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices
@ -38,11 +38,11 @@ class CommercialPaperTest {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
private val ledgerServices = MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also {
private val ledgerServices = MockServices(emptyList(), MEGA_CORP.name, rigorousMock<IdentityService>().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
doReturn(null).whenever(it).partyFromKey(BIG_CORP_PUBKEY)
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
}, MEGA_CORP.name)
})
// DOCSTART 1
fun getPaper(): ICommercialPaperState = CommercialPaper.State(

View File

@ -63,7 +63,6 @@ The name must also obey the following constraints:
* The organisation field of the name also obeys the following constraints:
* 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
character confusability attacks
@ -138,6 +137,25 @@ a single node to run the network map service, by putting its name in the ``netwo
.. warning:: When adding nodes, make sure that there are no port clashes!
Specifying a custom webserver
-----------------------------
By default, any node listing a webport will use the default development webserver, which is not production-ready. You
can use your own webserver JAR instead by using the ``webserverJar`` argument in a ``Cordform`` ``node`` configuration
block:
.. sourcecode:: groovy
node {
name "O=PartyA,L=New York,C=US"
webPort 10005
webserverJar "lib/my_webserver.jar"
}
The webserver JAR will be copied into the node's ``build`` folder with the name ``corda-webserver.jar``.
.. warning:: This is an experimental feature. There is currently no support for reading the webserver's port from the
node's ``node.conf`` file.
Running deployNodes
-------------------
To create the nodes defined in our ``deployNodes`` task, run the following command in a terminal window from the root

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