Merge remote-tracking branch 'remotes/open/master' into enterprise-merge-september-26

# Conflicts:
#	core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt
#	node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/Kryo.kt
#	settings.gradle
This commit is contained in:
sollecitom 2017-09-26 18:08:47 +01:00
commit 9284e731c0
3608 changed files with 12107 additions and 334367 deletions

View File

@ -1,15 +1,11 @@
Thank you for choosing to contribute to Corda.
👮🏻👮🏻👮🏻 !!!! DESCRIBE YOUR CHANGES HERE !!!! DO NOT FORGET !!!! 👮🏻👮🏻👮🏻
Your PR must be approved by one or more reviewers and all tests must be passed on TeamCity (https://ci.corda.r3cev.com) in order to be merged.
Once you have submitted a PR you are responsible for keeping it up to date until the time it is merged.
# PR Checklist:
PR Checklist:
- [ ] Have you run the unit, integration and smoke tests as described here? https://docs.corda.net/head/testing.html
- [ ] If you added/changed public APIs, did you write/update the JavaDocs?
- [ ] If the changes are of interest to application developers, have you added them to the changelog, and potentially release notes?
- [ ] If you are contributing for the first time, please read the agreement in CONTRIBUTING.md now and add to this Pull Request that you agree to it.
1. Ensure any new code is tested as described in https://docs.corda.net/testing.html
2. Ensure you have done any relevant automated testing and manual testing
3. Add your changes to docs/source/changelog.rst
4. Update any documentation in docs/source relating to your changes and learn how to build them in https://docs.corda.net/building-the-docs.html
5. If you are contributing for the first time please read the agreement in CONTRIBUTING.md now and add to this Pull Request that you have read, and agreed to, the agreement.
Please remove this message when you have read it.
Thanks for your code, it's appreciated! :)

16
.idea/compiler.xml generated
View File

@ -12,6 +12,8 @@
<module name="buildSrc_test" target="1.8" />
<module name="client_main" target="1.8" />
<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-project_main" target="1.8" />
<module name="corda-project_test" target="1.8" />
<module name="corda-webserver_integrationTest" target="1.8" />
@ -19,6 +21,9 @@
<module name="corda-webserver_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_main" target="1.8" />
<module name="core_test" target="1.8" />
<module name="demobench_main" target="1.8" />
@ -72,8 +77,12 @@
<module name="node_test" target="1.8" />
<module name="notary-demo_main" target="1.8" />
<module name="notary-demo_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" />
<module name="quasar-utils_test" target="1.8" />
<module name="rpc_integrationTest" target="1.8" />
<module name="rpc_main" target="1.8" />
<module name="rpc_smokeTest" target="1.8" />
@ -93,6 +102,13 @@
<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="test-common_main" target="1.8" />
<module name="test-common_test" target="1.8" />
<module name="test-utils_integrationTest" target="1.8" />
<module name="test-utils_main" target="1.8" />
<module name="test-utils_test" target="1.8" />
<module name="testing-node-driver_integrationTest" target="1.8" />
<module name="testing-node-driver_main" target="1.8" />
<module name="testing-node-driver_test" target="1.8" />

View File

@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="explorer" type="TORNADOFX_RUNCONFIGURATION" factoryName="TornadoFX Configuration Factory" run-type="App" live-views="false" live-stylesheets="false" dump-stylesheets="false">
<configuration default="false" name="Explorer - GUI" type="TORNADOFX_RUNCONFIGURATION" factoryName="TornadoFX Configuration Factory" run-type="App" live-views="false" live-stylesheets="false" dump-stylesheets="false">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="RUN_TYPE" value="App" />
<option name="VIEW_CLASS_NAME" />

View File

@ -36,7 +36,7 @@ buildscript {
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
ext.fileupload_version = '1.3.2'
ext.junit_version = '4.12'
ext.mockito_version = '1.10.19'
ext.mockito_version = '2.10.0'
ext.jopt_simple_version = '5.0.2'
ext.jansi_version = '1.14'
ext.hibernate_version = '5.2.6.Final'
@ -146,10 +146,16 @@ allprojects {
maven { url 'https://jitpack.io' }
}
configurations.compile {
// We want to use SLF4J's version of these bindings: jcl-over-slf4j
// Remove any transitive dependency on Apache's version.
exclude group: 'commons-logging', module: 'commons-logging'
configurations {
compile {
// We want to use SLF4J's version of these bindings: jcl-over-slf4j
// Remove any transitive dependency on Apache's version.
exclude group: 'commons-logging', module: 'commons-logging'
}
runtime {
// We never want isolated.jar on classPath, since we want to test jar being dynamically loaded as an attachment
exclude module: 'isolated'
}
}
}
@ -184,6 +190,7 @@ dependencies {
cordaRuntime project(':client:mock')
cordaRuntime project(':client:rpc')
cordaRuntime project(':core')
cordaRuntime project(':confidential-identities')
cordaRuntime project(':finance')
cordaRuntime project(':webserver')
testCompile project(':test-utils')
@ -252,7 +259,7 @@ bintrayConfig {
projectUrl = 'https://github.com/corda/corda'
gpgSign = true
gpgPassphrase = System.getenv('CORDA_BINTRAY_GPG_PASSPHRASE')
publications = ['corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver', 'corda-node-driver']
publications = ['corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver', 'corda-node-driver', 'corda-confidential-identities']
license {
name = 'Apache-2.0'
url = 'https://www.apache.org/licenses/LICENSE-2.0'
@ -287,7 +294,7 @@ artifactory {
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD')
}
defaults {
publications('corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'cordform-common', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver', 'corda-node-driver')
publications('corda-jfx', 'corda-mock', 'corda-rpc', 'corda-core', 'corda', 'cordform-common', 'corda-finance', 'corda-node', 'corda-node-api', 'corda-test-common', 'corda-test-utils', 'corda-jackson', 'corda-verifier', 'corda-webserver-impl', 'corda-webserver', 'corda-node-driver', 'corda-confidential-identities')
}
}
}

View File

@ -15,6 +15,7 @@ import net.corda.core.crypto.*
import net.corda.core.crypto.CompositeKey
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.NodeInfo
@ -30,7 +31,6 @@ import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.parsePublicKeyBase58
import net.corda.core.utilities.toBase58String
import net.i2p.crypto.eddsa.EdDSAPublicKey
import org.bouncycastle.asn1.x500.X500Name
import java.math.BigDecimal
import java.security.PublicKey
import java.util.*
@ -46,25 +46,25 @@ object JacksonSupport {
// If you change this API please update the docs in the docsite (json.rst)
interface PartyObjectMapper {
fun partyFromX500Name(name: X500Name): Party?
fun wellKnownPartyFromX500Name(name: CordaX500Name): Party?
fun partyFromKey(owningKey: PublicKey): Party?
fun partiesFromName(query: String): Set<Party>
}
class RpcObjectMapper(val rpc: CordaRPCOps, factory: JsonFactory, val fuzzyIdentityMatch: Boolean) : PartyObjectMapper, ObjectMapper(factory) {
override fun partyFromX500Name(name: X500Name): Party? = rpc.partyFromX500Name(name)
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = rpc.wellKnownPartyFromX500Name(name)
override fun partyFromKey(owningKey: PublicKey): Party? = rpc.partyFromKey(owningKey)
override fun partiesFromName(query: String) = rpc.partiesFromName(query, fuzzyIdentityMatch)
}
class IdentityObjectMapper(val identityService: IdentityService, factory: JsonFactory, val fuzzyIdentityMatch: Boolean) : PartyObjectMapper, ObjectMapper(factory) {
override fun partyFromX500Name(name: X500Name): Party? = identityService.partyFromX500Name(name)
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = identityService.wellKnownPartyFromX500Name(name)
override fun partyFromKey(owningKey: PublicKey): Party? = identityService.partyFromKey(owningKey)
override fun partiesFromName(query: String) = identityService.partiesFromName(query, fuzzyIdentityMatch)
}
class NoPartyObjectMapper(factory: JsonFactory) : PartyObjectMapper, ObjectMapper(factory) {
override fun partyFromX500Name(name: X500Name): Party? = throw UnsupportedOperationException()
override fun wellKnownPartyFromX500Name(name: CordaX500Name): Party? = throw UnsupportedOperationException()
override fun partyFromKey(owningKey: PublicKey): Party? = throw UnsupportedOperationException()
override fun partiesFromName(query: String) = throw UnsupportedOperationException()
}
@ -105,8 +105,8 @@ object JacksonSupport {
addSerializer(OpaqueBytes::class.java, OpaqueBytesSerializer)
// For X.500 distinguished names
addDeserializer(X500Name::class.java, X500NameDeserializer)
addSerializer(X500Name::class.java, X500NameSerializer)
addDeserializer(CordaX500Name::class.java, CordaX500NameDeserializer)
addSerializer(CordaX500Name::class.java, CordaX500NameSerializer)
// Mixins for transaction types to prevent some properties from being serialized
setMixInAnnotation(SignedTransaction::class.java, SignedTransactionMixin::class.java)
@ -191,8 +191,8 @@ object JacksonSupport {
// Base58 keys never include an equals character, while X.500 names always will, so we use that to determine
// how to parse the content
return if (parser.text.contains("=")) {
val principal = X500Name(parser.text)
mapper.partyFromX500Name(principal) ?: throw JsonParseException(parser, "Could not find a Party with name $principal")
val principal = CordaX500Name.parse(parser.text)
mapper.wellKnownPartyFromX500Name(principal) ?: throw JsonParseException(parser, "Could not find a Party with name $principal")
} else {
val nameMatches = mapper.partiesFromName(parser.text)
if (nameMatches.isEmpty()) {
@ -211,22 +211,22 @@ object JacksonSupport {
}
}
object X500NameSerializer : JsonSerializer<X500Name>() {
override fun serialize(obj: X500Name, generator: JsonGenerator, provider: SerializerProvider) {
object CordaX500NameSerializer : JsonSerializer<CordaX500Name>() {
override fun serialize(obj: CordaX500Name, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.toString())
}
}
object X500NameDeserializer : JsonDeserializer<X500Name>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): X500Name {
object CordaX500NameDeserializer : JsonDeserializer<CordaX500Name>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): CordaX500Name {
if (parser.currentToken == JsonToken.FIELD_NAME) {
parser.nextToken()
}
return try {
X500Name(parser.text)
CordaX500Name.parse(parser.text)
} catch(ex: IllegalArgumentException) {
throw JsonParseException(parser, "Invalid X.500 name ${parser.text}: ${ex.message}", ex)
throw JsonParseException(parser, "Invalid Corda X.500 name ${parser.text}: ${ex.message}", ex)
}
}
}

View File

@ -1,21 +1,24 @@
package net.corda.client.jackson
import com.fasterxml.jackson.databind.SerializationFeature
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.Amount
import net.corda.finance.USD
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.generateKeyPair
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.*
import net.corda.core.node.ServiceHub
import net.corda.core.transactions.SignedTransaction
import net.corda.finance.USD
import net.corda.testing.ALICE_PUBKEY
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.MINI_CORP
import net.corda.testing.TestDependencyInjectionBase
import net.corda.testing.contracts.DummyContract
import net.i2p.crypto.eddsa.EdDSAPublicKey
import org.junit.Before
import org.junit.Test
import java.util.*
import kotlin.reflect.jvm.jvmName
import kotlin.test.assertEquals
class JacksonSupportTest : TestDependencyInjectionBase() {
@ -23,6 +26,16 @@ class JacksonSupportTest : TestDependencyInjectionBase() {
val mapper = JacksonSupport.createNonRpcMapper()
}
private lateinit var services: ServiceHub
private lateinit var cordappProvider: CordappProvider
@Before
fun setup() {
services = mock()
cordappProvider = mock()
whenever(services.cordappProvider).thenReturn(cordappProvider)
}
@Test
fun publicKeySerializingWorks() {
val publicKey = generateKeyPair().public
@ -57,8 +70,12 @@ class JacksonSupportTest : TestDependencyInjectionBase() {
@Test
fun writeTransaction() {
val attachmentRef = SecureHash.randomSHA256()
whenever(cordappProvider.getContractAttachmentID(DummyContract.PROGRAM_ID))
.thenReturn(attachmentRef)
fun makeDummyTx(): SignedTransaction {
val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1)).toWireTransaction()
val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1))
.toWireTransaction(services)
val signatures = TransactionSignature(
ByteArray(1),
ALICE_PUBKEY,

View File

@ -7,9 +7,6 @@ description 'Corda client JavaFX modules'
//noinspection GroovyAssignabilityCheck
configurations {
// we don't want isolated.jar in classPath, since we want to test jar being dynamically loaded as an attachment
runtime.exclude module: 'isolated'
integrationTestCompile.extendsFrom testCompile
integrationTestRuntime.extendsFrom testRuntime
}

View File

@ -8,6 +8,8 @@ import net.corda.core.crypto.isFulfilledBy
import net.corda.core.crypto.keys
import net.corda.core.flows.FlowInitiator
import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.bufferUntilSubscribed
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.StateMachineTransactionMapping
@ -15,7 +17,6 @@ import net.corda.core.messaging.StateMachineUpdate
import net.corda.core.messaging.startFlow
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.Vault
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.OpaqueBytes
@ -25,21 +26,20 @@ import net.corda.finance.USD
import net.corda.finance.flows.CashExitFlow
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.FlowPermissions.Companion.startFlowPermission
import net.corda.nodeapi.internal.ServiceInfo
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.nodeapi.User
import net.corda.testing.*
import net.corda.testing.driver.driver
import net.corda.testing.node.DriverBasedTest
import org.bouncycastle.asn1.x500.X500Name
import org.junit.Test
import rx.Observable
class NodeMonitorModelTest : DriverBasedTest() {
lateinit var aliceNode: NodeInfo
lateinit var bobNode: NodeInfo
lateinit var notaryNode: NodeInfo
lateinit var notaryParty: Party
lateinit var rpc: CordaRPCOps
lateinit var rpcBob: CordaRPCOps
@ -50,20 +50,18 @@ class NodeMonitorModelTest : DriverBasedTest() {
lateinit var transactions: Observable<SignedTransaction>
lateinit var vaultUpdates: Observable<Vault.Update<ContractState>>
lateinit var networkMapUpdates: Observable<NetworkMapCache.MapChange>
lateinit var newNode: (X500Name) -> NodeInfo
lateinit var newNode: (CordaX500Name) -> NodeInfo
override fun setup() = driver {
override fun setup() = driver(extraCordappPackagesToScan = listOf("net.corda.finance")) {
val cashUser = User("user1", "test", permissions = setOf(
startFlowPermission<CashIssueFlow>(),
startFlowPermission<CashPaymentFlow>(),
startFlowPermission<CashExitFlow>())
)
val aliceNodeFuture = startNode(providedName = ALICE.name, rpcUsers = listOf(cashUser))
val notaryNodeFuture = startNode(providedName = DUMMY_NOTARY.name, advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
val notaryHandle = startNode(providedName = DUMMY_NOTARY.name, advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type))).getOrThrow()
val aliceNodeHandle = aliceNodeFuture.getOrThrow()
val notaryNodeHandle = notaryNodeFuture.getOrThrow()
aliceNode = aliceNodeHandle.nodeInfo
notaryNode = notaryNodeHandle.nodeInfo
newNode = { nodeName -> startNode(providedName = nodeName).getOrThrow().nodeInfo }
val monitor = NodeMonitorModel()
stateMachineTransactionMapping = monitor.stateMachineTransactionMapping.bufferUntilSubscribed()
@ -75,6 +73,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
monitor.register(aliceNodeHandle.configuration.rpcAddress!!, cashUser.username, cashUser.password, initialiseSerialization = false)
rpc = monitor.proxyObservable.value!!
notaryParty = notaryHandle.nodeInfo.legalIdentities[1]
val bobNodeHandle = startNode(providedName = BOB.name, rpcUsers = listOf(cashUser)).getOrThrow()
bobNode = bobNodeHandle.nodeInfo
@ -87,20 +86,20 @@ class NodeMonitorModelTest : DriverBasedTest() {
@Test
fun `network map update`() {
newNode(CHARLIE.name)
networkMapUpdates.filter { !it.node.advertisedServices.any { it.info.type.isNotary() } }
.filter { !it.node.advertisedServices.any { it.info.type == NetworkMapService.type } }
val charlieNode = newNode(CHARLIE.name)
val nonServiceIdentities = aliceNode.legalIdentitiesAndCerts + bobNode.legalIdentitiesAndCerts + charlieNode.legalIdentitiesAndCerts
networkMapUpdates.filter { it.node.legalIdentitiesAndCerts.any { it in nonServiceIdentities } }
.expectEvents(isStrict = false) {
sequence(
// TODO : Add test for remove when driver DSL support individual node shutdown.
expect { output: NetworkMapCache.MapChange ->
require(output.node.legalIdentity.name == ALICE.name) { "Expecting : ${ALICE.name}, Actual : ${output.node.legalIdentity.name}" }
require(output.node.chooseIdentity().name == ALICE.name) { "Expecting : ${ALICE.name}, Actual : ${output.node.chooseIdentity().name}" }
},
expect { output: NetworkMapCache.MapChange ->
require(output.node.legalIdentity.name == BOB.name) { "Expecting : ${BOB.name}, Actual : ${output.node.legalIdentity.name}" }
require(output.node.chooseIdentity().name == BOB.name) { "Expecting : ${BOB.name}, Actual : ${output.node.chooseIdentity().name}" }
},
expect { output: NetworkMapCache.MapChange ->
require(output.node.legalIdentity.name == CHARLIE.name) { "Expecting : ${CHARLIE.name}, Actual : ${output.node.legalIdentity.name}" }
require(output.node.chooseIdentity().name == CHARLIE.name) { "Expecting : ${CHARLIE.name}, Actual : ${output.node.chooseIdentity().name}" }
}
)
}
@ -111,7 +110,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
rpc.startFlow(::CashIssueFlow,
Amount(100, USD),
OpaqueBytes(ByteArray(1, { 1 })),
notaryNode.notaryIdentity
notaryParty
)
vaultUpdates.expectEvents(isStrict = false) {
@ -132,9 +131,8 @@ class NodeMonitorModelTest : DriverBasedTest() {
@Test
fun `cash issue and move`() {
val anonymous = false
rpc.startFlow(::CashIssueFlow, 100.DOLLARS, OpaqueBytes.of(1), notaryNode.notaryIdentity).returnValue.getOrThrow()
rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, bobNode.legalIdentity, anonymous).returnValue.getOrThrow()
val (_, issueIdentity) = rpc.startFlow(::CashIssueFlow, 100.DOLLARS, OpaqueBytes.of(1), notaryParty).returnValue.getOrThrow()
rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, bobNode.chooseIdentity()).returnValue.getOrThrow()
var issueSmId: StateMachineRunId? = null
var moveSmId: StateMachineRunId? = null
@ -152,7 +150,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
require(remove.id == issueSmId)
},
// MOVE - N.B. There are other framework flows that happen in parallel for the remote resolve transactions flow
expect(match = { it is StateMachineUpdate.Added && it.stateMachineInfo.flowLogicClassName == CashPaymentFlow::class.java.name }) { add: StateMachineUpdate.Added ->
expect(match = { it.stateMachineInfo.flowLogicClassName == CashPaymentFlow::class.java.name }) { add: StateMachineUpdate.Added ->
moveSmId = add.id
val initiator = add.stateMachineInfo.initiator
require(initiator is FlowInitiator.RPC && initiator.username == "user1")
@ -167,7 +165,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
// MOVE
expect { add: StateMachineUpdate.Added ->
val initiator = add.stateMachineInfo.initiator
require(initiator is FlowInitiator.Peer && initiator.party.name == aliceNode.legalIdentity.name)
require(initiator is FlowInitiator.Peer && aliceNode.isLegalIdentity(initiator.party))
}
)
}
@ -180,7 +178,7 @@ class NodeMonitorModelTest : DriverBasedTest() {
require(stx.tx.outputs.size == 1)
val signaturePubKeys = stx.sigs.map { it.by }.toSet()
// Only Alice signed
val aliceKey = aliceNode.legalIdentity.owningKey
val aliceKey = aliceNode.chooseIdentity().owningKey
require(signaturePubKeys.size <= aliceKey.keys.size)
require(aliceKey.isFulfilledBy(signaturePubKeys))
issueTx = stx
@ -191,8 +189,8 @@ class NodeMonitorModelTest : DriverBasedTest() {
require(stx.tx.outputs.size == 1)
val signaturePubKeys = stx.sigs.map { it.by }.toSet()
// Alice and Notary signed
require(aliceNode.legalIdentity.owningKey.isFulfilledBy(signaturePubKeys))
require(notaryNode.notaryIdentity.owningKey.isFulfilledBy(signaturePubKeys))
require(issueIdentity!!.owningKey.isFulfilledBy(signaturePubKeys))
require(notaryParty.owningKey.isFulfilledBy(signaturePubKeys))
moveTx = stx
}
)

View File

@ -11,20 +11,16 @@ import rx.Observer
import rx.subjects.Subject
import java.util.*
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
/**
* This file defines a global [Models] store and delegates to inject event streams/sinks. Note that all streams here
* are global.
*
* This allows decoupling of UI logic from stream initialisation and provides us with a central place to inspect data
* flows. It also allows detecting of looping logic by constructing a stream dependency graph TODO do this.
* Global models store to allow decoupling of UI logic from stream initialisation and provide a central place to
* inspect data flows. It also allows detecting of looping logic by constructing a stream dependency graph TODO do this.
*
* Usage:
* // Inject service -> client event stream
* private val serviceToClient: EventStream<ServiceToClientEvent> by eventStream(WalletMonitorModel::serviceToClient)
* private val serviceToClient: EventStream<ServiceToClientEvent> by Models.eventStream(WalletMonitorModel::serviceToClient)
*
* Each Screen code should have a code layout like this:
* Each `Screen` code should have a code layout like this:
*
* class Screen {
* val root = (..)
@ -41,12 +37,13 @@ import kotlin.reflect.KProperty
* }
*
* For example if I wanted to display a list of all USD cash states:
*
* class USDCashStatesScreen {
* val root: Pane by fxml()
*
* val usdCashStatesListView: ListView<Cash.State> by fxid("USDCashStatesListView")
*
* val cashStates: ObservableList<Cash.State> by observableList(ContractStateModel::cashStates)
* val cashStates: ObservableList<Cash.State> by Models.observableList(ContractStateModel::cashStates)
*
* val usdCashStates = cashStates.filter { it.(..).currency == USD }
*
@ -66,37 +63,6 @@ import kotlin.reflect.KProperty
* Another advantage of this separation is that once we start adding a lot of screens we can still track data dependencies
* in a central place as opposed to ad-hoc wiring up the observables.
*/
inline fun <reified M : Any, T> observable(noinline observableProperty: (M) -> Observable<T>) =
TrackedDelegate.ObservableDelegate(M::class, observableProperty)
inline fun <reified M : Any, T> observer(noinline observerProperty: (M) -> Observer<T>) =
TrackedDelegate.ObserverDelegate(M::class, observerProperty)
inline fun <reified M : Any, T> subject(noinline subjectProperty: (M) -> Subject<T, T>) =
TrackedDelegate.SubjectDelegate(M::class, subjectProperty)
inline fun <reified M : Any, T> eventStream(noinline streamProperty: (M) -> EventStream<T>) =
TrackedDelegate.EventStreamDelegate(M::class, streamProperty)
inline fun <reified M : Any, T> eventSink(noinline sinkProperty: (M) -> EventSink<T>) =
TrackedDelegate.EventSinkDelegate(M::class, sinkProperty)
inline fun <reified M : Any, T> observableValue(noinline observableValueProperty: (M) -> ObservableValue<T>) =
TrackedDelegate.ObservableValueDelegate(M::class, observableValueProperty)
inline fun <reified M : Any, T> writableValue(noinline writableValueProperty: (M) -> WritableValue<T>) =
TrackedDelegate.WritableValueDelegate(M::class, writableValueProperty)
inline fun <reified M : Any, T> objectProperty(noinline objectProperty: (M) -> ObjectProperty<T>) =
TrackedDelegate.ObjectPropertyDelegate(M::class, objectProperty)
inline fun <reified M : Any, T> observableList(noinline observableListProperty: (M) -> ObservableList<T>) =
TrackedDelegate.ObservableListDelegate(M::class, observableListProperty)
inline fun <reified M : Any, T> observableListReadOnly(noinline observableListProperty: (M) -> ObservableList<out T>) =
TrackedDelegate.ObservableListReadOnlyDelegate(M::class, observableListProperty)
object Models {
private val modelStore = HashMap<KClass<*>, Any>()
@ -120,68 +86,3 @@ object Models {
inline fun <reified M : Any> get(origin: KClass<*>): M = get(M::class, origin)
}
sealed class TrackedDelegate<M : Any>(val klass: KClass<M>) {
init {
Models.initModel(klass)
}
class ObservableDelegate<M : Any, T>(klass: KClass<M>, val observableProperty: (M) -> Observable<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): Observable<T> {
return observableProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class ObserverDelegate<M : Any, T>(klass: KClass<M>, val observerProperty: (M) -> Observer<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): Observer<T> {
return observerProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class SubjectDelegate<M : Any, T>(klass: KClass<M>, val subjectProperty: (M) -> Subject<T, T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): Subject<T, T> {
return subjectProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class EventStreamDelegate<M : Any, T>(klass: KClass<M>, val eventStreamProperty: (M) -> org.reactfx.EventStream<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): org.reactfx.EventStream<T> {
return eventStreamProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class EventSinkDelegate<M : Any, T>(klass: KClass<M>, val eventSinkProperty: (M) -> org.reactfx.EventSink<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): org.reactfx.EventSink<T> {
return eventSinkProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class ObservableValueDelegate<M : Any, T>(klass: KClass<M>, val observableValueProperty: (M) -> ObservableValue<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): ObservableValue<T> {
return observableValueProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class WritableValueDelegate<M : Any, T>(klass: KClass<M>, val writableValueProperty: (M) -> WritableValue<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): WritableValue<T> {
return writableValueProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class ObservableListDelegate<M : Any, T>(klass: KClass<M>, val observableListProperty: (M) -> ObservableList<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): ObservableList<T> {
return observableListProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class ObservableListReadOnlyDelegate<M : Any, out T>(klass: KClass<M>, val observableListReadOnlyProperty: (M) -> ObservableList<out T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): ObservableList<out T> {
return observableListReadOnlyProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class ObjectPropertyDelegate<M : Any, T>(klass: KClass<M>, val objectPropertyProperty: (M) -> ObjectProperty<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): ObjectProperty<T> {
return objectPropertyProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
}

View File

@ -0,0 +1,43 @@
@file:JvmName("ModelsUtils")
package net.corda.client.jfx.model
import javafx.beans.property.ObjectProperty
import javafx.beans.value.ObservableValue
import javafx.beans.value.WritableValue
import javafx.collections.ObservableList
import org.reactfx.EventSink
import org.reactfx.EventStream
import rx.Observable
import rx.Observer
import rx.subjects.Subject
import kotlin.reflect.KClass
inline fun <reified M : Any, T> observable(noinline observableProperty: (M) -> Observable<T>) =
TrackedDelegate.ObservableDelegate(M::class, observableProperty)
inline fun <reified M : Any, T> observer(noinline observerProperty: (M) -> Observer<T>) =
TrackedDelegate.ObserverDelegate(M::class, observerProperty)
inline fun <reified M : Any, T> subject(noinline subjectProperty: (M) -> Subject<T, T>) =
TrackedDelegate.SubjectDelegate(M::class, subjectProperty)
inline fun <reified M : Any, T> eventStream(noinline streamProperty: (M) -> EventStream<T>) =
TrackedDelegate.EventStreamDelegate(M::class, streamProperty)
inline fun <reified M : Any, T> eventSink(noinline sinkProperty: (M) -> EventSink<T>) =
TrackedDelegate.EventSinkDelegate(M::class, sinkProperty)
inline fun <reified M : Any, T> observableValue(noinline observableValueProperty: (M) -> ObservableValue<T>) =
TrackedDelegate.ObservableValueDelegate(M::class, observableValueProperty)
inline fun <reified M : Any, T> writableValue(noinline writableValueProperty: (M) -> WritableValue<T>) =
TrackedDelegate.WritableValueDelegate(M::class, writableValueProperty)
inline fun <reified M : Any, T> objectProperty(noinline objectProperty: (M) -> ObjectProperty<T>) =
TrackedDelegate.ObjectPropertyDelegate(M::class, objectProperty)
inline fun <reified M : Any, T> observableList(noinline observableListProperty: (M) -> ObservableList<T>) =
TrackedDelegate.ObservableListDelegate(M::class, observableListProperty)
inline fun <reified M : Any, T> observableListReadOnly(noinline observableListProperty: (M) -> ObservableList<out T>) =
TrackedDelegate.ObservableListReadOnlyDelegate(M::class, observableListProperty)

View File

@ -5,18 +5,20 @@ import com.google.common.cache.CacheLoader
import javafx.beans.value.ObservableValue
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import net.corda.client.jfx.utils.filterNotNull
import net.corda.client.jfx.utils.fold
import net.corda.client.jfx.utils.map
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache.MapChange
import net.corda.nodeapi.internal.ServiceType
import java.security.PublicKey
class NetworkIdentityModel {
private val networkIdentityObservable by observable(NodeMonitorModel::networkMap)
val networkIdentities: ObservableList<NodeInfo> =
private val networkIdentities: ObservableList<NodeInfo> =
networkIdentityObservable.fold(FXCollections.observableArrayList()) { list, update ->
list.removeIf {
when (update) {
@ -31,19 +33,17 @@ class NetworkIdentityModel {
private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable)
private val identityCache = CacheBuilder.newBuilder()
.build<PublicKey, ObservableValue<NodeInfo?>>(CacheLoader.from {
publicKey ->
publicKey?.let { rpcProxy.map { it?.nodeIdentityFromParty(AnonymousParty(publicKey)) } }
.build<PublicKey, ObservableValue<NodeInfo?>>(CacheLoader.from { publicKey ->
publicKey?.let { rpcProxy.map { it?.nodeInfoFromParty(AnonymousParty(publicKey)) } }
})
val parties: ObservableList<NodeInfo> = networkIdentities.filtered { !it.isCordaService() }
val notaries: ObservableList<NodeInfo> = networkIdentities.filtered { it.advertisedServices.any { it.info.type.isNotary() } }
val myIdentity = rpcProxy.map { it?.nodeIdentity() }
val notaries: ObservableList<Party> = networkIdentities.map {
it.legalIdentitiesAndCerts.find { it.name.commonName?.let { ServiceType.parse(it).isNotary() } ?: false }
}.map { it?.party }.filterNotNull()
private fun NodeInfo.isCordaService(): Boolean {
// TODO: better way to identify Corda service?
return advertisedServices.any { it.info.type.isNetworkMap() || it.info.type.isNotary() }
}
val notaryNodes: ObservableList<NodeInfo> = notaries.map { rpcProxy.value?.nodeInfoFromParty(it) }.filterNotNull()
val parties: ObservableList<NodeInfo> = networkIdentities.filtered { it.legalIdentities.all { it !in notaries } }
val myIdentity = rpcProxy.map { it?.nodeInfo()?.legalIdentitiesAndCerts?.first()?.party }
fun partyFromPublicKey(publicKey: PublicKey): ObservableValue<NodeInfo?> = identityCache[publicKey]
}

View File

@ -5,13 +5,17 @@ import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.CordaRPCClientConfiguration
import net.corda.core.contracts.ContractState
import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.Party
import net.corda.core.messaging.*
import net.corda.core.node.services.NetworkMapCache.MapChange
import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.*
import net.corda.core.utilities.seconds
import net.corda.core.node.services.vault.DEFAULT_PAGE_NUM
import net.corda.core.node.services.vault.MAX_PAGE_SIZE
import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.seconds
import rx.Observable
import rx.subjects.PublishSubject
@ -45,6 +49,7 @@ class NodeMonitorModel {
val networkMap: Observable<MapChange> = networkMapSubject
val proxyObservable = SimpleObjectProperty<CordaRPCOps?>()
lateinit var notaryIdentities: List<Party>
/**
* Register for updates to/from a given vault.
@ -60,6 +65,7 @@ class NodeMonitorModel {
)
val connection = client.start(username, password)
val proxy = connection.proxy
notaryIdentities = proxy.notaryIdentities()
val (stateMachines, stateMachineUpdates) = proxy.stateMachinesFeed()
// Extract the flow tracking stream
@ -83,8 +89,13 @@ class NodeMonitorModel {
stateMachineUpdates.startWith(currentStateMachines).subscribe(stateMachineUpdatesSubject)
// Vault snapshot (force single page load with MAX_PAGE_SIZE) + updates
val (vaultSnapshot, vaultUpdates) = proxy.vaultTrackBy<ContractState>(QueryCriteria.VaultQueryCriteria(Vault.StateStatus.ALL),
PageSpecification(DEFAULT_PAGE_NUM, MAX_PAGE_SIZE))
val (_, vaultUpdates) = proxy.vaultTrackBy<ContractState>(QueryCriteria.VaultQueryCriteria(Vault.StateStatus.ALL),
PageSpecification(DEFAULT_PAGE_NUM, MAX_PAGE_SIZE))
val vaultSnapshot = proxy.vaultQueryBy<ContractState>(QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED),
PageSpecification(DEFAULT_PAGE_NUM, MAX_PAGE_SIZE))
// We have to fetch the snapshot separately since vault query API doesn't allow different criteria for snapshot and updates.
// TODO : This will create a small window of opportunity for inconsistent updates, might need to change the vault API to handle this case.
val initialVaultUpdate = Vault.Update(setOf(), vaultSnapshot.states.toSet())
vaultUpdates.startWith(initialVaultUpdate).subscribe(vaultUpdatesSubject)

View File

@ -0,0 +1,79 @@
package net.corda.client.jfx.model
import javafx.beans.property.ObjectProperty
import javafx.beans.value.ObservableValue
import javafx.beans.value.WritableValue
import javafx.collections.ObservableList
import org.reactfx.EventSink
import org.reactfx.EventStream
import rx.Observable
import rx.Observer
import rx.subjects.Subject
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
sealed class TrackedDelegate<M : Any>(val klass: KClass<M>) {
init {
Models.initModel(klass)
}
class ObservableDelegate<M : Any, T>(klass: KClass<M>, val observableProperty: (M) -> Observable<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): Observable<T> {
return observableProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class ObserverDelegate<M : Any, T>(klass: KClass<M>, val observerProperty: (M) -> Observer<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): Observer<T> {
return observerProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class SubjectDelegate<M : Any, T>(klass: KClass<M>, val subjectProperty: (M) -> Subject<T, T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): Subject<T, T> {
return subjectProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class EventStreamDelegate<M : Any, T>(klass: KClass<M>, val eventStreamProperty: (M) -> EventStream<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): EventStream<T> {
return eventStreamProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class EventSinkDelegate<M : Any, T>(klass: KClass<M>, val eventSinkProperty: (M) -> EventSink<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): EventSink<T> {
return eventSinkProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class ObservableValueDelegate<M : Any, T>(klass: KClass<M>, val observableValueProperty: (M) -> ObservableValue<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): ObservableValue<T> {
return observableValueProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class WritableValueDelegate<M : Any, T>(klass: KClass<M>, val writableValueProperty: (M) -> WritableValue<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): WritableValue<T> {
return writableValueProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class ObservableListDelegate<M : Any, T>(klass: KClass<M>, val observableListProperty: (M) -> ObservableList<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): ObservableList<T> {
return observableListProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class ObservableListReadOnlyDelegate<M : Any, out T>(klass: KClass<M>, val observableListReadOnlyProperty: (M) -> ObservableList<out T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): ObservableList<out T> {
return observableListReadOnlyProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
class ObjectPropertyDelegate<M : Any, T>(klass: KClass<M>, val objectPropertyProperty: (M) -> ObjectProperty<T>) : TrackedDelegate<M>(klass) {
operator fun getValue(thisRef: Any, property: KProperty<*>): ObjectProperty<T> {
return objectPropertyProperty(Models.get(klass, thisRef.javaClass.kotlin))
}
}
}

View File

@ -54,7 +54,7 @@ data class PartiallyResolvedTransaction(
class TransactionDataModel {
private val transactions by observable(NodeMonitorModel::transactions)
private val collectedTransactions = transactions.recordInSequence()
private val transactionMap = collectedTransactions.associateBy(SignedTransaction::id)
private val transactionMap = transactions.recordAsAssociation(SignedTransaction::id)
val partiallyResolvedTransactions = collectedTransactions.map {
PartiallyResolvedTransaction.fromSignedTransaction(it, transactionMap)

View File

@ -5,12 +5,6 @@ apply plugin: 'com.jfrog.artifactory'
description 'Corda client mock modules'
//noinspection GroovyAssignabilityCheck
configurations {
// we don't want isolated.jar in classPath, since we want to test jar being dynamically loaded as an attachment
runtime.exclude module: 'isolated'
}
// To find potential version conflicts, run "gradle htmlDependencyReport" and then look in
// build/reports/project/dependencies/index.html for green highlighted parts of the tree.

View File

@ -59,7 +59,7 @@ class Generator<out A>(val generate: (SplittableRandom) -> Try<A>) {
fun generateOrFail(random: SplittableRandom, numberOfTries: Int = 1): A {
var error: Throwable? = null
for (i in 0..numberOfTries - 1) {
for (i in 0 until numberOfTries) {
val result = generate(random)
error = when (result) {
is Try.Success -> return result.value
@ -115,13 +115,14 @@ class Generator<out A>(val generate: (SplittableRandom) -> Try<A>) {
fun <A> frequency(vararg generators: Pair<Double, Generator<A>>) = frequency(generators.toList())
fun <A> sequence(generators: List<Generator<A>>) = Generator<List<A>> {
fun <A> sequence(generators: List<Generator<A>>) = Generator {
val result = mutableListOf<A>()
for (generator in generators) {
val element = generator.generate(it)
@Suppress("UNCHECKED_CAST")
when (element) {
is Try.Success -> result.add(element.value)
is Try.Failure -> return@Generator element
is Try.Failure -> return@Generator element as Try<List<A>>
}
}
Try.Success(result)
@ -135,12 +136,12 @@ class Generator<out A>(val generate: (SplittableRandom) -> Try<A>) {
fun intRange(range: IntRange) = intRange(range.first, range.last)
fun intRange(from: Int, to: Int): Generator<Int> = Generator.success {
(from + Math.abs(it.nextInt()) % (to - from + 1)).toInt()
(from + Math.abs(it.nextInt()) % (to - from + 1))
}
fun longRange(range: LongRange) = longRange(range.first, range.last)
fun longRange(from: Long, to: Long): Generator<Long> = Generator.success {
(from + Math.abs(it.nextLong()) % (to - from + 1)).toLong()
(from + Math.abs(it.nextLong()) % (to - from + 1))
}
fun double() = Generator.success { it.nextDouble() }
@ -153,7 +154,7 @@ class Generator<out A>(val generate: (SplittableRandom) -> Try<A>) {
if (Character.isValidCodePoint(codePoint)) {
return@Generator Try.Success(codePoint.toChar())
} else {
Try.Failure(IllegalStateException("Could not generate valid codepoint"))
Try.Failure<Any>(IllegalStateException("Could not generate valid codepoint"))
}
}
@ -174,7 +175,7 @@ class Generator<out A>(val generate: (SplittableRandom) -> Try<A>) {
}
fun <A> replicatePoisson(meanSize: Double, generator: Generator<A>, atLeastOne: Boolean = false) = Generator<List<A>> {
fun <A> replicatePoisson(meanSize: Double, generator: Generator<A>, atLeastOne: Boolean = false) = Generator {
val chance = (meanSize - 1) / meanSize
val result = mutableListOf<A>()
var finish = false
@ -190,7 +191,8 @@ class Generator<out A>(val generate: (SplittableRandom) -> Try<A>) {
}
}
if (res is Try.Failure) {
return@Generator res
@Suppress("UNCHECKED_CAST")
return@Generator res as Try<List<A>>
}
}
Try.Success(result)
@ -200,11 +202,11 @@ class Generator<out A>(val generate: (SplittableRandom) -> Try<A>) {
fun <A> pickN(number: Int, list: List<A>) = Generator<List<A>> {
val mask = BitSet(list.size)
val size = Math.min(list.size, number)
for (i in 0..size - 1) {
for (i in 0 until size) {
// mask[i] = 1 desugars into mask.set(i, 1), which sets a range instead of a bit
mask[i] = true
}
for (i in 0..list.size - 1) {
for (i in 0 until list.size) {
val bit = mask[i]
val swapIndex = i + it.nextInt(size - i)
mask[i] = mask[swapIndex]

View File

@ -7,9 +7,6 @@ description 'Corda client RPC modules'
//noinspection GroovyAssignabilityCheck
configurations {
// we don't want isolated.jar in classPath, since we want to test jar being dynamically loaded as an attachment
runtime.exclude module: 'isolated'
integrationTestCompile.extendsFrom testCompile
integrationTestRuntime.extendsFrom testRuntime

View File

@ -5,14 +5,17 @@ import net.corda.core.concurrent.CordaFuture;
import net.corda.core.contracts.Amount;
import net.corda.core.messaging.CordaRPCOps;
import net.corda.core.messaging.FlowHandle;
import net.corda.core.node.services.ServiceInfo;
import net.corda.core.utilities.OpaqueBytes;
import net.corda.finance.flows.AbstractCashFlow;
import net.corda.finance.flows.CashIssueFlow;
import net.corda.finance.flows.CashPaymentFlow;
import net.corda.finance.schemas.*;
import net.corda.node.internal.Node;
import net.corda.node.internal.StartedNode;
import net.corda.node.services.transactions.ValidatingNotaryService;
import net.corda.nodeapi.internal.ServiceInfo;
import net.corda.nodeapi.User;
import net.corda.testing.CoreTestUtils;
import net.corda.testing.node.NodeBasedTest;
import org.junit.After;
import org.junit.Before;
@ -22,14 +25,14 @@ import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutionException;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static java.util.Collections.*;
import static java.util.Objects.requireNonNull;
import static kotlin.test.AssertionsKt.assertEquals;
import static net.corda.client.rpc.CordaRPCClientConfiguration.getDefault;
import static net.corda.finance.CurrencyUtils.DOLLARS;
import static net.corda.finance.Currencies.DOLLARS;
import static net.corda.finance.contracts.GetBalances.getCashBalance;
import static net.corda.node.services.FlowPermissions.startFlowPermission;
import static net.corda.testing.CoreTestUtils.*;
import static net.corda.testing.TestConstants.getALICE;
public class CordaRPCJavaClientTest extends NodeBasedTest {
@ -37,7 +40,7 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
private Set<String> permSet = new HashSet<>(perms);
private User rpcUser = new User("user1", "test", permSet);
private Node node;
private StartedNode<Node> node;
private CordaRPCClient client;
private RPCClient.RPCConnection<CordaRPCOps> connection = null;
private CordaRPCOps rpcProxy;
@ -49,15 +52,18 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
@Before
public void setUp() throws ExecutionException, InterruptedException {
setCordappPackages("net.corda.finance.contracts");
Set<ServiceInfo> services = new HashSet<>(singletonList(new ServiceInfo(ValidatingNotaryService.Companion.getType(), null)));
CordaFuture<Node> nodeFuture = startNode(getALICE().getName(), 1, services, singletonList(rpcUser), emptyMap());
CordaFuture<StartedNode<Node>> nodeFuture = startNode(getALICE().getName(), 1, services, singletonList(rpcUser), emptyMap());
node = nodeFuture.get();
client = new CordaRPCClient(requireNonNull(node.getConfiguration().getRpcAddress()), null, getDefault(), false);
node.getInternals().registerCustomSchemas(Collections.singleton(CashSchemaV1.INSTANCE));
client = new CordaRPCClient(requireNonNull(node.getInternals().getConfiguration().getRpcAddress()), getDefault(), false);
}
@After
public void done() throws IOException {
connection.close();
unsetCordappPackages();
}
@Test
@ -71,7 +77,7 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
FlowHandle<AbstractCashFlow.Result> flowHandle = rpcProxy.startFlowDynamic(CashIssueFlow.class,
DOLLARS(123), OpaqueBytes.of("1".getBytes()),
node.info.getLegalIdentity());
CoreTestUtils.chooseIdentity(node.getInfo()));
System.out.println("Started issuing cash, waiting on result");
flowHandle.getReturnValue().get();

View File

@ -2,8 +2,10 @@ package net.corda.client.rpc
import net.corda.core.crypto.random63BitValue
import net.corda.core.flows.FlowInitiator
import net.corda.core.messaging.*
import net.corda.core.node.services.ServiceInfo
import net.corda.core.messaging.FlowProgressHandle
import net.corda.core.messaging.StateMachineUpdate
import net.corda.core.messaging.startFlow
import net.corda.core.messaging.startTrackedFlow
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS
@ -13,12 +15,18 @@ import net.corda.finance.contracts.getCashBalances
import net.corda.finance.flows.CashException
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.finance.schemas.CashSchemaV1
import net.corda.node.internal.Node
import net.corda.node.internal.StartedNode
import net.corda.node.services.FlowPermissions.Companion.startFlowPermission
import net.corda.nodeapi.internal.ServiceInfo
import net.corda.node.services.transactions.ValidatingNotaryService
import net.corda.nodeapi.User
import net.corda.testing.ALICE
import net.corda.testing.chooseIdentity
import net.corda.testing.node.NodeBasedTest
import net.corda.testing.setCordappPackages
import net.corda.testing.unsetCordappPackages
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.After
@ -33,7 +41,7 @@ class CordaRPCClientTest : NodeBasedTest() {
startFlowPermission<CashIssueFlow>(),
startFlowPermission<CashPaymentFlow>()
))
private lateinit var node: Node
private lateinit var node: StartedNode<Node>
private lateinit var client: CordaRPCClient
private var connection: CordaRPCConnection? = null
@ -43,13 +51,16 @@ class CordaRPCClientTest : NodeBasedTest() {
@Before
fun setUp() {
setCordappPackages("net.corda.finance.contracts")
node = startNode(ALICE.name, rpcUsers = listOf(rpcUser), advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type))).getOrThrow()
client = CordaRPCClient(node.configuration.rpcAddress!!, initialiseSerialization = false)
node.internals.registerCustomSchemas(setOf(CashSchemaV1))
client = CordaRPCClient(node.internals.configuration.rpcAddress!!, initialiseSerialization = false)
}
@After
fun done() {
connection?.close()
unsetCordappPackages()
}
@Test
@ -78,7 +89,7 @@ class CordaRPCClientTest : NodeBasedTest() {
println("Creating proxy")
println("Starting flow")
val flowHandle = connection!!.proxy.startTrackedFlow(::CashIssueFlow,
20.DOLLARS, OpaqueBytes.of(0), node.info.legalIdentity
20.DOLLARS, OpaqueBytes.of(0), node.info.chooseIdentity()
)
println("Started flow, waiting on result")
flowHandle.progress.subscribe {
@ -90,7 +101,7 @@ class CordaRPCClientTest : NodeBasedTest() {
@Test
fun `sub-type of FlowException thrown by flow`() {
login(rpcUser.username, rpcUser.password)
val handle = connection!!.proxy.startFlow(::CashPaymentFlow, 100.DOLLARS, node.info.legalIdentity)
val handle = connection!!.proxy.startFlow(::CashPaymentFlow, 100.DOLLARS, node.info.chooseIdentity())
assertThatExceptionOfType(CashException::class.java).isThrownBy {
handle.returnValue.getOrThrow()
}
@ -99,9 +110,8 @@ class CordaRPCClientTest : NodeBasedTest() {
@Test
fun `check basic flow has no progress`() {
login(rpcUser.username, rpcUser.password)
connection!!.proxy.startFlow(::CashPaymentFlow, 100.DOLLARS, node.info.legalIdentity).use {
connection!!.proxy.startFlow(::CashPaymentFlow, 100.DOLLARS, node.info.chooseIdentity()).use {
assertFalse(it is FlowProgressHandle<*>)
assertTrue(it is FlowHandle<*>)
}
}
@ -113,7 +123,7 @@ class CordaRPCClientTest : NodeBasedTest() {
assertTrue(startCash.isEmpty(), "Should not start with any cash")
val flowHandle = proxy.startFlow(::CashIssueFlow,
123.DOLLARS, OpaqueBytes.of(0), node.info.legalIdentity
123.DOLLARS, OpaqueBytes.of(0), node.info.chooseIdentity()
)
println("Started issuing cash, waiting on result")
flowHandle.returnValue.get()
@ -138,7 +148,7 @@ class CordaRPCClientTest : NodeBasedTest() {
countShellFlows++
}
}
val nodeIdentity = node.info.legalIdentity
val nodeIdentity = node.info.chooseIdentity()
node.services.startFlow(CashIssueFlow(2000.DOLLARS, OpaqueBytes.of(0), nodeIdentity), FlowInitiator.Shell).resultFuture.getOrThrow()
proxy.startFlow(::CashIssueFlow,
123.DOLLARS,

View File

@ -1,18 +1,13 @@
package net.corda.client.rpc
import net.corda.client.rpc.internal.KryoClientSerializationScheme
import net.corda.client.rpc.internal.RPCClient
import net.corda.client.rpc.internal.RPCClientConfiguration
import net.corda.client.rpc.serialization.KryoClientSerializationScheme
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport
import net.corda.nodeapi.ConnectionDirection
import net.corda.nodeapi.config.SSLConfiguration
import net.corda.nodeapi.internal.serialization.AMQPClientSerializationScheme
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 java.time.Duration
/** @see RPCClient.RPCConnection */
@ -38,9 +33,9 @@ data class CordaRPCClientConfiguration(
}
/** @see RPCClient */
//TODO Add SSL support
class CordaRPCClient(
hostAndPort: NetworkHostAndPort,
sslConfiguration: SSLConfiguration? = null,
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default,
initialiseSerialization: Boolean = true
) {
@ -49,12 +44,12 @@ class CordaRPCClient(
// others having registered first.
// TODO: allow clients to have serialization factory etc injected and align with RPC protocol version?
if (initialiseSerialization) {
initialiseSerialization()
KryoClientSerializationScheme.initialiseSerialization()
}
}
private val rpcClient = RPCClient<CordaRPCOps>(
tcpTransport(ConnectionDirection.Outbound(), hostAndPort, sslConfiguration),
tcpTransport(ConnectionDirection.Outbound(), hostAndPort, config = null),
configuration.toRpcClientConfiguration(),
KRYO_RPC_CLIENT_CONTEXT
)
@ -66,21 +61,4 @@ class CordaRPCClient(
inline fun <A> use(username: String, password: String, block: (CordaRPCConnection) -> A): A {
return start(username, password).use(block)
}
companion object {
fun initialiseSerialization() {
try {
SerializationDefaults.SERIALIZATION_FACTORY = SerializationFactoryImpl().apply {
registerScheme(KryoClientSerializationScheme())
registerScheme(AMQPClientSerializationScheme())
}
SerializationDefaults.P2P_CONTEXT = KRYO_P2P_CONTEXT
SerializationDefaults.RPC_CLIENT_CONTEXT = KRYO_RPC_CLIENT_CONTEXT
} catch(e: IllegalStateException) {
// Check that it's registered as we expect
check(SerializationDefaults.SERIALIZATION_FACTORY is SerializationFactoryImpl) { "RPC client encountered conflicting configuration of serialization subsystem." }
check((SerializationDefaults.SERIALIZATION_FACTORY as SerializationFactoryImpl).alreadyRegisteredSchemes.any { it is KryoClientSerializationScheme }) { "RPC client encountered conflicting configuration of serialization subsystem." }
}
}
}
}

View File

@ -0,0 +1,10 @@
package net.corda.client.rpc
import net.corda.core.serialization.CordaSerializable
/**
* Thrown to indicate that the calling user does not have permission for something they have requested (for example
* calling a method).
*/
@CordaSerializable
class PermissionException(msg: String) : RuntimeException(msg)

View File

@ -0,0 +1,11 @@
package net.corda.client.rpc
import net.corda.core.CordaRuntimeException
/**
* Thrown to indicate a fatal error in the RPC system itself, as opposed to an error generated by the invoked
* method.
*/
open class RPCException(message: String?, cause: Throwable?) : CordaRuntimeException(message, cause) {
constructor(msg: String) : this(msg, null)
}

View File

@ -0,0 +1,6 @@
package net.corda.client.rpc
/** Records the protocol version in which this RPC was added. */
@Target(AnnotationTarget.FUNCTION)
@MustBeDocumented
annotation class RPCSinceVersion(val version: Int)

View File

@ -0,0 +1,41 @@
package net.corda.client.rpc.internal
import com.esotericsoftware.kryo.pool.KryoPool
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.utilities.ByteSequence
import net.corda.nodeapi.internal.serialization.*
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 rpcClientKryoPool(context: SerializationContext): KryoPool {
return KryoPool.Builder {
DefaultKryoCustomizer.customize(RPCKryo(RpcClientObservableSerializer, context)).apply {
classLoader = context.deserializationClassLoader
}
}.build()
}
// We're on the client and don't have access to server classes.
override fun rpcServerKryoPool(context: SerializationContext): KryoPool = throw UnsupportedOperationException()
companion object {
fun initialiseSerialization() {
try {
SerializationDefaults.SERIALIZATION_FACTORY = SerializationFactoryImpl().apply {
registerScheme(KryoClientSerializationScheme())
registerScheme(AMQPClientSerializationScheme())
}
SerializationDefaults.P2P_CONTEXT = KRYO_P2P_CONTEXT
SerializationDefaults.RPC_CLIENT_CONTEXT = KRYO_RPC_CLIENT_CONTEXT
} catch(e: IllegalStateException) {
// Check that it's registered as we expect
check(SerializationDefaults.SERIALIZATION_FACTORY is SerializationFactoryImpl) { "RPC client encountered conflicting configuration of serialization subsystem." }
check((SerializationDefaults.SERIALIZATION_FACTORY as SerializationFactoryImpl).alreadyRegisteredSchemes.any { it is KryoClientSerializationScheme }) { "RPC client encountered conflicting configuration of serialization subsystem." }
}
}
}
}

View File

@ -1,5 +1,6 @@
package net.corda.client.rpc.internal
import net.corda.client.rpc.RPCException
import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.logElapsedTime
import net.corda.core.messaging.RPCOps
@ -12,7 +13,6 @@ import net.corda.core.utilities.seconds
import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport
import net.corda.nodeapi.ConnectionDirection
import net.corda.nodeapi.RPCApi
import net.corda.nodeapi.RPCException
import net.corda.nodeapi.config.SSLConfiguration
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.TransportConfiguration

View File

@ -10,6 +10,8 @@ import com.google.common.cache.RemovalCause
import com.google.common.cache.RemovalListener
import com.google.common.util.concurrent.SettableFuture
import com.google.common.util.concurrent.ThreadFactoryBuilder
import net.corda.client.rpc.RPCException
import net.corda.client.rpc.RPCSinceVersion
import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.LazyPool
import net.corda.core.internal.LazyStickyPool

View File

@ -1,27 +0,0 @@
package net.corda.client.rpc.serialization
import com.esotericsoftware.kryo.pool.KryoPool
import net.corda.client.rpc.internal.RpcClientObservableSerializer
import net.corda.core.serialization.SerializationContext
import net.corda.core.utilities.ByteSequence
import net.corda.nodeapi.RPCKryo
import net.corda.nodeapi.internal.serialization.AbstractKryoSerializationScheme
import net.corda.nodeapi.internal.serialization.DefaultKryoCustomizer
import net.corda.nodeapi.internal.serialization.KryoHeaderV0_1
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 rpcClientKryoPool(context: SerializationContext): KryoPool {
return KryoPool.Builder {
DefaultKryoCustomizer.customize(RPCKryo(RpcClientObservableSerializer, context)).apply { classLoader = context.deserializationClassLoader }
}.build()
}
// We're on the client and don't have access to server classes.
override fun rpcServerKryoPool(context: SerializationContext): KryoPool {
throw UnsupportedOperationException()
}
}

View File

@ -2,11 +2,12 @@ package net.corda.java.rpc;
import net.corda.client.rpc.CordaRPCConnection;
import net.corda.core.contracts.Amount;
import net.corda.core.identity.CordaX500Name;
import net.corda.core.identity.Party;
import net.corda.core.messaging.CordaRPCOps;
import net.corda.core.messaging.FlowHandle;
import net.corda.core.node.NodeInfo;
import net.corda.core.utilities.OpaqueBytes;
import net.corda.core.utilities.X500NameUtils;
import net.corda.finance.flows.AbstractCashFlow;
import net.corda.finance.flows.CashIssueFlow;
import net.corda.nodeapi.User;
@ -41,9 +42,10 @@ public class StandaloneCordaRPCJavaClientTest {
private CordaRPCOps rpcProxy;
private CordaRPCConnection connection;
private NodeInfo notaryNode;
private Party notaryNodeIdentity;
private NodeConfig notaryConfig = new NodeConfig(
X500NameUtils.getX500Name("Notary Service", "Zurich", "CH"),
new CordaX500Name("Notary Service", "Zurich", "CH"),
port.getAndIncrement(),
port.getAndIncrement(),
port.getAndIncrement(),
@ -60,6 +62,7 @@ public class StandaloneCordaRPCJavaClientTest {
connection = notary.connect();
rpcProxy = connection.getProxy();
notaryNode = fetchNotaryIdentity();
notaryNodeIdentity = rpcProxy.nodeInfo().getLegalIdentities().get(0);
}
@After
@ -106,7 +109,7 @@ public class StandaloneCordaRPCJavaClientTest {
FlowHandle<AbstractCashFlow.Result> flowHandle = rpcProxy.startFlowDynamic(CashIssueFlow.class,
dollars123, OpaqueBytes.of("1".getBytes()),
notaryNode.getLegalIdentity());
notaryNodeIdentity);
System.out.println("Started issuing cash, waiting on result");
flowHandle.getReturnValue().get();

View File

@ -4,12 +4,17 @@ import com.google.common.hash.Hashing
import com.google.common.hash.HashingInputStream
import net.corda.client.rpc.CordaRPCConnection
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.*
import net.corda.core.messaging.*
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.*
import net.corda.core.utilities.*
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.seconds
import net.corda.finance.DOLLARS
import net.corda.finance.POUNDS
import net.corda.finance.SWISS_FRANCS
@ -52,9 +57,10 @@ class StandaloneCordaRPClientTest {
private lateinit var rpcProxy: CordaRPCOps
private lateinit var connection: CordaRPCConnection
private lateinit var notaryNode: NodeInfo
private lateinit var notaryNodeIdentity: Party
private val notaryConfig = NodeConfig(
legalName = getX500Name(O = "Notary Service", OU = "corda", L = "Zurich", C = "CH"),
legalName = CordaX500Name(organisation = "Notary Service", locality = "Zurich", country = "CH"),
p2pPort = port.andIncrement,
rpcPort = port.andIncrement,
webPort = port.andIncrement,
@ -70,6 +76,7 @@ class StandaloneCordaRPClientTest {
connection = notary.connect()
rpcProxy = connection.proxy
notaryNode = fetchNotaryIdentity()
notaryNodeIdentity = rpcProxy.nodeInfo().legalIdentitiesAndCerts.first().party
}
@After
@ -106,7 +113,7 @@ class StandaloneCordaRPClientTest {
@Test
fun `test starting flow`() {
rpcProxy.startFlow(::CashIssueFlow, 127.POUNDS, OpaqueBytes.of(0), notaryNode.notaryIdentity)
rpcProxy.startFlow(::CashIssueFlow, 127.POUNDS, OpaqueBytes.of(0), notaryNodeIdentity)
.returnValue.getOrThrow(timeout)
}
@ -114,7 +121,7 @@ class StandaloneCordaRPClientTest {
fun `test starting tracked flow`() {
var trackCount = 0
val handle = rpcProxy.startTrackedFlow(
::CashIssueFlow, 429.DOLLARS, OpaqueBytes.of(0), notaryNode.notaryIdentity
::CashIssueFlow, 429.DOLLARS, OpaqueBytes.of(0), notaryNodeIdentity
)
val updateLatch = CountDownLatch(1)
handle.progress.subscribe { msg ->
@ -129,7 +136,7 @@ class StandaloneCordaRPClientTest {
@Test
fun `test network map`() {
assertEquals(notaryConfig.legalName, notaryNode.legalIdentity.name)
assertEquals(notaryConfig.legalName, notaryNodeIdentity.name)
}
@Test
@ -148,7 +155,7 @@ class StandaloneCordaRPClientTest {
}
// Now issue some cash
rpcProxy.startFlow(::CashIssueFlow, 513.SWISS_FRANCS, OpaqueBytes.of(0), notaryNode.notaryIdentity)
rpcProxy.startFlow(::CashIssueFlow, 513.SWISS_FRANCS, OpaqueBytes.of(0), notaryNodeIdentity)
.returnValue.getOrThrow(timeout)
updateLatch.await()
assertEquals(1, updateCount.get())
@ -166,7 +173,7 @@ class StandaloneCordaRPClientTest {
}
// Now issue some cash
rpcProxy.startFlow(::CashIssueFlow, 629.POUNDS, OpaqueBytes.of(0), notaryNode.notaryIdentity)
rpcProxy.startFlow(::CashIssueFlow, 629.POUNDS, OpaqueBytes.of(0), notaryNodeIdentity)
.returnValue.getOrThrow(timeout)
updateLatch.await()
@ -180,7 +187,7 @@ class StandaloneCordaRPClientTest {
@Test
fun `test vault query by`() {
// Now issue some cash
rpcProxy.startFlow(::CashIssueFlow, 629.POUNDS, OpaqueBytes.of(0), notaryNode.notaryIdentity)
rpcProxy.startFlow(::CashIssueFlow, 629.POUNDS, OpaqueBytes.of(0), notaryNodeIdentity)
.returnValue.getOrThrow(timeout)
val criteria = QueryCriteria.VaultQueryCriteria(status = Vault.StateStatus.ALL)
@ -191,7 +198,7 @@ class StandaloneCordaRPClientTest {
assertEquals(1, queryResults.totalStatesAvailable)
assertEquals(queryResults.states.first().state.data.amount.quantity, 629.POUNDS.quantity)
rpcProxy.startFlow(::CashPaymentFlow, 100.POUNDS, notaryNode.legalIdentity).returnValue.getOrThrow()
rpcProxy.startFlow(::CashPaymentFlow, 100.POUNDS, notaryNodeIdentity).returnValue.getOrThrow()
val moreResults = rpcProxy.vaultQueryBy<Cash.State>(criteria, paging, sorting)
assertEquals(3, moreResults.totalStatesAvailable) // 629 - 100 + 100
@ -209,7 +216,7 @@ class StandaloneCordaRPClientTest {
println(startCash)
assertTrue(startCash.isEmpty(), "Should not start with any cash")
val flowHandle = rpcProxy.startFlow(::CashIssueFlow, 629.DOLLARS, OpaqueBytes.of(0), notaryNode.legalIdentity)
val flowHandle = rpcProxy.startFlow(::CashIssueFlow, 629.DOLLARS, OpaqueBytes.of(0), notaryNodeIdentity)
println("Started issuing cash, waiting on result")
flowHandle.returnValue.get()

View File

@ -7,7 +7,6 @@ import net.corda.core.internal.concurrent.thenMatch
import net.corda.core.messaging.RPCOps
import net.corda.core.utilities.getOrThrow
import net.corda.node.services.messaging.getRpcContext
import net.corda.nodeapi.RPCSinceVersion
import net.corda.testing.RPCDriverExposedDSLInterface
import net.corda.testing.rpcDriver
import net.corda.testing.rpcTestUser

View File

@ -3,7 +3,6 @@ package net.corda.client.rpc
import net.corda.core.messaging.RPCOps
import net.corda.node.services.messaging.getRpcContext
import net.corda.node.services.messaging.requirePermission
import net.corda.nodeapi.PermissionException
import net.corda.nodeapi.User
import net.corda.testing.RPCDriverExposedDSLInterface
import net.corda.testing.rpcDriver

View File

@ -14,11 +14,11 @@ class RepeatingBytesInputStream(val bytesToRepeat: ByteArray, val numberOfBytes:
}
}
override fun read(byteArray: ByteArray, offset: Int, length: Int): Int {
val until = Math.min(Math.min(offset + length, byteArray.size), offset + bytesLeft)
for (i in offset .. until - 1) {
val lastIdx = Math.min(Math.min(offset + length, byteArray.size), offset + bytesLeft)
for (i in offset until lastIdx) {
byteArray[i] = bytesToRepeat[(numberOfBytes - bytesLeft + i - offset) % bytesToRepeat.size]
}
val bytesRead = until - offset
val bytesRead = lastIdx - offset
bytesLeft -= bytesRead
return if (bytesRead == 0 && bytesLeft == 0) -1 else bytesRead
}

View File

@ -0,0 +1,42 @@
// Experimental Confidential Identities support for 1.0
// This contains the prototype SwapIdentitiesFlow and SwapIdentitiesHandler, which can be used
// for exchanging confidential identities as part of a flow, until a permanent solution is prepared.
// Expect this module to be removed and merged into core in a later release.
apply plugin: 'kotlin'
apply plugin: CanonicalizerPlugin
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.cordformation'
apply plugin: 'com.jfrog.artifactory'
description 'Corda Experimental Confidential Identities'
dependencies {
// Note the :confidential-identities module is a CorDapp in its own right
// and CorDapps using :confidential-identities features should use 'cordapp' not 'compile' linkage.
cordaCompile project(':core')
// Quasar, for suspendable fibres.
compileOnly "co.paralleluniverse:quasar-core:$quasar_version:jdk8"
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testCompile "junit:junit:$junit_version"
// Guava: Google test library (collections test suite)
testCompile "com.google.guava:guava-testlib:$guava_version"
// Bring in the MockNode infrastructure for writing protocol unit tests.
testCompile project(":node")
testCompile project(":node-driver")
// AssertJ: for fluent assertions for testing
testCompile "org.assertj:assertj-core:$assertj_version"
}
jar {
baseName 'corda-confidential-identities'
}
publish {
name jar.baseName
}

View File

@ -1,9 +1,10 @@
package net.corda.core.flows
package net.corda.confidential
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.ContractState
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.ProgressTracker
@ -21,10 +22,10 @@ object IdentitySyncFlow {
* @return a mapping of well known identities to the confidential identities used in the transaction.
*/
// TODO: Can this be triggered automatically from [SendTransactionFlow]
class Send(val otherSides: Set<Party>,
class Send(val otherSideSessions: Set<FlowSession>,
val tx: WireTransaction,
override val progressTracker: ProgressTracker) : FlowLogic<Unit>() {
constructor(otherSide: Party, tx: WireTransaction) : this(setOf(otherSide), tx, tracker())
constructor(otherSide: FlowSession, tx: WireTransaction) : this(setOf(otherSide), tx, tracker())
companion object {
object SYNCING_IDENTITIES : ProgressTracker.Step("Syncing identities")
@ -39,14 +40,14 @@ object IdentitySyncFlow {
val identities: Set<AbstractParty> = states.flatMap { it.participants }.toSet()
// Filter participants down to the set of those not in the network map (are not well known)
val confidentialIdentities = identities
.filter { serviceHub.networkMapCache.getNodeByLegalIdentityKey(it.owningKey) == null }
.filter { serviceHub.networkMapCache.getNodesByLegalIdentityKey(it.owningKey).isEmpty() }
.toList()
val identityCertificates: Map<AbstractParty, PartyAndCertificate?> = identities
.map { Pair(it, serviceHub.identityService.certificateFromKey(it.owningKey)) }.toMap()
otherSides.forEach { otherSide ->
val requestedIdentities: List<AbstractParty> = sendAndReceive<List<AbstractParty>>(otherSide, confidentialIdentities).unwrap { req ->
require(req.all { it in identityCertificates.keys }) { "${otherSide} requested a confidential identity not part of transaction: ${tx.id}" }
otherSideSessions.forEach { otherSideSession ->
val requestedIdentities: List<AbstractParty> = otherSideSession.sendAndReceive<List<AbstractParty>>(confidentialIdentities).unwrap { req ->
require(req.all { it in identityCertificates.keys }) { "${otherSideSession.counterparty} requested a confidential identity not part of transaction: ${tx.id}" }
req
}
val sendIdentities: List<PartyAndCertificate?> = requestedIdentities.map {
@ -56,7 +57,7 @@ object IdentitySyncFlow {
else
throw IllegalStateException("Counterparty requested a confidential identity for which we do not have the certificate path: ${tx.id}")
}
send(otherSide, sendIdentities)
otherSideSession.send(sendIdentities)
}
}
@ -66,7 +67,7 @@ object IdentitySyncFlow {
* Handle an offer to provide proof of identity (in the form of certificate paths) for confidential identities which
* we do not yet know about.
*/
class Receive(val otherSide: Party) : FlowLogic<Unit>() {
class Receive(val otherSideSession: FlowSession) : FlowLogic<Unit>() {
companion object {
object RECEIVING_IDENTITIES : ProgressTracker.Step("Receiving confidential identities")
object RECEIVING_CERTIFICATES : ProgressTracker.Step("Receiving certificates for unknown identities")
@ -77,10 +78,10 @@ object IdentitySyncFlow {
@Suspendable
override fun call(): Unit {
progressTracker.currentStep = RECEIVING_IDENTITIES
val allIdentities = receive<List<AbstractParty>>(otherSide).unwrap { it }
val unknownIdentities = allIdentities.filter { serviceHub.identityService.partyFromAnonymous(it) == null }
val allIdentities = otherSideSession.receive<List<AbstractParty>>().unwrap { it }
val unknownIdentities = allIdentities.filter { serviceHub.identityService.wellKnownPartyFromAnonymous(it) == null }
progressTracker.currentStep = RECEIVING_CERTIFICATES
val missingIdentities = sendAndReceive<List<PartyAndCertificate>>(otherSide, unknownIdentities)
val missingIdentities = otherSideSession.sendAndReceive<List<PartyAndCertificate>>(unknownIdentities)
// Batch verify the identities we've received, so we know they're all correct before we start storing them in
// the identity service

View File

@ -1,23 +1,27 @@
package net.corda.core.flows
package net.corda.confidential
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.services.IdentityService
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap
/**
* Very basic flow which exchanges transaction key and certificate paths between two parties in a transaction.
* This is intended for use as a subflow of another flow.
* Very basic flow which generates new confidential identities for parties in a transaction and exchanges the transaction
* key and certificate paths between the parties. This is intended for use as a subflow of another flow which builds a
* transaction.
*/
@StartableByRPC
@InitiatingFlow
class TransactionKeyFlow(val otherSide: Party,
val revocationEnabled: Boolean,
class SwapIdentitiesFlow(private val otherParty: Party,
private val revocationEnabled: Boolean,
override val progressTracker: ProgressTracker) : FlowLogic<LinkedHashMap<Party, AnonymousParty>>() {
constructor(otherSide: Party) : this(otherSide, false, tracker())
constructor(otherParty: Party) : this(otherParty, false, tracker())
companion object {
object AWAITING_KEY : ProgressTracker.Step("Awaiting key")
@ -35,18 +39,19 @@ class TransactionKeyFlow(val otherSide: Party,
@Suspendable
override fun call(): LinkedHashMap<Party, AnonymousParty> {
progressTracker.currentStep = AWAITING_KEY
val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, revocationEnabled)
val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, revocationEnabled)
// Special case that if we're both parties, a single identity is generated
val identities = LinkedHashMap<Party, AnonymousParty>()
if (otherSide == serviceHub.myInfo.legalIdentity) {
identities.put(otherSide, legalIdentityAnonymous.party.anonymise())
if (serviceHub.myInfo.isLegalIdentity(otherParty)) {
identities.put(otherParty, legalIdentityAnonymous.party.anonymise())
} else {
val anonymousOtherSide = sendAndReceive<PartyAndCertificate>(otherSide, legalIdentityAnonymous).unwrap { confidentialIdentity ->
validateAndRegisterIdentity(serviceHub.identityService, otherSide, confidentialIdentity)
val otherSession = initiateFlow(otherParty)
val anonymousOtherSide = otherSession.sendAndReceive<PartyAndCertificate>(legalIdentityAnonymous).unwrap { confidentialIdentity ->
validateAndRegisterIdentity(serviceHub.identityService, otherSession.counterparty, confidentialIdentity)
}
identities.put(serviceHub.myInfo.legalIdentity, legalIdentityAnonymous.party.anonymise())
identities.put(otherSide, anonymousOtherSide.party.anonymise())
identities.put(ourIdentity, legalIdentityAnonymous.party.anonymise())
identities.put(otherSession.counterparty, anonymousOtherSide.party.anonymise())
}
return identities
}

View File

@ -0,0 +1,28 @@
package net.corda.confidential
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap
class SwapIdentitiesHandler(val otherSideSession: FlowSession, val revocationEnabled: Boolean) : FlowLogic<Unit>() {
constructor(otherSideSession: FlowSession) : this(otherSideSession, false)
companion object {
object SENDING_KEY : ProgressTracker.Step("Sending key")
}
override val progressTracker: ProgressTracker = ProgressTracker(SENDING_KEY)
@Suspendable
override fun call() {
val revocationEnabled = false
progressTracker.currentStep = SENDING_KEY
val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, revocationEnabled)
otherSideSession.sendAndReceive<PartyAndCertificate>(legalIdentityAnonymous).unwrap { confidentialIdentity ->
SwapIdentitiesFlow.validateAndRegisterIdentity(serviceHub.identityService, otherSideSession.counterparty, confidentialIdentity)
}
}
}

View File

@ -1,6 +1,10 @@
package net.corda.core.flows
package net.corda.confidential
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.InitiatingFlow
import net.corda.core.identity.Party
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.OpaqueBytes
@ -9,9 +13,7 @@ import net.corda.core.utilities.unwrap
import net.corda.finance.DOLLARS
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.testing.ALICE
import net.corda.testing.BOB
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.*
import net.corda.testing.node.MockNetwork
import org.junit.After
import org.junit.Before
@ -24,6 +26,7 @@ class IdentitySyncFlowTests {
@Before
fun before() {
setCordappPackages("net.corda.finance.contracts.asset")
// We run this in parallel threads to help catch any race conditions that may exist.
mockNet = MockNetwork(networkSendManuallyPumped = false, threadPerNode = true)
}
@ -31,6 +34,7 @@ class IdentitySyncFlowTests {
@After
fun cleanUp() {
mockNet.stopNodes()
unsetCordappPackages()
}
@Test
@ -39,25 +43,26 @@ class IdentitySyncFlowTests {
val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name)
val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name)
val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name)
val alice: Party = aliceNode.services.myInfo.legalIdentity
val bob: Party = bobNode.services.myInfo.legalIdentity
bobNode.registerInitiatedFlow(Receive::class.java)
val alice: Party = aliceNode.services.myInfo.chooseIdentity()
val bob: Party = bobNode.services.myInfo.chooseIdentity()
bobNode.internals.registerInitiatedFlow(Receive::class.java)
// Alice issues then pays some cash to a new confidential identity that Bob doesn't know about
val anonymous = true
val ref = OpaqueBytes.of(0x01)
val issueFlow = aliceNode.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, alice, anonymous, notaryNode.services.myInfo.notaryIdentity))
val notary = aliceNode.services.getDefaultNotary()
val issueFlow = aliceNode.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, alice, anonymous, notary))
val issueTx = issueFlow.resultFuture.getOrThrow().stx
val confidentialIdentity = issueTx.tx.outputs.map { it.data }.filterIsInstance<Cash.State>().single().owner
assertNull(bobNode.database.transaction { bobNode.services.identityService.partyFromAnonymous(confidentialIdentity) })
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()
val expected = aliceNode.database.transaction {
aliceNode.services.identityService.partyFromAnonymous(confidentialIdentity)
aliceNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity)
}
val actual = bobNode.database.transaction {
bobNode.services.identityService.partyFromAnonymous(confidentialIdentity)
bobNode.services.identityService.wellKnownPartyFromAnonymous(confidentialIdentity)
}
assertEquals(expected, actual)
}
@ -69,19 +74,20 @@ class IdentitySyncFlowTests {
class Initiator(val otherSide: Party, val tx: WireTransaction): FlowLogic<Boolean>() {
@Suspendable
override fun call(): Boolean {
subFlow(IdentitySyncFlow.Send(otherSide, tx))
val session = initiateFlow(otherSide)
subFlow(IdentitySyncFlow.Send(session, tx))
// Wait for the counterparty to indicate they're done
return receive<Boolean>(otherSide).unwrap { it }
return session.receive<Boolean>().unwrap { it }
}
}
@InitiatedBy(IdentitySyncFlowTests.Initiator::class)
class Receive(val otherSide: Party): FlowLogic<Unit>() {
class Receive(val otherSideSession: FlowSession): FlowLogic<Unit>() {
@Suspendable
override fun call() {
subFlow(IdentitySyncFlow.Receive(otherSide))
subFlow(IdentitySyncFlow.Receive(otherSideSession))
// Notify the initiator that we've finished syncing
send(otherSide, true)
otherSideSession.send(true)
}
}
}

View File

@ -1,4 +1,4 @@
package net.corda.core.flows
package net.corda.confidential
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
@ -7,6 +7,7 @@ import net.corda.core.utilities.getOrThrow
import net.corda.testing.ALICE
import net.corda.testing.BOB
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.chooseIdentity
import net.corda.testing.node.MockNetwork
import org.junit.Test
import kotlin.test.assertEquals
@ -14,7 +15,7 @@ import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
class TransactionKeyFlowTests {
class SwapIdentitiesFlowTests {
@Test
fun `issue key`() {
// We run this in parallel threads to help catch any race conditions that may exist.
@ -24,11 +25,12 @@ class TransactionKeyFlowTests {
val notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name)
val aliceNode = mockNet.createPartyNode(notaryNode.network.myAddress, ALICE.name)
val bobNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOB.name)
val alice: Party = aliceNode.services.myInfo.legalIdentity
val bob: Party = bobNode.services.myInfo.legalIdentity
val alice: Party = aliceNode.services.myInfo.chooseIdentity()
val bob: Party = bobNode.services.myInfo.chooseIdentity()
mockNet.registerIdentities()
// Run the flows
val requesterFlow = aliceNode.services.startFlow(TransactionKeyFlow(bob))
val requesterFlow = aliceNode.services.startFlow(SwapIdentitiesFlow(bob))
// Get the results
val actual: Map<Party, AnonymousParty> = requesterFlow.resultFuture.getOrThrow().toMap()
@ -40,8 +42,8 @@ class TransactionKeyFlowTests {
assertNotEquals<AbstractParty>(bob, bobAnonymousIdentity)
// Verify that the anonymous identities look sane
assertEquals(alice.name, aliceNode.database.transaction { aliceNode.services.identityService.partyFromAnonymous(aliceAnonymousIdentity)!!.name })
assertEquals(bob.name, bobNode.database.transaction { bobNode.services.identityService.partyFromAnonymous(bobAnonymousIdentity)!!.name })
assertEquals(alice.name, aliceNode.database.transaction { aliceNode.services.identityService.wellKnownPartyFromAnonymous(aliceAnonymousIdentity)!!.name })
assertEquals(bob.name, bobNode.database.transaction { bobNode.services.identityService.wellKnownPartyFromAnonymous(bobAnonymousIdentity)!!.name })
// Verify that the nodes have the right anonymous identities
assertTrue { aliceAnonymousIdentity.owningKey in aliceNode.services.keyManagementService.keys }

View File

@ -1,5 +1,5 @@
gradlePluginsVersion=0.16.2
kotlinVersion=1.1.4
gradlePluginsVersion=1.0.0
kotlinVersion=1.1.50
guavaVersion=21.0
bouncycastleVersion=1.57
typesafeConfigVersion=1.3.1

View File

@ -0,0 +1,37 @@
package net.corda.core.contracts
import net.corda.core.crypto.SecureHash
import net.corda.core.serialization.CordaSerializable
/** Constrain which contract-code-containing attachment can be used with a [ContractState]. */
@CordaSerializable
interface AttachmentConstraint {
/** Returns whether the given contract attachment can be used with the [ContractState] associated with this constraint object. */
fun isSatisfiedBy(attachment: Attachment): Boolean
}
/** An [AttachmentConstraint] where [isSatisfiedBy] always returns true. */
object AlwaysAcceptAttachmentConstraint : AttachmentConstraint {
override fun isSatisfiedBy(attachment: Attachment) = true
}
/** An [AttachmentConstraint] that verifies by hash */
data class HashAttachmentConstraint(val attachmentId: SecureHash) : AttachmentConstraint {
override fun isSatisfiedBy(attachment: Attachment) = attachment.id == attachmentId
}
/**
* This [AttachmentConstraint] is a convenience class that will be automatically resolved to a [HashAttachmentConstraint].
* The resolution occurs in [TransactionBuilder.toWireTransaction] and uses the [TransactionState.contract] value
* to find a corresponding loaded [Cordapp] that contains such a contract, and then uses that [Cordapp] as the
* [Attachment].
*
* If, for any reason, this class is not automatically resolved the default implementation is to fail, because the
* intent of this class is that it should be replaced by a correct [HashAttachmentConstraint] and verify against an
* actual [Attachment].
*/
object AutomaticHashConstraint : AttachmentConstraint {
override fun isSatisfiedBy(attachment: Attachment): Boolean {
throw UnsupportedOperationException("Contracts cannot be satisfied by an AutomaticHashConstraint placeholder")
}
}

View File

@ -0,0 +1,14 @@
package net.corda.core.contracts
/**
* An enum, for which each property corresponds to a transaction component group. The position in the enum class
* declaration (ordinal) is used for component-leaf ordering when computing the Merkle tree.
*/
enum class ComponentGroupEnum {
INPUTS_GROUP, // ordinal = 0.
OUTPUTS_GROUP, // ordinal = 1.
COMMANDS_GROUP, // ordinal = 2.
ATTACHMENTS_GROUP, // ordinal = 3.
NOTARY_GROUP, // ordinal = 4.
TIMEWINDOW_GROUP // ordinal = 5.
}

View File

@ -0,0 +1,12 @@
package net.corda.core.contracts
import net.corda.core.serialization.CordaSerializable
/**
* Wrap an attachment in this if it is to be used as an executable contract attachment
*
* @property attachment The attachment representing the contract JAR
* @property contract The contract name contained within the JAR
*/
@CordaSerializable
class ContractAttachment(val attachment: Attachment, val contract: ContractClassName) : Attachment by attachment

View File

@ -0,0 +1,29 @@
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
/**
* A contract state (or just "state") contains opaque data used by a contract program. It can be thought of as a disk
* file that the program can use to persist data across transactions. States are immutable: once created they are never
* updated, instead, any changes must generate a new successor state. States can be updated (consumed) only once: the
* notary is responsible for ensuring there is no "double spending" by only signing a transaction if the input states
* are all free.
*/
@CordaSerializable
interface ContractState {
/**
* A _participant_ is any party that is able to consume this state in a valid transaction.
*
* The list of participants is required for certain types of transactions. For example, when changing the notary
* for this state, every participant has to be involved and approve the transaction
* so that they receive the updated state, and don't end up in a situation where they can no longer use a state
* they possess, since someone consumed that state during the notary change process.
*
* The participants list should normally be derived from the contents of the state.
*/
val participants: List<AbstractParty>
}
// DOCEND 1

View File

@ -4,6 +4,7 @@ package net.corda.core.contracts
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.secureRandomBytes
import net.corda.core.crypto.toStringShort
import net.corda.core.flows.FlowLogicRef
import net.corda.core.flows.FlowLogicRefFactory
import net.corda.core.identity.AbstractParty
@ -15,111 +16,13 @@ import net.corda.core.utilities.OpaqueBytes
import java.security.PublicKey
import java.time.Instant
// DOCSTART 1
/** Implemented by anything that can be named by a secure hash value (e.g. transactions, attachments). */
interface NamedByHash {
val id: SecureHash
}
// DOCSTART 1
/**
* A contract state (or just "state") contains opaque data used by a contract program. It can be thought of as a disk
* file that the program can use to persist data across transactions. States are immutable: once created they are never
* updated, instead, any changes must generate a new successor state. States can be updated (consumed) only once: the
* notary is responsible for ensuring there is no "double spending" by only signing a transaction if the input states
* are all free.
*/
@CordaSerializable
interface ContractState {
/**
* An instance of the contract class that will verify this state.
*
* # Discussion
*
* This field is not the final design, it's just a piece of temporary scaffolding. Once the contract sandbox is
* further along, this field will become a description of which attachments are acceptable for defining the
* contract.
*
* Recall that an attachment is a zip file that can be referenced from any transaction. The contents of the
* attachments are merged together and cannot define any overlapping files, thus for any given transaction there
* is a miniature file system in which each file can be precisely mapped to the defining attachment.
*
* Attachments may contain many things (data files, legal documents, etc) but mostly they contain JVM bytecode.
* The class files inside define not only [Contract] implementations but also the classes that define the states.
* Within the rest of a transaction, user-providable components are referenced by name only.
*
* This means that a smart contract in Corda does two things:
*
* 1. Define the data structures that compose the ledger (the states)
* 2. Define the rules for updating those structures
*
* The first is merely a utility role ... in theory contract code could manually parse byte streams by hand.
* The second is vital to the integrity of the ledger. So this field needs to be able to express constraints like:
*
* - Only attachment 733c350f396a727655be1363c06635ba355036bd54a5ed6e594fd0b5d05f42f6 may be used with this state.
* - Any attachment signed by public key 2d1ce0e330c52b8055258d776c40 may be used with this state.
* - Attachments (1, 2, 3) may all be used with this state.
*
* and so on. In this way it becomes possible for the business logic governing a state to be evolved, if the
* constraints are flexible enough.
*
* Because contract classes often also define utilities that generate relevant transactions, and because attachments
* cannot know their own hashes, we will have to provide various utilities to assist with obtaining the right
* code constraints from within the contract code itself.
*
* TODO: Implement the above description. See COR-226
*/
val contract: Contract
/**
* A _participant_ is any party that is able to consume this state in a valid transaction.
*
* The list of participants is required for certain types of transactions. For example, when changing the notary
* for this state, every participant has to be involved and approve the transaction
* so that they receive the updated state, and don't end up in a situation where they can no longer use a state
* they possess, since someone consumed that state during the notary change process.
*
* The participants list should normally be derived from the contents of the state.
*/
val participants: List<AbstractParty>
}
// DOCEND 1
// DOCSTART 4
/**
* A wrapper for [ContractState] containing additional platform-level state information.
* This is the definitive state that is stored on the ledger and used in transaction outputs.
*/
@CordaSerializable
data class TransactionState<out T : ContractState> @JvmOverloads constructor(
/** The custom contract state */
val data: T,
/** Identity of the notary that ensures the state is not used as an input to a transaction more than once */
val notary: Party,
/**
* All contract states may be _encumbered_ by up to one other state.
*
* The encumbrance state, if present, forces additional controls over the encumbered state, since the platform checks
* that the encumbrance state is present as an input in the same transaction that consumes the encumbered state, and
* the contract code and rules of the encumbrance state will also be verified during the execution of the transaction.
* For example, a cash contract state could be encumbered with a time-lock contract state; the cash state is then only
* processable in a transaction that verifies that the time specified in the encumbrance time-lock has passed.
*
* The encumbered state refers to another by index, and the referred encumbrance state
* is an output state in a particular position on the same transaction that created the encumbered state. An alternative
* implementation would be encumbering by reference to a [StateRef], which would allow the specification of encumbrance
* by a state created in a prior transaction.
*
* Note that an encumbered state that is being consumed must have its encumbrance consumed in the same transaction,
* otherwise the transaction is not valid.
*/
val encumbrance: Int? = null)
// DOCEND 4
/** Wraps the [ContractState] in a [TransactionState] object */
infix fun <T : ContractState> T.`with notary`(newNotary: Party) = withNotary(newNotary)
infix fun <T : ContractState> T.withNotary(newNotary: Party) = TransactionState(this, newNotary)
/**
* The [Issued] data class holds the details of an on ledger digital asset.
* In particular it gives the public credentials of the entity that created these digital tokens
@ -250,7 +153,7 @@ data class StateAndRef<out T : ContractState>(val state: TransactionState<T>, va
/** Filters a list of [StateAndRef] objects according to the type of the states */
inline fun <reified T : ContractState> Iterable<StateAndRef<ContractState>>.filterStatesOfType(): List<StateAndRef<T>> {
return mapNotNull { if (it.state.data is T) StateAndRef(TransactionState(it.state.data, it.state.notary), it.ref) else null }
return mapNotNull { if (it.state.data is T) StateAndRef(TransactionState(it.state.data, it.state.contract, it.state.notary), it.ref) else null }
}
/**
@ -283,7 +186,7 @@ data class Command<T : CommandData>(val value: T, val signers: List<PublicKey>)
constructor(data: T, key: PublicKey) : this(data, listOf(key))
private fun commandDataToString() = value.toString().let { if (it.contains("@")) it.replace('$', '.').split("@")[0] else it }
override fun toString() = "${commandDataToString()} with pubkeys ${signers.joinToString()}"
override fun toString() = "${commandDataToString()} with pubkeys ${signers.map { it.toStringShort() }.joinToString()}"
}
/** A common move command for contract states which can change owner. */
@ -297,7 +200,7 @@ interface MoveCommand : CommandData {
}
/** Indicates that this transaction replaces the inputs contract state to another contract state */
data class UpgradeCommand(val upgradedContractClass: Class<out UpgradedContract<*, *>>) : CommandData
data class UpgradeCommand(val upgradedContractClass: ContractClassName) : CommandData
// DOCSTART 6
/** A [Command] where the signing parties have been looked up if they have a well known/recognised institutional key. */
@ -345,7 +248,7 @@ annotation class LegalProseReference(val uri: String)
* @param NewState the upgraded contract state.
*/
interface UpgradedContract<in OldState : ContractState, out NewState : ContractState> : Contract {
val legacyContract: Class<out Contract>
val legacyContract: ContractClassName
/**
* Upgrade contract's state object to a new state object.
*
@ -371,6 +274,14 @@ class PrivacySalt(bytes: ByteArray) : OpaqueBytes(bytes) {
init {
require(bytes.size == 32) { "Privacy salt should be 32 bytes." }
require(!bytes.all { it == 0.toByte() }) { "Privacy salt should not be all zeros." }
require(bytes.any { it != 0.toByte() }) { "Privacy salt should not be all zeros." }
}
}
/**
* A convenience class for passing around a state and it's contract
*
* @property state A state
* @property contract The contract that should verify the state
*/
data class StateAndContract(val state: ContractState, val contract: ContractClassName)

View File

@ -1,61 +0,0 @@
package net.corda.core.contracts
import net.corda.core.crypto.SecureHash
import net.corda.core.node.services.TransactionStorage
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import java.util.*
import java.util.concurrent.Callable
/**
* Given a map of transaction id to [SignedTransaction], performs a breadth first search of the dependency graph from
* the starting point down in order to find transactions that match the given query criteria.
*
* Currently, only one kind of query is supported: find any transaction that contains a command of the given type.
*
* In future, this should support restricting the search by time, and other types of useful query.
*
* @param transactions map of transaction id to [SignedTransaction].
* @param startPoints transactions to use as starting points for the search.
*/
class TransactionGraphSearch(val transactions: TransactionStorage,
val startPoints: List<WireTransaction>) : Callable<List<WireTransaction>> {
class Query(
val withCommandOfType: Class<out CommandData>? = null,
val followInputsOfType: Class<out ContractState>? = null
)
var query: Query = Query()
override fun call(): List<WireTransaction> {
val q = query
val alreadyVisited = HashSet<SecureHash>()
val next = ArrayList<WireTransaction>(startPoints)
val results = ArrayList<WireTransaction>()
while (next.isNotEmpty()) {
val tx = next.removeAt(next.lastIndex)
if (q.matches(tx))
results += tx
val inputsLeadingToUnvisitedTx: Iterable<StateRef> = tx.inputs.filter { it.txhash !in alreadyVisited }
val unvisitedInputTxs: Map<SecureHash, SignedTransaction> = inputsLeadingToUnvisitedTx.map { it.txhash }.toHashSet().map { transactions.getTransaction(it) }.filterNotNull().associateBy { it.id }
val unvisitedInputTxsWithInputIndex: Iterable<Pair<SignedTransaction, Int>> = inputsLeadingToUnvisitedTx.filter { it.txhash in unvisitedInputTxs.keys }.map { Pair(unvisitedInputTxs[it.txhash]!!, it.index) }
next += (unvisitedInputTxsWithInputIndex.filter { q.followInputsOfType == null || it.first.tx.outputs[it.second].data.javaClass == q.followInputsOfType }
.map { it.first }.filter { alreadyVisited.add(it.id) }.map { it.tx })
}
return results
}
private fun Query.matches(tx: WireTransaction): Boolean {
if (withCommandOfType != null) {
if (tx.commands.any { it.value.javaClass.isAssignableFrom(withCommandOfType) })
return true
}
return false
}
}

View File

@ -0,0 +1,53 @@
package net.corda.core.contracts
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
// DOCSTART 1
typealias ContractClassName = String
/**
* A wrapper for [ContractState] containing additional platform-level state information and contract information.
* This is the definitive state that is stored on the ledger and used in transaction outputs.
*/
@CordaSerializable
data class TransactionState<out T : ContractState> @JvmOverloads constructor(
/** The custom contract state */
val data: T,
/**
* The contract class name that will verify this state that will be created via reflection.
* The attachment containing this class will be automatically added to the transaction at transaction creation
* time.
*
* Currently these are loaded from the classpath of the node which includes the cordapp directory - at some
* point these will also be loaded and run from the attachment store directly, allowing contracts to be
* sent across, and run, from the network from within a sandbox environment.
*
* TODO: Implement the contract sandbox loading of the contract attachments
* */
val contract: ContractClassName,
/** Identity of the notary that ensures the state is not used as an input to a transaction more than once */
val notary: Party,
/**
* All contract states may be _encumbered_ by up to one other state.
*
* The encumbrance state, if present, forces additional controls over the encumbered state, since the platform checks
* that the encumbrance state is present as an input in the same transaction that consumes the encumbered state, and
* the contract code and rules of the encumbrance state will also be verified during the execution of the transaction.
* For example, a cash contract state could be encumbered with a time-lock contract state; the cash state is then only
* processable in a transaction that verifies that the time specified in the encumbrance time-lock has passed.
*
* The encumbered state refers to another by index, and the referred encumbrance state
* is an output state in a particular position on the same transaction that created the encumbered state. An alternative
* implementation would be encumbering by reference to a [StateRef], which would allow the specification of encumbrance
* by a state created in a prior transaction.
*
* Note that an encumbered state that is being consumed must have its encumbrance consumed in the same transaction,
* otherwise the transaction is not valid.
*/
val encumbrance: Int? = null,
/**
* A validator for the contract attachments on the transaction.
*/
val constraint: AttachmentConstraint = AutomaticHashConstraint)
// DOCEND 1

View File

@ -16,6 +16,15 @@ sealed class TransactionVerificationException(val txId: SecureHash, message: Str
class ContractRejection(txId: SecureHash, contract: Contract, cause: Throwable)
: TransactionVerificationException(txId, "Contract verification failed: ${cause.message}, contract: $contract", cause)
class ContractConstraintRejection(txId: SecureHash, contractClass: String)
: TransactionVerificationException(txId, "Contract constraints failed for $contractClass", null)
class MissingAttachmentRejection(txId: SecureHash, contractClass: String)
: TransactionVerificationException(txId, "Contract constraints failed, could not find attachment for: $contractClass", null)
class ContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable)
: TransactionVerificationException(txId, "Contract verification failed: ${cause.message}, could not create contract class: $contractClass", cause)
class MoreThanOneNotary(txId: SecureHash)
: TransactionVerificationException(txId, "More than one notary", null)

View File

@ -0,0 +1,33 @@
package net.corda.core.cordapp
import net.corda.core.flows.FlowLogic
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializeAsToken
import java.net.URL
/**
* Represents a cordapp by registering the JAR that contains it and all important classes for Corda.
* Instances of this class are generated automatically at startup of a node and can get retrieved from
* [CordappProvider.getAppContext] from the [CordappContext] it returns.
*
* This will only need to be constructed manually for certain kinds of tests.
*
* @property contractClassNames List of contracts
* @property initiatedFlows List of initiatable flow classes
* @property rpcFlows List of RPC initiable flows classes
* @property servies List of RPC services
* @property plugins List of Corda plugin registries
* @property customSchemas List of custom schemas
* @property jarPath The path to the JAR for this CorDapp
*/
interface Cordapp {
val contractClassNames: List<String>
val initiatedFlows: List<Class<out FlowLogic<*>>>
val rpcFlows: List<Class<out FlowLogic<*>>>
val services: List<Class<out SerializeAsToken>>
val plugins: List<CordaPluginRegistry>
val customSchemas: Set<MappedSchema>
val jarPath: URL
val cordappClasses: List<String>
}

View File

@ -0,0 +1,19 @@
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.
*
* A CordappContext is obtained from [CordappProvider.getAppContext] which resides on a [ServiceHub]. This will be
* used primarily from within flows.
*
* @property cordapp The cordapp this context is about
* @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
*/
class CordappContext(val cordapp: Cordapp, val attachmentId: SecureHash?, val classLoader: ClassLoader)

View File

@ -0,0 +1,28 @@
package net.corda.core.cordapp
import net.corda.core.contracts.ContractClassName
import net.corda.core.node.services.AttachmentId
/**
* Provides access to what the node knows about loaded applications.
*/
interface CordappProvider {
/**
* Exposes the current CorDapp context which will contain information and configuration of the CorDapp that
* is currently running.
*
* The calling application is found via stack walking and finding the first class on the stack that matches any class
* contained within the automatically resolved [Cordapp]s loaded by the [CordappLoader]
*
* @throws IllegalStateException When called from a non-app context
*/
fun getAppContext(): CordappContext
/**
* Resolve an attachment ID for a given contract name
*
* @param contractClassName The contract to find the attachment for
* @return An attachment ID if it exists
*/
fun getContractAttachmentID(contractClassName: ContractClassName): AttachmentId?
}

View File

@ -30,7 +30,7 @@ import java.util.*
@CordaSerializable
class CompositeKey private constructor(val threshold: Int, children: List<NodeAndWeight>) : PublicKey {
companion object {
val KEY_ALGORITHM = "COMPOSITE"
const val KEY_ALGORITHM = "COMPOSITE"
/**
* Build a composite key from a DER encoded form.
*/
@ -88,7 +88,9 @@ class CompositeKey private constructor(val threshold: Int, children: List<NodeAn
if (node is CompositeKey) {
val curVisitedMap = IdentityHashMap<CompositeKey, Boolean>()
curVisitedMap.putAll(visitedMap)
require(!curVisitedMap.contains(node)) { "Cycle detected for CompositeKey: $node" }
// We can't print the node details, because doing so involves serializing the node, which we can't
// do because of the cyclic graph.
require(!curVisitedMap.contains(node)) { "Cycle detected for CompositeKey" }
curVisitedMap.put(node, true)
node.cycleDetection(curVisitedMap)
}

View File

@ -12,6 +12,7 @@ import java.security.spec.AlgorithmParameterSpec
class CompositeSignature : Signature(SIGNATURE_ALGORITHM) {
companion object {
const val SIGNATURE_ALGORITHM = "COMPOSITESIG"
@JvmStatic
fun getService(provider: Provider) = Provider.Service(provider, "Signature", SIGNATURE_ALGORITHM, CompositeSignature::class.java.name, emptyList(), emptyMap())
}

View File

@ -1,10 +1,8 @@
package net.corda.core.crypto
import net.corda.core.internal.X509EdDSAEngine
import net.corda.core.serialization.serialize
import net.i2p.crypto.eddsa.EdDSAEngine
import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey
import net.i2p.crypto.eddsa.EdDSASecurityProvider
import net.i2p.crypto.eddsa.*
import net.i2p.crypto.eddsa.math.GroupElement
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
@ -22,7 +20,6 @@ import org.bouncycastle.asn1.sec.SECObjectIdentifiers
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateKey
@ -42,6 +39,8 @@ import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey
import org.bouncycastle.pqc.jcajce.spec.SPHINCS256KeyGenParameterSpec
import java.math.BigInteger
import java.security.*
import java.security.KeyFactory
import java.security.KeyPairGenerator
import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
@ -201,6 +200,8 @@ object Crypto {
private fun getBouncyCastleProvider() = BouncyCastleProvider().apply {
putAll(EdDSASecurityProvider())
// Override the normal EdDSA engine with one which can handle X509 keys.
put("Signature.${EdDSAEngine.SIGNATURE_ALGORITHM}", X509EdDSAEngine::class.qualifiedName)
addKeyInfoConverter(EDDSA_ED25519_SHA512.signatureOID.algorithm, KeyInfoConverter(EDDSA_ED25519_SHA512))
}
@ -920,8 +921,7 @@ object Crypto {
}
/**
* Convert a public key to a supported implementation. This method is usually required to retrieve a key from an
* [X509CertificateHolder].
* Convert a public key to a supported implementation.
*
* @param key a public key.
* @return a supported implementation of the input public key.

View File

@ -2,11 +2,15 @@
package net.corda.core.crypto
import net.corda.core.contracts.PrivacySalt
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.serialize
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.toBase58
import net.corda.core.utilities.toSHA256Bytes
import java.math.BigInteger
import net.corda.core.utilities.SgxSupport
import java.nio.ByteBuffer
import java.security.*
/**
@ -209,3 +213,27 @@ fun random63BitValue(): Long {
}
}
}
/**
* Compute the hash of each serialised component so as to be used as Merkle tree leaf. The resultant output (leaf) is
* calculated using the SHA256d algorithm, thus SHA256(SHA256(nonce || serializedComponent)), where nonce is computed
* from [computeNonce].
*/
fun componentHash(opaqueBytes: OpaqueBytes, privacySalt: PrivacySalt, componentGroupIndex: Int, internalIndex: Int): SecureHash =
componentHash(computeNonce(privacySalt, componentGroupIndex, internalIndex), opaqueBytes)
/** Return the SHA256(SHA256(nonce || serializedComponent)). */
fun componentHash(nonce: SecureHash, opaqueBytes: OpaqueBytes): SecureHash = SecureHash.sha256Twice(nonce.bytes + opaqueBytes.bytes)
/** Serialise the object and return the hash of the serialized bytes. */
fun <T : Any> serializedHash(x: T): SecureHash = x.serialize(context = SerializationDefaults.P2P_CONTEXT.withoutReferences()).bytes.sha256()
/**
* Method to compute a nonce based on privacySalt, component group index and component internal index.
* SHA256d (double SHA256) is used to prevent length extension attacks.
* @param privacySalt a [PrivacySalt].
* @param groupIndex the fixed index (ordinal) of this component group.
* @param internalIndex the internal index of this object in its corresponding components list.
* @return SHA256(SHA256(privacySalt || groupIndex || internalIndex))
*/
fun computeNonce(privacySalt: PrivacySalt, groupIndex: Int, internalIndex: Int) = SecureHash.sha256Twice(privacySalt.bytes + ByteBuffer.allocate(8).putInt(groupIndex).putInt(internalIndex).array())

View File

@ -121,6 +121,28 @@ class PartialMerkleTree(val root: PartialTree) {
}
}
}
/**
* Recursive calculation of root of this partial tree.
* Modifies usedHashes to later check for inclusion with hashes provided.
* @param node the partial Merkle tree for which we want to calculate the Merkle root.
* @param usedHashes a mutable list that at the end of this recursive algorithm, it will consist of the included leaves (hashes of the visible components).
* @return the root [SecureHash] of this partial Merkle tree.
*/
fun rootAndUsedHashes(node: PartialTree, usedHashes: MutableList<SecureHash>): SecureHash {
return when (node) {
is PartialTree.IncludedLeaf -> {
usedHashes.add(node.hash)
node.hash
}
is PartialTree.Leaf -> node.hash
is PartialTree.Node -> {
val leftHash = rootAndUsedHashes(node.left, usedHashes)
val rightHash = rootAndUsedHashes(node.right, usedHashes)
return leftHash.hashConcat(rightHash)
}
}
}
}
/**
@ -129,29 +151,10 @@ class PartialMerkleTree(val root: PartialTree) {
*/
fun verify(merkleRootHash: SecureHash, hashesToCheck: List<SecureHash>): Boolean {
val usedHashes = ArrayList<SecureHash>()
val verifyRoot = verify(root, usedHashes)
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)
}
/**
* Recursive calculation of root of this partial tree.
* Modifies usedHashes to later check for inclusion with hashes provided.
*/
private fun verify(node: PartialTree, usedHashes: MutableList<SecureHash>): SecureHash {
return when (node) {
is PartialTree.IncludedLeaf -> {
usedHashes.add(node.hash)
node.hash
}
is PartialTree.Leaf -> node.hash
is PartialTree.Node -> {
val leftHash = verify(node.left, usedHashes)
val rightHash = verify(node.right, usedHashes)
return leftHash.hashConcat(rightHash)
}
}
}
}

View File

@ -40,6 +40,7 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
@JvmStatic fun randomSHA256() = sha256(newSecureRandom().generateSeed(32))
val zeroHash = SecureHash.SHA256(ByteArray(32, { 0.toByte() }))
val allOnesHash = SecureHash.SHA256(ByteArray(32, { 255.toByte() }))
}
// In future, maybe SHA3, truncated hashes etc.

View File

@ -6,13 +6,14 @@ import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.identity.Party
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.excludeHostNode
import net.corda.core.identity.groupAbstractPartyByWellKnownParty
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.unwrap
import java.security.PublicKey
/**
* Abstract flow to be used for replacing one state with another, for example when changing the notary of a state.
@ -33,10 +34,8 @@ abstract class AbstractStateReplacementFlow {
* The assembled transaction for upgrading a contract.
*
* @param stx signed transaction to do the upgrade.
* @param participants the parties involved in the upgrade transaction.
* @param myKey key
*/
data class UpgradeTx(val stx: SignedTransaction, val participants: Iterable<PublicKey>, val myKey: PublicKey)
data class UpgradeTx(val stx: SignedTransaction)
/**
* The [Instigator] assembles the transaction for state replacement and sends out change proposals to all participants
@ -62,15 +61,11 @@ abstract class AbstractStateReplacementFlow {
@Suspendable
@Throws(StateReplacementException::class)
override fun call(): StateAndRef<T> {
val (stx, participantKeys, myKey) = assembleTx()
val (stx) = assembleTx()
val participantSessions = getParticipantSessions()
progressTracker.currentStep = SIGNING
val signatures = if (participantKeys.singleOrNull() == myKey) {
getNotarySignatures(stx)
} else {
collectSignatures(participantKeys - myKey, stx)
}
val signatures = collectSignatures(participantSessions, stx)
val finalTx = stx + signatures
serviceHub.recordTransactions(finalTx)
@ -89,35 +84,38 @@ abstract class AbstractStateReplacementFlow {
/**
* Build the upgrade transaction.
*
* @return a triple of the transaction, the public keys of all participants, and the participating public key of
* this node.
* @return the transaction
*/
abstract protected fun assembleTx(): UpgradeTx
@Suspendable
private fun collectSignatures(participants: Iterable<PublicKey>, stx: SignedTransaction): List<TransactionSignature> {
val parties = participants.map {
val participantNode = serviceHub.networkMapCache.getNodeByLegalIdentityKey(it) ?:
throw IllegalStateException("Participant $it to state $originalState not found on the network")
participantNode.legalIdentity
}
/**
* Initiate sessions with parties we want signatures from.
*/
open fun getParticipantSessions(): List<Pair<FlowSession, List<AbstractParty>>> {
return excludeHostNode(serviceHub, groupAbstractPartyByWellKnownParty(serviceHub, originalState.state.data.participants)).map { initiateFlow(it.key) to it.value }
}
val participantSignatures = parties.map { getParticipantSignature(it, stx) }
@Suspendable
private fun collectSignatures(sessions: List<Pair<FlowSession, List<AbstractParty>>>, stx: SignedTransaction): List<TransactionSignature> {
val participantSignatures = sessions.map { getParticipantSignature(it.first, it.second, stx) }
val allPartySignedTx = stx + participantSignatures
val allSignatures = participantSignatures + getNotarySignatures(allPartySignedTx)
parties.forEach { send(it, allSignatures) }
sessions.forEach { it.first.send(allSignatures) }
return allSignatures
}
@Suspendable
private fun getParticipantSignature(party: Party, stx: SignedTransaction): TransactionSignature {
private fun getParticipantSignature(session: FlowSession, party: List<AbstractParty>, stx: SignedTransaction): TransactionSignature {
require(party.size == 1) {
"We do not currently support multiple signatures from the same party ${session.counterparty}: $party"
}
val proposal = Proposal(originalState.ref, modification)
subFlow(SendTransactionFlow(party, stx))
return sendAndReceive<TransactionSignature>(party, proposal).unwrap {
check(party.owningKey.isFulfilledBy(it.by)) { "Not signed by the required participant" }
subFlow(SendTransactionFlow(session, stx))
return session.sendAndReceive<TransactionSignature>(proposal).unwrap {
check(party.single().owningKey.isFulfilledBy(it.by)) { "Not signed by the required participant" }
it.verify(stx.id)
it
}
@ -136,9 +134,9 @@ abstract class AbstractStateReplacementFlow {
// Type parameter should ideally be Unit but that prevents Java code from subclassing it (https://youtrack.jetbrains.com/issue/KT-15964).
// We use Void? instead of Unit? as that's what you'd use in Java.
abstract class Acceptor<in T>(val otherSide: Party,
abstract class Acceptor<in T>(val initiatingSession: FlowSession,
override val progressTracker: ProgressTracker = Acceptor.tracker()) : FlowLogic<Void?>() {
constructor(otherSide: Party) : this(otherSide, Acceptor.tracker())
constructor(initiatingSession: FlowSession) : this(initiatingSession, Acceptor.tracker())
companion object {
object VERIFYING : ProgressTracker.Step("Verifying state replacement proposal")
object APPROVING : ProgressTracker.Step("State replacement approved")
@ -151,9 +149,9 @@ abstract class AbstractStateReplacementFlow {
override fun call(): Void? {
progressTracker.currentStep = VERIFYING
// We expect stx to have insufficient signatures here
val stx = subFlow(ReceiveTransactionFlow(otherSide, checkSufficientSignatures = false))
val stx = subFlow(ReceiveTransactionFlow(initiatingSession, checkSufficientSignatures = false))
checkMySignatureRequired(stx)
val maybeProposal: UntrustworthyData<Proposal<T>> = receive(otherSide)
val maybeProposal: UntrustworthyData<Proposal<T>> = initiatingSession.receive()
maybeProposal.unwrap {
verifyProposal(stx, it)
}
@ -166,7 +164,7 @@ abstract class AbstractStateReplacementFlow {
progressTracker.currentStep = APPROVING
val mySignature = sign(stx)
val swapSignatures = sendAndReceive<List<TransactionSignature>>(otherSide, mySignature)
val swapSignatures = initiatingSession.sendAndReceive<List<TransactionSignature>>(mySignature)
// TODO: This step should not be necessary, as signatures are re-checked in verifyRequiredSignatures.
val allSignatures = swapSignatures.unwrap { signatures ->
@ -193,7 +191,8 @@ abstract class AbstractStateReplacementFlow {
private fun checkMySignatureRequired(stx: SignedTransaction) {
// TODO: use keys from the keyManagementService instead
val myKey = serviceHub.myInfo.legalIdentity.owningKey
// TODO Check the set of multiple identities?
val myKey = ourIdentity.owningKey
val requiredKeys = if (stx.isNotaryChangeTransaction()) {
stx.resolveNotaryChangeTransaction(serviceHub).requiredSigningKeys

View File

@ -1,28 +0,0 @@
package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.identity.Party
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NonEmptySet
/**
* Notify the specified parties about a transaction. The remote peers will download this transaction and its
* dependency graph, verifying them all. The flow returns when all peers have acknowledged the transactions
* as valid. Normally you wouldn't use this directly, it would be called via [FinalityFlow].
*
* @param notarisedTransaction transaction which has been notarised (if needed) and is ready to notify nodes about.
* @param participants a list of participants involved in the transaction.
* @return a list of participants who were successfully notified of the transaction.
*/
@InitiatingFlow
class BroadcastTransactionFlow(val notarisedTransaction: SignedTransaction,
val participants: NonEmptySet<Party>) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
// TODO: Messaging layer should handle this broadcast for us
participants.filter { it != serviceHub.myInfo.legalIdentity }.forEach { participant ->
// SendTransactionFlow allows otherParty to access our data to resolve the transaction.
subFlow(SendTransactionFlow(participant, notarisedTransaction))
}
}
}

View File

@ -3,9 +3,8 @@ package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.utilities.toBase58String
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.identity.groupPublicKeysByWellKnownParty
import net.corda.core.node.ServiceHub
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
@ -57,27 +56,31 @@ import java.security.PublicKey
* val stx = subFlow(CollectSignaturesFlow(ptx))
*
* @param partiallySignedTx Transaction to collect the remaining signatures for
* @param sessionsToCollectFrom A session for every party we need to collect a signature from. Must be an exact match.
* @param myOptionalKeys set of keys in the transaction which are owned by this node. This includes keys used on commands, not
* just in the states. If null, the default well known identity of the node is used.
*/
// TODO: AbstractStateReplacementFlow needs updating to use this flow.
class CollectSignaturesFlow @JvmOverloads constructor (val partiallySignedTx: SignedTransaction,
val sessionsToCollectFrom: Collection<FlowSession>,
val myOptionalKeys: Iterable<PublicKey>?,
override val progressTracker: ProgressTracker = CollectSignaturesFlow.tracker()) : FlowLogic<SignedTransaction>() {
@JvmOverloads constructor(partiallySignedTx: SignedTransaction, progressTracker: ProgressTracker = CollectSignaturesFlow.tracker()) : this(partiallySignedTx, null, progressTracker)
@JvmOverloads constructor(partiallySignedTx: SignedTransaction, sessionsToCollectFrom: Collection<FlowSession>, progressTracker: ProgressTracker = CollectSignaturesFlow.tracker()) : this(partiallySignedTx, sessionsToCollectFrom, null, progressTracker)
companion object {
object COLLECTING : ProgressTracker.Step("Collecting signatures from counter-parties.")
object VERIFYING : ProgressTracker.Step("Verifying collected signatures.")
@JvmStatic
fun tracker() = ProgressTracker(COLLECTING, VERIFYING)
// TODO: Make the progress tracker adapt to the number of counter-parties to collect from.
}
@Suspendable override fun call(): SignedTransaction {
// Check the signatures which have already been provided and that the transaction is valid.
// Usually just the Initiator and possibly an oracle would have signed at this point.
val myKeys: Iterable<PublicKey> = myOptionalKeys ?: listOf(serviceHub.myInfo.legalIdentity.owningKey)
val myKeys: Iterable<PublicKey> = myOptionalKeys ?: listOf(ourIdentity.owningKey)
val signed = partiallySignedTx.sigs.map { it.by }
val notSigned = partiallySignedTx.tx.requiredSigningKeys - signed
@ -100,8 +103,15 @@ class CollectSignaturesFlow @JvmOverloads constructor (val partiallySignedTx: Si
// If the unsigned counter-parties list is empty then we don't need to collect any more signatures here.
if (unsigned.isEmpty()) return partiallySignedTx
val partyToKeysMap = groupPublicKeysByWellKnownParty(serviceHub, unsigned)
// Check that we have a session for all parties. No more, no less.
require(sessionsToCollectFrom.map { it.counterparty }.toSet() == partyToKeysMap.keys) {
"The Initiator of CollectSignaturesFlow must pass in exactly the sessions required to sign the transaction."
}
// Collect signatures from all counter-parties and append them to the partially signed transaction.
val counterpartySignatures = keysToParties(unsigned).map { collectSignature(it.first, it.second) }
val counterpartySignatures = sessionsToCollectFrom.flatMap { session ->
subFlow(CollectSignatureFlow(partiallySignedTx, session, partyToKeysMap[session.counterparty]!!))
}
val stx = partiallySignedTx + counterpartySignatures
// Verify all but the notary's signature if the transaction requires a notary, otherwise verify all signatures.
@ -110,40 +120,38 @@ class CollectSignaturesFlow @JvmOverloads constructor (val partiallySignedTx: Si
return stx
}
}
/**
* Lookup the [Party] object for each [PublicKey] using the [ServiceHub.networkMapCache].
*
* @return a pair of the well known identity to contact for a signature, and the public key that party should sign
* with (this may belong to a confidential identity).
*/
@Suspendable private fun keysToParties(keys: Collection<PublicKey>): List<Pair<Party, PublicKey>> = keys.map {
val party = serviceHub.identityService.partyFromAnonymous(AnonymousParty(it))
?: throw IllegalStateException("Party ${it.toBase58String()} not found on the network.")
Pair(party, it)
}
// DOCSTART 1
/**
* Get and check the required signature.
*
* @param counterparty the party to request a signature from.
* @param signingKey the key the party should use to sign the transaction.
*/
@Suspendable private fun collectSignature(counterparty: Party, signingKey: PublicKey): TransactionSignature {
// DOCSTART 1
/**
* Get and check the required signature.
*
* @param partiallySignedTx the transaction to sign.
* @param session the [FlowSession] to connect to to get the signature.
* @param signingKeys the list of keys the party should use to sign the transaction.
*/
@Suspendable
class CollectSignatureFlow(val partiallySignedTx: SignedTransaction, val session: FlowSession, val signingKeys: List<PublicKey>) : FlowLogic<List<TransactionSignature>>() {
constructor(partiallySignedTx: SignedTransaction, session: FlowSession, vararg signingKeys: PublicKey) :
this(partiallySignedTx, session, listOf(*signingKeys))
@Suspendable
override fun call(): List<TransactionSignature> {
// SendTransactionFlow allows counterparty to access our data to resolve the transaction.
subFlow(SendTransactionFlow(counterparty, partiallySignedTx))
subFlow(SendTransactionFlow(session, partiallySignedTx))
// Send the key we expect the counterparty to sign with - this is important where they may have several
// keys to sign with, as it makes it faster for them to identify the key to sign with, and more straight forward
// for us to check we have the expected signature returned.
send(counterparty, signingKey)
return receive<TransactionSignature>(counterparty).unwrap {
require(signingKey.isFulfilledBy(it.by)) { "Not signed by the required signing key." }
it
session.send(signingKeys)
return session.receive<List<TransactionSignature>>().unwrap { signatures ->
require(signatures.size == signingKeys.size) { "Need signature for each signing key" }
signatures.forEachIndexed { index, signature ->
require(signingKeys[index].isFulfilledBy(signature.by)) { "Not signed by the required signing key." }
}
signatures
}
}
// DOCEND 1
}
// DOCEND 1
/**
* The [SignTransactionFlow] should be called in response to the [CollectSignaturesFlow]. It automates the signing of
@ -159,15 +167,15 @@ class CollectSignaturesFlow @JvmOverloads constructor (val partiallySignedTx: Si
* - Subclass [SignTransactionFlow] - this can be done inside an existing flow (as shown below)
* - Override the [checkTransaction] method to add some custom verification logic
* - Call the flow via [FlowLogic.subFlow]
* - The flow returns the fully signed transaction once it has been committed to the ledger
* - The flow returns the transaction signed with the additional signature.
*
* Example - checking and signing a transaction involving a [net.corda.core.contracts.DummyContract], see
* CollectSignaturesFlowTests.kt for further examples:
*
* class Responder(val otherParty: Party): FlowLogic<SignedTransaction>() {
* class Responder(val otherPartySession: FlowSession): FlowLogic<SignedTransaction>() {
* @Suspendable override fun call(): SignedTransaction {
* // [SignTransactionFlow] sub-classed as a singleton object.
* val flow = object : SignTransactionFlow(otherParty) {
* val flow = object : SignTransactionFlow(otherPartySession) {
* @Suspendable override fun checkTransaction(stx: SignedTransaction) = requireThat {
* val tx = stx.tx
* val magicNumberState = tx.outputs.single().data as DummyContract.MultiOwnerState
@ -182,9 +190,9 @@ class CollectSignaturesFlow @JvmOverloads constructor (val partiallySignedTx: Si
* }
* }
*
* @param otherParty The counter-party which is providing you a transaction to sign.
* @param otherSideSession The session which is providing you a transaction to sign.
*/
abstract class SignTransactionFlow(val otherParty: Party,
abstract class SignTransactionFlow(val otherSideSession: FlowSession,
override val progressTracker: ProgressTracker = SignTransactionFlow.tracker()) : FlowLogic<SignedTransaction>() {
companion object {
@ -192,23 +200,24 @@ abstract class SignTransactionFlow(val otherParty: Party,
object VERIFYING : ProgressTracker.Step("Verifying transaction proposal.")
object SIGNING : ProgressTracker.Step("Signing transaction proposal.")
@JvmStatic
fun tracker() = ProgressTracker(RECEIVING, VERIFYING, SIGNING)
}
@Suspendable override fun call(): SignedTransaction {
progressTracker.currentStep = RECEIVING
// Receive transaction and resolve dependencies, check sufficient signatures is disabled as we don't have all signatures.
val stx = subFlow(ReceiveTransactionFlow(otherParty, checkSufficientSignatures = false))
val stx = subFlow(ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = false))
// Receive the signing key that the party requesting the signature expects us to sign with. Having this provided
// means we only have to check we own that one key, rather than matching all keys in the transaction against all
// keys we own.
val signingKey = receive<PublicKey>(otherParty).unwrap {
val signingKeys = otherSideSession.receive<List<PublicKey>>().unwrap { keys ->
// TODO: We should have a faster way of verifying we own a single key
serviceHub.keyManagementService.filterMyKeys(listOf(it)).single()
serviceHub.keyManagementService.filterMyKeys(keys)
}
progressTracker.currentStep = VERIFYING
// Check that the Responder actually needs to sign.
checkMySignatureRequired(stx, signingKey)
checkMySignaturesRequired(stx, signingKeys)
// Check the signatures which have already been provided. Usually the Initiators and possibly an Oracle's.
checkSignatures(stx)
stx.tx.toLedgerTransaction(serviceHub).verify()
@ -223,18 +232,19 @@ abstract class SignTransactionFlow(val otherParty: Party,
}
// Sign and send back our signature to the Initiator.
progressTracker.currentStep = SIGNING
val mySignature = serviceHub.createSignature(stx, signingKey)
send(otherParty, mySignature)
val mySignatures = signingKeys.map { key ->
serviceHub.createSignature(stx, key)
}
otherSideSession.send(mySignatures)
// Return the fully signed transaction once it has been committed.
return waitForLedgerCommit(stx.id)
// Return the additionally signed transaction.
return stx + mySignatures
}
@Suspendable private fun checkSignatures(stx: SignedTransaction) {
val signingIdentities = stx.sigs.map(TransactionSignature::by).mapNotNull(serviceHub.identityService::partyFromKey)
val signingWellKnownIdentities = signingIdentities.mapNotNull(serviceHub.identityService::partyFromAnonymous)
require(otherParty in signingWellKnownIdentities) {
"The Initiator of CollectSignaturesFlow must have signed the transaction. Found ${signingWellKnownIdentities}, expected ${otherParty}"
val signingWellKnownIdentities = groupPublicKeysByWellKnownParty(serviceHub, stx.sigs.map(TransactionSignature::by))
require(otherSideSession.counterparty in signingWellKnownIdentities) {
"The Initiator of CollectSignaturesFlow must have signed the transaction. Found ${signingWellKnownIdentities}, expected ${otherSideSession}"
}
val signed = stx.sigs.map { it.by }
val allSigners = stx.tx.requiredSigningKeys
@ -266,9 +276,9 @@ abstract class SignTransactionFlow(val otherParty: Party,
@Throws(FlowException::class)
abstract protected fun checkTransaction(stx: SignedTransaction)
@Suspendable private fun checkMySignatureRequired(stx: SignedTransaction, signingKey: PublicKey) {
require(signingKey in stx.tx.requiredSigningKeys) {
"Party is not a participant for any of the input states of transaction ${stx.id}"
@Suspendable private fun checkMySignaturesRequired(stx: SignedTransaction, signingKeys: Iterable<PublicKey>) {
require(signingKeys.all { it in stx.tx.requiredSigningKeys }) {
"A signature was requested for a key that isn't part of the required signing keys for transaction ${stx.id}"
}
}
}

View File

@ -2,9 +2,8 @@ package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.*
import net.corda.core.identity.Party
import net.corda.core.internal.ContractUpgradeUtils
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import java.security.PublicKey
@ -18,11 +17,37 @@ import java.security.PublicKey
*/
object ContractUpgradeFlow {
@JvmStatic
fun verify(tx: LedgerTransaction) {
// Contract Upgrade transaction should have 1 input, 1 output and 1 command.
verify(tx.inputs.single().state,
tx.outputs.single(),
tx.commandsOfType<UpgradeCommand>().single())
}
@JvmStatic
fun verify(input: TransactionState<ContractState>, output: TransactionState<ContractState>, commandData: Command<UpgradeCommand>) {
val command = commandData.value
val participantKeys: Set<PublicKey> = input.data.participants.map { it.owningKey }.toSet()
val keysThatSigned: Set<PublicKey> = commandData.signers.toSet()
@Suppress("UNCHECKED_CAST")
val upgradedContract = javaClass.classLoader.loadClass(command.upgradedContractClass).newInstance() as UpgradedContract<ContractState, *>
requireThat {
"The signing keys include all participant keys" using keysThatSigned.containsAll(participantKeys)
"Inputs state reference the legacy contract" using (input.contract == upgradedContract.legacyContract)
"Outputs state reference the upgraded contract" using (output.contract == command.upgradedContractClass)
"Output state must be an upgraded version of the input state" using (output.data == upgradedContract.upgrade(input.data))
}
}
/**
* Authorise a contract state upgrade.
* This will store the upgrade authorisation in persistent store, and will be queried by [ContractUpgradeFlow.Acceptor] during contract upgrade process.
* Invoking this flow indicates the node is willing to upgrade the [StateAndRef] using the [UpgradedContract] class.
* This method will NOT initiate the upgrade process. To start the upgrade process, see [Initiator].
*
* This will store the upgrade authorisation in persistent store, and will be queried by [ContractUpgradeFlow.Acceptor]
* during contract upgrade process. Invoking this flow indicates the node is willing to upgrade the [StateAndRef] using
* the [UpgradedContract] class.
*
* This flow will NOT initiate the upgrade process. To start the upgrade process, see [Initiate].
*/
@StartableByRPC
class Authorise(
@ -32,7 +57,7 @@ object ContractUpgradeFlow {
@Suspendable
override fun call(): Void? {
val upgrade = upgradedContractClass.newInstance()
if (upgrade.legacyContract != stateAndRef.state.data.contract.javaClass) {
if (upgrade.legacyContract != stateAndRef.state.contract) {
throw FlowException("The contract state cannot be upgraded using provided UpgradedContract.")
}
serviceHub.contractUpgradeService.storeAuthorisedContractUpgrade(stateAndRef.ref, upgradedContractClass)
@ -46,9 +71,7 @@ object ContractUpgradeFlow {
* This will remove the upgrade authorisation from persistent store (and prevent any further upgrade)
*/
@StartableByRPC
class Deauthorise(
val stateRef: StateRef
) : FlowLogic< Void?>() {
class Deauthorise(val stateRef: StateRef) : FlowLogic<Void?>() {
@Suspendable
override fun call(): Void? {
serviceHub.contractUpgradeService.removeAuthorisedContractUpgrade(stateRef)
@ -56,9 +79,12 @@ object ContractUpgradeFlow {
}
}
/**
* This flow begins the contract upgrade process.
*/
@InitiatingFlow
@StartableByRPC
class Initiator<OldState : ContractState, out NewState : ContractState>(
class Initiate<OldState : ContractState, out NewState : ContractState>(
originalState: StateAndRef<OldState>,
newContractClass: Class<out UpgradedContract<OldState, NewState>>
) : AbstractStateReplacementFlow.Instigator<OldState, NewState, Class<out UpgradedContract<OldState, NewState>>>(originalState, newContractClass) {
@ -73,8 +99,8 @@ object ContractUpgradeFlow {
return TransactionBuilder(stateRef.state.notary)
.withItems(
stateRef,
contractUpgrade.upgrade(stateRef.state.data),
Command(UpgradeCommand(upgradedContractClass), stateRef.state.data.participants.map { it.owningKey }),
StateAndContract(contractUpgrade.upgrade(stateRef.state.data), upgradedContractClass.name),
Command(UpgradeCommand(upgradedContractClass.name), stateRef.state.data.participants.map { it.owningKey }),
privacySalt
)
}
@ -82,65 +108,12 @@ object ContractUpgradeFlow {
@Suspendable
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
val baseTx = assembleBareTx(originalState, modification, PrivacySalt())
val baseTx = ContractUpgradeUtils.assembleBareTx(originalState, modification, PrivacySalt())
val participantKeys = originalState.state.data.participants.map { it.owningKey }.toSet()
// TODO: We need a much faster way of finding our key in the transaction
val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
val stx = serviceHub.signInitialTransaction(baseTx, myKey)
return AbstractStateReplacementFlow.UpgradeTx(stx, participantKeys, myKey)
}
}
@StartableByRPC
@InitiatedBy(ContractUpgradeFlow.Initiator::class)
class Acceptor(otherSide: Party) : AbstractStateReplacementFlow.Acceptor<Class<out UpgradedContract<ContractState, *>>>(otherSide) {
companion object {
@JvmStatic
fun verify(tx: LedgerTransaction) {
// Contract Upgrade transaction should have 1 input, 1 output and 1 command.
verify(tx.inputStates.single(),
tx.outputStates.single(),
tx.commandsOfType<UpgradeCommand>().single())
}
@JvmStatic
fun verify(input: ContractState, output: ContractState, commandData: Command<UpgradeCommand>) {
val command = commandData.value
val participantKeys: Set<PublicKey> = input.participants.map { it.owningKey }.toSet()
val keysThatSigned: Set<PublicKey> = commandData.signers.toSet()
@Suppress("UNCHECKED_CAST")
val upgradedContract = command.upgradedContractClass.newInstance() as UpgradedContract<ContractState, *>
requireThat {
"The signing keys include all participant keys" using keysThatSigned.containsAll(participantKeys)
"Inputs state reference the legacy contract" using (input.contract.javaClass == upgradedContract.legacyContract)
"Outputs state reference the upgraded contract" using (output.contract.javaClass == command.upgradedContractClass)
"Output state must be an upgraded version of the input state" using (output == upgradedContract.upgrade(input))
}
}
}
@Suspendable
@Throws(StateReplacementException::class)
override fun verifyProposal(stx: SignedTransaction, proposal: AbstractStateReplacementFlow.Proposal<Class<out UpgradedContract<ContractState, *>>>) {
// Retrieve signed transaction from our side, we will apply the upgrade logic to the transaction on our side, and
// verify outputs matches the proposed upgrade.
val ourSTX = serviceHub.validatedTransactions.getTransaction(proposal.stateRef.txhash)
requireNotNull(ourSTX) { "We don't have a copy of the referenced state" }
val oldStateAndRef = ourSTX!!.tx.outRef<ContractState>(proposal.stateRef.index)
val authorisedUpgrade = serviceHub.contractUpgradeService.getAuthorisedContractUpgrade(oldStateAndRef.ref) ?:
throw IllegalStateException("Contract state upgrade is unauthorised. State hash : ${oldStateAndRef.ref}")
val proposedTx = stx.tx
val expectedTx = ContractUpgradeFlow.Initiator.assembleBareTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt).toWireTransaction()
requireThat {
"The instigator is one of the participants" using (otherSide in oldStateAndRef.state.data.participants)
"The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification.name == authorisedUpgrade)
"The proposed tx matches the expected tx for this upgrade" using (proposedTx == expectedTx)
}
ContractUpgradeFlow.Acceptor.verify(
oldStateAndRef.state.data,
expectedTx.outRef<ContractState>(0).state.data,
expectedTx.toLedgerTransaction(serviceHub).commandsOfType<UpgradeCommand>().single())
return AbstractStateReplacementFlow.UpgradeTx(stx)
}
}
}

View File

@ -1,46 +1,36 @@
package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TransactionState
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.internal.ResolveTransactionsFlow
import net.corda.core.node.ServiceHub
import net.corda.core.identity.groupAbstractPartyByWellKnownParty
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.toNonEmptySet
/**
* Verifies the given transactions, then sends them to the named notary. If the notary agrees that the transactions
* are acceptable then they are from that point onwards committed to the ledger, and will be written through to the
* vault. Additionally they will be distributed to the parties reflected in the participants list of the states.
* Verifies the given transaction, then sends it to the named notary. If the notary agrees that the transaction
* is acceptable then it is from that point onwards committed to the ledger, and will be written through to the
* vault. Additionally it will be distributed to the parties reflected in the participants list of the states.
*
* The transactions will be topologically sorted before commitment to ensure that dependencies are committed before
* dependers, so you don't need to do this yourself.
* The transaction is expected to have already been resolved: if its dependencies are not available in local
* storage, verification will fail. It must have signatures from all necessary parties other than the notary.
*
* The transactions are expected to have already been resolved: if their dependencies are not available in local
* storage or within the given set, verification will fail. They must have signatures from all necessary parties
* other than the notary.
* If specified, the extra recipients are sent the given transaction. The base set of parties to inform are calculated
* from the contract-given set of participants.
*
* If specified, the extra recipients are sent all the given transactions. The base set of parties to inform of each
* transaction are calculated on a per transaction basis from the contract-given set of participants.
* The flow returns the same transaction but with the additional signatures from the notary.
*
* The flow returns the same transactions, in the same order, with the additional signatures.
*
* @param transactions What to commit.
* @param transaction What to commit.
* @param extraRecipients A list of additional participants to inform of the transaction.
*/
open class FinalityFlow(val transactions: Iterable<SignedTransaction>,
val extraRecipients: Set<Party>,
override val progressTracker: ProgressTracker) : FlowLogic<List<SignedTransaction>>() {
val extraParticipants: Set<Participant> = extraRecipients.map { it -> Participant(it, it) }.toSet()
constructor(transaction: SignedTransaction, extraParticipants: Set<Party>) : this(listOf(transaction), extraParticipants, tracker())
constructor(transaction: SignedTransaction) : this(listOf(transaction), emptySet(), tracker())
constructor(transaction: SignedTransaction, progressTracker: ProgressTracker) : this(listOf(transaction), emptySet(), progressTracker)
@InitiatingFlow
class FinalityFlow(val transaction: SignedTransaction,
private val extraRecipients: Set<Party>,
override val progressTracker: ProgressTracker) : FlowLogic<SignedTransaction>() {
constructor(transaction: SignedTransaction, extraParticipants: Set<Party>) : this(transaction, extraParticipants, tracker())
constructor(transaction: SignedTransaction) : this(transaction, emptySet(), tracker())
constructor(transaction: SignedTransaction, progressTracker: ProgressTracker) : this(transaction, emptySet(), progressTracker)
companion object {
object NOTARISING : ProgressTracker.Step("Requesting signature by notary service") {
@ -49,56 +39,41 @@ open class FinalityFlow(val transactions: Iterable<SignedTransaction>,
object BROADCASTING : ProgressTracker.Step("Broadcasting transaction to participants")
// TODO: Make all tracker() methods @JvmStatic
@JvmStatic
fun tracker() = ProgressTracker(NOTARISING, BROADCASTING)
}
open protected val me
get() = serviceHub.myInfo.legalIdentity
@Suspendable
@Throws(NotaryException::class)
override fun call(): List<SignedTransaction> {
override fun call(): SignedTransaction {
// Note: this method is carefully broken up to minimize the amount of data reachable from the stack at
// the point where subFlow is invoked, as that minimizes the checkpointing work to be done.
//
// Lookup the resolved transactions and use them to map each signed transaction to the list of participants.
// Then send to the notary if needed, record locally and distribute.
val parties = getPartiesToSend(verifyTx())
progressTracker.currentStep = NOTARISING
val notarisedTxns: List<Pair<SignedTransaction, Set<Participant>>> = resolveDependenciesOf(transactions)
.map { (stx, ltx) -> Pair(notariseAndRecord(stx), lookupParties(ltx)) }
val notarised = notariseAndRecord()
// Each transaction has its own set of recipients, but extra recipients get them all.
progressTracker.currentStep = BROADCASTING
for ((stx, parties) in notarisedTxns) {
broadcastTransaction(stx, (parties + extraParticipants).filter { it.wellKnown != me })
for (party in parties) {
if (!serviceHub.myInfo.isLegalIdentity(party)) {
val session = initiateFlow(party)
subFlow(SendTransactionFlow(session, notarised))
}
}
return notarisedTxns.map { it.first }
}
/**
* Broadcast a transaction to the participants. By default calls [BroadcastTransactionFlow], however can be
* overridden for more complex transaction delivery protocols (for example where not all parties know each other).
* This implementation will filter out any participants for whom there is no well known identity.
*
* @param participants the participants to send the transaction to. This is expected to include extra participants
* and exclude the local node.
*/
@Suspendable
open protected fun broadcastTransaction(stx: SignedTransaction, participants: Iterable<Participant>) {
val wellKnownParticipants = participants.map { it.wellKnown }.filterNotNull()
if (wellKnownParticipants.isNotEmpty()) {
subFlow(BroadcastTransactionFlow(stx, wellKnownParticipants.toNonEmptySet()))
}
return notarised
}
@Suspendable
private fun notariseAndRecord(stx: SignedTransaction): SignedTransaction {
val notarised = if (needsNotarySignature(stx)) {
val notarySignatures = subFlow(NotaryFlow.Client(stx))
stx + notarySignatures
private fun notariseAndRecord(): SignedTransaction {
val notarised = if (needsNotarySignature(transaction)) {
val notarySignatures = subFlow(NotaryFlow.Client(transaction))
transaction + notarySignatures
} else {
stx
transaction
}
serviceHub.recordTransactions(notarised)
return notarised
@ -108,7 +83,6 @@ open class FinalityFlow(val transactions: Iterable<SignedTransaction>,
val wtx = stx.tx
val needsNotarisation = wtx.inputs.isNotEmpty() || wtx.timeWindow != null
return needsNotarisation && hasNoNotarySignature(stx)
}
private fun hasNoNotarySignature(stx: SignedTransaction): Boolean {
@ -117,55 +91,17 @@ open class FinalityFlow(val transactions: Iterable<SignedTransaction>,
return !(notaryKey?.isFulfilledBy(signers) ?: false)
}
/**
* Resolve the parties involved in a transaction.
*
* @return the set of participants and their resolved well known identities (where known).
*/
open protected fun lookupParties(ltx: LedgerTransaction): Set<Participant> {
// Calculate who is meant to see the results based on the participants involved.
return extractParticipants(ltx)
.map(this::partyFromAnonymous)
.toSet()
private fun getPartiesToSend(ltx: LedgerTransaction): Set<Party> {
val participants = ltx.outputStates.flatMap { it.participants } + ltx.inputStates.flatMap { it.participants }
return groupAbstractPartyByWellKnownParty(serviceHub, participants).keys + extraRecipients
}
/**
* Helper function to extract all participants from a ledger transaction. Intended to help implement [lookupParties]
* overriding functions.
*/
protected fun extractParticipants(ltx: LedgerTransaction): List<AbstractParty> {
return ltx.outputStates.flatMap { it.participants } + ltx.inputStates.flatMap { it.participants }
private fun verifyTx(): LedgerTransaction {
val notary = transaction.tx.notary
// The notary signature(s) are allowed to be missing but no others.
if (notary != null) transaction.verifySignaturesExcept(notary.owningKey) else transaction.verifyRequiredSignatures()
val ltx = transaction.toLedgerTransaction(serviceHub, false)
ltx.verify()
return ltx
}
/**
* Helper function which wraps [IdentityService.partyFromAnonymous] so it can be called as a lambda function.
*/
protected fun partyFromAnonymous(anon: AbstractParty): Participant {
return Participant(anon, serviceHub.identityService.partyFromAnonymous(anon))
}
private fun resolveDependenciesOf(signedTransactions: Iterable<SignedTransaction>): List<Pair<SignedTransaction, LedgerTransaction>> {
// Make sure the dependencies come before the dependers.
val sorted = ResolveTransactionsFlow.topologicalSort(signedTransactions.toList())
// Build a ServiceHub that consults the argument list as well as what's in local tx storage so uncommitted
// transactions can depend on each other.
val augmentedLookup = object : ServiceHub by serviceHub {
val hashToTx = sorted.associateBy { it.id }
override fun loadState(stateRef: StateRef): TransactionState<*> {
val provided: TransactionState<ContractState>? = hashToTx[stateRef.txhash]?.let { it.tx.outputs[stateRef.index] }
return provided ?: super.loadState(stateRef)
}
}
// Load and verify each transaction.
return sorted.map { stx ->
val notary = stx.tx.notary
// The notary signature(s) are allowed to be missing but no others.
if (notary != null) stx.verifySignaturesExcept(notary.owningKey) else stx.verifyRequiredSignatures()
val ltx = stx.toLedgerTransaction(augmentedLookup, false)
ltx.verify()
stx to ltx
}
}
data class Participant(val participant: AbstractParty, val wellKnown: Party?)
}

View File

@ -7,7 +7,7 @@ import net.corda.core.CordaRuntimeException
/**
* Exception which can be thrown by a [FlowLogic] at any point in its logic to unexpectedly bring it to a permanent end.
* The exception will propagate to all counterparty flows and will be thrown on their end the next time they wait on a
* [FlowLogic.receive] or [FlowLogic.sendAndReceive]. Any flow which no longer needs to do a receive, or has already ended,
* [FlowSession.receive] or [FlowSession.sendAndReceive]. Any flow which no longer needs to do a receive, or has already ended,
* will not receive the exception (if this is required then have them wait for a confirmation message).
*
* [FlowException] (or a subclass) can be a valid expected response from a flow, particularly ones which act as a service.

View File

@ -3,9 +3,11 @@ package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.FlowStateMachine
import net.corda.core.internal.abbreviate
import net.corda.core.messaging.DataFeed
import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
@ -53,15 +55,39 @@ abstract class FlowLogic<out T> {
*/
val serviceHub: ServiceHub get() = stateMachine.serviceHub
@Suspendable
fun initiateFlow(party: Party): FlowSession = stateMachine.initiateFlow(party, flowUsedForSessions)
/**
* Returns a [FlowContext] object describing the flow [otherParty] is using. With [FlowContext.flowVersion] it
* Specifies the identity, with certificate, to use for this flow. This will be one of the multiple identities that
* belong to this node.
* @see NodeInfo.legalIdentitiesAndCerts
*
* Note: The current implementation returns the single identity of the node. This will change once multiple identities
* is implemented.
*/
val ourIdentityAndCert: PartyAndCertificate get() = stateMachine.ourIdentityAndCert
/**
* Specifies the identity to use for this flow. This will be one of the multiple identities that belong to this node.
* This is the same as calling `ourIdentityAndCert.party`.
* @see NodeInfo.legalIdentities
*
* Note: The current implementation returns the single identity of the node. This will change once multiple identities
* is implemented.
*/
val ourIdentity: Party get() = ourIdentityAndCert.party
/**
* Returns a [FlowInfo] object describing the flow [otherParty] is using. With [FlowInfo.flowVersion] it
* provides the necessary information needed for the evolution of flows and enabling backwards compatibility.
*
* This method can be called before any send or receive has been done with [otherParty]. In such a case this will force
* them to start their flow.
*/
@Deprecated("Use FlowSession.getFlowInfo()", level = DeprecationLevel.WARNING)
@Suspendable
fun getFlowContext(otherParty: Party): FlowContext = stateMachine.getFlowContext(otherParty, flowUsedForSessions)
fun getFlowInfo(otherParty: Party): FlowInfo = stateMachine.getFlowInfo(otherParty, flowUsedForSessions)
/**
* Serializes and queues the given [payload] object for sending to the [otherParty]. Suspends until a response
@ -76,6 +102,7 @@ abstract class FlowLogic<out T> {
*
* @returns an [UntrustworthyData] wrapper around the received object.
*/
@Deprecated("Use FlowSession.sendAndReceive()", level = DeprecationLevel.WARNING)
inline fun <reified R : Any> sendAndReceive(otherParty: Party, payload: Any): UntrustworthyData<R> {
return sendAndReceive(R::class.java, otherParty, payload)
}
@ -91,6 +118,7 @@ abstract class FlowLogic<out T> {
*
* @returns an [UntrustworthyData] wrapper around the received object.
*/
@Deprecated("Use FlowSession.sendAndReceive()", level = DeprecationLevel.WARNING)
@Suspendable
open fun <R : Any> sendAndReceive(receiveType: Class<R>, otherParty: Party, payload: Any): UntrustworthyData<R> {
return stateMachine.sendAndReceive(receiveType, otherParty, payload, flowUsedForSessions)
@ -105,8 +133,19 @@ abstract class FlowLogic<out T> {
* oracle services. If one or more nodes in the service cluster go down mid-session, the message will be redelivered
* to a different one, so there is no need to wait until the initial node comes back up to obtain a response.
*/
@Deprecated("Use FlowSession.sendAndReceiveWithRetry()", level = DeprecationLevel.WARNING)
internal inline fun <reified R : Any> sendAndReceiveWithRetry(otherParty: Party, payload: Any): UntrustworthyData<R> {
return stateMachine.sendAndReceive(R::class.java, otherParty, payload, flowUsedForSessions, true)
return stateMachine.sendAndReceive(R::class.java, otherParty, payload, flowUsedForSessions, retrySend = true)
}
@Suspendable
internal fun <R : Any> FlowSession.sendAndReceiveWithRetry(receiveType: Class<R>, payload: Any): UntrustworthyData<R> {
return stateMachine.sendAndReceive(receiveType, counterparty, payload, flowUsedForSessions, retrySend = true)
}
@Suspendable
internal inline fun <reified R : Any> FlowSession.sendAndReceiveWithRetry(payload: Any): UntrustworthyData<R> {
return stateMachine.sendAndReceive(R::class.java, counterparty, payload, flowUsedForSessions, retrySend = true)
}
/**
@ -116,6 +155,7 @@ abstract class FlowLogic<out T> {
* verified for consistency and that all expectations are satisfied, as a malicious peer may send you subtly
* corrupted data in order to exploit your code.
*/
@Deprecated("Use FlowSession.receive()", level = DeprecationLevel.WARNING)
inline fun <reified R : Any> receive(otherParty: Party): UntrustworthyData<R> = receive(R::class.java, otherParty)
/**
@ -127,6 +167,7 @@ abstract class FlowLogic<out T> {
*
* @returns an [UntrustworthyData] wrapper around the received object.
*/
@Deprecated("Use FlowSession.receive()", level = DeprecationLevel.WARNING)
@Suspendable
open fun <R : Any> receive(receiveType: Class<R>, otherParty: Party): UntrustworthyData<R> {
return stateMachine.receive(receiveType, otherParty, flowUsedForSessions)
@ -139,6 +180,7 @@ abstract class FlowLogic<out T> {
* is offline then message delivery will be retried until it comes back or until the message is older than the
* network's event horizon time.
*/
@Deprecated("Use FlowSession.send()", level = DeprecationLevel.WARNING)
@Suspendable
open fun send(otherParty: Party, payload: Any) = stateMachine.send(otherParty, payload, flowUsedForSessions)
@ -294,7 +336,7 @@ abstract class FlowLogic<out T> {
* Version and name of the CorDapp hosting the other side of the flow.
*/
@CordaSerializable
data class FlowContext(
data class FlowInfo(
/**
* The integer flow version the other side is using.
* @see InitiatingFlow

View File

@ -0,0 +1,109 @@
package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.identity.Party
import net.corda.core.utilities.UntrustworthyData
/**
* To port existing flows:
*
* Look for [Deprecated] usages of send/receive/sendAndReceive/getFlowInfo.
*
* If it's an InitiatingFlow:
*
* Look for the send/receive that kicks off the counter flow. Insert a
*
* val session = initiateFlow(party)
*
* and use this session afterwards for send/receives.
* For example:
* send(party, something)
* will become
* session.send(something)
*
* If it's an InitiatedBy flow:
*
* Change the constructor to take an otherSideSession: FlowSession instead of a counterparty: Party
* Then look for usages of the deprecated functions and change them to use the FlowSession
* For example:
* send(counterparty, something)
* will become
* otherSideSession.send(something)
*/
abstract class FlowSession {
abstract val counterparty: Party
/**
* Returns a [FlowInfo] object describing the flow [counterparty] is using. With [FlowInfo.flowVersion] it
* provides the necessary information needed for the evolution of flows and enabling backwards compatibility.
*
* This method can be called before any send or receive has been done with [counterparty]. In such a case this will force
* them to start their flow.
*/
@Suspendable
abstract fun getCounterpartyFlowInfo(): FlowInfo
/**
* Serializes and queues the given [payload] object for sending to the [counterparty]. Suspends until a response
* is received, which must be of the given [R] type.
*
* Remember that when receiving data from other parties the data should not be trusted until it's been thoroughly
* verified for consistency and that all expectations are satisfied, as a malicious peer may send you subtly
* corrupted data in order to exploit your code.
*
* Note that this function is not just a simple send+receive pair: it is more efficient and more correct to
* use this when you expect to do a message swap than do use [send] and then [receive] in turn.
*
* @returns an [UntrustworthyData] wrapper around the received object.
*/
@Suspendable
inline fun <reified R : Any> sendAndReceive(payload: Any): UntrustworthyData<R> {
return sendAndReceive(R::class.java, payload)
}
/**
* Serializes and queues the given [payload] object for sending to the [counterparty]. Suspends until a response
* is received, which must be of the given [receiveType]. Remember that when receiving data from other parties the data
* should not be trusted until it's been thoroughly verified for consistency and that all expectations are
* satisfied, as a malicious peer may send you subtly corrupted data in order to exploit your code.
*
* Note that this function is not just a simple send+receive pair: it is more efficient and more correct to
* use this when you expect to do a message swap than do use [send] and then [receive] in turn.
*
* @returns an [UntrustworthyData] wrapper around the received object.
*/
@Suspendable
abstract fun <R : Any> sendAndReceive(receiveType: Class<R>, payload: Any): UntrustworthyData<R>
/**
* Suspends until [counterparty] sends us a message of type [R].
*
* Remember that when receiving data from other parties the data should not be trusted until it's been thoroughly
* verified for consistency and that all expectations are satisfied, as a malicious peer may send you subtly
* corrupted data in order to exploit your code.
*/
@Suspendable
inline fun <reified R : Any> receive(): UntrustworthyData<R> {
return receive(R::class.java)
}
/**
* Suspends until [counterparty] sends us a message of type [receiveType].
*
* Remember that when receiving data from other parties the data should not be trusted until it's been thoroughly
* verified for consistency and that all expectations are satisfied, as a malicious peer may send you subtly
* corrupted data in order to exploit your code.
*
* @returns an [UntrustworthyData] wrapper around the received object.
*/
@Suspendable
abstract fun <R : Any> receive(receiveType: Class<R>): UntrustworthyData<R>
/**
* Queues the given [payload] for sending to the [counterparty] and continues without suspending.
*
* Note that the other party may receive the message at some arbitrary later point or not at all: if [counterparty]
* is offline then message delivery will be retried until it comes back or until the message is older than the
* network's event horizon time.
*/
@Suspendable
abstract fun send(payload: Any)
}

View File

@ -1,20 +0,0 @@
package net.corda.core.flows
import net.corda.core.identity.Party
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
/**
* Alternative finality flow which only does not attempt to take participants from the transaction, but instead all
* participating parties must be provided manually.
*
* @param transactions What to commit.
* @param extraRecipients A list of additional participants to inform of the transaction.
*/
class ManualFinalityFlow(transactions: Iterable<SignedTransaction>,
recipients: Set<Party>,
progressTracker: ProgressTracker) : FinalityFlow(transactions, recipients, progressTracker) {
constructor(transaction: SignedTransaction, extraParticipants: Set<Party>) : this(listOf(transaction), extraParticipants, tracker())
override fun lookupParties(ltx: LedgerTransaction): Set<Participant> = emptySet()
}

View File

@ -43,7 +43,7 @@ class NotaryChangeFlow<out T : ContractState>(
val mySignature = serviceHub.keyManagementService.sign(signableData, myKey)
val stx = SignedTransaction(tx, listOf(mySignature))
return AbstractStateReplacementFlow.UpgradeTx(stx, participantKeys, myKey)
return AbstractStateReplacementFlow.UpgradeTx(stx)
}
/** Resolves the encumbrance state chain for the given [state] */

View File

@ -13,13 +13,15 @@ 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.FilteredTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.unwrap
import java.security.SignatureException
import java.util.function.Predicate
object NotaryFlow {
class NotaryFlow {
/**
* A flow to be used by a party for obtaining signature(s) from a [NotaryService] ascertaining the transaction
* time-window is correct and none of its inputs have been used in another completed transaction.
@ -42,14 +44,12 @@ object NotaryFlow {
fun tracker() = ProgressTracker(REQUESTING, VALIDATING)
}
lateinit var notaryParty: Party
@Suspendable
@Throws(NotaryException::class)
override fun call(): List<TransactionSignature> {
progressTracker.currentStep = REQUESTING
notaryParty = stx.notary ?: throw IllegalStateException("Transaction does not specify a Notary")
val notaryParty = stx.notary ?: throw IllegalStateException("Transaction does not specify a Notary")
check(stx.inputs.all { stateRef -> serviceHub.loadState(stateRef).notary == notaryParty }) {
"Input states must have the same Notary"
}
@ -65,16 +65,17 @@ object NotaryFlow {
}
val response = try {
val session = initiateFlow(notaryParty)
if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) {
subFlow(SendTransactionWithRetry(notaryParty, stx))
receive<List<TransactionSignature>>(notaryParty)
subFlow(SendTransactionWithRetry(session, stx))
session.receive<List<TransactionSignature>>()
} else {
val tx: Any = if (stx.isNotaryChangeTransaction()) {
stx.notaryChangeTx
} else {
stx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow })
stx.buildFilteredTransaction(Predicate { it is StateRef || it is TimeWindow || it == notaryParty })
}
sendAndReceiveWithRetry(notaryParty, tx)
session.sendAndReceiveWithRetry(tx)
}
} catch (e: NotaryException) {
if (e.error is NotaryError.Conflict) {
@ -84,17 +85,28 @@ object NotaryFlow {
}
return response.unwrap { signatures ->
signatures.forEach { validateSignature(it, stx.id) }
signatures.forEach { validateSignature(it, stx.id, notaryParty) }
signatures
}
}
private fun validateSignature(sig: TransactionSignature, txId: SecureHash) {
private fun validateSignature(sig: TransactionSignature, txId: SecureHash, notaryParty: Party) {
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)
}
}
/**
* A flow run by a notary service that handles notarisation requests.
*
@ -104,13 +116,14 @@ object NotaryFlow {
* Additional transaction validation logic can be added when implementing [receiveAndVerifyTx].
*/
// See AbstractStateReplacementFlow.Acceptor for why it's Void?
abstract class Service(val otherSide: Party, val service: TrustedAuthorityNotaryService) : FlowLogic<Void?>() {
abstract class Service(val otherSideSession: FlowSession, val service: TrustedAuthorityNotaryService) : FlowLogic<Void?>() {
@Suspendable
override fun call(): Void? {
val (id, inputs, timeWindow) = receiveAndVerifyTx()
val (id, inputs, timeWindow, notary) = receiveAndVerifyTx()
checkNotary(notary)
service.validateTimeWindow(timeWindow)
service.commitInputStates(inputs, id, otherSide)
service.commitInputStates(inputs, id, otherSideSession.counterparty)
signAndSendResponse(id)
return null
}
@ -122,10 +135,17 @@ object NotaryFlow {
@Suspendable
abstract fun receiveAndVerifyTx(): TransactionParts
// Check if transaction is intended to be signed by this notary.
@Suspendable
protected fun checkNotary(notary: Party?) {
if (notary !in serviceHub.myInfo.legalIdentities)
throw NotaryException(NotaryError.WrongNotary)
}
@Suspendable
private fun signAndSendResponse(txId: SecureHash) {
val signature = service.sign(txId)
send(otherSide, listOf(signature))
otherSideSession.send(listOf(signature))
}
}
}
@ -134,7 +154,7 @@ object NotaryFlow {
* The minimum amount of information needed to notarise a transaction. Note that this does not include
* any sensitive transaction details.
*/
data class TransactionParts(val id: SecureHash, val inputs: List<StateRef>, val timestamp: TimeWindow?)
data class TransactionParts(val id: SecureHash, val inputs: List<StateRef>, val timestamp: TimeWindow?, val notary: Party?)
class NotaryException(val error: NotaryError) : FlowException("Error response from Notary - $error")
@ -150,13 +170,6 @@ sealed class NotaryError {
data class TransactionInvalid(val cause: Throwable) : NotaryError() {
override fun toString() = cause.toString()
}
}
/**
* 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(otherSide: Party, stx: SignedTransaction) : SendTransactionFlow(otherSide, stx) {
@Suspendable
override fun sendPayloadAndReceiveDataRequest(otherSide: Party, payload: Any) = sendAndReceiveWithRetry<FetchDataFlow.Request>(otherSide, payload)
object WrongNotary: NotaryError()
}

View File

@ -2,7 +2,6 @@ package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.*
import net.corda.core.identity.Party
import net.corda.core.internal.ResolveTransactionsFlow
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.unwrap
@ -11,18 +10,26 @@ import java.security.SignatureException
/**
* The [ReceiveTransactionFlow] should be called in response to the [SendTransactionFlow].
*
* This flow is a combination of [receive], resolve and [SignedTransaction.verify]. This flow will receive the [SignedTransaction]
* and perform the resolution back-and-forth required to check the dependencies and download any missing attachments.
* The flow will return the [SignedTransaction] after it is resolved and then verified using [SignedTransaction.verify].
* This flow is a combination of [FlowSession.receive], resolve and [SignedTransaction.verify]. This flow will receive the
* [SignedTransaction] and perform the resolution back-and-forth required to check the dependencies and download any missing
* attachments. The flow will return the [SignedTransaction] after it is resolved and then verified using [SignedTransaction.verify].
*
* @param otherSideSession session to the other side which is calling [SendTransactionFlow].
* @param checkSufficientSignatures if true checks all required signatures are present. See [SignedTransaction.verify].
*/
class ReceiveTransactionFlow
@JvmOverloads
constructor(private val otherParty: Party, private val checkSufficientSignatures: Boolean = true) : FlowLogic<SignedTransaction>() {
class ReceiveTransactionFlow(private val otherSideSession: FlowSession,
private val checkSufficientSignatures: Boolean) : FlowLogic<SignedTransaction>() {
/** Receives a [SignedTransaction] from [otherSideSession], verifies it and then records it in the vault. */
constructor(otherSideSession: FlowSession) : this(otherSideSession, true)
@Suspendable
@Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class)
@Throws(SignatureException::class,
AttachmentResolutionException::class,
TransactionResolutionException::class,
TransactionVerificationException::class)
override fun call(): SignedTransaction {
return receive<SignedTransaction>(otherParty).unwrap {
subFlow(ResolveTransactionsFlow(it, otherParty))
return otherSideSession.receive<SignedTransaction>().unwrap {
subFlow(ResolveTransactionsFlow(it, otherSideSession))
it.verify(serviceHub, checkSufficientSignatures)
it
}
@ -32,16 +39,16 @@ constructor(private val otherParty: Party, private val checkSufficientSignatures
/**
* The [ReceiveStateAndRefFlow] should be called in response to the [SendStateAndRefFlow].
*
* This flow is a combination of [receive] and resolve. This flow will receive a list of [StateAndRef]
* This flow is a combination of [FlowSession.receive] and resolve. This flow will receive a list of [StateAndRef]
* and perform the resolution back-and-forth required to check the dependencies.
* The flow will return the list of [StateAndRef] after it is resolved.
*/
// @JvmSuppressWildcards is used to suppress wildcards in return type when calling `subFlow(new ReceiveStateAndRef<T>(otherParty))` in java.
class ReceiveStateAndRefFlow<out T : ContractState>(private val otherParty: Party) : FlowLogic<@JvmSuppressWildcards List<StateAndRef<T>>>() {
class ReceiveStateAndRefFlow<out T : ContractState>(private val otherSideSession: FlowSession) : FlowLogic<@JvmSuppressWildcards List<StateAndRef<T>>>() {
@Suspendable
override fun call(): List<StateAndRef<T>> {
return receive<List<StateAndRef<T>>>(otherParty).unwrap {
subFlow(ResolveTransactionsFlow(it.map { it.ref.txhash }.toSet(), otherParty))
return otherSideSession.receive<List<StateAndRef<T>>>().unwrap {
subFlow(ResolveTransactionsFlow(it.map { it.ref.txhash }.toSet(), otherSideSession))
it
}
}

View File

@ -2,7 +2,6 @@ package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.StateAndRef
import net.corda.core.identity.Party
import net.corda.core.internal.FetchDataFlow
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.unwrap
@ -14,9 +13,9 @@ import net.corda.core.utilities.unwrap
* to check the dependencies and download any missing attachments.
*
* @param otherSide the target party.
* @param stx the [SignedTransaction] being sent to the [otherSide].
* @param stx the [SignedTransaction] being sent to the [otherSideSession].
*/
open class SendTransactionFlow(otherSide: Party, stx: SignedTransaction) : DataVendingFlow(otherSide, stx)
open class SendTransactionFlow(otherSide: FlowSession, stx: SignedTransaction) : DataVendingFlow(otherSide, stx)
/**
* The [SendStateAndRefFlow] should be used to send a list of input [StateAndRef] to another peer that wishes to verify
@ -24,14 +23,14 @@ open class SendTransactionFlow(otherSide: Party, stx: SignedTransaction) : DataV
* at the right point in the conversation to receive the input state and ref and perform the resolution back-and-forth
* required to check the dependencies.
*
* @param otherSide the target party.
* @param stateAndRefs the list of [StateAndRef] being sent to the [otherSide].
* @param otherSideSession the target session.
* @param stateAndRefs the list of [StateAndRef] being sent to the [otherSideSession].
*/
open class SendStateAndRefFlow(otherSide: Party, stateAndRefs: List<StateAndRef<*>>) : DataVendingFlow(otherSide, stateAndRefs)
open class SendStateAndRefFlow(otherSideSession: FlowSession, stateAndRefs: List<StateAndRef<*>>) : DataVendingFlow(otherSideSession, stateAndRefs)
sealed class DataVendingFlow(val otherSide: Party, val payload: Any) : FlowLogic<Void?>() {
sealed class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any) : FlowLogic<Void?>() {
@Suspendable
protected open fun sendPayloadAndReceiveDataRequest(otherSide: Party, payload: Any) = sendAndReceive<FetchDataFlow.Request>(otherSide, payload)
protected open fun sendPayloadAndReceiveDataRequest(otherSideSession: FlowSession, payload: Any) = otherSideSession.sendAndReceive<FetchDataFlow.Request>(payload)
@Suspendable
protected open fun verifyDataRequest(dataRequest: FetchDataFlow.Request.Data) {
@ -42,11 +41,11 @@ sealed class DataVendingFlow(val otherSide: Party, val payload: Any) : FlowLogic
override fun call(): Void? {
// The first payload will be the transaction data, subsequent payload will be the transaction/attachment data.
var payload = payload
// This loop will receive [FetchDataFlow.Request] continuously until the `otherSide` has all the data they need
// to resolve the transaction, a [FetchDataFlow.EndRequest] will be sent from the `otherSide` to indicate end of
// This loop will receive [FetchDataFlow.Request] continuously until the `otherSideSession` has all the data they need
// to resolve the transaction, a [FetchDataFlow.EndRequest] will be sent from the `otherSideSession` to indicate end of
// data request.
while (true) {
val dataRequest = sendPayloadAndReceiveDataRequest(otherSide, payload).unwrap { request ->
val dataRequest = sendPayloadAndReceiveDataRequest(otherSideSession, payload).unwrap { request ->
when (request) {
is FetchDataFlow.Request.Data -> {
// Security TODO: Check for abnormally large or malformed data requests

View File

@ -3,7 +3,6 @@ package net.corda.core.identity
import net.corda.core.contracts.PartyAndReference
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.OpaqueBytes
import org.bouncycastle.asn1.x500.X500Name
import java.security.PublicKey
/**
@ -15,7 +14,7 @@ abstract class AbstractParty(val owningKey: PublicKey) {
/** Anonymised parties do not include any detail apart from owning key, so equality is dependent solely on the key */
override fun equals(other: Any?): Boolean = other === this || other is AbstractParty && other.owningKey == owningKey
override fun hashCode(): Int = owningKey.hashCode()
abstract fun nameOrNull(): X500Name?
abstract fun nameOrNull(): CordaX500Name?
/**
* Build a reference to something being stored or issued by a party e.g. in a vault or (more likely) on their normal

View File

@ -3,7 +3,6 @@ package net.corda.core.identity
import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.toStringShort
import net.corda.core.utilities.OpaqueBytes
import org.bouncycastle.asn1.x500.X500Name
import java.security.PublicKey
/**
@ -11,7 +10,7 @@ import java.security.PublicKey
* information such as name. It is intended to represent a party on the distributed ledger.
*/
class AnonymousParty(owningKey: PublicKey) : AbstractParty(owningKey) {
override fun nameOrNull(): X500Name? = null
override fun nameOrNull(): CordaX500Name? = null
override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this, bytes)
override fun toString() = "Anonymous(${owningKey.toStringShort()})"
}

View File

@ -0,0 +1,132 @@
package net.corda.core.identity
import com.google.common.collect.ImmutableSet
import net.corda.core.internal.LegalNameValidator
import net.corda.core.internal.x500Name
import net.corda.core.serialization.CordaSerializable
import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import org.bouncycastle.asn1.x500.AttributeTypeAndValue
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x500.style.BCStyle
import java.util.Locale
import javax.security.auth.x500.X500Principal
/**
* X.500 distinguished name data type customised to how Corda uses names. This restricts the attributes to those Corda
* supports, and requires that organisation, locality and country attributes are specified. See also RFC 4519 for
* the underlying attribute type definitions
*
* @property commonName optional name by the which the entity is usually known. Used only for services (for
* organisations, the [organisation] property is the name). Corresponds to the "CN" attribute type.
* @property organisationUnit optional name of a unit within the [organisation]. Corresponds to the "OU" attribute type.
* @property organisation name of the organisation. Corresponds to the "O" attribute type.
* @property locality locality of the organisation, typically nearest major city. For distributed services this would be
* where one of the organisations is based. Corresponds to the "L" attribute type.
* @property state the full name of the state or province the organisation is based in. Corresponds to the "ST"
* attribute type.
* @property country country the organisation is in, as an ISO 3166-1 2-letter country code. Corresponds to the "C"
* attribute type.
*/
@CordaSerializable
data class CordaX500Name(val commonName: String?,
val organisationUnit: String?,
val organisation: String,
val locality: String,
val state: String?,
val country: String) {
constructor(commonName: String, organisation: String, locality: String, country: String) : this(commonName = commonName, organisationUnit = null, organisation = organisation, locality = locality, state = null, country = country)
/**
* @param organisation name of the organisation.
* @param locality locality of the organisation, typically nearest major city.
* @param country country the organisation is in, as an ISO 3166-1 2-letter country code.
*/
constructor(organisation: String, locality: String, country: String) : this(null, null, organisation, locality, null, country)
init {
// Legal name checks.
LegalNameValidator.validateLegalName(organisation)
// 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) {
"Organisation attribute (O) must contain less then $MAX_LENGTH_ORGANISATION characters."
}
require(locality.length < MAX_LENGTH_LOCALITY) { "Locality attribute (L) must contain less then $MAX_LENGTH_LOCALITY characters." }
state?.let { require(it.length < MAX_LENGTH_STATE) { "State attribute (ST) must contain less then $MAX_LENGTH_STATE characters." } }
organisationUnit?.let {
require(it.length < MAX_LENGTH_ORGANISATION_UNIT) {
"Organisation Unit attribute (OU) must contain less then $MAX_LENGTH_ORGANISATION_UNIT characters."
}
}
commonName?.let {
require(it.length < MAX_LENGTH_COMMON_NAME) {
"Common Name attribute (CN) must contain less then $MAX_LENGTH_COMMON_NAME characters."
}
}
}
companion object {
const val LENGTH_COUNTRY = 2
const val MAX_LENGTH_ORGANISATION = 128
const val MAX_LENGTH_LOCALITY = 64
const val MAX_LENGTH_STATE = 64
const val MAX_LENGTH_ORGANISATION_UNIT = 64
const val MAX_LENGTH_COMMON_NAME = 64
private val supportedAttributes = setOf(BCStyle.O, BCStyle.C, BCStyle.L, BCStyle.CN, BCStyle.ST, BCStyle.OU)
private val countryCodes: Set<String> = ImmutableSet.copyOf(Locale.getISOCountries())
@JvmStatic
fun build(principal: X500Principal) : CordaX500Name {
val x500Name = X500Name.getInstance(principal.encoded)
val attrsMap: Map<ASN1ObjectIdentifier, ASN1Encodable> = x500Name.rdNs
.flatMap { it.typesAndValues.asList() }
.groupBy(AttributeTypeAndValue::getType, AttributeTypeAndValue::getValue)
.mapValues {
require(it.value.size == 1) { "Duplicate attribute ${it.key}" }
it.value[0]
}
// Supported attribute checks.
(attrsMap.keys - supportedAttributes).let { unsupported ->
require(unsupported.isEmpty()) {
"The following attribute${if (unsupported.size > 1) "s are" else " is"} not supported in Corda: " +
unsupported.map { BCStyle.INSTANCE.oidToDisplayName(it) }
}
}
val CN = attrsMap[BCStyle.CN]?.toString()
val OU = attrsMap[BCStyle.OU]?.toString()
val O = attrsMap[BCStyle.O]?.toString() ?: throw IllegalArgumentException("Corda X.500 names must include an O attribute")
val L = attrsMap[BCStyle.L]?.toString() ?: throw IllegalArgumentException("Corda X.500 names must include an L attribute")
val ST = attrsMap[BCStyle.ST]?.toString()
val C = attrsMap[BCStyle.C]?.toString() ?: throw IllegalArgumentException("Corda X.500 names must include an C attribute")
return CordaX500Name(CN, OU, O, L, ST, C)
}
@JvmStatic
fun parse(name: String) : CordaX500Name = build(X500Principal(name))
}
@Transient
private var x500Cache: X500Name? = null
val x500Principal: X500Principal
get() {
if (x500Cache == null) {
x500Cache = this.x500Name
}
return X500Principal(x500Cache!!.encoded)
}
override fun toString(): String {
if (x500Cache == null) {
x500Cache = this.x500Name
}
return x500Cache.toString()
}
}

View File

@ -0,0 +1,79 @@
@file:JvmName("IdentityUtils")
package net.corda.core.identity
import net.corda.core.internal.toMultiMap
import net.corda.core.node.ServiceHub
import net.corda.core.transactions.SignedTransaction
import java.security.PublicKey
/**
* Group each [PublicKey] by the well known party using the [ServiceHub.identityService], in preparation for
* creating [FlowSession]s, for example.
*
* @param publicKeys the [PublicKey]s to group.
* @param ignoreUnrecognisedParties if this is false, throw an exception if some of the [PublicKey]s cannot be mapped
* to a [Party].
* @return a map of well known [Party] to associated [PublicKey]s.
*/
@Throws(IllegalArgumentException::class)
fun groupPublicKeysByWellKnownParty(serviceHub: ServiceHub, publicKeys: Collection<PublicKey>, ignoreUnrecognisedParties: Boolean): Map<Party, List<PublicKey>> =
groupAbstractPartyByWellKnownParty(serviceHub, publicKeys.map { AnonymousParty(it) }, ignoreUnrecognisedParties).mapValues { it.value.map { it.owningKey } }
/**
* Group each [PublicKey] by the well known party using the [ServiceHub.identityService], in preparation for
* creating [FlowSession]s, for example. Throw an exception if some of the [PublicKey]s cannot be mapped
* to a [Party].
*
* @param publicKeys the [PublicKey]s to group.
* @return a map of well known [Party] to associated [PublicKey]s.
*/
// Cannot use @JvmOverloads in interface
@Throws(IllegalArgumentException::class)
fun groupPublicKeysByWellKnownParty(serviceHub: ServiceHub, publicKeys: Collection<PublicKey>): Map<Party, List<PublicKey>> = groupPublicKeysByWellKnownParty(serviceHub, publicKeys, false)
/**
* Group each [AbstractParty] by the well known party using the [ServiceHub.identityService], in preparation for
* creating [FlowSession]s, for example.
*
* @param parties the [AbstractParty]s to group.
* @param ignoreUnrecognisedParties if this is false, throw an exception if some of the [AbstractParty]s cannot be mapped
* to a [Party].
* @return a map of well known [Party] to associated [AbstractParty]s.
*/
@Throws(IllegalArgumentException::class)
fun groupAbstractPartyByWellKnownParty(serviceHub: ServiceHub, parties: Collection<AbstractParty>, ignoreUnrecognisedParties: Boolean): Map<Party, List<AbstractParty>> {
val partyToPublicKey: Iterable<Pair<Party, AbstractParty>> = parties.mapNotNull {
(serviceHub.identityService.wellKnownPartyFromAnonymous(it) ?: if (ignoreUnrecognisedParties) return@mapNotNull null else throw IllegalArgumentException("Could not find Party for $it")) to it
}
return partyToPublicKey.toMultiMap()
}
/**
* Group each [AbstractParty] by the well known party using the [ServiceHub.identityService], in preparation for
* creating [FlowSession]s, for example. Throw an exception if some of the [AbstractParty]s cannot be mapped
* to a [Party].
*
* @param parties the [AbstractParty]s to group.
* @return a map of well known [Party] to associated [AbstractParty]s.
*/
// Cannot use @JvmOverloads in interface
@Throws(IllegalArgumentException::class)
fun groupAbstractPartyByWellKnownParty(serviceHub: ServiceHub, parties: Collection<AbstractParty>): Map<Party, List<AbstractParty>> {
return groupAbstractPartyByWellKnownParty(serviceHub, parties, false)
}
/**
* Remove this node from a map of well known [Party]s.
*
* @return a new copy of the map, with he well known [Party] for this node removed.
*/
fun <T> excludeHostNode(serviceHub: ServiceHub, map: Map<Party, T>): Map<Party, T> = map.filterKeys { !serviceHub.myInfo.isLegalIdentity(it) }
/**
* Remove the [Party] associated with the notary of a [SignedTransaction] from the a map of [Party]s. It is a no-op
* if the notary is null.
*
* @return a new copy of the map, with the well known [Party] for the notary removed.
*/
fun <T> excludeNotary(map: Map<Party, T>, stx: SignedTransaction): Map<Party, T> = map.filterKeys { it != stx.notary }

View File

@ -1,12 +1,11 @@
package net.corda.core.identity
import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Crypto
import net.corda.core.utilities.OpaqueBytes
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import java.security.PublicKey
import java.security.cert.X509Certificate
/**
* The [Party] class represents an entity on the network, which is typically identified by a legal [name] and public key
@ -27,9 +26,10 @@ import java.security.PublicKey
*
* @see CompositeKey
*/
class Party(val name: X500Name, owningKey: PublicKey) : AbstractParty(owningKey) {
constructor(certificate: X509CertificateHolder) : this(certificate.subject, Crypto.toSupportedPublicKey(certificate.subjectPublicKeyInfo))
override fun nameOrNull(): X500Name = name
class Party(val name: CordaX500Name, owningKey: PublicKey) : AbstractParty(owningKey) {
constructor(certificate: X509Certificate)
: this(CordaX500Name.build(certificate.subjectX500Principal), Crypto.toSupportedPublicKey(certificate.publicKey))
override fun nameOrNull(): CordaX500Name = name
fun anonymise(): AnonymousParty = AnonymousParty(owningKey)
override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this, bytes)
override fun toString() = name.toString()

View File

@ -1,8 +1,6 @@
package net.corda.core.identity
import net.corda.core.internal.toX509CertHolder
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import net.corda.core.serialization.CordaSerializable
import java.security.PublicKey
import java.security.cert.*
@ -11,23 +9,23 @@ import java.security.cert.*
* [PartyAndCertificate] instances is based on the party only, as certificate and path are data associated with the party,
* not part of the identifier themselves.
*/
//TODO Is VerifiableIdentity a better name?
@CordaSerializable
class PartyAndCertificate(val certPath: CertPath) {
@Transient val certificate: X509CertificateHolder
@Transient val certificate: X509Certificate
init {
require(certPath.type == "X.509") { "Only X.509 certificates supported" }
val certs = certPath.certificates
require(certs.size >= 2) { "Certificate path must at least include subject and issuing certificates" }
certificate = certs[0].toX509CertHolder()
certificate = certs[0] as X509Certificate
}
@Transient val party: Party = Party(certificate)
val owningKey: PublicKey get() = party.owningKey
val name: X500Name get() = party.name
val name: CordaX500Name get() = party.name
operator fun component1(): Party = party
operator fun component2(): X509CertificateHolder = certificate
operator fun component2(): X509Certificate = certificate
override fun equals(other: Any?): Boolean = other === this || other is PartyAndCertificate && other.party == party
override fun hashCode(): Int = party.hashCode()

View File

@ -10,6 +10,7 @@ import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.security.CodeSigner
import java.security.cert.X509Certificate
import java.util.jar.JarInputStream
abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment {
@ -23,7 +24,6 @@ abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment {
/** @see <https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File> */
private val unsignableEntryName = "META-INF/(?:.*[.](?:SF|DSA|RSA)|SIG-.*)".toRegex()
private val shredder = ByteArray(1024)
}
protected val attachmentData: ByteArray by lazy(dataLoader)
@ -32,6 +32,7 @@ abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment {
// Can't start with empty set if we're doing intersections. Logically the null means "all possible signers":
var attachmentSigners: MutableSet<CodeSigner>? = null
openAsJAR().use { jar ->
val shredder = ByteArray(1024)
while (true) {
val entry = jar.nextJarEntry ?: break
if (entry.isDirectory || unsignableEntryName.matches(entry.name)) continue
@ -44,7 +45,7 @@ abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment {
}
}
(attachmentSigners ?: emptySet<CodeSigner>()).map {
Party(it.signerCertPath.certificates[0].toX509CertHolder())
Party(it.signerCertPath.certificates[0] as X509Certificate)
}.sortedBy { it.name.toString() } // Determinism.
}

View File

@ -0,0 +1,21 @@
package net.corda.core.internal
import net.corda.core.contracts.*
import net.corda.core.transactions.TransactionBuilder
object ContractUpgradeUtils {
fun <OldState : ContractState, NewState : ContractState> assembleBareTx(
stateRef: StateAndRef<OldState>,
upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>,
privacySalt: PrivacySalt
): TransactionBuilder {
val contractUpgrade = upgradedContractClass.newInstance()
return TransactionBuilder(stateRef.state.notary)
.withItems(
stateRef,
StateAndContract(contractUpgrade.upgrade(stateRef.state.data), upgradedContractClass.name),
Command(UpgradeCommand(upgradedContractClass.name), stateRef.state.data.participants.map { it.owningKey }),
privacySalt
)
}
}

View File

@ -7,7 +7,7 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.Party
import net.corda.core.flows.FlowSession
import net.corda.core.internal.FetchDataFlow.DownloadedVsRequestedDataMismatch
import net.corda.core.internal.FetchDataFlow.HashNotFound
import net.corda.core.serialization.CordaSerializable
@ -38,7 +38,7 @@ import java.util.*
*/
sealed class FetchDataFlow<T : NamedByHash, in W : Any>(
protected val requests: Set<SecureHash>,
protected val otherSide: Party,
protected val otherSideSession: FlowSession,
protected val dataType: DataType) : FlowLogic<FetchDataFlow.Result<T>>() {
@CordaSerializable
@ -72,7 +72,7 @@ sealed class FetchDataFlow<T : NamedByHash, in W : Any>(
return if (toFetch.isEmpty()) {
Result(fromDisk, emptyList())
} else {
logger.info("Requesting ${toFetch.size} dependency(s) for verification from ${otherSide.name}")
logger.info("Requesting ${toFetch.size} dependency(s) for verification from ${otherSideSession.counterparty.name}")
// TODO: Support "large message" response streaming so response sizes are not limited by RAM.
// We can then switch to requesting items in large batches to minimise the latency penalty.
@ -85,11 +85,11 @@ sealed class FetchDataFlow<T : NamedByHash, in W : Any>(
for (hash in toFetch) {
// We skip the validation here (with unwrap { it }) because we will do it below in validateFetchResponse.
// The only thing checked is the object type. It is a protocol violation to send results out of order.
maybeItems += sendAndReceive<List<W>>(otherSide, Request.Data(NonEmptySet.of(hash), dataType)).unwrap { it }
maybeItems += otherSideSession.sendAndReceive<List<W>>(Request.Data(NonEmptySet.of(hash), dataType)).unwrap { it }
}
// Check for a buggy/malicious peer answering with something that we didn't ask for.
val downloaded = validateFetchResponse(UntrustworthyData(maybeItems), toFetch)
logger.info("Fetched ${downloaded.size} elements from ${otherSide.name}")
logger.info("Fetched ${downloaded.size} elements from ${otherSideSession.counterparty.name}")
maybeWriteToDisk(downloaded)
Result(fromDisk, downloaded)
}
@ -140,7 +140,7 @@ sealed class FetchDataFlow<T : NamedByHash, in W : Any>(
* attachments are saved to local storage automatically.
*/
class FetchAttachmentsFlow(requests: Set<SecureHash>,
otherSide: Party) : FetchDataFlow<Attachment, ByteArray>(requests, otherSide, DataType.ATTACHMENT) {
otherSide: FlowSession) : FetchDataFlow<Attachment, ByteArray>(requests, otherSide, DataType.ATTACHMENT) {
override fun load(txid: SecureHash): Attachment? = serviceHub.attachments.openAttachment(txid)
@ -171,7 +171,7 @@ class FetchAttachmentsFlow(requests: Set<SecureHash>,
* results in a [FetchDataFlow.HashNotFound] exception. Note that returned transactions are not inserted into
* the database, because it's up to the caller to actually verify the transactions are valid.
*/
class FetchTransactionsFlow(requests: Set<SecureHash>, otherSide: Party) :
class FetchTransactionsFlow(requests: Set<SecureHash>, otherSide: FlowSession) :
FetchDataFlow<SignedTransaction, SignedTransaction>(requests, otherSide, DataType.TRANSACTION) {
override fun load(txid: SecureHash): SignedTransaction? = serviceHub.validatedTransactions.getTransaction(txid)

View File

@ -5,6 +5,7 @@ import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.ServiceHub
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.UntrustworthyData
@ -13,7 +14,10 @@ import org.slf4j.Logger
/** This is an internal interface that is implemented by code in the node module. You should look at [FlowLogic]. */
interface FlowStateMachine<R> {
@Suspendable
fun getFlowContext(otherParty: Party, sessionFlow: FlowLogic<*>): FlowContext
fun getFlowInfo(otherParty: Party, sessionFlow: FlowLogic<*>): FlowInfo
@Suspendable
fun initiateFlow(otherParty: Party, sessionFlow: FlowLogic<*>): FlowSession
@Suspendable
fun <T : Any> sendAndReceive(receiveType: Class<T>,
@ -46,4 +50,5 @@ interface FlowStateMachine<R> {
val id: StateMachineRunId
val resultFuture: CordaFuture<R>
val flowInitiator: FlowInitiator
val ourIdentityAndCert: PartyAndCertificate
}

View File

@ -3,6 +3,7 @@ package net.corda.core.internal
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.slf4j.Logger
import rx.Observable
import rx.Observer
@ -15,6 +16,8 @@ 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.cert.Certificate
import java.security.cert.X509Certificate
import java.time.Duration
import java.time.temporal.Temporal
import java.util.*
@ -26,6 +29,7 @@ import java.util.zip.Deflater
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import kotlin.reflect.KClass
import kotlin.reflect.full.createInstance
val Throwable.rootCause: Throwable get() = cause?.rootCause ?: this
fun Throwable.getStackTraceAsString() = StringWriter().also { printStackTrace(PrintWriter(it)) }.toString()
@ -166,8 +170,8 @@ fun <T> logElapsedTime(label: String, logger: Logger? = null, body: () -> T): T
}
}
fun java.security.cert.Certificate.toX509CertHolder() = X509CertificateHolder(encoded)
fun javax.security.cert.Certificate.toX509CertHolder() = X509CertificateHolder(encoded)
fun Certificate.toX509CertHolder() = X509CertificateHolder(encoded)
val X509CertificateHolder.cert: X509Certificate get() = JcaX509CertificateConverter().getCertificate(this)
/** Convert a [ByteArrayOutputStream] to [InputStreamAndHash]. */
fun ByteArrayOutputStream.toInputStreamAndHash(): InputStreamAndHash {
@ -239,6 +243,11 @@ fun <T> Any.declaredField(name: String): DeclaredField<T> = DeclaredField(javaCl
*/
fun <T> Any.declaredField(clazz: KClass<*>, name: String): DeclaredField<T> = DeclaredField(clazz.java, name, this)
/** creates a new instance if not a Kotlin object */
fun <T: Any> KClass<T>.objectOrNewInstance(): T {
return this.objectInstance ?: this.createInstance()
}
/**
* A simple wrapper around a [Field] object providing type safe read and write access using [value], ignoring the field's
* visibility.
@ -260,3 +269,8 @@ class DeclaredField<T>(clazz: Class<*>, name: String, private val receiver: Any?
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class VisibleForTesting
@Suppress("UNCHECKED_CAST")
fun <T, U : T> uncheckedCast(obj: T) = obj as U
fun <K, V> Iterable<Pair<K, V>>.toMultiMap(): Map<K, List<V>> = this.groupBy({ it.first }) { it.second }

View File

@ -0,0 +1,117 @@
package net.corda.core.internal
import java.lang.Character.UnicodeScript.*
import java.text.Normalizer
import java.util.regex.Pattern
import javax.security.auth.x500.X500Principal
object LegalNameValidator {
/**
* The validation function will validate the input string using the following rules:
*
* - No blacklisted words like "node", "server".
* - Restrict names to Latin scripts for now to avoid right-to-left issues, debugging issues when we can't pronounce
* names over the phone, and character confusability attacks.
* - Must consist of at least three letters and should start with a capital letter.
* - No commas or equals signs.
* - No dollars or quote marks, we might need to relax the quote mark constraint in future to handle Irish company names.
*
* @throws IllegalArgumentException if the name does not meet the required rules. The message indicates why not.
*/
fun validateLegalName(normalizedLegalName: String) {
Rule.legalNameRules.forEach { it.validate(normalizedLegalName) }
}
/**
* The normalize function will trim the input string, replace any multiple spaces with a single space,
* and normalize the string according to NFKC normalization form.
*/
fun normaliseLegalName(legalName: String): String {
val trimmedLegalName = legalName.trim().replace(WHITESPACE, " ")
return Normalizer.normalize(trimmedLegalName, Normalizer.Form.NFKC)
}
val WHITESPACE = "\\s++".toRegex()
sealed class Rule<in T> {
companion object {
val legalNameRules: List<Rule<String>> = listOf(
UnicodeNormalizationRule(),
CharacterRule(',', '=', '$', '"', '\'', '\\'),
WordRule("node", "server"),
LengthRule(maxLength = 255),
// TODO: Implement confusable character detection if we add more scripts.
UnicodeRangeRule(LATIN, COMMON, INHERITED),
CapitalLetterRule(),
X500NameRule(),
MustHaveAtLeastTwoLettersRule()
)
}
abstract fun validate(legalName: T)
private class UnicodeNormalizationRule : Rule<String>() {
override fun validate(legalName: String) {
require(legalName == normaliseLegalName(legalName)) { "Legal name must be normalized. Please use 'normaliseLegalName' to normalize the legal name before validation." }
}
}
private class UnicodeRangeRule(vararg supportScripts: Character.UnicodeScript) : Rule<String>() {
private val pattern = supportScripts.map { "\\p{Is$it}" }.joinToString(separator = "", prefix = "[", postfix = "]*").let { Pattern.compile(it) }
override fun validate(legalName: String) {
require(pattern.matcher(legalName).matches()) {
val illegalChars = legalName.replace(pattern.toRegex(), "").toSet()
if (illegalChars.size > 1) {
"Forbidden characters $illegalChars in \"$legalName\"."
} else {
"Forbidden character $illegalChars in \"$legalName\"."
}
}
}
}
private class CharacterRule(vararg val bannedChars: Char) : Rule<String>() {
override fun validate(legalName: String) {
bannedChars.forEach {
require(!legalName.contains(it, true)) { "Character not allowed in legal names: $it" }
}
}
}
private class WordRule(vararg val bannedWords: String) : Rule<String>() {
override fun validate(legalName: String) {
bannedWords.forEach {
require(!legalName.contains(it, ignoreCase = true)) { "Word not allowed in legal names: $it" }
}
}
}
private class LengthRule(val maxLength: Int) : Rule<String>() {
override fun validate(legalName: String) {
require(legalName.length <= maxLength) { "Legal name longer then $maxLength characters." }
}
}
private class CapitalLetterRule : Rule<String>() {
override fun validate(legalName: String) {
val capitalizedLegalName = legalName.capitalize()
require(legalName == capitalizedLegalName) { "Legal name should be capitalized. i.e. '$capitalizedLegalName'" }
}
}
private class X500NameRule : Rule<String>() {
override fun validate(legalName: String) {
// This will throw IllegalArgumentException if the name does not comply with X500 name format.
X500Principal("CN=$legalName")
}
}
private class MustHaveAtLeastTwoLettersRule : Rule<String>() {
override fun validate(legalName: String) {
// Try to exclude names like "/", "£", "X" etc.
require(legalName.count { it.isLetter() } >= 2) { "Illegal input legal name '$legalName'. Legal name must have at least two letters" }
}
}
}
}

View File

@ -3,7 +3,7 @@ package net.corda.core.internal
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.Party
import net.corda.core.flows.FlowSession
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.exactAdd
@ -17,14 +17,14 @@ import java.util.*
* @return a list of verified [SignedTransaction] objects, in a depth-first order.
*/
class ResolveTransactionsFlow(private val txHashes: Set<SecureHash>,
private val otherSide: Party) : FlowLogic<List<SignedTransaction>>() {
private val otherSide: FlowSession) : FlowLogic<List<SignedTransaction>>() {
/**
* Resolves and validates the dependencies of the specified [signedTransaction]. Fetches the attachments, but does
* *not* validate or store the [signedTransaction] itself.
*
* @return a list of verified [SignedTransaction] objects, in a depth-first order.
*/
constructor(signedTransaction: SignedTransaction, otherSide: Party) : this(dependencyIDs(signedTransaction), otherSide) {
constructor(signedTransaction: SignedTransaction, otherSide: FlowSession) : this(dependencyIDs(signedTransaction), otherSide) {
this.signedTransaction = signedTransaction
}
companion object {
@ -82,7 +82,7 @@ class ResolveTransactionsFlow(private val txHashes: Set<SecureHash>,
// Start fetching data.
val newTxns = downloadDependencies(txHashes)
fetchMissingAttachments(signedTransaction?.let { newTxns + it } ?: newTxns)
send(otherSide, FetchDataFlow.Request.End)
otherSide.send(FetchDataFlow.Request.End)
// Finish fetching data.
val result = topologicalSort(newTxns)

View File

@ -0,0 +1,34 @@
@file:JvmName("X500NameUtils")
package net.corda.core.internal
import net.corda.core.identity.CordaX500Name
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x500.X500NameBuilder
import org.bouncycastle.asn1.x500.style.BCStyle
val X500Name.commonName: String? get() = getRDNValueString(BCStyle.CN)
val X500Name.state: String? get() = getRDNValueString(BCStyle.ST)
val X500Name.organisation: String get() = getRDNValueString(BCStyle.O) ?: throw IllegalArgumentException("Malformed X500 name, organisation attribute (O) cannot be empty.")
val X500Name.locality: String get() = getRDNValueString(BCStyle.L) ?: throw IllegalArgumentException("Malformed X500 name, locality attribute (L) cannot be empty.")
val X500Name.country: String get() = getRDNValueString(BCStyle.C) ?: throw IllegalArgumentException("Malformed X500 name, country attribute (C) cannot be empty.")
private fun X500Name.getRDNValueString(identifier: ASN1ObjectIdentifier): String? = getRDNs(identifier).firstOrNull()?.first?.value?.toString()
/**
* Return the underlying X.500 name from this Corda-safe X.500 name. These are guaranteed to have a consistent
* ordering, such that their `toString()` function returns the same value every time for the same [CordaX500Name].
*/
val CordaX500Name.x500Name: X500Name
get() {
return X500NameBuilder(BCStyle.INSTANCE).apply {
addRDN(BCStyle.C, country)
state?.let { addRDN(BCStyle.ST, it) }
addRDN(BCStyle.L, locality)
addRDN(BCStyle.O, organisation)
organisationUnit?.let { addRDN(BCStyle.OU, it) }
commonName?.let { addRDN(BCStyle.CN, it) }
}.build()
}

View File

@ -0,0 +1,51 @@
package net.corda.core.internal
import net.i2p.crypto.eddsa.EdDSAEngine
import net.i2p.crypto.eddsa.EdDSAPublicKey
import java.security.*
import java.security.spec.AlgorithmParameterSpec
import java.security.spec.X509EncodedKeySpec
/**
* Wrapper around [EdDSAEngine] which can intelligently rewrite X509Keys to a [EdDSAPublicKey]. This is a temporary
* solution until this is integrated upstream and/or a custom certificate factory implemented to force the correct
* key type. Only intercepts public keys passed into [engineInitVerify], as there is no equivalent issue with private
* keys.
*/
class X509EdDSAEngine : Signature {
private val engine: EdDSAEngine
constructor() : super(EdDSAEngine.SIGNATURE_ALGORITHM) {
engine = EdDSAEngine()
}
constructor(digest: MessageDigest) : super(EdDSAEngine.SIGNATURE_ALGORITHM) {
engine = EdDSAEngine(digest)
}
override fun engineInitSign(privateKey: PrivateKey) = engine.initSign(privateKey)
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
}
engine.initVerify(parsedKey)
}
override fun engineSign(): ByteArray = engine.sign()
override fun engineVerify(sigBytes: ByteArray): Boolean = engine.verify(sigBytes)
override fun engineUpdate(b: Byte) = engine.update(b)
override fun engineUpdate(b: ByteArray, off: Int, len: Int) = engine.update(b, off, len)
override fun engineGetParameters(): AlgorithmParameters = engine.parameters
override fun engineSetParameter(params: AlgorithmParameterSpec) = engine.setParameter(params)
@Suppress("DEPRECATION")
override fun engineGetParameter(param: String): Any = engine.getParameter(param)
@Suppress("DEPRECATION")
override fun engineSetParameter(param: String, value: Any?) = engine.setParameter(param, value)
}

View File

@ -0,0 +1,24 @@
package net.corda.core.internal.cordapp
import net.corda.core.cordapp.Cordapp
import net.corda.core.flows.FlowLogic
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializeAsToken
import java.net.URL
data class CordappImpl(
override val contractClassNames: List<String>,
override val initiatedFlows: List<Class<out FlowLogic<*>>>,
override val rpcFlows: List<Class<out FlowLogic<*>>>,
override val services: List<Class<out SerializeAsToken>>,
override val plugins: List<CordaPluginRegistry>,
override val customSchemas: Set<MappedSchema>,
override val jarPath: URL) : Cordapp {
/**
* An exhaustive list of all classes relevant to the node within this CorDapp
*
* TODO: Also add [SchedulableFlow] as a Cordapp class
*/
override val cordappClasses = ((rpcFlows + initiatedFlows + services + plugins.map { javaClass }).map { it.name } + contractClassNames)
}

View File

@ -2,13 +2,12 @@ package net.corda.core.messaging
import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.UpgradedContract
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowInitiator
import net.corda.core.flows.FlowLogic
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.NodeInfo
import net.corda.core.node.services.NetworkMapCache
@ -21,7 +20,6 @@ import net.corda.core.node.services.vault.Sort
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.Try
import org.bouncycastle.asn1.x500.X500Name
import rx.Observable
import java.io.InputStream
import java.security.PublicKey
@ -58,15 +56,14 @@ interface CordaRPCOps : RPCOps {
* Returns the RPC protocol version, which is the same the node's Platform Version. Exists since version 1 so guaranteed
* to be present.
*/
override val protocolVersion: Int get() = nodeIdentity().platformVersion
override val protocolVersion: Int get() = nodeInfo().platformVersion
/**
* Returns a list of currently in-progress state machine infos.
*/
/** Returns a list of currently in-progress state machine infos. */
fun stateMachinesSnapshot(): List<StateMachineInfo>
/**
* Returns a data feed of currently in-progress state machine infos and an observable of future state machine adds/removes.
* Returns a data feed of currently in-progress state machine infos and an observable of
* future state machine adds/removes.
*/
@RPCReturnsObservables
fun stateMachinesFeed(): DataFeed<List<StateMachineInfo>, StateMachineUpdate>
@ -97,24 +94,24 @@ interface CordaRPCOps : RPCOps {
fun <T : ContractState> vaultQueryBy(criteria: QueryCriteria,
paging: PageSpecification,
sorting: Sort,
contractType: Class<out T>): Vault.Page<T>
contractStateType: Class<out T>): Vault.Page<T>
// DOCEND VaultQueryByAPI
// Note: cannot apply @JvmOverloads to interfaces nor interface implementations
// Java Helpers
// DOCSTART VaultQueryAPIHelpers
fun <T : ContractState> vaultQuery(contractType: Class<out T>): Vault.Page<T> {
return vaultQueryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractType)
fun <T : ContractState> vaultQuery(contractStateType: Class<out T>): Vault.Page<T> {
return vaultQueryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType)
}
fun <T : ContractState> vaultQueryByCriteria(criteria: QueryCriteria, contractType: Class<out T>): Vault.Page<T> {
return vaultQueryBy(criteria, PageSpecification(), Sort(emptySet()), contractType)
fun <T : ContractState> vaultQueryByCriteria(criteria: QueryCriteria, contractStateType: Class<out T>): Vault.Page<T> {
return vaultQueryBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType)
}
fun <T : ContractState> vaultQueryByWithPagingSpec(contractType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): Vault.Page<T> {
return vaultQueryBy(criteria, paging, Sort(emptySet()), contractType)
fun <T : ContractState> vaultQueryByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): Vault.Page<T> {
return vaultQueryBy(criteria, paging, Sort(emptySet()), contractStateType)
}
fun <T : ContractState> vaultQueryByWithSorting(contractType: Class<out T>, criteria: QueryCriteria, sorting: Sort): Vault.Page<T> {
return vaultQueryBy(criteria, PageSpecification(), sorting, contractType)
fun <T : ContractState> vaultQueryByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): Vault.Page<T> {
return vaultQueryBy(criteria, PageSpecification(), sorting, contractStateType)
}
// DOCEND VaultQueryAPIHelpers
@ -135,24 +132,24 @@ interface CordaRPCOps : RPCOps {
fun <T : ContractState> vaultTrackBy(criteria: QueryCriteria,
paging: PageSpecification,
sorting: Sort,
contractType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>>
contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>>
// DOCEND VaultTrackByAPI
// Note: cannot apply @JvmOverloads to interfaces nor interface implementations
// Java Helpers
// DOCSTART VaultTrackAPIHelpers
fun <T : ContractState> vaultTrack(contractType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return vaultTrackBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractType)
fun <T : ContractState> vaultTrack(contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return vaultTrackBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType)
}
fun <T : ContractState> vaultTrackByCriteria(contractType: Class<out T>, criteria: QueryCriteria): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return vaultTrackBy(criteria, PageSpecification(), Sort(emptySet()), contractType)
fun <T : ContractState> vaultTrackByCriteria(contractStateType: Class<out T>, criteria: QueryCriteria): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return vaultTrackBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType)
}
fun <T : ContractState> vaultTrackByWithPagingSpec(contractType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return vaultTrackBy(criteria, paging, Sort(emptySet()), contractType)
fun <T : ContractState> vaultTrackByWithPagingSpec(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return vaultTrackBy(criteria, paging, Sort(emptySet()), contractStateType)
}
fun <T : ContractState> vaultTrackByWithSorting(contractType: Class<out T>, criteria: QueryCriteria, sorting: Sort): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return vaultTrackBy(criteria, PageSpecification(), sorting, contractType)
fun <T : ContractState> vaultTrackByWithSorting(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return vaultTrackBy(criteria, PageSpecification(), sorting, contractStateType)
}
// DOCEND VaultTrackAPIHelpers
@ -173,9 +170,7 @@ interface CordaRPCOps : RPCOps {
@RPCReturnsObservables
fun internalVerifiedTransactionsFeed(): DataFeed<List<SignedTransaction>, SignedTransaction>
/**
* Returns a snapshot list of existing state machine id - recorded transaction hash mappings.
*/
/** Returns a snapshot list of existing state machine id - recorded transaction hash mappings. */
fun stateMachineRecordedTransactionMappingSnapshot(): List<StateMachineTransactionMapping>
/**
@ -185,19 +180,19 @@ interface CordaRPCOps : RPCOps {
@RPCReturnsObservables
fun stateMachineRecordedTransactionMappingFeed(): DataFeed<List<StateMachineTransactionMapping>, StateMachineTransactionMapping>
/**
* Returns all parties currently visible on the network with their advertised services.
*/
/** Returns all parties currently visible on the network with their advertised services. */
fun networkMapSnapshot(): List<NodeInfo>
/**
* Returns all parties currently visible on the network with their advertised services and an observable of future updates to the network.
* Returns all parties currently visible on the network with their advertised services and an observable of
* future updates to the network.
*/
@RPCReturnsObservables
fun networkMapFeed(): DataFeed<List<NodeInfo>, NetworkMapCache.MapChange>
/**
* Start the given flow with the given arguments. [logicType] must be annotated with [net.corda.core.flows.StartableByRPC].
* Start the given flow with the given arguments. [logicType] must be annotated
* with [net.corda.core.flows.StartableByRPC].
*/
@RPCReturnsObservables
fun <T> startFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowHandle<T>
@ -209,39 +204,32 @@ interface CordaRPCOps : RPCOps {
@RPCReturnsObservables
fun <T> startTrackedFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowProgressHandle<T>
/**
* Returns Node's identity, assuming this will not change while the node is running.
*/
fun nodeIdentity(): NodeInfo
/** Returns Node's NodeInfo, assuming this will not change while the node is running. */
fun nodeInfo(): NodeInfo
/*
* Add note(s) to an existing Vault transaction
/**
* Returns network's notary identities, assuming this will not change while the node is running.
*
* Note that the identities are sorted based on legal name, and the ordering might change once new notaries are introduced.
*/
fun notaryIdentities(): List<Party>
/** Add note(s) to an existing Vault transaction. */
fun addVaultTransactionNote(txnId: SecureHash, txnNote: String)
/*
* Retrieve existing note(s) for a given Vault transaction
*/
/** Retrieve existing note(s) for a given Vault transaction. */
fun getVaultTransactionNotes(txnId: SecureHash): Iterable<String>
/**
* Checks whether an attachment with the given hash is stored on the node.
*/
/** Checks whether an attachment with the given hash is stored on the node. */
fun attachmentExists(id: SecureHash): Boolean
/**
* Download an attachment JAR by ID
*/
/** Download an attachment JAR by ID. */
fun openAttachment(id: SecureHash): InputStream
/**
* Uploads a jar to the node, returns it's hash.
*/
/** Uploads a jar to the node, returns it's hash. */
fun uploadAttachment(jar: InputStream): SecureHash
/**
* Returns the node's current time.
*/
/** Returns the node's current time. */
fun currentNodeTime(): Instant
/**
@ -261,16 +249,12 @@ interface CordaRPCOps : RPCOps {
* @param party identity to determine well known identity for.
* @return well known identity, if found.
*/
fun partyFromAnonymous(party: AbstractParty): Party?
/**
* Returns the [Party] corresponding to the given key, if found.
*/
fun wellKnownPartyFromAnonymous(party: AbstractParty): Party?
/** Returns the [Party] corresponding to the given key, if found. */
fun partyFromKey(key: PublicKey): Party?
/**
* Returns the [Party] with the X.500 principal as it's [Party.name]
*/
fun partyFromX500Name(x500Name: X500Name): Party?
/** Returns the [Party] with the X.500 principal as it's [Party.name]. */
fun wellKnownPartyFromX500Name(x500Name: CordaX500Name): Party?
/**
* Returns a list of candidate matches for a given string, with optional fuzzy(ish) matching. Fuzzy matching may
@ -286,15 +270,13 @@ interface CordaRPCOps : RPCOps {
fun registeredFlows(): List<String>
/**
* Returns a node's identity from the network map cache, where known.
* Returns a node's info from the network map cache, where known.
*
* @return the node info if available.
*/
fun nodeIdentityFromParty(party: AbstractParty): NodeInfo?
fun nodeInfoFromParty(party: AbstractParty): NodeInfo?
/**
* Clear all network map data from local node cache.
*/
/** Clear all network map data from local node cache. */
fun clearNetworkMapCache()
}
@ -310,15 +292,9 @@ inline fun <reified T : ContractState> CordaRPCOps.vaultTrackBy(criteria: QueryC
return vaultTrackBy(criteria, paging, sorting, T::class.java)
}
/**
* These allow type safe invocations of flows from Kotlin, e.g.:
*
* val rpc: CordaRPCOps = (..)
* rpc.startFlow(::ResolveTransactionsFlow, setOf<SecureHash>(), aliceIdentity)
*
* Note that the passed in constructor function is only used for unification of other type parameters and reification of
* the Class instance of the flow. This could be changed to use the constructor function directly.
*/
// Note that the passed in constructor function is only used for unification of other type parameters and reification of
// the Class instance of the flow. This could be changed to use the constructor function directly.
inline fun <T, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
@Suppress("UNUSED_PARAMETER")
flowConstructor: () -> R
@ -330,6 +306,12 @@ inline fun <T , A, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
arg0: A
): FlowHandle<T> = startFlowDynamic(R::class.java, arg0)
/**
* Extension function for type safe invocation of flows from Kotlin, for example:
*
* val rpc: CordaRPCOps = (..)
* rpc.startFlow(::ResolveTransactionsFlow, setOf<SecureHash>(), aliceIdentity)
*/
inline fun <T, A, B, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
@Suppress("UNUSED_PARAMETER")
flowConstructor: (A, B) -> R,
@ -376,7 +358,7 @@ inline fun <T, A, B, C, D, E, F, reified R : FlowLogic<T>> CordaRPCOps.startFlow
): FlowHandle<T> = startFlowDynamic(R::class.java, arg0, arg1, arg2, arg3, arg4, arg5)
/**
* Same again, except this time with progress-tracking enabled.
* Extension function for type safe invocation of flows from Kotlin, with progress tracking enabled.
*/
@Suppress("unused")
inline fun <T, reified R : FlowLogic<T>> CordaRPCOps.startTrackedFlow(

View File

@ -2,7 +2,6 @@ package net.corda.core.messaging
import net.corda.core.serialization.CordaSerializable
/** The interface for a group of message recipients (which may contain only one recipient) */
@CordaSerializable
interface MessageRecipients

View File

@ -22,13 +22,4 @@ abstract class CordaPluginRegistry {
* @return true if you register types, otherwise you will be filtered out of the list of plugins considered in future.
*/
open fun customizeSerialization(custom: SerializationCustomization): Boolean = false
/**
* Optionally, custom schemas to be used for contract state persistence and vault custom querying
*
* For example, if you implement the [QueryableState] interface on a new [ContractState]
* it needs to be registered here if you wish to perform custom queries on schema entity attributes using the
* [VaultQueryService] API
*/
open val requiredSchemas: Set<MappedSchema> get() = emptySet()
}

View File

@ -2,49 +2,29 @@ package net.corda.core.node
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.locality
/**
* Information for an advertised service including the service specific identity information.
* The identity can be used in flows and is distinct from the Node's legalIdentity
*/
@CordaSerializable
data class ServiceEntry(val info: ServiceInfo, val identity: PartyAndCertificate)
/**
* Info about a network node that acts on behalf of some form of contract party.
* @param legalIdentitiesAndCerts is a non-empty list, where the first identity is assumed to be the default identity of the node.
*/
// TODO We currently don't support multi-IP/multi-identity nodes, we only left slots in the data structures.
@CordaSerializable
data class NodeInfo(val addresses: List<NetworkHostAndPort>,
// TODO After removing of services these two fields will be merged together and made NonEmptySet.
val legalIdentityAndCert: PartyAndCertificate,
val legalIdentitiesAndCerts: Set<PartyAndCertificate>,
val legalIdentitiesAndCerts: List<PartyAndCertificate>,
val platformVersion: Int,
val advertisedServices: List<ServiceEntry> = emptyList(),
val serial: Long
) {
init {
require(advertisedServices.none { it.identity == legalIdentityAndCert }) {
"Service identities must be different from node legal identity"
}
require(legalIdentitiesAndCerts.isNotEmpty()) { "Node should have at least one legal identity" }
}
val legalIdentity: Party get() = legalIdentityAndCert.party
val notaryIdentity: Party get() = advertisedServices.single { it.info.type.isNotary() }.identity.party
fun serviceIdentities(type: ServiceType): List<Party> {
return advertisedServices.mapNotNull { if (it.info.type.isSubTypeOf(type)) it.identity.party else null }
@Transient private var _legalIdentities: List<Party>? = null
val legalIdentities: List<Party> get() {
return _legalIdentities ?: legalIdentitiesAndCerts.map { it.party }.also { _legalIdentities = it }
}
/**
* Uses node's owner X500 name to infer the node's location. Used in Explorer in map view.
*/
fun getWorldMapLocation(): WorldMapLocation? {
val nodeOwnerLocation = legalIdentity.name.locality
return nodeOwnerLocation.let { CityDatabase[it] }
}
/** Returns true if [party] is one of the identities of this node, else false. */
fun isLegalIdentity(party: Party): Boolean = party in legalIdentities
}

View File

@ -1,13 +0,0 @@
package net.corda.core.node
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.Party
/**
* A service hub to be used by the [CordaPluginRegistry]
*/
interface PluginServiceHub : ServiceHub {
@Deprecated("This is no longer used. Instead annotate the flows produced by your factory with @InitiatedBy and have " +
"them point to the initiating flow class.", level = DeprecationLevel.ERROR)
fun registerFlowInitiator(initiatingFlowClass: Class<out FlowLogic<*>>, serviceFlowFactory: (Party) -> FlowLogic<*>) = Unit
}

View File

@ -1,10 +1,15 @@
package net.corda.core.node
import net.corda.core.contracts.*
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.internal.toMultiMap
import net.corda.core.node.services.*
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.transactions.FilteredTransaction
@ -26,6 +31,9 @@ interface ServicesForResolution {
/** Provides access to storage of arbitrary JAR files (which may contain only data, no code). */
val attachments: AttachmentStorage
/** Provides access to anything relating to cordapps including contract attachment resolution and app context */
val cordappProvider: CordappProvider
/**
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
*
@ -123,38 +131,17 @@ interface ServiceHub : ServicesForResolution {
fun <T : ContractState> toStateAndRef(stateRef: StateRef): StateAndRef<T> {
val stx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
return if (stx.isNotaryChangeTransaction()) {
stx.resolveNotaryChangeTransaction(this).outRef<T>(stateRef.index)
stx.resolveNotaryChangeTransaction(this).outRef(stateRef.index)
} else {
stx.tx.outRef<T>(stateRef.index)
stx.tx.outRef(stateRef.index)
}
}
/**
* Helper property to shorten code for fetching the the [PublicKey] portion of the
* Node's primary signing identity.
* Typical use is during signing in flows and for unit test signing.
* When this [PublicKey] is passed into the signing methods below, or on the KeyManagementService
* the matching [java.security.PrivateKey] will be looked up internally and used to sign.
* If the key is actually a CompositeKey, the first leaf key hosted on this node
* will be used to create the signature.
*/
val legalIdentityKey: PublicKey get() = this.myInfo.legalIdentity.owningKey
/**
* Helper property to shorten code for fetching the the [PublicKey] portion of the
* Node's Notary signing identity. It is required that the Node hosts a notary service,
* otherwise an [IllegalArgumentException] will be thrown.
* Typical use is during signing in flows and for unit test signing.
* When this [PublicKey] is passed into the signing methods below, or on the KeyManagementService
* the matching [java.security.PrivateKey] will be looked up internally and used to sign.
* If the key is actually a [net.corda.core.crypto.CompositeKey], the first leaf key hosted on this node
* will be used to create the signature.
*/
val notaryIdentityKey: PublicKey get() = this.myInfo.notaryIdentity.owningKey
private val legalIdentityKey: PublicKey get() = this.myInfo.legalIdentitiesAndCerts.first().owningKey
// Helper method to construct an initial partially signed transaction from a [TransactionBuilder].
private fun signInitialTransaction(builder: TransactionBuilder, publicKey: PublicKey, signatureMetadata: SignatureMetadata): SignedTransaction {
return builder.toSignedTransaction(keyManagementService, publicKey, signatureMetadata)
return builder.toSignedTransaction(keyManagementService, publicKey, signatureMetadata, this)
}
/**

View File

@ -6,6 +6,8 @@ import java.io.IOException
import java.io.InputStream
import java.nio.file.FileAlreadyExistsException
typealias AttachmentId = SecureHash
/**
* An attachment store records potentially large binary objects, identified by their hash.
*/
@ -14,7 +16,7 @@ interface AttachmentStorage {
* Returns a handle to a locally stored attachment, or null if it's not known. The handle can be used to open
* a stream for the data, which will be a zip/jar file.
*/
fun openAttachment(id: SecureHash): Attachment?
fun openAttachment(id: AttachmentId): Attachment?
/**
* Inserts the given attachment into the store, does *not* close the input stream. This can be an intensive
@ -28,6 +30,6 @@ interface AttachmentStorage {
* @throws IOException if something went wrong.
*/
@Throws(FileAlreadyExistsException::class, IOException::class)
fun importAttachment(jar: InputStream): SecureHash
fun importAttachment(jar: InputStream): AttachmentId
}

View File

@ -1,19 +1,19 @@
package net.corda.core.node.services
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SingletonSerializeAsToken
import kotlin.annotation.AnnotationTarget.CLASS
/**
* Annotate any class that needs to be a long-lived service within the node, such as an oracle, with this annotation.
* Such a class needs to have a constructor with a single parameter of type [net.corda.core.node.PluginServiceHub]. This
* construtor will be invoked during node start to initialise the service. The service hub provided can be used to get
* information about the node that may be necessary for the service. Corda services are created as singletons within
* the node and are available to flows via [net.corda.core.node.ServiceHub.cordaService].
* Such a class needs to have a constructor with a single parameter of type [ServiceHub]. This constructor will be invoked
* during node start to initialise the service. The service hub provided can be used to get information about the node
* that may be necessary for the service. Corda services are created as singletons within the node and are available
* to flows via [ServiceHub.cordaService].
*
* The service class has to implement [net.corda.core.serialization.SerializeAsToken] to ensure correct usage within flows.
* (If possible extend [net.corda.core.serialization.SingletonSerializeAsToken] instead as it removes the boilerplate.)
*
* The annotated class should expose its [ServiceType] via a public static field named `type`, so that the service is
* only loaded in nodes that declare the type in their advertisedServices.
* The service class has to implement [SerializeAsToken] to ensure correct usage within flows. (If possible extend
* [SingletonSerializeAsToken] instead as it removes the boilerplate.)
*/
// TODO Handle the singleton serialisation of Corda services automatically, removing the need to implement SerializeAsToken
// TODO Perhaps this should be an interface or abstract class due to the need for it to implement SerializeAsToken and

View File

@ -1,12 +1,7 @@
package net.corda.core.node.services
import net.corda.core.contracts.PartyAndReference
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import net.corda.core.identity.*
import java.security.InvalidAlgorithmParameterException
import java.security.PublicKey
import java.security.cert.*
@ -18,20 +13,9 @@ import java.security.cert.*
*/
interface IdentityService {
val trustRoot: X509Certificate
val trustRootHolder: X509CertificateHolder
val trustAnchor: TrustAnchor
val caCertStore: CertStore
/**
* Verify and then store an identity.
*
* @param party a party representing a legal entity and the certificate path linking them to the network trust root.
* @throws IllegalArgumentException if the certificate path is invalid.
*/
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
@Deprecated("Use verifyAndRegisterIdentity() instead, which is the same function with a better name")
fun registerIdentity(party: PartyAndCertificate)
/**
* Verify and then store an identity.
*
@ -58,46 +42,48 @@ interface IdentityService {
fun getAllIdentities(): Iterable<PartyAndCertificate>
/**
* Get the certificate and path for a well known identity's owning key.
* Get the certificate and path for a known identity's owning key.
*
* @return the party and certificate, or null if unknown.
*/
fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate?
/**
* Get the certificate and path for a well known identity.
*
* @return the party and certificate.
* @throws IllegalArgumentException if the certificate and path are unknown. This should never happen for a well
* known identity.
* Converts an owning [PublicKey] to the X500Name extended [Party] object if the [Party] has been
* previously registered with the [IdentityService] either as a well known network map identity,
* or as a part of flows creating and exchanging the identity.
* @param key The owning [PublicKey] of the [Party].
* @return Returns a [Party] with a matching owningKey if known, else returns null.
*/
fun certificateFromParty(party: Party): PartyAndCertificate
// There is no method for removing identities, as once we are made aware of a Party we want to keep track of them
// indefinitely. It may be that in the long term we need to drop or archive very old Party information for space,
// but for now this is not supported.
fun partyFromKey(key: PublicKey): Party?
fun partyFromX500Name(principal: X500Name): Party?
/**
* Resolves a party name to the well known identity [Party] instance for this name.
* @param name The [CordaX500Name] to search for.
* @return If known the canonical [Party] with that name, else null.
*/
fun wellKnownPartyFromX500Name(name: CordaX500Name): Party?
/**
* Returns the well known identity from an abstract party. This is intended to resolve the well known identity
* from a confidential identity, however it transparently handles returning the well known identity back if
* Returns the well known identity from an [AbstractParty]. This is intended to resolve the well known identity,
* as visible in the [NetworkMapCache] from a confidential identity.
* It transparently handles returning the well known identity back if
* a well known identity is passed in.
*
* @param party identity to determine well known identity for.
* @return well known identity, if found.
*/
fun partyFromAnonymous(party: AbstractParty): Party?
fun wellKnownPartyFromAnonymous(party: AbstractParty): Party?
/**
* Resolve the well known identity of a party. If the party passed in is already a well known identity
* (i.e. a [Party]) this returns it as-is.
* Returns the well known identity from a PartyAndReference. This is intended to resolve the well known identity,
* as visible in the [NetworkMapCache] from a confidential identity.
* It transparently handles returning the well known identity back if
* a well known identity is passed in.
*
* @return the well known identity, or null if unknown.
*/
fun partyFromAnonymous(partyRef: PartyAndReference) = partyFromAnonymous(partyRef.party)
fun wellKnownPartyFromAnonymous(partyRef: PartyAndReference) = wellKnownPartyFromAnonymous(partyRef.party)
/**
* Resolve the well known identity of a party. Throws an exception if the party cannot be identified.
@ -106,7 +92,7 @@ interface IdentityService {
* @return the well known identity.
* @throws IllegalArgumentException
*/
fun requirePartyFromAnonymous(party: AbstractParty): Party
fun requireWellKnownPartyFromAnonymous(party: AbstractParty): Party
/**
* Returns a list of candidate matches for a given string, with optional fuzzy(ish) matching. Fuzzy matching may

View File

@ -1,15 +1,13 @@
package net.corda.core.node.services
import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.Contract
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.randomOrNull
import net.corda.core.messaging.DataFeed
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.NetworkHostAndPort
import org.bouncycastle.asn1.x500.X500Name
import rx.Observable
import java.security.PublicKey
@ -30,19 +28,14 @@ interface NetworkMapCache {
data class Modified(override val node: NodeInfo, val previousNode: NodeInfo) : MapChange()
}
/** A list of all nodes the cache is aware of */
val partyNodes: List<NodeInfo>
/** A list of nodes that advertise a network map service */
val networkMapNodes: List<NodeInfo>
/** A list of nodes that advertise a notary service */
val notaryNodes: List<NodeInfo> get() = getNodesWithService(ServiceType.notary)
/**
* A list of nodes that advertise a regulatory service. Identifying the correct regulator for a trade is outside
* the scope of the network map service, and this is intended solely as a sanity check on configuration stored
* elsewhere.
* A list of notary services available on the network.
*
* Note that the identities are sorted based on legal name, and the ordering might change once new notaries are introduced.
*/
val regulatorNodes: List<NodeInfo> get() = getNodesWithService(ServiceType.regulator)
/** Tracks changes to the network map cache */
// TODO this list will be taken from NetworkParameters distributed by NetworkMap.
val notaryIdentities: List<Party>
/** Tracks changes to the network map cache. */
val changed: Observable<MapChange>
/** Future to track completion of the NetworkMapService registration. */
val nodeReady: CordaFuture<Void?>
@ -53,18 +46,6 @@ interface NetworkMapCache {
*/
fun track(): DataFeed<List<NodeInfo>, MapChange>
/** Get the collection of nodes which advertise a specific service. */
fun getNodesWithService(serviceType: ServiceType): List<NodeInfo> {
return partyNodes.filter { it.advertisedServices.any { it.info.type.isSubTypeOf(serviceType) } }
}
/**
* Get a recommended node that advertises a service, and is suitable for the specified contract and parties.
* Implementations might understand, for example, the correct regulator to use for specific contracts/parties,
* or the appropriate oracle for a contract.
*/
fun getRecommended(type: ServiceType, contract: Contract, vararg party: Party): NodeInfo? = getNodesWithService(type).firstOrNull()
/**
* Look up the node info for a specific party. Will attempt to de-anonymise the party if applicable; if the party
* is anonymised and the well known party cannot be resolved, it is impossible ot identify the node and therefore this
@ -77,80 +58,41 @@ interface NetworkMapCache {
fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo?
/** Look up the node info for a legal name. */
fun getNodeByLegalName(principal: X500Name): NodeInfo?
fun getNodeByLegalName(name: CordaX500Name): NodeInfo?
/** Look up the node info for a host and port. */
fun getNodeByAddress(address: NetworkHostAndPort): NodeInfo?
fun getPeerByLegalName(name: CordaX500Name): Party? = getNodeByLegalName(name)?.let {
it.legalIdentitiesAndCerts.singleOrNull { it.name == name }?.party
}
/** Return all [NodeInfo]s the node currently is aware of (including ourselves). */
val allNodes: List<NodeInfo>
/**
* Look up the node infos for a specific peer key.
* In general, nodes can advertise multiple identities: a legal identity, and separate identities for each of
* the services it provides. In case of a distributed service run by multiple nodes each participant advertises
* the identity of the *whole group*.
*/
/** Look up the node info for a specific peer key. */
fun getNodeByLegalIdentityKey(identityKey: PublicKey): NodeInfo?
/** Look up all nodes advertising the service owned by [publicKey] */
fun getNodesByAdvertisedServiceIdentityKey(publicKey: PublicKey): List<NodeInfo> {
return partyNodes.filter { it.advertisedServices.any { it.identity.owningKey == publicKey } }
}
fun getNodesByLegalIdentityKey(identityKey: PublicKey): List<NodeInfo>
/** Returns information about the party, which may be a specific node or a service */
fun getPartyInfo(party: Party): PartyInfo?
/** Gets a notary identity by the given name. */
fun getNotary(principal: X500Name): Party? {
val notaryNode = notaryNodes.filter {
it.advertisedServices.any { it.info.type.isSubTypeOf(ServiceType.notary) && it.info.name == principal }
}.randomOrNull()
return notaryNode?.notaryIdentity
}
fun getNotary(name: CordaX500Name): Party? = notaryIdentities.firstOrNull { it.name == name }
/**
* Returns a notary identity advertised by any of the nodes on the network (chosen at random)
* @param type Limits the result to notaries of the specified type (optional)
*/
fun getAnyNotary(type: ServiceType? = null): Party? {
val nodes = if (type == null) {
notaryNodes
} else {
require(type != ServiceType.notary && type.isSubTypeOf(ServiceType.notary)) {
"The provided type must be a specific notary sub-type"
}
notaryNodes.filter { it.advertisedServices.any { it.info.type == type } }
}
return nodes.randomOrNull()?.notaryIdentity
}
/** Checks whether a given party is an advertised notary identity. */
fun isNotary(party: Party): Boolean = party in notaryIdentities
/**
* Returns a service identity advertised by one of the nodes on the network
* @param type Specifies the type of the service
*/
fun getAnyServiceOfType(type: ServiceType): Party? {
for (node in partyNodes) {
val serviceIdentities = node.serviceIdentities(type)
if (serviceIdentities.isNotEmpty()) {
return serviceIdentities.randomOrNull()
}
}
return null;
}
/** Checks whether a given party is an advertised notary identity */
fun isNotary(party: Party): Boolean = notaryNodes.any { it.notaryIdentity == party }
/** Checks whether a given party is an advertised validating notary identity */
/** Checks whether a given party is an validating notary identity. */
fun isValidatingNotary(party: Party): Boolean {
val notary = notaryNodes.firstOrNull { it.notaryIdentity == party }
?: throw IllegalArgumentException("No notary found with identity $party. This is most likely caused " +
"by using the notary node's legal identity instead of its advertised notary identity. " +
"Your options are: ${notaryNodes.map { "\"${it.notaryIdentity.name}\"" }.joinToString()}.")
return notary.advertisedServices.any { it.info.type.isValidatingNotary() }
require(isNotary(party)) { "No notary found with identity $party." }
return !party.name.toString().contains("corda.notary.simple", true) // TODO This implementation will change after introducing of NetworkParameters.
}
/**
* Clear all network map data from local node cache.
*/
/** Clear all network map data from local node cache. */
fun clearNetworkMapCache()
}

View File

@ -3,28 +3,27 @@ package net.corda.core.node.services
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.*
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.NotaryError
import net.corda.core.flows.NotaryException
import net.corda.core.flows.NotaryFlow
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.serialize
import net.corda.core.utilities.loggerFor
import org.slf4j.Logger
import java.security.PublicKey
abstract class NotaryService : SingletonSerializeAsToken() {
abstract val services: ServiceHub
abstract val notaryIdentityKey: PublicKey
abstract fun start()
abstract fun stop()
/**
* Produces a notary service flow which has the corresponding sends and receives as [NotaryFlow.Client].
* @param otherParty client [Party] making the request
* @param otherPartySession client [Party] making the request
*/
abstract fun createServiceFlow(otherParty: Party): FlowLogic<Void?>
abstract fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?>
}
/**
@ -70,11 +69,11 @@ abstract class TrustedAuthorityNotaryService : NotaryService() {
}
fun sign(bits: ByteArray): DigitalSignature.WithKey {
return services.keyManagementService.sign(bits, services.notaryIdentityKey)
return services.keyManagementService.sign(bits, notaryIdentityKey)
}
fun sign(txId: SecureHash): TransactionSignature {
val signableData = SignableData(txId, SignatureMetadata(services.myInfo.platformVersion, Crypto.findSignatureScheme(services.notaryIdentityKey).schemeNumberID))
return services.keyManagementService.sign(signableData, services.notaryIdentityKey)
val signableData = SignableData(txId, SignatureMetadata(services.myInfo.platformVersion, Crypto.findSignatureScheme(notaryIdentityKey).schemeNumberID))
return services.keyManagementService.sign(signableData, notaryIdentityKey)
}
}

View File

@ -1,21 +1,13 @@
package net.corda.core.node.services
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceEntry
import net.corda.core.utilities.NetworkHostAndPort
/**
* Holds information about a [Party], which may refer to either a specific node or a service.
*/
sealed class PartyInfo {
abstract val party: PartyAndCertificate
data class Node(val node: NodeInfo) : PartyInfo() {
override val party get() = node.legalIdentityAndCert
}
data class Service(val service: ServiceEntry) : PartyInfo() {
override val party get() = service.identity
}
abstract val party: Party
data class SingleNode(override val party: Party, val addresses: List<NetworkHostAndPort>): PartyInfo()
data class DistributedNode(override val party: Party): PartyInfo()
}

View File

@ -1,5 +1,6 @@
package net.corda.core.node.services
import net.corda.core.CordaException
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
@ -32,5 +33,4 @@ interface UniquenessProvider {
data class ConsumingTx(val id: SecureHash, val inputIndex: Int, val requestingParty: Party)
}
@CordaSerializable
class UniquenessException(val error: UniquenessProvider.Conflict) : Exception()
class UniquenessException(val error: UniquenessProvider.Conflict) : CordaException(UniquenessException::class.java.name)

View File

@ -33,7 +33,7 @@ interface VaultQueryService {
fun <T : ContractState> _queryBy(criteria: QueryCriteria,
paging: PageSpecification,
sorting: Sort,
contractType: Class<out T>): Vault.Page<T>
contractStateType: Class<out T>): Vault.Page<T>
/**
* Generic vault query function which takes a [QueryCriteria] object to define filters,
@ -51,49 +51,49 @@ interface VaultQueryService {
fun <T : ContractState> _trackBy(criteria: QueryCriteria,
paging: PageSpecification,
sorting: Sort,
contractType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>>
contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>>
// DOCEND VaultQueryAPI
// Note: cannot apply @JvmOverloads to interfaces nor interface implementations
// Java Helpers
fun <T : ContractState> queryBy(contractType: Class<out T>): Vault.Page<T> {
return _queryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractType)
fun <T : ContractState> queryBy(contractStateType: Class<out T>): Vault.Page<T> {
return _queryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType)
}
fun <T : ContractState> queryBy(contractType: Class<out T>, criteria: QueryCriteria): Vault.Page<T> {
return _queryBy(criteria, PageSpecification(), Sort(emptySet()), contractType)
fun <T : ContractState> queryBy(contractStateType: Class<out T>, criteria: QueryCriteria): Vault.Page<T> {
return _queryBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType)
}
fun <T : ContractState> queryBy(contractType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): Vault.Page<T> {
return _queryBy(criteria, paging, Sort(emptySet()), contractType)
fun <T : ContractState> queryBy(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): Vault.Page<T> {
return _queryBy(criteria, paging, Sort(emptySet()), contractStateType)
}
fun <T : ContractState> queryBy(contractType: Class<out T>, criteria: QueryCriteria, sorting: Sort): Vault.Page<T> {
return _queryBy(criteria, PageSpecification(), sorting, contractType)
fun <T : ContractState> queryBy(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): Vault.Page<T> {
return _queryBy(criteria, PageSpecification(), sorting, contractStateType)
}
fun <T : ContractState> queryBy(contractType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification, sorting: Sort): Vault.Page<T> {
return _queryBy(criteria, paging, sorting, contractType)
fun <T : ContractState> queryBy(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification, sorting: Sort): Vault.Page<T> {
return _queryBy(criteria, paging, sorting, contractStateType)
}
fun <T : ContractState> trackBy(contractType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return _trackBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractType)
fun <T : ContractState> trackBy(contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return _trackBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType)
}
fun <T : ContractState> trackBy(contractType: Class<out T>, criteria: QueryCriteria): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return _trackBy(criteria, PageSpecification(), Sort(emptySet()), contractType)
fun <T : ContractState> trackBy(contractStateType: Class<out T>, criteria: QueryCriteria): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return _trackBy(criteria, PageSpecification(), Sort(emptySet()), contractStateType)
}
fun <T : ContractState> trackBy(contractType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return _trackBy(criteria, paging, Sort(emptySet()), contractType)
fun <T : ContractState> trackBy(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return _trackBy(criteria, paging, Sort(emptySet()), contractStateType)
}
fun <T : ContractState> trackBy(contractType: Class<out T>, criteria: QueryCriteria, sorting: Sort): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return _trackBy(criteria, PageSpecification(), sorting, contractType)
fun <T : ContractState> trackBy(contractStateType: Class<out T>, criteria: QueryCriteria, sorting: Sort): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return _trackBy(criteria, PageSpecification(), sorting, contractStateType)
}
fun <T : ContractState> trackBy(contractType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification, sorting: Sort): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return _trackBy(criteria, paging, sorting, contractType)
fun <T : ContractState> trackBy(contractStateType: Class<out T>, criteria: QueryCriteria, paging: PageSpecification, sorting: Sort): DataFeed<Vault.Page<T>, Vault.Update<T>> {
return _trackBy(criteria, paging, sorting, contractStateType)
}
}

View File

@ -9,7 +9,6 @@ import net.corda.core.identity.AbstractParty
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.serialization.CordaSerializable
import net.corda.core.toFuture
import net.corda.core.transactions.CoreTransaction
import net.corda.core.utilities.NonEmptySet
import rx.Observable
import rx.subjects.PublishSubject
@ -228,11 +227,11 @@ interface VaultService {
* However, this is fully generic and can operate with custom [FungibleAsset] states.
* @param lockId The [FlowLogic.runId.uuid] of the current flow used to soft lock the states.
* @param eligibleStatesQuery A custom query object that selects down to the appropriate subset of all states of the
* [contractType]. e.g. by selecting on account, issuer, etc. The query is internally augmented with the UNCONSUMED,
* [contractStateType]. e.g. by selecting on account, issuer, etc. The query is internally augmented with the UNCONSUMED,
* soft lock and contract type requirements.
* @param amount The required amount of the asset, but with the issuer stripped off.
* It is assumed that compatible issuer states will be filtered out by the [eligibleStatesQuery].
* @param contractType class type of the result set.
* @param contractStateType class type of the result set.
* @return Returns a locked subset of the [eligibleStatesQuery] sufficient to satisfy the requested amount,
* or else an empty list and no change in the stored lock states when their are insufficient resources available.
*/
@ -241,7 +240,7 @@ interface VaultService {
fun <T : FungibleAsset<U>, U : Any> tryLockFungibleStatesForSpending(lockId: UUID,
eligibleStatesQuery: QueryCriteria,
amount: Amount<U>,
contractType: Class<out T>): List<StateAndRef<T>>
contractStateType: Class<out T>): List<StateAndRef<T>>
}

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