Merge branch 'os-merge-point' into os-merge-shams

# Conflicts:
#	build.gradle
#	core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt
#	core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt
#	docs/source/api-persistence.rst
#	node-api/src/main/kotlin/net/corda/nodeapi/internal/bridging/BridgeControlListener.kt
#	node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
#	node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
#	settings.gradle
#	testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt
#	testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TransactionDSLInterpreter.kt
#	testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt
This commit is contained in:
Shams Asari
2018-11-14 18:13:01 +00:00
115 changed files with 1262 additions and 687 deletions

14
.ci/kill_corda_procs.cmd Normal file
View File

@ -0,0 +1,14 @@
@echo off
REM Setlocal EnableDelayedExpansion
FOR /F "tokens=1,2 delims= " %%G IN ('jps -l') DO (call :sub %%H %%G)
goto :eof
:sub
IF %1==net.corda.webserver.WebServer taskkill /F /PID %2
IF %1==net.corda.node.Corda taskkill /F /PID %2
IF %1==corda.jar taskkill /F /PID %2
IF %1==corda-webserver.jar taskkill /F /PID %2
IF %1==org.gradle.launcher.daemon.bootstrap.GradleDaemon taskkill /F /PID %2
goto :eof

View File

@ -121,6 +121,7 @@ see changes to this list.
* Lulu Ren (Monad-Labs) * Lulu Ren (Monad-Labs)
* Maksymilian Pawlak (R3) * Maksymilian Pawlak (R3)
* Manila Gauns (Persistent Systems Limited) * Manila Gauns (Persistent Systems Limited)
* Manos Batsis
* Marek Scocovsky (ABSA) * Marek Scocovsky (ABSA)
* marekdapps * marekdapps
* Mark Lauer (Westpac) * Mark Lauer (Westpac)

View File

@ -412,6 +412,8 @@ bintrayConfig {
'corda-notary-bft-smart', 'corda-notary-bft-smart',
'corda-notary-jpa', 'corda-notary-jpa',
'corda-notary-mysql' 'corda-notary-mysql'
'corda-common-configuration-parsing',
'corda-common-validation'
] ]
license { license {
name = 'Apache-2.0' name = 'Apache-2.0'

View File

@ -351,7 +351,11 @@ object JacksonSupport {
val nameMatches = mapper.partiesFromName(parser.text) val nameMatches = mapper.partiesFromName(parser.text)
return when { return when {
nameMatches.isEmpty() -> { nameMatches.isEmpty() -> {
val publicKey = parser.readValueAs<PublicKey>() val publicKey = try {
parser.readValueAs<PublicKey>()
} catch (e: Exception) {
throw JsonParseException(parser, "No matching Party found, then tried to directly deserialise ${parser.text} as a PublicKey with no success", e)
}
mapper.partyFromKey(publicKey) mapper.partyFromKey(publicKey)
?: throw JsonParseException(parser, "Could not find a Party with key ${publicKey.toStringShort()}") ?: throw JsonParseException(parser, "Could not find a Party with key ${publicKey.toStringShort()}")
} }

View File

@ -204,13 +204,13 @@ class NodeMonitorModel : AutoCloseable {
val nodeInfo = _connection.proxy.nodeInfo() val nodeInfo = _connection.proxy.nodeInfo()
require(nodeInfo.legalIdentitiesAndCerts.isNotEmpty()) require(nodeInfo.legalIdentitiesAndCerts.isNotEmpty())
_connection _connection
} catch (throwable: Throwable) { } catch (exception: Exception) {
if (shouldRetry) { if (shouldRetry) {
// Deliberately not logging full stack trace as it will be full of internal stacktraces. // Deliberately not logging full stack trace as it will be full of internal stacktraces.
logger.info("Exception upon establishing connection: {}", throwable.message) logger.info("Exception upon establishing connection: {}", exception.message)
null null
} else { } else {
throw throwable throw exception
} }
} }

View File

@ -124,7 +124,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance")) {
nodeIsShut.onCompleted() nodeIsShut.onCompleted()
} catch (e: ActiveMQSecurityException) { } catch (e: ActiveMQSecurityException) {
// nothing here - this happens if trying to connect before the node is started // nothing here - this happens if trying to connect before the node is started
} catch (e: Throwable) { } catch (e: Exception) {
nodeIsShut.onError(e) nodeIsShut.onError(e)
} }
}, 1, 1, TimeUnit.SECONDS) }, 1, 1, TimeUnit.SECONDS)

View File

@ -100,6 +100,8 @@ class RPCClientProxyHandler(
private val log = contextLogger() private val log = contextLogger()
// To check whether toString() is being invoked // To check whether toString() is being invoked
val toStringMethod: Method = Object::toString.javaMethod!! val toStringMethod: Method = Object::toString.javaMethod!!
val equalsMethod: Method = Object::equals.javaMethod!!
val hashCodeMethod: Method = Object::hashCode.javaMethod!!
private fun addRpcCallSiteToThrowable(throwable: Throwable, callSite: CallSite) { private fun addRpcCallSiteToThrowable(throwable: Throwable, callSite: CallSite) {
var currentThrowable = throwable var currentThrowable = throwable
@ -234,7 +236,13 @@ class RPCClientProxyHandler(
lifeCycle.requireState { it == State.STARTED || it == State.SERVER_VERSION_NOT_SET } lifeCycle.requireState { it == State.STARTED || it == State.SERVER_VERSION_NOT_SET }
checkProtocolVersion(method) checkProtocolVersion(method)
if (method == toStringMethod) { if (method == toStringMethod) {
return "Client RPC proxy for $rpcOpsClass" return toString()
}
if (method == equalsMethod) {
return equals(arguments?.getOrNull(0))
}
if (method == hashCodeMethod) {
return hashCode()
} }
if (consumerSession!!.isClosed) { if (consumerSession!!.isClosed) {
throw RPCException("RPC Proxy is closed") throw RPCException("RPC Proxy is closed")
@ -364,6 +372,8 @@ class RPCClientProxyHandler(
close(false) close(false)
} }
/** /**
* Closes this handler and sends notifications to all observables, so it can immediately clean up resources. * Closes this handler and sends notifications to all observables, so it can immediately clean up resources.
* Notifications sent to observables are to be acknowledged, therefore this call blocks until all acknowledgements are received. * Notifications sent to observables are to be acknowledged, therefore this call blocks until all acknowledgements are received.
@ -554,8 +564,8 @@ class RPCClientProxyHandler(
observationExecutorPool.run(k) { observationExecutorPool.run(k) {
try { try {
m[k]?.onError(ConnectionFailureException()) m[k]?.onError(ConnectionFailureException())
} catch (th: Throwable) { } catch (e: Exception) {
log.error("Unexpected exception when RPC connection failure handling", th) log.error("Unexpected exception when RPC connection failure handling", e)
} }
} }
} }
@ -568,6 +578,32 @@ class RPCClientProxyHandler(
rpcReplyMap.clear() rpcReplyMap.clear()
callSiteMap?.clear() callSiteMap?.clear()
} }
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as RPCClientProxyHandler
if (rpcUsername != other.rpcUsername) return false
if (clientAddress != other.clientAddress) return false
if (sessionId != other.sessionId) return false
if (targetLegalIdentity != other.targetLegalIdentity) return false
return true
}
override fun hashCode(): Int {
var result = rpcUsername.hashCode()
result = 31 * result + clientAddress.hashCode()
result = 31 * result + sessionId.hashCode()
result = 31 * result + (targetLegalIdentity?.hashCode() ?: 0)
return result
}
override fun toString(): String {
return "{rpcUsername='$rpcUsername', clientAddress=$clientAddress, sessionId=$sessionId, targetLegalIdentity=$targetLegalIdentity}"
}
} }
private typealias RpcObservableMap = Cache<InvocationId, UnicastSubject<Notification<*>>> private typealias RpcObservableMap = Cache<InvocationId, UnicastSubject<Notification<*>>>

View File

@ -17,7 +17,7 @@ dependencies {
} }
jar { jar {
baseName 'common-configuration-parsing' baseName 'corda-common-configuration-parsing'
} }
publish { publish {

View File

@ -117,7 +117,7 @@ private class OptionalPropertyWithDefault<TYPE : Any>(delegate: Configuration.Pr
private class FunctionalProperty<TYPE, MAPPED : Any>(delegate: Configuration.Property.Definition.Standard<TYPE>, private val mappedTypeName: String, internal val extractListValue: (Config, String) -> List<TYPE>, private val convert: (TYPE) -> Valid<MAPPED>) : RequiredDelegatedProperty<MAPPED, Configuration.Property.Definition.Standard<TYPE>>(delegate), Configuration.Property.Definition.Standard<MAPPED> { private class FunctionalProperty<TYPE, MAPPED : Any>(delegate: Configuration.Property.Definition.Standard<TYPE>, private val mappedTypeName: String, internal val extractListValue: (Config, String) -> List<TYPE>, private val convert: (TYPE) -> Valid<MAPPED>) : RequiredDelegatedProperty<MAPPED, Configuration.Property.Definition.Standard<TYPE>>(delegate), Configuration.Property.Definition.Standard<MAPPED> {
override fun valueIn(configuration: Config) = convert.invoke(delegate.valueIn(configuration)).orThrow() override fun valueIn(configuration: Config) = convert.invoke(delegate.valueIn(configuration)).value()
override val typeName: String = if (super.typeName == "#$mappedTypeName") super.typeName else "$mappedTypeName(${super.typeName})" override val typeName: String = if (super.typeName == "#$mappedTypeName") super.typeName else "$mappedTypeName(${super.typeName})"

View File

@ -39,7 +39,7 @@ class SpecificationTest {
val rpcSettings = RpcSettingsSpec.parse(configuration) val rpcSettings = RpcSettingsSpec.parse(configuration)
assertThat(rpcSettings.isValid).isTrue() assertThat(rpcSettings.isValid).isTrue()
assertThat(rpcSettings.orThrow()).satisfies { value -> assertThat(rpcSettings.value()).satisfies { value ->
assertThat(value.useSsl).isEqualTo(useSslValue) assertThat(value.useSsl).isEqualTo(useSslValue)
assertThat(value.addresses).satisfies { addresses -> assertThat(value.addresses).satisfies { addresses ->

View File

@ -18,7 +18,7 @@ class VersionExtractorTest {
val versionValue = Configuration.Version.Extractor.DEFAULT_VERSION_VALUE + 1 val versionValue = Configuration.Version.Extractor.DEFAULT_VERSION_VALUE + 1
val rawConfiguration = configObject("configuration" to configObject("metadata" to configObject("version" to versionValue), "node" to configObject("p2pAddress" to "localhost:8080"))).toConfig() val rawConfiguration = configObject("configuration" to configObject("metadata" to configObject("version" to versionValue), "node" to configObject("p2pAddress" to "localhost:8080"))).toConfig()
val version = extractVersion.invoke(rawConfiguration).orThrow() val version = extractVersion.invoke(rawConfiguration).value()
assertThat(version).isEqualTo(versionValue) assertThat(version).isEqualTo(versionValue)
} }
@ -27,7 +27,7 @@ class VersionExtractorTest {
val rawConfiguration = configObject("configuration" to configObject("node" to configObject("p2pAddress" to "localhost:8080"))).toConfig() val rawConfiguration = configObject("configuration" to configObject("node" to configObject("p2pAddress" to "localhost:8080"))).toConfig()
val version = extractVersion.invoke(rawConfiguration).orThrow() val version = extractVersion.invoke(rawConfiguration).value()
assertThat(version).isEqualTo(Configuration.Version.Extractor.DEFAULT_VERSION_VALUE) assertThat(version).isEqualTo(Configuration.Version.Extractor.DEFAULT_VERSION_VALUE)
} }
@ -36,7 +36,7 @@ class VersionExtractorTest {
val rawConfiguration = configObject("configuration" to configObject("metadata" to configObject(), "node" to configObject("p2pAddress" to "localhost:8080"))).toConfig() val rawConfiguration = configObject("configuration" to configObject("metadata" to configObject(), "node" to configObject("p2pAddress" to "localhost:8080"))).toConfig()
val version = extractVersion.invoke(rawConfiguration).orThrow() val version = extractVersion.invoke(rawConfiguration).value()
assertThat(version).isEqualTo(Configuration.Version.Extractor.DEFAULT_VERSION_VALUE) assertThat(version).isEqualTo(Configuration.Version.Extractor.DEFAULT_VERSION_VALUE)
} }
@ -46,7 +46,7 @@ class VersionExtractorTest {
val rawConfiguration = configObject("configuration" to configObject("metadata" to configObject("version" to null), "node" to configObject("p2pAddress" to "localhost:8080"))).toConfig() val rawConfiguration = configObject("configuration" to configObject("metadata" to configObject("version" to null), "node" to configObject("p2pAddress" to "localhost:8080"))).toConfig()
val version = extractVersion.invoke(rawConfiguration).orThrow() val version = extractVersion.invoke(rawConfiguration).value()
assertThat(version).isEqualTo(Configuration.Version.Extractor.DEFAULT_VERSION_VALUE) assertThat(version).isEqualTo(Configuration.Version.Extractor.DEFAULT_VERSION_VALUE)
} }
@ -56,7 +56,7 @@ class VersionExtractorTest {
val rawConfiguration = configObject().toConfig() val rawConfiguration = configObject().toConfig()
val version = extractVersion.invoke(rawConfiguration).orThrow() val version = extractVersion.invoke(rawConfiguration).value()
assertThat(version).isEqualTo(Configuration.Version.Extractor.DEFAULT_VERSION_VALUE) assertThat(version).isEqualTo(Configuration.Version.Extractor.DEFAULT_VERSION_VALUE)
} }

View File

@ -52,7 +52,7 @@ class VersionedParsingExampleTest {
private fun assertResult(result: Valid<RpcSettings>, principalAddressValue: Address, adminAddressValue: Address) { private fun assertResult(result: Valid<RpcSettings>, principalAddressValue: Address, adminAddressValue: Address) {
assertThat(result.isValid).isTrue() assertThat(result.isValid).isTrue()
assertThat(result.orThrow()).satisfies { value -> assertThat(result.value()).satisfies { value ->
assertThat(value.principal).isEqualTo(principalAddressValue) assertThat(value.principal).isEqualTo(principalAddressValue)
assertThat(value.admin).isEqualTo(adminAddressValue) assertThat(value.admin).isEqualTo(adminAddressValue)
@ -94,7 +94,7 @@ class VersionedParsingExampleTest {
val adminAddress = addressFor(adminHost, adminPort) val adminAddress = addressFor(adminHost, adminPort)
return if (principalAddress.isValid && adminAddress.isValid) { return if (principalAddress.isValid && adminAddress.isValid) {
return valid(RpcSettings(principalAddress.value, adminAddress.value)) return valid(RpcSettings(principalAddress.value(), adminAddress.value()))
} else { } else {
invalid(principalAddress.errors + adminAddress.errors) invalid(principalAddress.errors + adminAddress.errors)
} }
@ -128,4 +128,4 @@ class VersionedParsingExampleTest {
} }
} }
private fun Configuration.Version.Extractor.parseRequired(config: Config, options: Configuration.Validation.Options = Configuration.Validation.Options.defaults) = parse(config, options).map { it ?: throw IllegalStateException("Absent version value.") } private fun Configuration.Version.Extractor.parseRequired(config: Config, options: Configuration.Validation.Options = Configuration.Validation.Options.defaults) = parse(config, options).map { it }

View File

@ -11,7 +11,7 @@ dependencies {
} }
jar { jar {
baseName 'common-validation' baseName 'corda-common-validation'
} }
publish { publish {

View File

@ -8,11 +8,11 @@ import java.util.Collections.emptySet
*/ */
interface Validated<TARGET, ERROR> { interface Validated<TARGET, ERROR> {
/** /**
* The valid [TARGET] value. * Returns a valid [TARGET] if no validation errors are present. Otherwise, it throws the exception produced by [exceptionOnErrors], defaulting to [IllegalStateException].
* *
* @throws IllegalStateException if accessed in presence of validation errors. * @throws IllegalStateException or the result of [exceptionOnErrors] if there are errors.
*/ */
val value: TARGET fun value(exceptionOnErrors: (Set<ERROR>) -> Exception = { errors -> IllegalStateException(errors.joinToString(System.lineSeparator())) }): TARGET
/** /**
* The errors produced during validation, if any. * The errors produced during validation, if any.
@ -32,14 +32,7 @@ interface Validated<TARGET, ERROR> {
/** /**
* Returns the underlying value as optional, with a null result instead of an exception if validation rules were violated. * Returns the underlying value as optional, with a null result instead of an exception if validation rules were violated.
*/ */
val optional: TARGET? get() = if (isValid) value else null val optional: TARGET? get() = if (isValid) value() else null
/**
* Returns a valid [TARGET] if no validation errors are present. Otherwise, it throws the exception produced by [exceptionOnErrors], defaulting to [IllegalStateException].
*
* @throws IllegalStateException or the result of [exceptionOnErrors] if there are errors.
*/
fun orThrow(exceptionOnErrors: (Set<ERROR>) -> Exception = { errors -> IllegalStateException(errors.joinToString(System.lineSeparator())) }): TARGET
/** /**
* Applies the [convert] function to the [TARGET] value, if valid. Otherwise, returns a [Validated] monad with a [MAPPED] generic type and the current errors set. * Applies the [convert] function to the [TARGET] value, if valid. Otherwise, returns a [Validated] monad with a [MAPPED] generic type and the current errors set.
@ -62,7 +55,7 @@ interface Validated<TARGET, ERROR> {
*/ */
fun doIfValid(action: (TARGET) -> Unit): Validated<TARGET, ERROR> { fun doIfValid(action: (TARGET) -> Unit): Validated<TARGET, ERROR> {
if (isValid) { if (isValid) {
action.invoke(value) action.invoke(value())
} }
return this return this
} }
@ -110,10 +103,10 @@ interface Validated<TARGET, ERROR> {
/** /**
* A successful validation result, containing a valid [TARGET] value and no [ERROR]s. * A successful validation result, containing a valid [TARGET] value and no [ERROR]s.
*/ */
class Successful<TARGET, ERROR>(override val value: TARGET) : Result<TARGET, ERROR>(), Validated<TARGET, ERROR> { class Successful<TARGET, ERROR>(private val value: TARGET) : Result<TARGET, ERROR>(), Validated<TARGET, ERROR> {
override val errors: Set<ERROR> = emptySet<ERROR>() override val errors: Set<ERROR> = emptySet<ERROR>()
override fun orThrow(exceptionOnErrors: (Set<ERROR>) -> Exception) = value override fun value(exceptionOnErrors: (Set<ERROR>) -> Exception) = value
override fun <MAPPED> map(convert: (TARGET) -> MAPPED): Validated<MAPPED, ERROR> { override fun <MAPPED> map(convert: (TARGET) -> MAPPED): Validated<MAPPED, ERROR> {
return valid(convert.invoke(value)) return valid(convert.invoke(value))
@ -136,9 +129,7 @@ interface Validated<TARGET, ERROR> {
require(errors.isNotEmpty()) require(errors.isNotEmpty())
} }
override val value: TARGET get() = throw IllegalStateException("Invalid state.") override fun value(exceptionOnErrors: (Set<ERROR>) -> Exception) = throw exceptionOnErrors.invoke(errors)
override fun orThrow(exceptionOnErrors: (Set<ERROR>) -> Exception) = throw exceptionOnErrors.invoke(errors)
override fun <MAPPED> map(convert: (TARGET) -> MAPPED): Validated<MAPPED, ERROR> { override fun <MAPPED> map(convert: (TARGET) -> MAPPED): Validated<MAPPED, ERROR> {
return invalid(errors) return invalid(errors)

View File

@ -4,6 +4,7 @@ import net.corda.core.KeepForDJVM
import net.corda.core.internal.extractFile import net.corda.core.internal.extractFile
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import java.security.PublicKey import java.security.PublicKey
@ -36,10 +37,10 @@ interface Attachment : NamedByHash {
@JvmDefault @JvmDefault
fun openAsJAR(): JarInputStream { fun openAsJAR(): JarInputStream {
val stream = open() val stream = open()
try { return try {
return JarInputStream(stream) JarInputStream(stream)
} catch (t: Throwable) { } catch (e: IOException) {
stream.use { throw t } stream.use { throw e }
} }
} }

View File

@ -149,6 +149,16 @@ abstract class TransactionVerificationException(val txId: SecureHash, message: S
"is not satisfied. Encumbered states should also be referenced as an encumbrance of another state to form " + "is not satisfied. Encumbered states should also be referenced as an encumbrance of another state to form " +
"a full cycle. Offending indices $nonMatching", null) "a full cycle. Offending indices $nonMatching", null)
/**
* All encumbered states should be assigned to the same notary. This is due to the fact that multi-notary
* transactions are not supported and thus two encumbered states with different notaries cannot be consumed
* in the same transaction.
*/
@KeepForDJVM
class TransactionNotaryMismatchEncumbranceException(txId: SecureHash, encumberedIndex: Int, encumbranceIndex: Int, encumberedNotary: Party, encumbranceNotary: Party)
: TransactionVerificationException(txId, "Encumbered output states assigned to different notaries found. " +
"Output state with index $encumberedIndex is assigned to notary [$encumberedNotary], while its encumbrance with index $encumbranceIndex is assigned to notary [$encumbranceNotary]", null)
/** Whether the inputs or outputs list contains an encumbrance issue, see [TransactionMissingEncumbranceException]. */ /** Whether the inputs or outputs list contains an encumbrance issue, see [TransactionMissingEncumbranceException]. */
@CordaSerializable @CordaSerializable
@KeepForDJVM @KeepForDJVM

View File

@ -49,6 +49,7 @@ sealed class NotaryError {
} }
/** Occurs when the transaction sent for notarisation is assigned to a different notary identity. */ /** Occurs when the transaction sent for notarisation is assigned to a different notary identity. */
@Deprecated("Deprecated since platform version 4. This object is no longer used, [TransactionInvalid] will be reported in case of notary mismatch")
object WrongNotary : NotaryError() object WrongNotary : NotaryError()
/** Occurs when the notarisation request signature does not verify for the provided transaction. */ /** Occurs when the notarisation request signature does not verify for the provided transaction. */

View File

@ -72,9 +72,9 @@ object JarSignatureCollector {
private fun Sequence<JarEntry>.toFileSignerSet(): Sequence<Pair<String, Set<CodeSigner>>> = private fun Sequence<JarEntry>.toFileSignerSet(): Sequence<Pair<String, Set<CodeSigner>>> =
map { entry -> entry.name to (entry.codeSigners?.toSet() ?: emptySet()) } map { entry -> entry.name to (entry.codeSigners?.toSet() ?: emptySet()) }
private fun Set<CodeSigner>.toOrderedPublicKeys(): List<PublicKey> = map { private fun Set<CodeSigner>.toPartiesOrderedByName(): List<Party> = map {
(it.signerCertPath.certificates[0] as X509Certificate).publicKey Party(it.signerCertPath.certificates[0] as X509Certificate)
}.sortedBy { it.hash} // Sorted for determinism. }.sortedBy { it.name.toString() } // Sorted for determinism.
private fun Set<CodeSigner>.toPartiesOrderedByName(): List<Party> = map { private fun Set<CodeSigner>.toPartiesOrderedByName(): List<Party> = map {
Party(it.signerCertPath.certificates[0] as X509Certificate) Party(it.signerCertPath.certificates[0] as X509Certificate)

View File

@ -71,8 +71,8 @@ fun <V, W> CordaFuture<out V>.flatMap(transform: (V) -> CordaFuture<out W>): Cor
thenMatch(success@ { thenMatch(success@ {
result.captureLater(try { result.captureLater(try {
transform(it) transform(it)
} catch (t: Throwable) { } catch (e: Exception) {
result.setException(t) result.setException(e)
return@success return@success
}) })
}, { }, {
@ -128,8 +128,8 @@ interface ValueOrException<in V> {
fun capture(block: () -> V): Boolean { fun capture(block: () -> V): Boolean {
return set(try { return set(try {
block() block()
} catch (t: Throwable) { } catch (e: Exception) {
return setException(t) return setException(e)
}) })
} }
} }
@ -153,8 +153,8 @@ internal class CordaFutureImpl<V>(private val impl: CompletableFuture<V> = Compl
impl.whenComplete { _, _ -> impl.whenComplete { _, _ ->
try { try {
callback(this) callback(this)
} catch (t: Throwable) { } catch (e: Exception) {
log.error(listenerFailedMessage, t) log.error(listenerFailedMessage, e)
} }
} }
} }

View File

@ -24,61 +24,84 @@ abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service:
private const val maxAllowedInputsAndReferences = 10_000 private const val maxAllowedInputsAndReferences = 10_000
} }
private var transactionId: SecureHash? = null
@Suspendable @Suspendable
override fun call(): Void? { override fun call(): Void? {
check(serviceHub.myInfo.legalIdentities.any { serviceHub.networkMapCache.isNotary(it) }) { check(serviceHub.myInfo.legalIdentities.any { serviceHub.networkMapCache.isNotary(it) }) {
"We are not a notary on the network" "We are not a notary on the network"
} }
val requestPayload = otherSideSession.receive<NotarisationPayload>().unwrap { it } val requestPayload = otherSideSession.receive<NotarisationPayload>().unwrap { it }
var txId: SecureHash? = null
try { try {
val parts = validateRequest(requestPayload) val tx: TransactionParts = validateRequest(requestPayload)
txId = parts.id val request = NotarisationRequest(tx.inputs, tx.id)
checkNotary(parts.notary) validateRequestSignature(request, requestPayload.requestSignature)
verifyTransaction(requestPayload)
service.commitInputStates( service.commitInputStates(
parts.inputs, tx.inputs,
txId, tx.id,
otherSideSession.counterparty, otherSideSession.counterparty,
requestPayload.requestSignature, requestPayload.requestSignature,
parts.timestamp, tx.timeWindow,
parts.references tx.references)
)
signTransactionAndSendResponse(txId)
} catch (e: NotaryInternalException) { } catch (e: NotaryInternalException) {
throw NotaryException(e.error, txId) logError(e.error)
// Any exception that's not a NotaryInternalException is assumed to be an unexpected internal error
// that is not relayed back to the client.
throw NotaryException(e.error, transactionId)
} }
signTransactionAndSendResponse(transactionId!!)
return null return null
} }
/** Checks whether the number of input states is too large. */ private fun validateRequest(requestPayload: NotarisationPayload): TransactionParts {
protected fun checkInputs(inputs: List<StateRef>) { try {
if (inputs.size > maxAllowedInputsAndReferences) { val transaction = extractParts(requestPayload)
val error = NotaryError.TransactionInvalid( transactionId = transaction.id
IllegalArgumentException("A transaction cannot have more than $maxAllowedInputsAndReferences " + checkNotary(transaction.notary)
"inputs or references, received: ${inputs.size}") checkInputs(transaction.inputs + transaction.references)
) return transaction
} catch (e: Exception) {
val error = NotaryError.TransactionInvalid(e)
throw NotaryInternalException(error) throw NotaryInternalException(error)
} }
} }
/** /** Extract the common transaction components required for notarisation. */
* Implement custom logic to perform transaction verification based on validity and privacy requirements. protected abstract fun extractParts(requestPayload: NotarisationPayload): TransactionParts
*/
/** Check if transaction is intended to be signed by this notary. */
@Suspendable @Suspendable
protected abstract fun validateRequest(requestPayload: NotarisationPayload): TransactionParts private fun checkNotary(notary: Party?) {
require(notary?.owningKey == service.notaryIdentityKey) {
"The notary specified on the transaction: [$notary] does not match the notary service's identity: [${service.notaryIdentityKey}] "
}
}
/** Checks whether the number of input states is too large. */
private fun checkInputs(inputs: List<StateRef>) {
require(inputs.size < maxAllowedInputsAndReferences) {
"A transaction cannot have more than $maxAllowedInputsAndReferences " +
"inputs or references, received: ${inputs.size}"
}
}
/** Verifies that the correct notarisation request was signed by the counterparty. */ /** Verifies that the correct notarisation request was signed by the counterparty. */
protected fun validateRequestSignature(request: NotarisationRequest, signature: NotarisationRequestSignature) { private fun validateRequestSignature(request: NotarisationRequest, signature: NotarisationRequestSignature) {
val requestingParty = otherSideSession.counterparty val requestingParty = otherSideSession.counterparty
request.verifySignature(signature, requestingParty) request.verifySignature(signature, requestingParty)
} }
/** Check if transaction is intended to be signed by this notary. */ /**
* Override to implement custom logic to perform transaction verification based on validity and privacy requirements.
*/
@Suspendable @Suspendable
protected fun checkNotary(notary: Party?) { protected open fun verifyTransaction(requestPayload: NotarisationPayload) {
if (notary?.owningKey != service.notaryIdentityKey) {
throw NotaryInternalException(NotaryError.WrongNotary)
}
} }
@Suspendable @Suspendable
@ -91,15 +114,23 @@ abstract class NotaryServiceFlow(val otherSideSession: FlowSession, val service:
* The minimum amount of information needed to notarise a transaction. Note that this does not include * The minimum amount of information needed to notarise a transaction. Note that this does not include
* any sensitive transaction details. * any sensitive transaction details.
*/ */
protected data class TransactionParts @JvmOverloads constructor( protected data class TransactionParts(
val id: SecureHash, val id: SecureHash,
val inputs: List<StateRef>, val inputs: List<StateRef>,
val timestamp: TimeWindow?, val timeWindow: TimeWindow?,
val notary: Party?, val notary: Party?,
val references: List<StateRef> = emptyList() val references: List<StateRef> = emptyList()
) { )
fun copy(id: SecureHash, inputs: List<StateRef>, timestamp: TimeWindow?, notary: Party?): TransactionParts {
return TransactionParts(id, inputs, timestamp, notary, references) private fun logError(error: NotaryError) {
val errorCause = when (error) {
is NotaryError.RequestSignatureInvalid -> error.cause
is NotaryError.TransactionInvalid -> error.cause
is NotaryError.General -> error.cause
else -> null
}
if (errorCause != null) {
logger.error("Error notarising transaction $transactionId", errorCause)
} }
} }
} }

View File

@ -2,43 +2,32 @@ package net.corda.core.internal.notary
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.isFulfilledBy import net.corda.core.crypto.isFulfilledBy
import net.corda.core.flows.* import net.corda.core.flows.*
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import java.security.InvalidKeyException import net.corda.core.utilities.toBase58String
import java.security.SignatureException
import java.time.Instant import java.time.Instant
/** Verifies the signature against this notarisation request. Checks that the signature is issued by the right party. */ /** Verifies the signature against this notarisation request. Checks that the signature is issued by the right party. */
fun NotarisationRequest.verifySignature(requestSignature: NotarisationRequestSignature, intendedSigner: Party) { fun NotarisationRequest.verifySignature(requestSignature: NotarisationRequestSignature, intendedSigner: Party) {
try {
val signature = requestSignature.digitalSignature val signature = requestSignature.digitalSignature
if (intendedSigner.owningKey != signature.by) { require(intendedSigner.owningKey == signature.by) {
val errorMessage = "Expected a signature by ${intendedSigner.owningKey}, but received by ${signature.by}}" "Expected a signature by ${intendedSigner.owningKey.toBase58String()}, but received by ${signature.by.toBase58String()}}"
throw NotaryInternalException(NotaryError.RequestSignatureInvalid(IllegalArgumentException(errorMessage)))
} }
// TODO: if requestSignature was generated over an old version of NotarisationRequest, we need to be able to // TODO: if requestSignature was generated over an old version of NotarisationRequest, we need to be able to
// reserialize it in that version to get the exact same bytes. Modify the serialization logic once that's // reserialize it in that version to get the exact same bytes. Modify the serialization logic once that's
// available. // available.
val expectedSignedBytes = this.serialize().bytes val expectedSignedBytes = this.serialize().bytes
verifyCorrectBytesSigned(signature, expectedSignedBytes) signature.verify(expectedSignedBytes)
}
private fun verifyCorrectBytesSigned(signature: DigitalSignature.WithKey, bytes: ByteArray) {
try {
signature.verify(bytes)
} catch (e: Exception) { } catch (e: Exception) {
when (e) {
is InvalidKeyException, is SignatureException -> {
val error = NotaryError.RequestSignatureInvalid(e) val error = NotaryError.RequestSignatureInvalid(e)
throw NotaryInternalException(error) throw NotaryInternalException(error)
} }
else -> throw e
}
}
} }
/** /**

View File

@ -53,6 +53,7 @@ abstract class SinglePartyNotaryService : NotaryService() {
references references
) )
) )
if (result is UniquenessProvider.Result.Failure) { if (result is UniquenessProvider.Result.Failure) {
throw NotaryInternalException(result.error) throw NotaryInternalException(result.error)
} }

View File

@ -18,6 +18,8 @@ import net.corda.core.transactions.TransactionBuilder
import java.security.PublicKey import java.security.PublicKey
import java.sql.Connection import java.sql.Connection
import java.time.Clock import java.time.Clock
import java.util.function.Consumer
import javax.persistence.EntityManager
/** /**
* Subset of node services that are used for loading transactions from the wire into fully resolved, looked up * Subset of node services that are used for loading transactions from the wire into fully resolved, looked up
@ -358,6 +360,28 @@ interface ServiceHub : ServicesForResolution {
*/ */
fun jdbcSession(): Connection fun jdbcSession(): Connection
/**
* Exposes the Java Persistence API (JPA) to flows via a restricted [EntityManager]. This method can be used to
* persist and query entities which inherit from [MappedSchema]. This is particularly useful if off-ledger data
* needs to be kept in conjunction with on-ledger state data.
*
* NOTE: Suspendable flow operations such as send, receive, subFlow and sleep, cannot be called within the lambda.
*
* @param block a lambda function with access to an [EntityManager].
*/
fun <T : Any> withEntityManager(block: EntityManager.() -> T): T
/**
* Exposes the Java Persistence API (JPA) to flows via a restricted [EntityManager]. This method can be used to
* persist and query entities which inherit from [MappedSchema]. This is particularly useful if off-ledger data
* needs to be kept in conjunction with on-ledger state data.
*
* NOTE: Suspendable flow operations such as send, receive, subFlow and sleep, cannot be called within the lambda.
*
* @param block a lambda function with access to an [EntityManager].
*/
fun withEntityManager(block: Consumer<EntityManager>)
/** /**
* Allows the registration of a callback that may inform services when the app is shutting down. * Allows the registration of a callback that may inform services when the app is shutting down.
* *

View File

@ -11,9 +11,11 @@ import net.corda.core.internal.checkMinimumPlatformVersion
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.Try
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import java.util.* import java.util.*
import java.util.function.Predicate import java.util.function.Predicate
import kotlin.collections.HashSet
import net.corda.core.utilities.warnOnce import net.corda.core.utilities.warnOnce
/** /**
@ -58,6 +60,17 @@ data class LedgerTransaction @JvmOverloads constructor(
private companion object { private companion object {
val logger = loggerFor<LedgerTransaction>() val logger = loggerFor<LedgerTransaction>()
private fun contractClassFor(className: ContractClassName, classLoader: ClassLoader?): Try<Class<out Contract>> {
return Try.on {
(classLoader ?: this::class.java.classLoader)
.loadClass(className)
.asSubclass(Contract::class.java)
}
}
private fun stateToContractClass(state: TransactionState<ContractState>): Try<Class<out Contract>> {
return contractClassFor(state.contract, state.data::class.java.classLoader)
}
} }
val inputStates: List<ContractState> get() = inputs.map { it.state.data } val inputStates: List<ContractState> get() = inputs.map { it.state.data }
@ -91,25 +104,7 @@ data class LedgerTransaction @JvmOverloads constructor(
} }
/** /**
* For all input and output [TransactionState]s, validates that the wrapped [ContractState] matches up with the * Verify that package ownership is respected.
* wrapped [Contract], as declared by the [BelongsToContract] annotation on the [ContractState]'s class.
*
* A warning will be written to the log if any mismatch is detected.
*/
private fun validateStatesAgainstContract() = allStates.forEach(::validateStateAgainstContract)
private fun validateStateAgainstContract(state: TransactionState<ContractState>) {
state.data.requiredContractClassName?.let { requiredContractClassName ->
if (state.contract != requiredContractClassName)
logger.warnOnce("""
State of class ${state.data::class.java.typeName} belongs to contract $requiredContractClassName, but
is bundled in TransactionState with ${state.contract}.
""".trimIndent().replace('\n', ' '))
}
}
/**
* Verify that for each contract the network wide package owner is respected.
* *
* TODO - revisit once transaction contains network parameters. * TODO - revisit once transaction contains network parameters.
*/ */
@ -132,6 +127,24 @@ data class LedgerTransaction @JvmOverloads constructor(
} }
} }
/**
* For all input and output [TransactionState]s, validates that the wrapped [ContractState] matches up with the
* wrapped [Contract], as declared by the [BelongsToContract] annotation on the [ContractState]'s class.
*
* A warning will be written to the log if any mismatch is detected.
*/
private fun validateStatesAgainstContract() = allStates.forEach(::validateStateAgainstContract)
private fun validateStateAgainstContract(state: TransactionState<ContractState>) {
state.data.requiredContractClassName?.let { requiredContractClassName ->
if (state.contract != requiredContractClassName)
logger.warnOnce("""
State of class ${state.data::class.java.typeName} belongs to contract $requiredContractClassName, but
is bundled in TransactionState with ${state.contract}.
""".trimIndent().replace('\n', ' '))
}
}
/** /**
* Enforces the validity of the actual constraints. * Enforces the validity of the actual constraints.
* * Constraints should be one of the valid supported ones. * * Constraints should be one of the valid supported ones.
@ -181,6 +194,9 @@ data class LedgerTransaction @JvmOverloads constructor(
val constraintAttachment = AttachmentWithContext(contractAttachment, state.contract, val constraintAttachment = AttachmentWithContext(contractAttachment, state.contract,
networkParameters?.whitelistedContractImplementations) networkParameters?.whitelistedContractImplementations)
if (state.constraint is SignatureAttachmentConstraint)
checkMinimumPlatformVersion(networkParameters?.minimumPlatformVersion ?: 1, 4, "Signature constraints")
if (!state.constraint.isSatisfiedBy(constraintAttachment)) { if (!state.constraint.isSatisfiedBy(constraintAttachment)) {
throw TransactionVerificationException.ContractConstraintRejection(id, state.contract) throw TransactionVerificationException.ContractConstraintRejection(id, state.contract)
} }
@ -268,10 +284,39 @@ data class LedgerTransaction @JvmOverloads constructor(
// Check that in the outputs, // Check that in the outputs,
// a) an encumbered state does not refer to itself as the encumbrance // a) an encumbered state does not refer to itself as the encumbrance
// b) the number of outputs can contain the encumbrance // b) the number of outputs can contain the encumbrance
// c) the bi-directionality (full cycle) property is satisfied. // c) the bi-directionality (full cycle) property is satisfied
// d) encumbered output states are assigned to the same notary.
val statesAndEncumbrance = outputs.withIndex().filter { it.value.encumbrance != null }.map { Pair(it.index, it.value.encumbrance!!) } val statesAndEncumbrance = outputs.withIndex().filter { it.value.encumbrance != null }.map { Pair(it.index, it.value.encumbrance!!) }
if (!statesAndEncumbrance.isEmpty()) { if (!statesAndEncumbrance.isEmpty()) {
checkOutputEncumbrances(statesAndEncumbrance) checkBidirectionalOutputEncumbrances(statesAndEncumbrance)
checkNotariesOutputEncumbrance(statesAndEncumbrance)
}
}
// Method to check if all encumbered states are assigned to the same notary Party.
// This method should be invoked after [checkBidirectionalOutputEncumbrances], because it assumes that the
// bi-directionality property is already satisfied.
private fun checkNotariesOutputEncumbrance(statesAndEncumbrance: List<Pair<Int, Int>>) {
// We only check for transactions in which notary is null (i.e., issuing transactions).
// Note that if a notary is defined for a transaction, we already check if all outputs are assigned
// to the same notary (transaction's notary) in [checkNoNotaryChange()].
if (notary == null) {
// indicesAlreadyChecked is used to bypass already checked indices and to avoid cycles.
val indicesAlreadyChecked = HashSet<Int>()
statesAndEncumbrance.forEach {
checkNotary(it.first, indicesAlreadyChecked)
}
}
}
private tailrec fun checkNotary(index: Int, indicesAlreadyChecked: HashSet<Int>) {
if (indicesAlreadyChecked.add(index)) {
val encumbranceIndex = outputs[index].encumbrance!!
if (outputs[index].notary != outputs[encumbranceIndex].notary) {
throw TransactionVerificationException.TransactionNotaryMismatchEncumbranceException(id, index, encumbranceIndex, outputs[index].notary, outputs[encumbranceIndex].notary)
} else {
checkNotary(encumbranceIndex, indicesAlreadyChecked)
}
} }
} }
@ -309,7 +354,7 @@ data class LedgerTransaction @JvmOverloads constructor(
// b -> c and c -> b // b -> c and c -> b
// c -> a b -> a // c -> a b -> a
// and form a full cycle, meaning that the bi-directionality property is satisfied. // and form a full cycle, meaning that the bi-directionality property is satisfied.
private fun checkOutputEncumbrances(statesAndEncumbrance: List<Pair<Int, Int>>) { private fun checkBidirectionalOutputEncumbrances(statesAndEncumbrance: List<Pair<Int, Int>>) {
// [Set] of "from" (encumbered states). // [Set] of "from" (encumbered states).
val encumberedSet = mutableSetOf<Int>() val encumberedSet = mutableSetOf<Int>()
// [Set] of "to" (encumbrance states). // [Set] of "to" (encumbrance states).

View File

@ -265,8 +265,11 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
override fun toString(): String = "${javaClass.simpleName}(id=$id)" override fun toString(): String = "${javaClass.simpleName}(id=$id)"
private companion object { private companion object {
private fun missingSignatureMsg(missing: Set<PublicKey>, descriptions: List<String>, id: SecureHash): String = private fun missingSignatureMsg(missing: Set<PublicKey>, descriptions: List<String>, id: SecureHash): String {
"Missing signatures for $descriptions on transaction ${id.prefixChars()} for ${missing.joinToString()}" return "Missing signatures on transaction ${id.prefixChars()} for " +
"keys: ${missing.joinToString { it.toStringShort() }}, " +
"by signers: ${descriptions.joinToString()} "
}
} }
@KeepForDJVM @KeepForDJVM

View File

@ -9,6 +9,7 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignableData import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.keys import net.corda.core.crypto.keys
import net.corda.core.crypto.*
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.AttachmentWithContext import net.corda.core.internal.AttachmentWithContext
import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.FlowStateMachine
@ -24,6 +25,8 @@ import net.corda.core.node.services.KeyManagementService
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationFactory import net.corda.core.serialization.SerializationFactory
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.warnOnce
import java.security.PublicKey import java.security.PublicKey
import java.time.Duration import java.time.Duration
import java.time.Instant import java.time.Instant
@ -89,7 +92,7 @@ open class TransactionBuilder @JvmOverloads constructor(
) { ) {
private companion object { private companion object {
val logger = loggerFor<TransactionBuilder>() private val log = contextLogger()
} }
private val inputsWithTransactionState = arrayListOf<TransactionState<ContractState>>() private val inputsWithTransactionState = arrayListOf<TransactionState<ContractState>>()
@ -213,14 +216,16 @@ open class TransactionBuilder @JvmOverloads constructor(
} }
// For each contract, resolve the AutomaticPlaceholderConstraint, and select the attachment. // For each contract, resolve the AutomaticPlaceholderConstraint, and select the attachment.
val contractAttachmentsAndResolvedOutputStates: List<Pair<AttachmentId, List<TransactionState<ContractState>>?>> = allContracts.toSet().map { ctr -> val contractAttachmentsAndResolvedOutputStates: List<Pair<AttachmentId, List<TransactionState<ContractState>>?>> = allContracts.toSet()
.map { ctr ->
handleContract(ctr, inputContractGroups[ctr], outputContractGroups[ctr], explicitAttachmentContractsMap[ctr], serializationContext, services) handleContract(ctr, inputContractGroups[ctr], outputContractGroups[ctr], explicitAttachmentContractsMap[ctr], serializationContext, services)
} }
val resolvedStates: List<TransactionState<ContractState>> = contractAttachmentsAndResolvedOutputStates.mapNotNull { it.second }.flatten() val resolvedStates: List<TransactionState<ContractState>> = contractAttachmentsAndResolvedOutputStates.mapNotNull { it.second }
.flatten()
// The output states need to preserve the order in which they were added. // The output states need to preserve the order in which they were added.
val resolvedOutputStatesInTheOriginalOrder: List<TransactionState<ContractState>> = outputStates().map { os -> resolvedStates.find { rs -> rs.data == os.data && rs.encumbrance == os.encumbrance}!! } val resolvedOutputStatesInTheOriginalOrder: List<TransactionState<ContractState>> = outputStates().map { os -> resolvedStates.find { rs -> rs.data == os.data && rs.encumbrance == os.encumbrance }!! }
val attachments: Collection<AttachmentId> = contractAttachmentsAndResolvedOutputStates.map { it.first } + refStateContractAttachments val attachments: Collection<AttachmentId> = contractAttachmentsAndResolvedOutputStates.map { it.first } + refStateContractAttachments
@ -342,8 +347,18 @@ open class TransactionBuilder @JvmOverloads constructor(
attachmentToUse: ContractAttachment, attachmentToUse: ContractAttachment,
services: ServicesForResolution): AttachmentConstraint = when { services: ServicesForResolution): AttachmentConstraint = when {
inputStates != null -> attachmentConstraintsTransition(inputStates.groupBy { it.constraint }.keys, attachmentToUse) inputStates != null -> attachmentConstraintsTransition(inputStates.groupBy { it.constraint }.keys, attachmentToUse)
useWhitelistedByZoneAttachmentConstraint(contractClassName, services.networkParameters) -> WhitelistedByZoneAttachmentConstraint attachmentToUse.signers.isNotEmpty() && services.networkParameters.minimumPlatformVersion < 4 -> {
log.warnOnce("Signature constraints not available on network requiring a minimum platform version of 4. Current is: ${services.networkParameters.minimumPlatformVersion}.")
if (useWhitelistedByZoneAttachmentConstraint(contractClassName, services.networkParameters)) {
log.warnOnce("Reverting back to using whitelisted zone constraints for contract $contractClassName")
WhitelistedByZoneAttachmentConstraint
} else {
log.warnOnce("Reverting back to using hash constraints for contract $contractClassName")
HashAttachmentConstraint(attachmentToUse.id)
}
}
attachmentToUse.signers.isNotEmpty() -> makeSignatureAttachmentConstraint(attachmentToUse.signers) attachmentToUse.signers.isNotEmpty() -> makeSignatureAttachmentConstraint(attachmentToUse.signers)
useWhitelistedByZoneAttachmentConstraint(contractClassName, services.networkParameters) -> WhitelistedByZoneAttachmentConstraint
else -> HashAttachmentConstraint(attachmentToUse.id) else -> HashAttachmentConstraint(attachmentToUse.id)
} }
@ -469,7 +484,7 @@ open class TransactionBuilder @JvmOverloads constructor(
addReferenceState(resolvedStateAndRef.referenced()) addReferenceState(resolvedStateAndRef.referenced())
} }
} else { } else {
logger.warn("WARNING: You must pass in a ServiceHub reference to TransactionBuilder to resolve " + log.warn("WARNING: You must pass in a ServiceHub reference to TransactionBuilder to resolve " +
"state pointers outside of flows. If you are writing a unit test then pass in a " + "state pointers outside of flows. If you are writing a unit test then pass in a " +
"MockServices instance.") "MockServices instance.")
return return
@ -539,7 +554,7 @@ open class TransactionBuilder @JvmOverloads constructor(
state: ContractState, state: ContractState,
contract: ContractClassName = requireNotNull(state.requiredContractClassName) { contract: ContractClassName = requireNotNull(state.requiredContractClassName) {
//TODO: add link to docsite page, when there is one. //TODO: add link to docsite page, when there is one.
""" """
Unable to infer Contract class name because state class ${state::class.java.name} is not annotated with Unable to infer Contract class name because state class ${state::class.java.name} is not annotated with
@BelongsToContract, and does not have an enclosing class which implements Contract. Either annotate ${state::class.java.name} @BelongsToContract, and does not have an enclosing class which implements Contract. Either annotate ${state::class.java.name}
with @BelongsToContract, or supply an explicit contract parameter to addOutputState(). with @BelongsToContract, or supply an explicit contract parameter to addOutputState().
@ -555,7 +570,7 @@ with @BelongsToContract, or supply an explicit contract parameter to addOutputSt
state: ContractState, state: ContractState,
contract: ContractClassName = requireNotNull(state.requiredContractClassName) { contract: ContractClassName = requireNotNull(state.requiredContractClassName) {
//TODO: add link to docsite page, when there is one. //TODO: add link to docsite page, when there is one.
""" """
Unable to infer Contract class name because state class ${state::class.java.name} is not annotated with Unable to infer Contract class name because state class ${state::class.java.name} is not annotated with
@BelongsToContract, and does not have an enclosing class which implements Contract. Either annotate ${state::class.java.name} @BelongsToContract, and does not have an enclosing class which implements Contract. Either annotate ${state::class.java.name}
with @BelongsToContract, or supply an explicit contract parameter to addOutputState(). with @BelongsToContract, or supply an explicit contract parameter to addOutputState().

View File

@ -19,8 +19,8 @@ sealed class Try<out A> {
inline fun <T> on(body: () -> T): Try<T> { inline fun <T> on(body: () -> T): Try<T> {
return try { return try {
Success(body()) Success(body())
} catch (t: Throwable) { } catch (e: Exception) {
Failure(t) Failure(e)
} }
} }
} }

View File

@ -2,10 +2,7 @@ package net.corda.core.transactions
import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.Contract import net.corda.core.contracts.*
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.TransactionVerificationException
import net.corda.core.contracts.requireThat
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.finance.DOLLARS import net.corda.finance.DOLLARS
@ -18,6 +15,7 @@ import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.ledger import net.corda.testing.node.ledger
import org.assertj.core.api.AssertionsForClassTypes
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import java.time.Instant import java.time.Instant
@ -33,6 +31,7 @@ class TransactionEncumbranceTests {
private companion object { private companion object {
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
val DUMMY_NOTARY2 = TestIdentity(DUMMY_NOTARY_NAME.copy(organisation = "${DUMMY_NOTARY_NAME.organisation}2"), 30).party
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")) val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
val MINI_CORP = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).party val MINI_CORP = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).party
val MEGA_CORP get() = megaCorp.party val MEGA_CORP get() = megaCorp.party
@ -77,7 +76,7 @@ class TransactionEncumbranceTests {
} }
@Test @Test
fun `states can be bi-directionally encumbered`() { fun `states must be bi-directionally encumbered`() {
// Basic encumbrance example for encumbrance index links 0 -> 1 and 1 -> 0 // Basic encumbrance example for encumbrance index links 0 -> 1 and 1 -> 0
ledgerServices.ledger(DUMMY_NOTARY) { ledgerServices.ledger(DUMMY_NOTARY) {
transaction { transaction {
@ -316,4 +315,42 @@ class TransactionEncumbranceTests {
} }
} }
} }
@Test
fun `encumbered states cannot be assigned to different notaries`() {
// Single encumbrance with different notaries.
assertFailsWith<TransactionVerificationException.TransactionNotaryMismatchEncumbranceException> {
TransactionBuilder()
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 1, AutomaticHashConstraint)
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY2, 0, AutomaticHashConstraint)
.addCommand(Cash.Commands.Issue(), MEGA_CORP.owningKey)
.toLedgerTransaction(ledgerServices)
}
// More complex encumbrance (full cycle of size 4) where one of the encumbered states is assigned to a different notary.
// 0 -> 1, 1 -> 3, 3 -> 2, 2 -> 0
// We expect that state at index 3 cannot be encumbered with the state at index 2, due to mismatched notaries.
AssertionsForClassTypes.assertThatExceptionOfType(TransactionVerificationException.TransactionNotaryMismatchEncumbranceException::class.java).isThrownBy {
TransactionBuilder()
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 1, AutomaticHashConstraint)
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 3, AutomaticHashConstraint)
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY2, 0, AutomaticHashConstraint)
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 2, AutomaticHashConstraint)
.addCommand(Cash.Commands.Issue(), MEGA_CORP.owningKey)
.toLedgerTransaction(ledgerServices)
}.withMessageContaining("index 3 is assigned to notary [O=Notary Service, L=Zurich, C=CH], while its encumbrance with index 2 is assigned to notary [O=Notary Service2, L=Zurich, C=CH]")
// Two different encumbrance chains, where only one fails due to mismatched notary.
// 0 -> 1, 1 -> 0, 2 -> 3, 3 -> 2 where encumbered states with indices 2 and 3, respectively, are assigned
// to different notaries.
AssertionsForClassTypes.assertThatExceptionOfType(TransactionVerificationException.TransactionNotaryMismatchEncumbranceException::class.java).isThrownBy {
TransactionBuilder()
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 1, AutomaticHashConstraint)
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 0, AutomaticHashConstraint)
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY, 3, AutomaticHashConstraint)
.addOutputState(stateWithNewOwner, Cash.PROGRAM_ID, DUMMY_NOTARY2, 2, AutomaticHashConstraint)
.addCommand(Cash.Commands.Issue(), MEGA_CORP.owningKey)
.toLedgerTransaction(ledgerServices)
}.withMessageContaining("index 2 is assigned to notary [O=Notary Service, L=Zurich, C=CH], while its encumbrance with index 3 is assigned to notary [O=Notary Service2, L=Zurich, C=CH]")
}
} }

View File

@ -153,3 +153,102 @@ which is then referenced within a custom flow:
:end-before: DOCEND TopupIssuer :end-before: DOCEND TopupIssuer
For examples on testing ``@CordaService`` implementations, see the oracle example :doc:`here <oracles>` For examples on testing ``@CordaService`` implementations, see the oracle example :doc:`here <oracles>`
JPA Support
-----------
In addition to ``jdbcSession``, ``ServiceHub`` also exposes the Java Persistence API to flows via the ``withEntityManager``
method. This method can be used to persist and query entities which inherit from ``MappedSchema``. This is particularly
useful if off-ledger data must be maintained in conjunction with on-ledger state data.
.. note:: Your entity must be included as a mappedType in as part of a MappedSchema for it to be added to Hibernate
as a custom schema. See Samples below.
The code snippet below defines a ``PersistentFoo`` type inside ``FooSchemaV1``. Note that ``PersistentFoo`` is added to
a list of mapped types which is passed to ``MappedSChema``. This is exactly how state schemas are defined, except that
the entity in this case should not subclass ``PersistentState`` (as it is not a state object). See examples:
.. container:: codeset
.. sourcecode:: java
public class FooSchema {}
@CordaSerializable
public class FooSchemaV1 extends MappedSchema {
FooSchemaV1() {
super(FooSchema.class, 1, ImmutableList.of(PersistentFoo.class));
}
@Entity
@Table(name = "foos")
class PersistentFoo implements Serializable {
@Id
@Column(name = "foo_id")
String fooId;
@Column(name = "foo_data")
String fooData;
}
}
.. sourcecode:: kotlin
object FooSchema
object FooSchemaV1 : MappedSchema(schemaFamily = FooSchema.javaClass, version = 1, mappedTypes = listOf(PersistentFoo::class.java)) {
@Entity
@Table(name = "foos")
class PersistentFoo(@Id @Column(name = "foo_id") var fooId: String, @Column(name = "foo_data") var fooData: String) : Serializable
}
Instances of ``PersistentFoo`` can be persisted inside a flow as follows:
.. container:: codeset
.. sourcecode:: java
PersistentFoo foo = new PersistentFoo(new UniqueIdentifier().getId().toString(), "Bar");
node.getServices().withEntityManager(entityManager -> {
entityManager.persist(foo);
entityManager.flush();
return null;
});
.. sourcecode:: kotlin
val foo = FooSchemaV1.PersistentFoo(UniqueIdentifier().id.toString(), "Bar")
serviceHub.withEntityManager {
persist(foo)
}
And retrieved via a query, as follows:
.. container:: codeset
.. sourcecode:: java
node.getServices().withEntityManager((EntityManager entityManager) -> {
CriteriaQuery<PersistentFoo> query = entityManager.getCriteriaBuilder().createQuery(PersistentFoo.class);
Root<PersistentFoo> type = query.from(PersistentFoo.class);
query.select(type);
return entityManager.createQuery(query).getResultList();
});
.. sourcecode:: kotlin
val result: MutableList<FooSchemaV1.PersistentFoo> = services.withEntityManager {
val query = criteriaBuilder.createQuery(FooSchemaV1.PersistentFoo::class.java)
val type = query.from(FooSchemaV1.PersistentFoo::class.java)
query.select(type)
createQuery(query).resultList
}
Please note that suspendable flow operations such as:
* ``FlowSession.send``
* ``FlowSession.receive``
* ``FlowLogic.receiveAll``
* ``FlowLogic.sleep``
* ``FlowLogic.subFlow``
Cannot be used within the lambda function passed to ``withEntityManager``.

View File

@ -2,16 +2,19 @@ package net.corda.docs.java.tutorial.helloworld;
import co.paralleluniverse.fibers.Suspendable; import co.paralleluniverse.fibers.Suspendable;
import com.template.TemplateContract; import com.template.TemplateContract;
import net.corda.core.flows.*; import net.corda.core.flows.FlowException;
import net.corda.core.flows.FlowLogic;
import net.corda.core.flows.InitiatingFlow;
import net.corda.core.flows.StartableByRPC;
import net.corda.core.utilities.ProgressTracker;
// DOCSTART 01 // DOCSTART 01
// Add these imports: // Add these imports:
import net.corda.core.contracts.Command; import net.corda.core.contracts.Command;
import net.corda.core.contracts.CommandData; import net.corda.core.flows.FinalityFlow;
import net.corda.core.identity.Party; import net.corda.core.identity.Party;
import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.SignedTransaction;
import net.corda.core.transactions.TransactionBuilder; import net.corda.core.transactions.TransactionBuilder;
import net.corda.core.utilities.ProgressTracker;
// Replace Initiator's definition with: // Replace Initiator's definition with:
@InitiatingFlow @InitiatingFlow
@ -46,13 +49,12 @@ public class IOUFlow extends FlowLogic<Void> {
// We create the transaction components. // We create the transaction components.
IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty); IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty);
CommandData cmdType = new TemplateContract.Commands.Action(); Command command = new Command<>(new TemplateContract.Commands.Action(), getOurIdentity().getOwningKey());
Command cmd = new Command<>(cmdType, getOurIdentity().getOwningKey());
// We create a transaction builder and add the components. // We create a transaction builder and add the components.
TransactionBuilder txBuilder = new TransactionBuilder(notary) TransactionBuilder txBuilder = new TransactionBuilder(notary)
.addOutputState(outputState, TemplateContract.ID) .addOutputState(outputState, TemplateContract.ID)
.addCommand(cmd); .addCommand(command);
// Signing the transaction. // Signing the transaction.
SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder); SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);

View File

@ -2,11 +2,11 @@ package net.corda.docs.java.tutorial.helloworld;
import net.corda.core.contracts.ContractState; import net.corda.core.contracts.ContractState;
import net.corda.core.identity.AbstractParty; import net.corda.core.identity.AbstractParty;
import java.util.Arrays;
import java.util.List; import java.util.List;
// DOCSTART 01 // DOCSTART 01
// Add these imports: // Add this import:
import com.google.common.collect.ImmutableList;
import net.corda.core.identity.Party; import net.corda.core.identity.Party;
// Replace TemplateState's definition with: // Replace TemplateState's definition with:
@ -35,7 +35,7 @@ public class IOUState implements ContractState {
@Override @Override
public List<AbstractParty> getParticipants() { public List<AbstractParty> getParticipants() {
return ImmutableList.of(lender, borrower); return Arrays.asList(lender, borrower);
} }
} }
// DOCEND 01 // DOCEND 01

View File

@ -6,15 +6,14 @@ import net.corda.core.transactions.LedgerTransaction;
// DOCSTART 01 // DOCSTART 01
// Add these imports: // Add these imports:
import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.CommandWithParties; import net.corda.core.contracts.CommandWithParties;
import net.corda.core.identity.Party; import net.corda.core.identity.Party;
import java.security.PublicKey; import java.security.PublicKey;
import java.util.Arrays;
import java.util.List; import java.util.List;
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand; import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
import static net.corda.core.contracts.ContractsDSL.requireThat;
// Replace TemplateContract's definition with: // Replace TemplateContract's definition with:
public class IOUContract implements Contract { public class IOUContract implements Contract {
@ -28,26 +27,29 @@ public class IOUContract implements Contract {
public void verify(LedgerTransaction tx) { public void verify(LedgerTransaction tx) {
final CommandWithParties<IOUContract.Create> command = requireSingleCommand(tx.getCommands(), IOUContract.Create.class); final CommandWithParties<IOUContract.Create> command = requireSingleCommand(tx.getCommands(), IOUContract.Create.class);
requireThat(check -> {
// Constraints on the shape of the transaction. // Constraints on the shape of the transaction.
check.using("No inputs should be consumed when issuing an IOU.", tx.getInputs().isEmpty()); if (!tx.getInputs().isEmpty())
check.using("There should be one output state of type IOUState.", tx.getOutputs().size() == 1); throw new IllegalArgumentException("No inputs should be consumed when issuing an IOU.");
if (!(tx.getOutputs().size() == 1))
throw new IllegalArgumentException("There should be one output state of type IOUState.");
// IOU-specific constraints. // IOU-specific constraints.
final IOUState out = tx.outputsOfType(IOUState.class).get(0); final IOUState output = tx.outputsOfType(IOUState.class).get(0);
final Party lender = out.getLender(); final Party lender = output.getLender();
final Party borrower = out.getBorrower(); final Party borrower = output.getBorrower();
check.using("The IOU's value must be non-negative.", out.getValue() > 0); if (output.getValue() <= 0)
check.using("The lender and the borrower cannot be the same entity.", lender != borrower); throw new IllegalArgumentException("The IOU's value must be non-negative.");
if (lender.equals(borrower))
throw new IllegalArgumentException("The lender and the borrower cannot be the same entity.");
// Constraints on the signers. // Constraints on the signers.
final List<PublicKey> signers = command.getSigners(); final List<PublicKey> requiredSigners = command.getSigners();
check.using("There must be two signers.", signers.size() == 2); final List<PublicKey> expectedSigners = Arrays.asList(borrower.getOwningKey(), lender.getOwningKey());
check.using("The borrower and lender must be signers.", signers.containsAll( if (requiredSigners.size() != 2)
ImmutableList.of(borrower.getOwningKey(), lender.getOwningKey()))); throw new IllegalArgumentException("There must be two signers.");
if (!(requiredSigners.containsAll(expectedSigners)))
throw new IllegalArgumentException("The borrower and lender must be signers.");
return null;
});
} }
} }
// DOCEND 01 // DOCEND 01

View File

@ -2,9 +2,7 @@ package net.corda.docs.java.tutorial.twoparty;
// DOCSTART 01 // DOCSTART 01
import co.paralleluniverse.fibers.Suspendable; import co.paralleluniverse.fibers.Suspendable;
import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.Command; import net.corda.core.contracts.Command;
import net.corda.core.contracts.StateAndContract;
import net.corda.core.flows.*; import net.corda.core.flows.*;
import net.corda.core.identity.Party; import net.corda.core.identity.Party;
import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.SignedTransaction;
@ -12,6 +10,7 @@ import net.corda.core.transactions.TransactionBuilder;
import net.corda.core.utilities.ProgressTracker; import net.corda.core.utilities.ProgressTracker;
import java.security.PublicKey; import java.security.PublicKey;
import java.util.Arrays;
import java.util.List; import java.util.List;
// DOCEND 01 // DOCEND 01
@ -42,22 +41,19 @@ public class IOUFlow extends FlowLogic<Void> {
@Suspendable @Suspendable
@Override @Override
public Void call() throws FlowException { public Void call() throws FlowException {
// DOCSTART 02
// We retrieve the notary identity from the network map. // We retrieve the notary identity from the network map.
Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0); Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
// DOCSTART 02
// We create a transaction builder.
TransactionBuilder txBuilder = new TransactionBuilder();
txBuilder.setNotary(notary);
// We create the transaction components. // We create the transaction components.
IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty); IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty);
StateAndContract outputContractAndState = new StateAndContract(outputState, IOUContract.ID); List<PublicKey> requiredSigners = Arrays.asList(getOurIdentity().getOwningKey(), otherParty.getOwningKey());
List<PublicKey> requiredSigners = ImmutableList.of(getOurIdentity().getOwningKey(), otherParty.getOwningKey()); Command command = new Command<>(new IOUContract.Create(), requiredSigners);
Command cmd = new Command<>(new IOUContract.Create(), requiredSigners);
// We add the items to the builder. // We create a transaction builder and add the components.
txBuilder.withItems(outputContractAndState, cmd); TransactionBuilder txBuilder = new TransactionBuilder(notary)
.addOutputState(outputState, IOUContract.ID)
.addCommand(command);
// Verifying the transaction. // Verifying the transaction.
txBuilder.verify(getServiceHub()); txBuilder.verify(getServiceHub());
@ -70,7 +66,7 @@ public class IOUFlow extends FlowLogic<Void> {
// Obtaining the counterparty's signature. // Obtaining the counterparty's signature.
SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow( SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(
signedTx, ImmutableList.of(otherPartySession), CollectSignaturesFlow.tracker())); signedTx, Arrays.asList(otherPartySession), CollectSignaturesFlow.tracker()));
// Finalising the transaction. // Finalising the transaction.
subFlow(new FinalityFlow(fullySignedTx)); subFlow(new FinalityFlow(fullySignedTx));

View File

@ -1,10 +1,10 @@
package net.corda.docs.java.tutorial.twoparty; package net.corda.docs.java.tutorial.twoparty;
import com.google.common.collect.ImmutableList;
import net.corda.core.contracts.ContractState; import net.corda.core.contracts.ContractState;
import net.corda.core.identity.AbstractParty; import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.Party; import net.corda.core.identity.Party;
import java.util.Arrays;
import java.util.List; import java.util.List;
public class IOUState implements ContractState { public class IOUState implements ContractState {
@ -32,6 +32,6 @@ public class IOUState implements ContractState {
@Override @Override
public List<AbstractParty> getParticipants() { public List<AbstractParty> getParticipants() {
return ImmutableList.of(lender, borrower); return Arrays.asList(lender, borrower);
} }
} }

View File

@ -8,13 +8,13 @@ import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC import net.corda.core.flows.StartableByRPC
import net.corda.core.utilities.ProgressTracker
// DOCSTART 01 // DOCSTART 01
// Add these imports: // Add these imports:
import net.corda.core.contracts.Command import net.corda.core.contracts.Command
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
// Replace Initiator's definition with: // Replace Initiator's definition with:
@InitiatingFlow @InitiatingFlow
@ -33,12 +33,12 @@ class IOUFlow(val iouValue: Int,
// We create the transaction components. // We create the transaction components.
val outputState = IOUState(iouValue, ourIdentity, otherParty) val outputState = IOUState(iouValue, ourIdentity, otherParty)
val cmd = Command(TemplateContract.Commands.Action(), ourIdentity.owningKey) val command = Command(TemplateContract.Commands.Action(), ourIdentity.owningKey)
// We create a transaction builder and add the components. // We create a transaction builder and add the components.
val txBuilder = TransactionBuilder(notary = notary) val txBuilder = TransactionBuilder(notary = notary)
.addOutputState(outputState, TemplateContract.ID) .addOutputState(outputState, TemplateContract.ID)
.addCommand(cmd) .addCommand(command)
// We sign the transaction. // We sign the transaction.
val signedTx = serviceHub.signInitialTransaction(txBuilder) val signedTx = serviceHub.signInitialTransaction(txBuilder)

View File

@ -5,7 +5,7 @@ package net.corda.docs.kotlin.tutorial.helloworld
import net.corda.core.contracts.ContractState import net.corda.core.contracts.ContractState
// DOCSTART 01 // DOCSTART 01
// Add these imports: // Add this import:
import net.corda.core.identity.Party import net.corda.core.identity.Party
// Replace TemplateState's definition with: // Replace TemplateState's definition with:

View File

@ -5,7 +5,7 @@ import net.corda.core.contracts.Contract
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
// DOCSTART 01 // DOCSTART 01
// Add these imports: // Add this import:
import net.corda.core.contracts.* import net.corda.core.contracts.*
class IOUContract : Contract { class IOUContract : Contract {
@ -25,14 +25,14 @@ class IOUContract : Contract {
"There should be one output state of type IOUState." using (tx.outputs.size == 1) "There should be one output state of type IOUState." using (tx.outputs.size == 1)
// IOU-specific constraints. // IOU-specific constraints.
val out = tx.outputsOfType<IOUState>().single() val output = tx.outputsOfType<IOUState>().single()
"The IOU's value must be non-negative." using (out.value > 0) "The IOU's value must be non-negative." using (output.value > 0)
"The lender and the borrower cannot be the same entity." using (out.lender != out.borrower) "The lender and the borrower cannot be the same entity." using (output.lender != output.borrower)
// Constraints on the signers. // Constraints on the signers.
val expectedSigners = listOf(output.borrower.owningKey, output.lender.owningKey)
"There must be two signers." using (command.signers.toSet().size == 2) "There must be two signers." using (command.signers.toSet().size == 2)
"The borrower and lender must be signers." using (command.signers.containsAll(listOf( "The borrower and lender must be signers." using (command.signers.containsAll(expectedSigners))
out.borrower.owningKey, out.lender.owningKey)))
} }
} }
} }

View File

@ -5,7 +5,6 @@ package net.corda.docs.kotlin.tutorial.twoparty
// DOCSTART 01 // DOCSTART 01
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Command import net.corda.core.contracts.Command
import net.corda.core.contracts.StateAndContract
import net.corda.core.flows.CollectSignaturesFlow import net.corda.core.flows.CollectSignaturesFlow
import net.corda.core.flows.FinalityFlow import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
@ -27,20 +26,18 @@ class IOUFlow(val iouValue: Int,
/** The flow logic is encapsulated within the call() method. */ /** The flow logic is encapsulated within the call() method. */
@Suspendable @Suspendable
override fun call() { override fun call() {
// DOCSTART 02
// We retrieve the notary identity from the network map. // We retrieve the notary identity from the network map.
val notary = serviceHub.networkMapCache.notaryIdentities[0] val notary = serviceHub.networkMapCache.notaryIdentities[0]
// DOCSTART 02
// We create a transaction builder.
val txBuilder = TransactionBuilder(notary = notary)
// We create the transaction components. // We create the transaction components.
val outputState = IOUState(iouValue, ourIdentity, otherParty) val outputState = IOUState(iouValue, ourIdentity, otherParty)
val outputContractAndState = StateAndContract(outputState, IOUContract.ID) val command = Command(IOUContract.Create(), listOf(ourIdentity.owningKey, otherParty.owningKey))
val cmd = Command(IOUContract.Create(), listOf(ourIdentity.owningKey, otherParty.owningKey))
// We add the items to the builder. // We create a transaction builder and add the components.
txBuilder.withItems(outputContractAndState, cmd) val txBuilder = TransactionBuilder(notary = notary)
.addOutputState(outputState, IOUContract.ID)
.addCommand(command)
// Verifying the transaction. // Verifying the transaction.
txBuilder.verify(serviceHub) txBuilder.verify(serviceHub)

View File

@ -3,10 +3,7 @@
package net.corda.docs.kotlin.tutorial.twoparty package net.corda.docs.kotlin.tutorial.twoparty
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FlowLogic import net.corda.core.flows.*
import net.corda.core.flows.FlowSession
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.SignTransactionFlow
import net.corda.docs.kotlin.tutorial.helloworld.IOUFlow import net.corda.docs.kotlin.tutorial.helloworld.IOUFlow
import net.corda.docs.kotlin.tutorial.helloworld.IOUState import net.corda.docs.kotlin.tutorial.helloworld.IOUState

View File

@ -40,8 +40,7 @@ FlowLogic
--------- ---------
All flows must subclass ``FlowLogic``. You then define the steps taken by the flow by overriding ``FlowLogic.call``. All flows must subclass ``FlowLogic``. You then define the steps taken by the flow by overriding ``FlowLogic.call``.
Let's define our ``IOUFlow`` in either ``Initiator.java`` or ``Flows.kt``. Delete the two existing flows in the Let's define our ``IOUFlow``. Delete the existing ``Responder`` flow. Then replace the definition of ``Initiator`` with the following:
template (``Initiator`` and ``Responder``), and replace them with the following:
.. container:: codeset .. container:: codeset

View File

@ -13,29 +13,38 @@ By this point, :doc:`your dev environment should be set up <getting-set-up>`, yo
:doc:`your first CorDapp <tutorial-cordapp>`, and you're familiar with Corda's :doc:`key concepts <key-concepts>`. What :doc:`your first CorDapp <tutorial-cordapp>`, and you're familiar with Corda's :doc:`key concepts <key-concepts>`. What
comes next? comes next?
If you're a developer, the next step is to write your own CorDapp. CorDapps are plugins that are installed on one or If you're a developer, the next step is to write your own CorDapp. CorDapps are applications that are installed on one or
more Corda nodes, and give the nodes' owners the ability to make their node conduct some new process - anything from more Corda nodes, and that allow the node's operator to instruct their node to perform some new process - anything from
issuing a debt instrument to making a restaurant booking. issuing a debt instrument to making a restaurant booking.
Our use-case Our use-case
------------ ------------
Our CorDapp will model IOUs on-ledger. An IOU short for “I O(we) (yo)U” records the fact that one person owes We will write a CorDapp to model IOUs on the blockchain. Each IOU short for “I O(we) (yo)U” will record the fact that one node owes
another person a given amount of money. Clearly this is sensitive information that we'd only want to communicate on another node a certain amount. This simple CorDapp will showcase several key benefits of Corda as a blockchain platform:
a need-to-know basis between the lender and the borrower. Fortunately, this is one of the areas where Corda excels.
Corda makes it easy to allow a small set of parties to agree on a shared fact without needing to share this fact with
everyone else on the network, as is the norm in blockchain platforms.
To serve any useful function, our CorDapp will need at least two things: * **Privacy** - Since IOUs represent sensitive information, we will be taking advantage of Corda's ability to only share
ledger updates with other nodes on a need-to-know basis, instead of using a gossip protocol to share this information with every node on
the network as you would with a traditional blockchain platform
* **States**, the shared facts that Corda nodes reach consensus over and are then stored on the ledger * **Well-known identities** - Each Corda node has a well-known identity on the network. This allows us to write code in terms of real
* **Flows**, which encapsulate the procedure for carrying out a specific ledger update identities, rather than anonymous public keys
Our IOU CorDapp is no exception. It will define both a state and a flow: * **Re-use of existing, proven technologies** - We will be writing our CorDapp using standard Java. It will run on a Corda node, which is
simply a Java process and runs on a regular Java machine (e.g. on your local machine or in the cloud). The nodes will store their data in
a standard SQL database
CorDapps usually define at least three things:
* **States** - the (possibly shared) facts that are written to the ledger
* **Flows** - the procedures for carrying out specific ledger updates
* **Contracts** - the constraints governing how states of a given type can evolve over time
Our IOU CorDapp is no exception. It will define the following components:
The IOUState The IOUState
^^^^^^^^^^^^ ^^^^^^^^^^^^
Our state will be the ``IOUState``. It will store the value of the IOU, as well as the identities of the lender and the Our state will be the ``IOUState``, representing an IOU. It will contain the IOU's value, its lender and its borrower. We can visualize
borrower. We can visualize ``IOUState`` as follows: ``IOUState`` as follows:
.. image:: resources/tutorial-state.png .. image:: resources/tutorial-state.png
:scale: 25% :scale: 25%
@ -43,20 +52,20 @@ borrower. We can visualize ``IOUState`` as follows:
The IOUFlow The IOUFlow
^^^^^^^^^^^ ^^^^^^^^^^^
Our flow will be the ``IOUFlow``. This flow will completely automate the process of issuing a new IOU onto a ledger. It Our flow will be the ``IOUFlow``. This flow will completely automate the process of issuing a new IOU onto a ledger. It has the following
is composed of the following steps: steps:
.. image:: resources/simple-tutorial-flow.png .. image:: resources/simple-tutorial-flow.png
:scale: 25% :scale: 25%
:align: center :align: center
In traditional distributed ledger systems, where all data is broadcast to every network participant, you dont need to The IOUContract
think about data flows you simply package up your ledger update and send it to everyone else on the network. But in ^^^^^^^^^^^^^^^
Corda, where privacy is a core focus, flows allow us to carefully control who sees what during the process of For this tutorial, we will use the default ``TemplateContract``. We will update it to create a fully-fledged ``IOUContract`` in the next
agreeing a ledger update. tutorial.
Progress so far Progress so far
--------------- ---------------
We've sketched out a simple CorDapp that will allow nodes to confidentially issue new IOUs onto a ledger. We've designed a simple CorDapp that will allow nodes to agree new IOUs on the blockchain.
Next, we'll be taking a look at the template project we'll be using as the basis for our CorDapp. Next, we'll take a look at the template project we'll be using as the basis for our CorDapp.

View File

@ -107,9 +107,7 @@ commands.
We want to create an IOU of 99 with PartyB. We start the ``IOUFlow`` by typing: We want to create an IOU of 99 with PartyB. We start the ``IOUFlow`` by typing:
.. container:: codeset .. code-block:: bash
.. code-block:: kotlin
start IOUFlow iouValue: 99, otherParty: "O=PartyB,L=New York,C=US" start IOUFlow iouValue: 99, otherParty: "O=PartyB,L=New York,C=US"
@ -162,12 +160,27 @@ The vaults of PartyA and PartyB should both display the following output:
This is the transaction issuing our ``IOUState`` onto a ledger. This is the transaction issuing our ``IOUState`` onto a ledger.
However, if we run the same command on the other node (the notary), we will see the following:
.. code:: bash
{
"states" : [ ],
"statesMetadata" : [ ],
"totalStatesAvailable" : -1,
"stateTypes" : "UNCONSUMED",
"otherResults" : [ ]
}
This is the result of Corda's privacy model. Because the notary was not involved in the transaction and had no need to see the data, the
transaction was not distributed to them.
Conclusion Conclusion
---------- ----------
We have written a simple CorDapp that allows IOUs to be issued onto the ledger. Our CorDapp is made up of two key We have written a simple CorDapp that allows IOUs to be issued onto the ledger. Our CorDapp is made up of two key
parts: parts:
* The ``IOUState``, representing IOUs on the ledger * The ``IOUState``, representing IOUs on the blockchain
* The ``IOUFlow``, orchestrating the process of agreeing the creation of an IOU on-ledger * The ``IOUFlow``, orchestrating the process of agreeing the creation of an IOU on-ledger
After completing this tutorial, your CorDapp should look like this: After completing this tutorial, your CorDapp should look like this:

View File

@ -7,7 +7,7 @@
Writing the state Writing the state
================= =================
In Corda, shared facts on the ledger are represented as states. Our first task will be to define a new state type to In Corda, shared facts on the blockchain are represented as states. Our first task will be to define a new state type to
represent an IOU. represent an IOU.
The ContractState interface The ContractState interface
@ -28,7 +28,7 @@ We can see that the ``ContractState`` interface has a single field, ``participan
entities for which this state is relevant. entities for which this state is relevant.
Beyond this, our state is free to define any fields, methods, helpers or inner classes it requires to accurately Beyond this, our state is free to define any fields, methods, helpers or inner classes it requires to accurately
represent a given type of shared fact on the ledger. represent a given type of shared fact on the blockchain.
.. note:: .. note::
@ -46,7 +46,7 @@ represent a given type of shared fact on the ledger.
Modelling IOUs Modelling IOUs
-------------- --------------
How should we define the ``IOUState`` representing IOUs on the ledger? Beyond implementing the ``ContractState`` How should we define the ``IOUState`` representing IOUs on the blockchain? Beyond implementing the ``ContractState``
interface, our ``IOUState`` will also need properties to track the relevant features of the IOU: interface, our ``IOUState`` will also need properties to track the relevant features of the IOU:
* The value of the IOU * The value of the IOU
@ -99,7 +99,7 @@ Corda are simply classes that implement the ``ContractState`` interface. They ca
methods you like. methods you like.
All that's left to do is write the ``IOUFlow`` that will allow a node to orchestrate the creation of a new ``IOUState`` All that's left to do is write the ``IOUFlow`` that will allow a node to orchestrate the creation of a new ``IOUState``
on the ledger, while only sharing information on a need-to-know basis. on the blockchain, while only sharing information on a need-to-know basis.
What about the contract? What about the contract?
------------------------ ------------------------

View File

@ -7,41 +7,39 @@
The CorDapp Template The CorDapp Template
==================== ====================
When writing a new CorDapp, youll generally want to base it on the standard templates: When writing a new CorDapp, youll generally want to start from one of the standard templates:
* The `Java Cordapp Template <https://github.com/corda/cordapp-template-java>`_ * The `Java Cordapp Template <https://github.com/corda/cordapp-template-java>`_
* The `Kotlin Cordapp Template <https://github.com/corda/cordapp-template-kotlin>`_ * The `Kotlin Cordapp Template <https://github.com/corda/cordapp-template-kotlin>`_
The Cordapp templates provide the required boilerplate for developing a CorDapp, and allow you to quickly deploy your The Cordapp templates provide the boilerplate for developing a new CorDapp. CorDapps can be written in either Java or Kotlin. We will be
CorDapp onto a local test network of dummy nodes to test its functionality. providing the code in both languages throughout this tutorial.
CorDapps can be written in both Java and Kotlin, and will be providing the code in both languages in this tutorial. Note that there's no need to download and install Corda itself. The required libraries are automatically downloaded from an online Maven
repository and cached locally.
Note that there's no need to download and install Corda itself. Corda's required libraries will be downloaded
automatically from an online Maven repository.
Downloading the template Downloading the template
------------------------ ------------------------
To download the template, open a terminal window in the directory where you want to download the CorDapp template, and Open a terminal window in the directory where you want to download the CorDapp template, and run the following command:
run the following command:
.. code-block:: bash .. container:: codeset
.. code-block:: java
git clone https://github.com/corda/cordapp-template-java.git ; cd cordapp-template-java git clone https://github.com/corda/cordapp-template-java.git ; cd cordapp-template-java
*or* .. code-block:: kotlin
git clone https://github.com/corda/cordapp-template-kotlin.git ; cd cordapp-template-kotlin git clone https://github.com/corda/cordapp-template-kotlin.git ; cd cordapp-template-kotlin
Opening the template in IntelliJ Opening the template in IntelliJ
-------------------------------- --------------------------------
Once the template is download, open it in IntelliJ by following the instructions here: Once the template is download, open it in IntelliJ by following the instructions here:
https://docs.corda.net/tutorial-cordapp.html#opening-the-example-cordapp-in-intellij. https://docs.corda.net/tutorial-cordapp.html#opening-the-example-cordapp-in-intellij.
Template structure Template structure
------------------ ------------------
The template has a number of files, but we can ignore most of them. We will only be modifying the following files: For this tutorial, we will only be modifying the following files:
.. container:: codeset .. container:: codeset

View File

@ -154,7 +154,7 @@ Transaction constraints
~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~
We also want our transaction to have no inputs and only a single output - an issuance transaction. We also want our transaction to have no inputs and only a single output - an issuance transaction.
To impose this and the subsequent constraints, we are using Corda's built-in ``requireThat`` block. ``requireThat`` In Kotlin, we impose these and the subsequent constraints using Corda's built-in ``requireThat`` block. ``requireThat``
provides a terse way to write the following: provides a terse way to write the following:
* If the condition on the right-hand side doesn't evaluate to true... * If the condition on the right-hand side doesn't evaluate to true...
@ -162,6 +162,8 @@ provides a terse way to write the following:
As before, the act of throwing this exception causes the transaction to be considered invalid. As before, the act of throwing this exception causes the transaction to be considered invalid.
In Java, we simply throw an ``IllegalArgumentException`` manually instead.
IOU constraints IOU constraints
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
We want to impose two constraints on the ``IOUState`` itself: We want to impose two constraints on the ``IOUState`` itself:
@ -169,9 +171,7 @@ We want to impose two constraints on the ``IOUState`` itself:
* Its value must be non-negative * Its value must be non-negative
* The lender and the borrower cannot be the same entity * The lender and the borrower cannot be the same entity
We impose these constraints in the same ``requireThat`` block as before. You can see that we're not restricted to only writing constraints inside ``verify``. We can also write
You can see that we're not restricted to only writing constraints in the ``requireThat`` block. We can also write
other statements - in this case, extracting the transaction's single ``IOUState`` and assigning it to a variable. other statements - in this case, extracting the transaction's single ``IOUState`` and assigning it to a variable.
Signer constraints Signer constraints
@ -180,7 +180,7 @@ Finally, we require both the lender and the borrower to be required signers on t
required signers is equal to the union of all the signers listed on the commands. We therefore extract the signers from required signers is equal to the union of all the signers listed on the commands. We therefore extract the signers from
the ``Create`` command we retrieved earlier. the ``Create`` command we retrieved earlier.
This is an absolutely essential constraint - it ensures that no ``IOUState`` can ever be created on the ledger without This is an absolutely essential constraint - it ensures that no ``IOUState`` can ever be created on the blockchain without
the express agreement of both the lender and borrower nodes. the express agreement of both the lender and borrower nodes.
Progress so far Progress so far

View File

@ -31,8 +31,7 @@ In ``IOUFlow.java``/``Flows.kt``, change the imports block to the following:
:start-after: DOCSTART 01 :start-after: DOCSTART 01
:end-before: DOCEND 01 :end-before: DOCEND 01
And update ``IOUFlow.call`` by changing the code following the retrieval of the notary's identity from the network as And update ``IOUFlow.call`` to the following:
follows:
.. container:: codeset .. container:: codeset
@ -136,7 +135,7 @@ defined in ``IOUContract``. We can now re-run our updated CorDapp, using the
:doc:`same instructions as before <hello-world-running>`. :doc:`same instructions as before <hello-world-running>`.
Our CorDapp now imposes restrictions on the issuance of IOUs. Most importantly, IOU issuance now requires agreement Our CorDapp now imposes restrictions on the issuance of IOUs. Most importantly, IOU issuance now requires agreement
from both the lender and the borrower before an IOU can be created on the ledger. This prevents either the lender or from both the lender and the borrower before an IOU can be created on the blockchain. This prevents either the lender or
the borrower from unilaterally updating the ledger in a way that only benefits themselves. the borrower from unilaterally updating the ledger in a way that only benefits themselves.
After completing this tutorial, your CorDapp should look like this: After completing this tutorial, your CorDapp should look like this:

View File

@ -6,10 +6,10 @@ Hello, World! Pt.2 - Contract constraints
In the Hello, World tutorial, we built a CorDapp allowing us to model IOUs on ledger. Our CorDapp was made up of two In the Hello, World tutorial, we built a CorDapp allowing us to model IOUs on ledger. Our CorDapp was made up of two
elements: elements:
* An ``IOUState``, representing IOUs on the ledger * An ``IOUState``, representing IOUs on the blockchain
* An ``IOUFlow``, orchestrating the process of agreeing the creation of an IOU on-ledger * An ``IOUFlow``, orchestrating the process of agreeing the creation of an IOU on-ledger
However, our CorDapp did not impose any constraints on the evolution of IOUs on the ledger over time. Anyone was free However, our CorDapp did not impose any constraints on the evolution of IOUs on the blockchain over time. Anyone was free
to create IOUs of any value, between any party. to create IOUs of any value, between any party.
In this tutorial, we'll write a contract to imposes rules on how an ``IOUState`` can change over time. In turn, this In this tutorial, we'll write a contract to imposes rules on how an ``IOUState`` can change over time. In turn, this

View File

@ -1,31 +0,0 @@
ext {
javaassist_version = "3.12.1.GA"
}
apply plugin: 'kotlin'
apply plugin: 'idea'
description 'A javaagent to allow hooking into Kryo'
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile "javassist:javassist:$javaassist_version"
compile "com.esotericsoftware:kryo:4.0.0"
compile "$quasar_group:quasar-core:$quasar_version:jdk8"
}
jar {
archiveName = "${project.name}.jar"
manifest {
attributes(
'Premain-Class': 'net.corda.kryohook.KryoHookAgent',
'Can-Redefine-Classes': 'true',
'Can-Retransform-Classes': 'true',
'Can-Set-Native-Method-Prefix': 'true',
'Implementation-Title': "KryoHook",
'Implementation-Version': rootProject.version
)
}
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}

View File

@ -1,164 +0,0 @@
package net.corda.kryohook
import co.paralleluniverse.strands.Strand
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.io.Output
import javassist.ClassPool
import javassist.CtClass
import java.lang.instrument.ClassFileTransformer
import java.lang.instrument.Instrumentation
import java.security.ProtectionDomain
import java.util.concurrent.ConcurrentHashMap
class KryoHookAgent {
companion object {
@JvmStatic
fun premain(@Suppress("UNUSED_PARAMETER") argumentsString: String?, instrumentation: Instrumentation) {
Runtime.getRuntime().addShutdownHook(Thread {
val statsTrees = KryoHook.events.values.flatMap {
readTrees(it, 0).second
}
val builder = StringBuilder()
statsTrees.forEach {
prettyStatsTree(0, it, builder)
}
print(builder.toString())
})
instrumentation.addTransformer(KryoHook)
}
}
}
fun prettyStatsTree(indent: Int, statsTree: StatsTree, builder: StringBuilder) {
when (statsTree) {
is StatsTree.Object -> {
builder.append(kotlin.CharArray(indent) { ' ' })
builder.append(statsTree.className)
builder.append(" ")
builder.append(statsTree.size)
builder.append("\n")
for (child in statsTree.children) {
prettyStatsTree(indent + 2, child, builder)
}
}
}
}
/**
* The hook simply records the write() entries and exits together with the output offset at the time of the call.
* This is recorded in a StrandID -> List<StatsEvent> map.
*
* Later we "parse" these lists into a tree.
*/
object KryoHook : ClassFileTransformer {
val classPool = ClassPool.getDefault()!!
val hookClassName = javaClass.name!!
override fun transform(
loader: ClassLoader?,
className: String,
classBeingRedefined: Class<*>?,
protectionDomain: ProtectionDomain?,
classfileBuffer: ByteArray
): ByteArray? {
if (className.startsWith("java") || className.startsWith("javassist") || className.startsWith("kotlin")) {
return null
}
return try {
val clazz = classPool.makeClass(classfileBuffer.inputStream())
instrumentClass(clazz)?.toBytecode()
} catch (throwable: Throwable) {
println("SOMETHING WENT WRONG")
throwable.printStackTrace(System.out)
null
}
}
private fun instrumentClass(clazz: CtClass): CtClass? {
for (method in clazz.declaredBehaviors) {
if (method.name == "write") {
val parameterTypeNames = method.parameterTypes.map { it.name }
if (parameterTypeNames == listOf("com.esotericsoftware.kryo.Kryo", "com.esotericsoftware.kryo.io.Output", "java.lang.Object")) {
if (method.isEmpty) continue
println("Instrumenting ${clazz.name}")
method.insertBefore("$hookClassName.${this::writeEnter.name}($1, $2, $3);")
method.insertAfter("$hookClassName.${this::writeExit.name}($1, $2, $3);")
return clazz
}
}
}
return null
}
// StrandID -> StatsEvent map
val events = ConcurrentHashMap<Long, ArrayList<StatsEvent>>()
@JvmStatic
fun writeEnter(@Suppress("UNUSED_PARAMETER") kryo: Kryo, output: Output, obj: Any) {
events.getOrPut(Strand.currentStrand().id) { ArrayList() }.add(
StatsEvent.Enter(obj.javaClass.name, output.total())
)
}
@JvmStatic
fun writeExit(@Suppress("UNUSED_PARAMETER") kryo: Kryo, output: Output, obj: Any) {
events[Strand.currentStrand().id]!!.add(
StatsEvent.Exit(obj.javaClass.name, output.total())
)
}
}
/**
* TODO we could add events on entries/exits to field serializers to get more info on what's being serialised.
*/
sealed class StatsEvent {
data class Enter(val className: String, val offset: Long) : StatsEvent()
data class Exit(val className: String, val offset: Long) : StatsEvent()
}
/**
* TODO add Field constructor.
*/
sealed class StatsTree {
data class Object(
val className: String,
val size: Long,
val children: List<StatsTree>
) : StatsTree()
}
fun readTree(events: List<StatsEvent>, index: Int): Pair<Int, StatsTree> {
val event = events[index]
when (event) {
is StatsEvent.Enter -> {
val (nextIndex, children) = readTrees(events, index + 1)
val exit = events[nextIndex] as StatsEvent.Exit
require(event.className == exit.className)
return Pair(nextIndex + 1, StatsTree.Object(event.className, exit.offset - event.offset, children))
}
is StatsEvent.Exit -> {
throw IllegalStateException("Wasn't expecting Exit")
}
}
}
fun readTrees(events: List<StatsEvent>, index: Int): Pair<Int, List<StatsTree>> {
val trees = ArrayList<StatsTree>()
var i = index
while (true) {
val event = events.getOrNull(i)
when (event) {
is StatsEvent.Enter -> {
val (nextIndex, tree) = readTree(events, i)
trees.add(tree)
i = nextIndex
}
is StatsEvent.Exit -> {
return Pair(i, trees)
}
null -> {
return Pair(i, trees)
}
}
}
}

View File

@ -1,23 +0,0 @@
What is this
------------
This is a javaagent that hooks into Kryo serializers to record a breakdown of how many bytes objects take in the output.
The dump is quite ugly now, but the in-memory representation is a simple tree so we could put some nice visualisation on
top if we want.
How do I run it
---------------
Build the agent:
```
./gradlew experimental:kryo-hook:jar
```
Add this JVM flag to what you're running:
```
-javaagent:<PROJECT>/experimental/kryo-hook/build/libs/kryo-hook.jar
```
The agent will dump the output when the JVM shuts down.

View File

@ -191,6 +191,7 @@ object QuasarInstrumentationHook : ClassFileTransformer {
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
println("SOMETHING WENT WRONG") println("SOMETHING WENT WRONG")
throwable.printStackTrace(System.out) throwable.printStackTrace(System.out)
if (throwable is VirtualMachineError) throw throwable
classfileBuffer classfileBuffer
} }
} }

View File

@ -220,8 +220,8 @@ object RPCApi {
companion object { companion object {
private fun Any.safeSerialize(context: SerializationContext, wrap: (Throwable) -> Any) = try { private fun Any.safeSerialize(context: SerializationContext, wrap: (Throwable) -> Any) = try {
serialize(context = context) serialize(context = context)
} catch (t: Throwable) { } catch (e: Exception) {
wrap(t).serialize(context = context) wrap(e).serialize(context = context)
} }
fun fromClientMessage(context: SerializationContext, message: ClientMessage): ServerToClient { fun fromClientMessage(context: SerializationContext, message: ClientMessage): ServerToClient {

View File

@ -1,5 +1,6 @@
package net.corda.nodeapi.internal.crypto package net.corda.nodeapi.internal.crypto
import net.corda.core.crypto.Crypto.SPHINCS256_SHA256
import net.corda.core.crypto.SignatureScheme import net.corda.core.crypto.SignatureScheme
import org.bouncycastle.asn1.x509.AlgorithmIdentifier import org.bouncycastle.asn1.x509.AlgorithmIdentifier
import org.bouncycastle.operator.ContentSigner import org.bouncycastle.operator.ContentSigner
@ -17,7 +18,9 @@ object ContentSignerBuilder {
fun build(signatureScheme: SignatureScheme, privateKey: PrivateKey, provider: Provider, random: SecureRandom? = null): ContentSigner { fun build(signatureScheme: SignatureScheme, privateKey: PrivateKey, provider: Provider, random: SecureRandom? = null): ContentSigner {
val sigAlgId = signatureScheme.signatureOID val sigAlgId = signatureScheme.signatureOID
val sig = Signature.getInstance(signatureScheme.signatureName, provider).apply { val sig = Signature.getInstance(signatureScheme.signatureName, provider).apply {
if (random != null) { // TODO special handling for Sphincs due to a known BouncyCastle's Sphincs bug we reported.
// It is fixed in BC 161b12, so consider updating the below if-statement after updating BouncyCastle.
if (random != null && signatureScheme != SPHINCS256_SHA256) {
initSign(privateKey, random) initSign(privateKey, random)
} else { } else {
initSign(privateKey) initSign(privateKey)

View File

@ -38,3 +38,5 @@ interface CryptoService {
*/ */
fun getSigner(alias: String): ContentSigner fun getSigner(alias: String): ContentSigner
} }
open class CryptoServiceException(message: String?, cause: Throwable? = null) : Exception(message, cause)

View File

@ -169,8 +169,8 @@ class CordaPersistence(
var recoverableFailureCount = 0 var recoverableFailureCount = 0
fun <T> quietly(task: () -> T) = try { fun <T> quietly(task: () -> T) = try {
task() task()
} catch (t: Throwable) { } catch (e: Exception) {
log.warn("Cleanup task failed:", t) log.warn("Cleanup task failed:", e)
} }
while (true) { while (true) {
val transaction = contextDatabase.currentOrNew(isolationLevel) // XXX: Does this code really support statement changing the contextDatabase? val transaction = contextDatabase.currentOrNew(isolationLevel) // XXX: Does this code really support statement changing the contextDatabase?
@ -178,7 +178,7 @@ class CordaPersistence(
val answer = transaction.statement() val answer = transaction.statement()
transaction.commit() transaction.commit()
return answer return answer
} catch (e: Throwable) { } catch (e: Exception) {
quietly(transaction::rollback) quietly(transaction::rollback)
if (e is SQLException || (recoverAnyNestedSQLException && e.hasSQLExceptionCause())) { if (e is SQLException || (recoverAnyNestedSQLException && e.hasSQLExceptionCause())) {
if (++recoverableFailureCount > recoverableFailureTolerance) throw e if (++recoverableFailureCount > recoverableFailureTolerance) throw e

View File

@ -7,6 +7,7 @@ import org.hibernate.Transaction
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import java.sql.Connection import java.sql.Connection
import java.util.* import java.util.*
import javax.persistence.EntityManager
fun currentDBSession(): Session = contextTransaction.session fun currentDBSession(): Session = contextTransaction.session
private val _contextTransaction = ThreadLocal<DatabaseTransaction>() private val _contextTransaction = ThreadLocal<DatabaseTransaction>()
@ -65,6 +66,12 @@ class DatabaseTransaction(
session session
} }
// Returns a delegate which overrides certain operations that we do not want CorDapp developers to call.
val restrictedEntityManager: RestrictedEntityManager by lazy {
val entityManager = session as EntityManager
RestrictedEntityManager(entityManager)
}
val session: Session by sessionDelegate val session: Session by sessionDelegate
private lateinit var hibernateTransaction: Transaction private lateinit var hibernateTransaction: Transaction
@ -111,3 +118,4 @@ class DatabaseTransaction(
boundary.filter { !it.success }.subscribe { callback() } boundary.filter { !it.success }.subscribe { callback() }
} }
} }

View File

@ -0,0 +1,19 @@
package net.corda.nodeapi.internal.persistence
import javax.persistence.EntityManager
/**
* A delegate of [EntityManager] which disallows some operations.
*/
class RestrictedEntityManager(private val delegate: EntityManager) : EntityManager by delegate {
override fun close() {
throw UnsupportedOperationException("This method cannot be called via ServiceHub.withEntityManager.")
}
override fun clear() {
throw UnsupportedOperationException("This method cannot be called via ServiceHub.withEntityManager.")
}
// TODO: Figure out which other methods on EntityManager need to be blocked?
}

View File

@ -330,7 +330,7 @@ class X509UtilitiesTest {
lock.notifyAll() lock.notifyAll()
} }
sslServerSocket.close() sslServerSocket.close()
} catch (ex: Throwable) { } catch (ex: Exception) {
serverError = true serverError = true
} }
} }

View File

@ -150,6 +150,7 @@ dependencies {
// Capsule is a library for building independently executable fat JARs. // Capsule is a library for building independently executable fat JARs.
// We only need this dependency to compile our Caplet against. // We only need this dependency to compile our Caplet against.
compileOnly "co.paralleluniverse:capsule:$capsule_version" compileOnly "co.paralleluniverse:capsule:$capsule_version"
testCompile "co.paralleluniverse:capsule:$capsule_version"
// OkHTTP: Simple HTTP library. // OkHTTP: Simple HTTP library.
compile "com.squareup.okhttp3:okhttp:$okhttp_version" compile "com.squareup.okhttp3:okhttp:$okhttp_version"

View File

@ -35,7 +35,7 @@ class ErrorCodeLoggingTests : IntegrationTest() {
node.rpc.startFlow(::MyFlow).waitForCompletion() node.rpc.startFlow(::MyFlow).waitForCompletion()
val logFile = node.logFile() val logFile = node.logFile()
val linesWithErrorCode = logFile.useLines { lines -> lines.filter { line -> line.contains("[errorCode=") }.toList() } val linesWithErrorCode = logFile.useLines { lines -> lines.filter { line -> line.contains("[errorCode=") }.filter { line -> line.contains("moreInformationAt=https://errors.corda.net/") }.toList() }
assertThat(linesWithErrorCode).isNotEmpty assertThat(linesWithErrorCode).isNotEmpty
} }

View File

@ -0,0 +1,21 @@
package net.corda.node.services.rpc
import net.corda.core.utilities.getOrThrow
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import org.assertj.core.api.Assertions.assertThatCode
import org.junit.Test
class NodeHandleTests {
@Test
fun object_defined_functions_are_static_for_node_rpc_ops() {
driver(DriverParameters(startNodesInProcess = true)) {
val rpcClient = startNode().getOrThrow().rpc
assertThatCode { rpcClient.hashCode() }.doesNotThrowAnyException()
@Suppress("UnusedEquals")
assertThatCode { rpcClient == rpcClient }.doesNotThrowAnyException()
assertThatCode { rpcClient.toString() }.doesNotThrowAnyException()
}
}
}

View File

@ -25,8 +25,7 @@ public class CordaCaplet extends Capsule {
} }
private Config parseConfigFile(List<String> args) { private Config parseConfigFile(List<String> args) {
String baseDirOption = getOption(args, "--base-directory"); this.baseDir = getBaseDirectory(args);
this.baseDir = Paths.get((baseDirOption == null) ? "." : baseDirOption).toAbsolutePath().normalize().toString();
String config = getOption(args, "--config-file"); String config = getOption(args, "--config-file");
File configFile = (config == null) ? new File(baseDir, "node.conf") : new File(config); File configFile = (config == null) ? new File(baseDir, "node.conf") : new File(config);
try { try {
@ -41,17 +40,44 @@ public class CordaCaplet extends Capsule {
} }
} }
File getConfigFile(List<String> args, String baseDir) {
String config = getOptionMultiple(args, Arrays.asList("--config-file", "-f"));
return (config == null || config.equals("")) ? new File(baseDir, "node.conf") : new File(config);
}
String getBaseDirectory(List<String> args) {
String baseDir = getOptionMultiple(args, Arrays.asList("--base-directory", "-b"));
return Paths.get((baseDir == null) ? "." : baseDir).toAbsolutePath().normalize().toString();
}
private String getOptionMultiple(List<String> args, List<String> possibleOptions) {
String result = null;
for(String option: possibleOptions) {
result = getOption(args, option);
if (result != null) break;
}
return result;
}
private String getOption(List<String> args, String option) { private String getOption(List<String> args, String option) {
final String lowerCaseOption = option.toLowerCase(); final String lowerCaseOption = option.toLowerCase();
int index = 0; int index = 0;
for (String arg : args) { for (String arg : args) {
if (arg.toLowerCase().equals(lowerCaseOption)) { if (arg.toLowerCase().equals(lowerCaseOption)) {
if (index < args.size() - 1) { if (index < args.size() - 1 && !args.get(index + 1).startsWith("-")) {
return args.get(index + 1); return args.get(index + 1);
} else { } else {
return null; return null;
} }
} }
if (arg.toLowerCase().startsWith(lowerCaseOption)) {
if (arg.length() > option.length() && arg.substring(option.length(), option.length() + 1).equals("=")) {
return arg.substring(option.length() + 1);
} else {
return null;
}
}
index++; index++;
} }
return null; return null;
@ -96,7 +122,10 @@ public class CordaCaplet extends Capsule {
File cordappsDir = new File(baseDir, "cordapps"); File cordappsDir = new File(baseDir, "cordapps");
// Create cordapps directory if it doesn't exist. // Create cordapps directory if it doesn't exist.
requireCordappsDirExists(cordappsDir); if (!checkIfCordappDirExists(cordappsDir)) {
// If it fails, just return the existing class path. The main Corda jar will detect the error and fail gracefully.
return cp;
}
// Add additional directories of JARs to the classpath (at the end), e.g., for JDBC drivers. // Add additional directories of JARs to the classpath (at the end), e.g., for JDBC drivers.
augmentClasspath(cp, new File(baseDir, "drivers")); augmentClasspath(cp, new File(baseDir, "drivers"));
augmentClasspath(cp, cordappsDir); augmentClasspath(cp, cordappsDir);
@ -173,17 +202,18 @@ public class CordaCaplet extends Capsule {
} }
} }
private void requireCordappsDirExists(File dir) { private Boolean checkIfCordappDirExists(File dir) {
try { try {
if (!dir.mkdir() && !dir.exists()) { // It is unlikely to enter this if-branch, but just in case. if (!dir.mkdir() && !dir.exists()) { // It is unlikely to enter this if-branch, but just in case.
logOnFailedCordappDir(); logOnFailedCordappDir();
throw new RuntimeException("Cordapps dir could not be created"); // Let Capsule handle the error (log error, clean up, die). return false;
} }
} }
catch (SecurityException | NullPointerException e) { catch (SecurityException | NullPointerException e) {
logOnFailedCordappDir(); logOnFailedCordappDir();
throw e; // Let Capsule handle the error (log error, clean up, die). return false;
} }
return true;
} }
private void logOnFailedCordappDir() { private void logOnFailedCordappDir() {

View File

@ -9,10 +9,10 @@ import net.corda.confidential.SwapIdentitiesHandler
import net.corda.core.CordaException import net.corda.core.CordaException
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.context.InvocationContext import net.corda.core.context.InvocationContext
import net.corda.core.crypto.internal.AliasPrivateKey
import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.isCRLDistributionPointBlacklisted import net.corda.core.crypto.isCRLDistributionPointBlacklisted
import net.corda.core.crypto.internal.AliasPrivateKey
import net.corda.core.crypto.newSecureRandom import net.corda.core.crypto.newSecureRandom
import net.corda.core.flows.* import net.corda.core.flows.*
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
@ -105,6 +105,8 @@ import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit.MINUTES import java.util.concurrent.TimeUnit.MINUTES
import java.util.concurrent.TimeUnit.SECONDS import java.util.concurrent.TimeUnit.SECONDS
import java.util.function.Consumer
import javax.persistence.EntityManager
import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair
/** /**
@ -505,8 +507,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val republishInterval = try { val republishInterval = try {
networkMapClient.publish(signedNodeInfo) networkMapClient.publish(signedNodeInfo)
heartbeatInterval heartbeatInterval
} catch (t: Throwable) { } catch (e: Exception) {
log.warn("Error encountered while publishing node info, will retry again", t) log.warn("Error encountered while publishing node info, will retry again", e)
// TODO: Exponential backoff? It should reach max interval of eventHorizon/2. // TODO: Exponential backoff? It should reach max interval of eventHorizon/2.
1.minutes 1.minutes
} }
@ -802,7 +804,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because // Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with // the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
// the identity key. But the infrastructure to make that easy isn't here yet. // the identity key. But the infrastructure to make that easy isn't here yet.
return BasicHSMKeyManagementService(cacheFactory,identityService, database, cryptoService) return BasicHSMKeyManagementService(cacheFactory, identityService, database, cryptoService)
} }
open fun stop() { open fun stop() {
@ -988,6 +990,14 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
override fun jdbcSession(): Connection = database.createSession() override fun jdbcSession(): Connection = database.createSession()
override fun <T : Any> withEntityManager(block: EntityManager.() -> T): T {
return block(contextTransaction.restrictedEntityManager)
}
override fun withEntityManager(block: Consumer<EntityManager>) {
block.accept(contextTransaction.restrictedEntityManager)
}
// allows services to register handlers to be informed when the node stop method is called // allows services to register handlers to be informed when the node stop method is called
override fun registerUnloadHandler(runOnStop: () -> Unit) { override fun registerUnloadHandler(runOnStop: () -> Unit) {
this@AbstractNode.runOnStop += runOnStop this@AbstractNode.runOnStop += runOnStop
@ -1032,7 +1042,6 @@ class FlowStarterImpl(private val smm: StateMachineManager, private val flowLogi
private val _future = openFuture<FlowStateMachine<T>>() private val _future = openFuture<FlowStateMachine<T>>()
override val future: CordaFuture<FlowStateMachine<T>> override val future: CordaFuture<FlowStateMachine<T>>
get() = _future get() = _future
} }
return startFlow(startFlowEvent) return startFlow(startFlowEvent)
} }

View File

@ -362,7 +362,7 @@ open class Node(configuration: NodeConfiguration,
log.info("Retrieved public IP from Network Map Service: $this. This will be used instead of the provided \"$host\" as the advertised address.") log.info("Retrieved public IP from Network Map Service: $this. This will be used instead of the provided \"$host\" as the advertised address.")
} }
retrievedHostName retrievedHostName
} catch (ignore: Throwable) { } catch (ignore: Exception) {
// Cannot reach the network map service, ignore the exception and use provided P2P address instead. // Cannot reach the network map service, ignore the exception and use provided P2P address instead.
log.warn("Cannot connect to the network map service for public IP detection.") log.warn("Cannot connect to the network map service for public IP detection.")
null null

View File

@ -191,7 +191,7 @@ open class NodeStartup : NodeStartupLogging {
node.startupComplete.then { node.startupComplete.then {
try { try {
InteractiveShell.runLocalShell(node::stop) InteractiveShell.runLocalShell(node::stop)
} catch (e: Throwable) { } catch (e: Exception) {
logger.error("Shell failed to start", e) logger.error("Shell failed to start", e)
} }
} }

View File

@ -1,7 +1,6 @@
package net.corda.node.services.api package net.corda.node.services.api
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.CertRole import net.corda.core.internal.CertRole
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
@ -36,22 +35,17 @@ interface IdentityServiceInternal : IdentityService {
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
fun verifyAndRegisterIdentity(identity: PartyAndCertificate, isNewRandomIdentity: Boolean): PartyAndCertificate? fun verifyAndRegisterIdentity(identity: PartyAndCertificate, isNewRandomIdentity: Boolean): PartyAndCertificate?
fun partiesFromName(query: String, exactMatch: Boolean, x500name: CordaX500Name, results: LinkedHashSet<Party>, party: Party) {
val components = listOfNotNull(x500name.commonName, x500name.organisationUnit, x500name.organisation, x500name.locality, x500name.state, x500name.country)
components.forEach { component ->
if (exactMatch && component == query) {
results += party
} else if (!exactMatch) {
// We can imagine this being a query over a lucene index in future. // We can imagine this being a query over a lucene index in future.
// //
// Kostas says: We can easily use the Jaro-Winkler distance metric as it is best suited for short // Kostas says: When exactMatch = false, we can easily use the Jaro-Winkler distance metric as it is best suited for short
// strings such as entity/company names, and to detect small typos. We can also apply it for city // strings such as entity/company names, and to detect small typos. We can also apply it for city
// or any keyword related search in lists of records (not raw text - for raw text we need indexing) // or any keyword related search in lists of records (not raw text - for raw text we need indexing)
// and we can return results in hierarchical order (based on normalised String similarity 0.0-1.0). // and we can return results in hierarchical order (based on normalised String similarity 0.0-1.0).
if (component.contains(query, ignoreCase = true)) /** Check if [x500name] matches the [query]. */
results += party fun x500Matches(query: String, exactMatch: Boolean, x500name: CordaX500Name): Boolean {
} val components = listOfNotNull(x500name.commonName, x500name.organisationUnit, x500name.organisation, x500name.locality, x500name.state, x500name.country)
} return components.any { (exactMatch && it == query)
|| (!exactMatch && it.contains(query, ignoreCase = true)) }
} }
/** /**

View File

@ -113,8 +113,8 @@ interface NodeConfiguration {
fun makeCryptoService(): CryptoService { fun makeCryptoService(): CryptoService {
return when(cryptoServiceName) { return when(cryptoServiceName) {
SupportedCryptoServices.BC_SIMPLE -> BCCryptoService(this) // Pick default BCCryptoService when null.
null -> BCCryptoService(this) // Pick default BCCryptoService when null. SupportedCryptoServices.BC_SIMPLE, null -> BCCryptoService(this.myLegalName.x500Principal, this.signingCertificateStore)
} }
} }
} }
@ -123,7 +123,7 @@ data class FlowOverrideConfig(val overrides: List<FlowOverride> = listOf())
data class FlowOverride(val initiator: String, val responder: String) data class FlowOverride(val initiator: String, val responder: String)
/** /**
* Currently registered JMX Reporters * Currently registered JMX Reporters.
*/ */
enum class JmxReporterType { enum class JmxReporterType {
JOLOKIA, NEW_RELIC JOLOKIA, NEW_RELIC

View File

@ -63,8 +63,10 @@ class InMemoryIdentityService(identities: List<PartyAndCertificate> = emptyList(
override fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> { override fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> {
val results = LinkedHashSet<Party>() val results = LinkedHashSet<Party>()
for ((x500name, partyAndCertificate) in principalToParties) { principalToParties.forEach { (x500name, partyAndCertificate) ->
partiesFromName(query, exactMatch, x500name, results, partyAndCertificate.party) if (x500Matches(query, exactMatch, x500name)) {
results += partyAndCertificate.party
}
} }
return results return results
} }

View File

@ -143,7 +143,7 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
val key = mapToKey(identity) val key = mapToKey(identity)
if (isNewRandomIdentity) { if (isNewRandomIdentity) {
// Because this is supposed to be new and random, there's no way we have it in the database already, so skip the pessimistic check. // Because this is supposed to be new and random, there's no way we have it in the database already, so skip the pessimistic check.
keyToParties.set(key, identity) keyToParties[key] = identity
} else { } else {
keyToParties.addWithDuplicatesAllowed(key, identity) keyToParties.addWithDuplicatesAllowed(key, identity)
} }
@ -174,8 +174,10 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
override fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> { override fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> {
return database.transaction { return database.transaction {
val results = LinkedHashSet<Party>() val results = LinkedHashSet<Party>()
for ((x500name, partyId) in principalToParties.allPersisted()) { principalToParties.allPersisted().forEach { (x500name, partyId) ->
partiesFromName(query, exactMatch, x500name, results, keyToParties[partyId]!!.party) if (x500Matches(query, exactMatch, x500name)) {
results += keyToParties[partyId]!!.party
}
} }
results results
} }

View File

@ -2,31 +2,39 @@ package net.corda.node.services.keys.cryptoservice
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.newSecureRandom import net.corda.core.crypto.newSecureRandom
import net.corda.core.crypto.sha256
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.internal.config.CertificateStore import net.corda.nodeapi.internal.config.CertificateStore
import net.corda.nodeapi.internal.config.CertificateStoreSupplier
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.cryptoservice.CryptoService import net.corda.nodeapi.internal.cryptoservice.CryptoService
import net.corda.nodeapi.internal.cryptoservice.CryptoServiceException
import org.bouncycastle.operator.ContentSigner import org.bouncycastle.operator.ContentSigner
import java.security.KeyPair import java.security.KeyPair
import java.security.KeyStore import java.security.KeyStore
import java.security.PublicKey import java.security.PublicKey
import javax.security.auth.x500.X500Principal
/** /**
* Basic implementation of a [CryptoService] that uses BouncyCastle for cryptographic operations * Basic implementation of a [CryptoService] that uses BouncyCastle for cryptographic operations
* and a Java KeyStore in the form of [CertificateStore] to store private keys. * and a Java KeyStore in the form of [CertificateStore] to store private keys.
* This service reuses the [NodeConfiguration.signingCertificateStore] to store keys. * This service reuses the [NodeConfiguration.signingCertificateStore] to store keys.
*/ */
class BCCryptoService(private val nodeConf: NodeConfiguration) : CryptoService { class BCCryptoService(private val legalName: X500Principal, private val certificateStoreSupplier: CertificateStoreSupplier) : CryptoService {
// TODO check if keyStore exists. // TODO check if keyStore exists.
// TODO make it private when E2ETestKeyManagementService does not require direct access to the private key. // TODO make it private when E2ETestKeyManagementService does not require direct access to the private key.
internal var certificateStore: CertificateStore = nodeConf.signingCertificateStore.get(true) internal var certificateStore: CertificateStore = certificateStoreSupplier.get(true)
override fun generateKeyPair(alias: String, schemeNumberID: Int): PublicKey { override fun generateKeyPair(alias: String, schemeNumberID: Int): PublicKey {
try {
val keyPair = Crypto.generateKeyPair(Crypto.findSignatureScheme(schemeNumberID)) val keyPair = Crypto.generateKeyPair(Crypto.findSignatureScheme(schemeNumberID))
importKey(alias, keyPair) importKey(alias, keyPair)
return keyPair.public return keyPair.public
} catch (e: Exception) {
throw CryptoServiceException("Cannot generate key for alias $alias and signature scheme with id $schemeNumberID", e)
}
} }
override fun containsKey(alias: String): Boolean { override fun containsKey(alias: String): Boolean {
@ -34,17 +42,29 @@ class BCCryptoService(private val nodeConf: NodeConfiguration) : CryptoService {
} }
override fun getPublicKey(alias: String): PublicKey { override fun getPublicKey(alias: String): PublicKey {
try {
return certificateStore.query { getPublicKey(alias) } return certificateStore.query { getPublicKey(alias) }
} catch (e: Exception) {
throw CryptoServiceException("Cannot get public key for alias $alias", e)
}
} }
override fun sign(alias: String, data: ByteArray): ByteArray { override fun sign(alias: String, data: ByteArray): ByteArray {
return Crypto.doSign(certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) } , data) try {
return Crypto.doSign(certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) }, data)
} catch (e: Exception) {
throw CryptoServiceException("Cannot sign using the key with alias $alias. SHA256 of data to be signed: ${data.sha256()}", e)
}
} }
override fun getSigner(alias: String): ContentSigner { override fun getSigner(alias: String): ContentSigner {
try {
val privateKey = certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) } val privateKey = certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) }
val signatureScheme = Crypto.findSignatureScheme(privateKey) val signatureScheme = Crypto.findSignatureScheme(privateKey)
return ContentSignerBuilder.build(signatureScheme, privateKey, Crypto.findProvider(signatureScheme.providerName), newSecureRandom()) return ContentSignerBuilder.build(signatureScheme, privateKey, Crypto.findProvider(signatureScheme.providerName), newSecureRandom())
} catch (e: Exception) {
throw CryptoServiceException("Cannot get Signer for key with alias $alias", e)
}
} }
/** /**
@ -53,14 +73,18 @@ class BCCryptoService(private val nodeConf: NodeConfiguration) : CryptoService {
* loaded [certificateStore] in memory with the contents of the corresponding [KeyStore] file. * loaded [certificateStore] in memory with the contents of the corresponding [KeyStore] file.
*/ */
fun resyncKeystore() { fun resyncKeystore() {
certificateStore = nodeConf.signingCertificateStore.get(true) certificateStore = certificateStoreSupplier.get(true)
} }
/** Import an already existing [KeyPair] to this [CryptoService]. */ /** Import an already existing [KeyPair] to this [CryptoService]. */
fun importKey(alias: String, keyPair: KeyPair) { fun importKey(alias: String, keyPair: KeyPair) {
try {
// Store a self-signed certificate, as Keystore requires to store certificates instead of public keys. // Store a self-signed certificate, as Keystore requires to store certificates instead of public keys.
// We could probably add a null cert, but we store a self-signed cert that will be used to retrieve the public key. // We could probably add a null cert, but we store a self-signed cert that will be used to retrieve the public key.
val cert = X509Utilities.createSelfSignedCACertificate(nodeConf.myLegalName.x500Principal, keyPair) val cert = X509Utilities.createSelfSignedCACertificate(legalName, keyPair)
certificateStore.query { setPrivateKey(alias, keyPair.private, listOf(cert), certificateStore.entryPassword) } certificateStore.query { setPrivateKey(alias, keyPair.private, listOf(cert), certificateStore.entryPassword) }
} catch (e: Exception) {
throw CryptoServiceException("Cannot import key with alias $alias", e)
}
} }
} }

View File

@ -268,6 +268,7 @@ class RPCServer<OPS : RPCOps>(
log.error("Failed to send message, kicking client. Message was ${job.message}", throwable) log.error("Failed to send message, kicking client. Message was ${job.message}", throwable)
serverControl!!.closeConsumerConnectionsForAddress(job.clientAddress.toString()) serverControl!!.closeConsumerConnectionsForAddress(job.clientAddress.toString())
invalidateClient(job.clientAddress) invalidateClient(job.clientAddress)
if (throwable is VirtualMachineError) throw throwable
} }
} }

View File

@ -94,8 +94,8 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
override fun run() { override fun run() {
val nextScheduleDelay = try { val nextScheduleDelay = try {
updateNetworkMapCache() updateNetworkMapCache()
} catch (t: Throwable) { } catch (e: Exception) {
logger.warn("Error encountered while updating network map, will retry in $defaultRetryInterval", t) logger.warn("Error encountered while updating network map, will retry in $defaultRetryInterval", e)
defaultRetryInterval defaultRetryInterval
} }
// Schedule the next update. // Schedule the next update.

View File

@ -223,9 +223,13 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
val result = logic.call() val result = logic.call()
suspend(FlowIORequest.WaitForSessionConfirmations, maySkipCheckpoint = true) suspend(FlowIORequest.WaitForSessionConfirmations, maySkipCheckpoint = true)
Try.Success(result) Try.Success(result)
} catch (throwable: Throwable) { } catch (t: Throwable) {
logger.info("Flow threw exception... sending it to flow hospital", throwable) if(t is VirtualMachineError) {
Try.Failure<R>(throwable) logger.error("Caught unrecoverable error from flow. Forcibly terminating the JVM, this might leave resources open, and most likely will.", t)
Runtime.getRuntime().halt(1)
}
logger.info("Flow raised an error... sending it to flow hospital", t)
Try.Failure<R>(t)
} }
val softLocksId = if (hasSoftLockedStates) logic.runId.uuid else null val softLocksId = if (hasSoftLockedStates) logic.runId.uuid else null
val finalEvent = when (resultOrError) { val finalEvent = when (resultOrError) {
@ -377,8 +381,8 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
maySkipCheckpoint = skipPersistingCheckpoint, maySkipCheckpoint = skipPersistingCheckpoint,
fiber = this.checkpointSerialize(context = serializationContext.value) fiber = this.checkpointSerialize(context = serializationContext.value)
) )
} catch (throwable: Throwable) { } catch (exception: Exception) {
Event.Error(throwable) Event.Error(exception)
} }
// We must commit the database transaction before returning from this closure otherwise Quasar may schedule // We must commit the database transaction before returning from this closure otherwise Quasar may schedule

View File

@ -43,6 +43,7 @@ import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction
import net.corda.serialization.internal.CheckpointSerializeAsTokenContextImpl import net.corda.serialization.internal.CheckpointSerializeAsTokenContextImpl
import net.corda.serialization.internal.withTokenContext import net.corda.serialization.internal.withTokenContext
import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.activemq.artemis.utils.ReusableLatch
import org.apache.logging.log4j.LogManager
import rx.Observable import rx.Observable
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import java.security.SecureRandom import java.security.SecureRandom
@ -135,8 +136,14 @@ class SingleThreadedStateMachineManager(
val fibers = restoreFlowsFromCheckpoints() val fibers = restoreFlowsFromCheckpoints()
metrics.register("Flows.InFlight", Gauge<Int> { mutex.content.flows.size }) metrics.register("Flows.InFlight", Gauge<Int> { mutex.content.flows.size })
Fiber.setDefaultUncaughtExceptionHandler { fiber, throwable -> Fiber.setDefaultUncaughtExceptionHandler { fiber, throwable ->
if (throwable is VirtualMachineError) {
(fiber as FlowStateMachineImpl<*>).logger.error("Caught unrecoverable error from flow. Forcibly terminating the JVM, this might leave resources open, and most likely will.", throwable)
LogManager.shutdown(true)
Runtime.getRuntime().halt(1)
} else {
(fiber as FlowStateMachineImpl<*>).logger.warn("Caught exception from flow", throwable) (fiber as FlowStateMachineImpl<*>).logger.warn("Caught exception from flow", throwable)
} }
}
serviceHub.networkMapCache.nodeReady.then { serviceHub.networkMapCache.nodeReady.then {
logger.info("Node ready, info: ${serviceHub.myInfo}") logger.info("Node ready, info: ${serviceHub.myInfo}")
resumeRestoredFlows(fibers) resumeRestoredFlows(fibers)
@ -606,7 +613,7 @@ class SingleThreadedStateMachineManager(
private fun deserializeCheckpoint(serializedCheckpoint: SerializedBytes<Checkpoint>): Checkpoint? { private fun deserializeCheckpoint(serializedCheckpoint: SerializedBytes<Checkpoint>): Checkpoint? {
return try { return try {
serializedCheckpoint.checkpointDeserialize(context = checkpointSerializationContext!!) serializedCheckpoint.checkpointDeserialize(context = checkpointSerializationContext!!)
} catch (exception: Throwable) { } catch (exception: Exception) {
logger.error("Encountered unrestorable checkpoint!", exception) logger.error("Encountered unrestorable checkpoint!", exception)
null null
} }

View File

@ -39,7 +39,7 @@ class TransitionExecutorImpl(
for (action in transition.actions) { for (action in transition.actions) {
try { try {
actionExecutor.executeAction(fiber, action) actionExecutor.executeAction(fiber, action)
} catch (exception: Throwable) { } catch (exception: Exception) {
contextTransactionOrNull?.close() contextTransactionOrNull?.close()
if (transition.newState.checkpoint.errorState is ErrorState.Errored) { if (transition.newState.checkpoint.errorState is ErrorState.Errored) {
// If we errored while transitioning to an error state then we cannot record the additional // If we errored while transitioning to an error state then we cannot record the additional

View File

@ -77,8 +77,8 @@ class FiberDeserializationChecker {
is Job.Check -> { is Job.Check -> {
try { try {
job.serializedFiber.checkpointDeserialize(context = checkpointSerializationContext) job.serializedFiber.checkpointDeserialize(context = checkpointSerializationContext)
} catch (throwable: Throwable) { } catch (exception: Exception) {
log.error("Encountered unrestorable checkpoint!", throwable) log.error("Encountered unrestorable checkpoint!", exception)
foundUnrestorableFibers = true foundUnrestorableFibers = true
} }
} }

View File

@ -1,19 +1,15 @@
package net.corda.node.services.transactions package net.corda.node.services.transactions
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.ComponentGroupEnum import net.corda.core.contracts.ComponentGroupEnum
import net.corda.core.flows.FlowSession import net.corda.core.flows.FlowSession
import net.corda.core.flows.NotarisationPayload import net.corda.core.flows.NotarisationPayload
import net.corda.core.flows.NotarisationRequest
import net.corda.core.internal.notary.SinglePartyNotaryService
import net.corda.core.internal.notary.NotaryServiceFlow import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.core.internal.notary.SinglePartyNotaryService
import net.corda.core.transactions.ContractUpgradeFilteredTransaction import net.corda.core.transactions.ContractUpgradeFilteredTransaction
import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.FilteredTransaction import net.corda.core.transactions.FilteredTransaction
import net.corda.core.transactions.NotaryChangeWireTransaction import net.corda.core.transactions.NotaryChangeWireTransaction
class NonValidatingNotaryFlow(otherSideSession: FlowSession, service: SinglePartyNotaryService) : NotaryServiceFlow(otherSideSession, service) { /**
/**
* The received transaction is not checked for contract-validity, as that would require fully * The received transaction is not checked for contract-validity, as that would require fully
* resolving it into a [TransactionForVerification], for which the caller would have to reveal the whole transaction * resolving it into a [TransactionForVerification], for which the caller would have to reveal the whole transaction
* history chain. * history chain.
@ -21,18 +17,9 @@ class NonValidatingNotaryFlow(otherSideSession: FlowSession, service: SinglePart
* the caller, it is possible to raise a dispute and verify the validity of the transaction and subsequently * the caller, it is possible to raise a dispute and verify the validity of the transaction and subsequently
* undo the commit of the input states (the exact mechanism still needs to be worked out). * undo the commit of the input states (the exact mechanism still needs to be worked out).
*/ */
@Suspendable class NonValidatingNotaryFlow(otherSideSession: FlowSession, service: SinglePartyNotaryService) : NotaryServiceFlow(otherSideSession, service) {
override fun validateRequest(requestPayload: NotarisationPayload): TransactionParts { override fun extractParts(requestPayload: NotarisationPayload): TransactionParts {
val transaction = requestPayload.coreTransaction val tx = requestPayload.coreTransaction
checkInputs(transaction.inputs + transaction.references)
val request = NotarisationRequest(transaction.inputs, transaction.id)
validateRequestSignature(request, requestPayload.requestSignature)
val parts = extractParts(transaction)
checkNotary(parts.notary)
return parts
}
private fun extractParts(tx: CoreTransaction): TransactionParts {
return when (tx) { return when (tx) {
is FilteredTransaction -> { is FilteredTransaction -> {
tx.apply { tx.apply {
@ -43,7 +30,7 @@ class NonValidatingNotaryFlow(otherSideSession: FlowSession, service: SinglePart
} }
TransactionParts(tx.id, tx.inputs, tx.timeWindow, tx.notary, tx.references) TransactionParts(tx.id, tx.inputs, tx.timeWindow, tx.notary, tx.references)
} }
is ContractUpgradeFilteredTransaction -> TransactionParts(tx.id, tx.inputs, null, tx.notary) is ContractUpgradeFilteredTransaction,
is NotaryChangeWireTransaction -> TransactionParts(tx.id, tx.inputs, null, tx.notary) is NotaryChangeWireTransaction -> TransactionParts(tx.id, tx.inputs, null, tx.notary)
else -> { else -> {
throw IllegalArgumentException("Received unexpected transaction type: ${tx::class.java.simpleName}," + throw IllegalArgumentException("Received unexpected transaction type: ${tx::class.java.simpleName}," +

View File

@ -2,19 +2,16 @@ package net.corda.node.services.transactions
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.TimeWindow import net.corda.core.contracts.TimeWindow
import net.corda.core.contracts.TransactionVerificationException
import net.corda.core.flows.FlowSession import net.corda.core.flows.FlowSession
import net.corda.core.flows.NotarisationPayload import net.corda.core.flows.NotarisationPayload
import net.corda.core.flows.NotarisationRequest
import net.corda.core.flows.NotaryError import net.corda.core.flows.NotaryError
import net.corda.core.internal.ResolveTransactionsFlow import net.corda.core.internal.ResolveTransactionsFlow
import net.corda.core.internal.notary.SinglePartyNotaryService
import net.corda.core.internal.notary.NotaryInternalException import net.corda.core.internal.notary.NotaryInternalException
import net.corda.core.internal.notary.NotaryServiceFlow import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.core.internal.notary.SinglePartyNotaryService
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionWithSignatures import net.corda.core.transactions.TransactionWithSignatures
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import java.security.SignatureException
/** /**
* A notary commit flow that makes sure a given transaction is valid before committing it. This does mean that the calling * A notary commit flow that makes sure a given transaction is valid before committing it. This does mean that the calling
@ -22,29 +19,25 @@ import java.security.SignatureException
* has its input states "blocked" by a transaction from another party, and needs to establish whether that transaction was * has its input states "blocked" by a transaction from another party, and needs to establish whether that transaction was
* indeed valid. * indeed valid.
*/ */
class ValidatingNotaryFlow(otherSideSession: FlowSession, service: SinglePartyNotaryService) : NotaryServiceFlow(otherSideSession, service) { open class ValidatingNotaryFlow(otherSideSession: FlowSession, service: SinglePartyNotaryService) : NotaryServiceFlow(otherSideSession, service) {
override fun extractParts(requestPayload: NotarisationPayload): TransactionParts {
val stx = requestPayload.signedTransaction
val timeWindow: TimeWindow? = if (stx.coreTransaction is WireTransaction) stx.tx.timeWindow else null
return TransactionParts(stx.id, stx.inputs, timeWindow, stx.notary, stx.references)
}
/** /**
* Fully resolves the received transaction and its dependencies, runs contract verification logic and checks that * Fully resolves the received transaction and its dependencies, runs contract verification logic and checks that
* the transaction in question has all required signatures apart from the notary's. * the transaction in question has all required signatures apart from the notary's.
*/ */
@Suspendable @Suspendable
override fun validateRequest(requestPayload: NotarisationPayload): TransactionParts { override fun verifyTransaction(requestPayload: NotarisationPayload) {
try { try {
val stx = requestPayload.signedTransaction val stx = requestPayload.signedTransaction
checkInputs(stx.inputs + stx.references)
validateRequestSignature(NotarisationRequest(stx.inputs, stx.id), requestPayload.requestSignature)
val notary = stx.notary
checkNotary(notary)
resolveAndContractVerify(stx) resolveAndContractVerify(stx)
verifySignatures(stx) verifySignatures(stx)
val timeWindow: TimeWindow? = if (stx.coreTransaction is WireTransaction) stx.tx.timeWindow else null
return TransactionParts(stx.id, stx.inputs, timeWindow, notary!!, stx.references)
} catch (e: Exception) { } catch (e: Exception) {
throw when (e) { throw NotaryInternalException(NotaryError.TransactionInvalid(e))
is TransactionVerificationException,
is SignatureException -> NotaryInternalException(NotaryError.TransactionInvalid(e))
else -> e
}
} }
} }
@ -60,10 +53,6 @@ class ValidatingNotaryFlow(otherSideSession: FlowSession, service: SinglePartyNo
} }
private fun checkSignatures(tx: TransactionWithSignatures) { private fun checkSignatures(tx: TransactionWithSignatures) {
try {
tx.verifySignaturesExcept(service.notaryIdentityKey) tx.verifySignaturesExcept(service.notaryIdentityKey)
} catch (e: SignatureException) {
throw NotaryInternalException(NotaryError.TransactionInvalid(e))
}
} }
} }

View File

@ -0,0 +1,39 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import static org.junit.Assert.assertEquals;
@RunWith(Parameterized.class)
public class CordaCapletBaseDirectoryParsingFailureTest {
@Parameterized.Parameters
public static Collection<Object[]> CombinationsToTest() {
return Arrays.asList(
new Object[][]{
{new String[]{"--base-directory", "--another-option"}},
{new String[]{"--base-directory=", "-a"}},
{new String[]{"-b", "--another-option"}},
{new String[]{"-b=", "-a"}}
}
);
}
private String[] cmdLineArguments;
public CordaCapletBaseDirectoryParsingFailureTest(String[] baseOption) {
this.cmdLineArguments = baseOption;
}
@Test
public void testThatBaseDirectoryFallsBackToCurrentWhenBaseDirectoryIsNotSupplied() {
final CordaCaplet caplet = CordaCapletTestUtils.getCaplet();
final String returnPath = caplet.getBaseDirectory(Arrays.asList(cmdLineArguments));
final String expected = Paths.get(".").toAbsolutePath().normalize().toString();
assertEquals(expected, returnPath);
}
}

View File

@ -0,0 +1,39 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import static org.junit.Assert.assertEquals;
@RunWith(Parameterized.class)
public class CordaCapletBaseDirectoryParsingTest {
@Parameterized.Parameters
public static Collection<Object[]> CombinationsToTest() {
return Arrays.asList(
new Object[][]{
{new String[]{"--base-directory", "blah"}},
{new String[]{"--base-directory=blah"}},
{new String[]{"-b", "blah"}},
{new String[]{"-b=blah"}}
});
}
private String[] cmdLineArguments;
public CordaCapletBaseDirectoryParsingTest(String[] arr) {
this.cmdLineArguments = arr;
}
@Test
public void testThatBaseDirectoryParameterIsRecognised() {
final CordaCaplet caplet = CordaCapletTestUtils.getCaplet();
final String returnPath = caplet.getBaseDirectory(Arrays.asList(cmdLineArguments));
final String expected = Paths.get(".").resolve("blah").toAbsolutePath().normalize().toString();
assertEquals(expected, returnPath);
}
}

View File

@ -0,0 +1,40 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.io.File;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import static org.junit.Assert.assertEquals;
@RunWith(Parameterized.class)
public class CordaCapletConfigFileParsingFailureTest {
@Parameterized.Parameters
public static Collection<Object[]> CombinationsToTest() {
return Arrays.asList(
new Object[][]{
{new String[]{"--config-file", "--another-option"}},
{new String[]{"--config-file=", "-a"}},
{new String[]{"-f", "--another-option"}},
{new String[]{"-f=", "-a"}}
}
);
}
private String[] cmdLineArguments;
public CordaCapletConfigFileParsingFailureTest(String[] baseOption) {
this.cmdLineArguments = baseOption;
}
@Test
public void testThatBaseDirectoryFallsBackToDefaultWhenConfigFileIsNotSupplied() {
final CordaCaplet caplet = CordaCapletTestUtils.getCaplet();
final File returnPath = caplet.getConfigFile(Arrays.asList(cmdLineArguments), CordaCapletTestUtils.getBaseDir());
final File expected = Paths.get(".").resolve("node.conf").toAbsolutePath().normalize().toFile();
assertEquals(expected, returnPath);
}
}

View File

@ -0,0 +1,40 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.io.File;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import static org.junit.Assert.assertEquals;
@RunWith(Parameterized.class)
public class CordaCapletConfigFileParsingTest {
@Parameterized.Parameters
public static Collection<Object[]> CombinationsToTest() {
return Arrays.asList(
new Object[][]{
{new String[]{"--config-file", "blah.conf"}},
{new String[]{"--config-file=blah.conf"}},
{new String[]{"-f", "blah.conf"}},
{new String[]{"-f=blah.conf"}}
});
}
private String[] cmdLineArguments;
public CordaCapletConfigFileParsingTest(String[] arr) {
this.cmdLineArguments = arr;
}
@Test
public void testThatConfigFileParameterIsRecognised() {
final CordaCaplet caplet = CordaCapletTestUtils.getCaplet();
final File returnPath = caplet.getConfigFile(Arrays.asList(cmdLineArguments), CordaCapletTestUtils.getBaseDir());
final File expected = Paths.get(".").resolve("blah.conf").toAbsolutePath().normalize().toFile();
assertEquals(expected, returnPath.getAbsoluteFile());
}
}

View File

@ -0,0 +1,16 @@
import java.io.File;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Objects;
class CordaCapletTestUtils {
static CordaCaplet getCaplet() {
final String path = System.getProperty("user.dir") + File.separator + "build" + File.separator + "libs" + File.separator;
final File jar = Arrays.stream(Objects.requireNonNull(new File(path).listFiles())).filter(x -> x.getName().startsWith("corda-node") && x.getName().endsWith(".jar")).findFirst().get();
return new CordaCaplet(new Capsule(jar.toPath()));
}
static String getBaseDir() {
return Paths.get(".").toAbsolutePath().normalize().toString();
}
}

View File

@ -13,7 +13,6 @@ import net.corda.core.internal.FlowIORequest
import net.corda.core.internal.ResolveTransactionsFlow import net.corda.core.internal.ResolveTransactionsFlow
import net.corda.core.internal.bufferUntilSubscribed import net.corda.core.internal.bufferUntilSubscribed
import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.core.internal.notary.SinglePartyNotaryService import net.corda.core.internal.notary.SinglePartyNotaryService
import net.corda.core.internal.notary.UniquenessProvider import net.corda.core.internal.notary.UniquenessProvider
import net.corda.core.node.NotaryInfo import net.corda.core.node.NotaryInfo
@ -22,6 +21,7 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.transactions.ValidatingNotaryFlow
import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.network.NetworkParametersCopier import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
@ -199,9 +199,9 @@ class TimedFlowTests {
} }
/** A notary flow that will yield without returning a response on the very first received request. */ /** A notary flow that will yield without returning a response on the very first received request. */
private class TestNotaryFlow(otherSide: FlowSession, service: TestNotaryService) : NotaryServiceFlow(otherSide, service) { private class TestNotaryFlow(otherSide: FlowSession, service: TestNotaryService) : ValidatingNotaryFlow(otherSide, service) {
@Suspendable @Suspendable
override fun validateRequest(requestPayload: NotarisationPayload): TransactionParts { override fun verifyTransaction(requestPayload: NotarisationPayload) {
val myIdentity = serviceHub.myInfo.legalIdentities.first() val myIdentity = serviceHub.myInfo.legalIdentities.first()
MDC.put("name", myIdentity.name.toString()) MDC.put("name", myIdentity.name.toString())
logger.info("Received a request from ${otherSideSession.counterparty.name}") logger.info("Received a request from ${otherSideSession.counterparty.name}")
@ -215,7 +215,6 @@ class TimedFlowTests {
} else { } else {
logger.info("Processing") logger.info("Processing")
} }
return TransactionParts(stx.id, stx.inputs, stx.tx.timeWindow, stx.notary)
} }
} }
} }

View File

@ -276,7 +276,7 @@ class NodeConfigurationImplTest {
@Test @Test
fun `jmxReporterType is null and defaults to Jokolia`() { fun `jmxReporterType is null and defaults to Jokolia`() {
val rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true))) val rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true)))
val nodeConfig = rawConfig.parseAsNodeConfiguration().orThrow() val nodeConfig = rawConfig.parseAsNodeConfiguration().value()
assertTrue(JmxReporterType.JOLOKIA.toString() == nodeConfig.jmxReporterType.toString()) assertTrue(JmxReporterType.JOLOKIA.toString() == nodeConfig.jmxReporterType.toString())
} }
@ -284,7 +284,7 @@ class NodeConfigurationImplTest {
fun `jmxReporterType is not null and is set to New Relic`() { fun `jmxReporterType is not null and is set to New Relic`() {
var rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true))) var rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true)))
rawConfig = rawConfig.withValue("jmxReporterType", ConfigValueFactory.fromAnyRef("NEW_RELIC")) rawConfig = rawConfig.withValue("jmxReporterType", ConfigValueFactory.fromAnyRef("NEW_RELIC"))
val nodeConfig = rawConfig.parseAsNodeConfiguration().orThrow() val nodeConfig = rawConfig.parseAsNodeConfiguration().value()
assertTrue(JmxReporterType.NEW_RELIC.toString() == nodeConfig.jmxReporterType.toString()) assertTrue(JmxReporterType.NEW_RELIC.toString() == nodeConfig.jmxReporterType.toString())
} }
@ -292,7 +292,7 @@ class NodeConfigurationImplTest {
fun `jmxReporterType is not null and set to Jokolia`() { fun `jmxReporterType is not null and set to Jokolia`() {
var rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true))) var rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true)))
rawConfig = rawConfig.withValue("jmxReporterType", ConfigValueFactory.fromAnyRef("JOLOKIA")) rawConfig = rawConfig.withValue("jmxReporterType", ConfigValueFactory.fromAnyRef("JOLOKIA"))
val nodeConfig = rawConfig.parseAsNodeConfiguration().orThrow() val nodeConfig = rawConfig.parseAsNodeConfiguration().value()
assertTrue(JmxReporterType.JOLOKIA.toString() == nodeConfig.jmxReporterType.toString()) assertTrue(JmxReporterType.JOLOKIA.toString() == nodeConfig.jmxReporterType.toString())
} }

View File

@ -0,0 +1,83 @@
package net.corda.node.services.keys.cryptoservice
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignatureScheme
import net.corda.core.internal.div
import net.corda.core.utilities.days
import net.corda.nodeapi.internal.config.CertificateStoreSupplier
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.cryptoservice.CryptoServiceException
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.internal.stubs.CertificateStoreStubs
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.time.Duration
import javax.security.auth.x500.X500Principal
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class BCCryptoServiceTests {
companion object {
val clearData = "data".toByteArray()
}
@Rule
@JvmField
val temporaryFolder = TemporaryFolder()
private lateinit var signingCertificateStore: CertificateStoreSupplier
@Before
fun setUp() {
val baseDirectory = temporaryFolder.root.toPath()
val certificatesDirectory = baseDirectory / "certificates"
signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory)
}
@Test
fun `BCCryptoService generate key pair and sign both data and cert`() {
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore)
// Testing every supported scheme.
Crypto.supportedSignatureSchemes().filter { it != Crypto.COMPOSITE_KEY }.forEach { generateKeyAndSignForScheme(cryptoService, it) }
}
private fun generateKeyAndSignForScheme(cryptoService: BCCryptoService, signatureScheme: SignatureScheme) {
val schemeNumberID = signatureScheme.schemeNumberID
val alias = "signature$schemeNumberID"
val pubKey = cryptoService.generateKeyPair(alias, schemeNumberID)
assertTrue { cryptoService.containsKey(alias) }
val signatureData = cryptoService.sign(alias, clearData)
assertTrue(Crypto.doVerify(pubKey, signatureData, clearData))
// Test that getSigner can indeed sign a certificate.
val signer = cryptoService.getSigner(alias)
val x500Principal = X500Principal("CN=Test")
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 365.days)
val certificate = X509Utilities.createCertificate(
CertificateType.CONFIDENTIAL_LEGAL_IDENTITY,
x500Principal,
pubKey,
signer,
x500Principal,
pubKey,
window)
certificate.checkValidity()
certificate.verify(pubKey)
}
@Test
fun `When key does not exist getPublicKey, sign and getSigner should throw`() {
val nonExistingAlias = "nonExistingAlias"
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore)
assertFalse { cryptoService.containsKey(nonExistingAlias) }
assertFailsWith<CryptoServiceException> { cryptoService.getPublicKey(nonExistingAlias) }
assertFailsWith<CryptoServiceException> { cryptoService.sign(nonExistingAlias, clearData) }
assertFailsWith<CryptoServiceException> { cryptoService.getSigner(nonExistingAlias) }
}
}

View File

@ -0,0 +1,89 @@
package net.corda.node.services.persistence
import co.paralleluniverse.fibers.Suspendable
import com.esotericsoftware.kryo.KryoException
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowLogic.Companion.sleep
import net.corda.core.identity.CordaX500Name
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.CordaSerializable
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.TestIdentity
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockServices
import net.corda.testing.node.makeTestIdentityService
import org.junit.BeforeClass
import org.junit.ClassRule
import org.junit.Test
import java.io.Serializable
import java.time.Duration
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Id
import javax.persistence.Table
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class ExposeJpaToFlowsTests {
object FooSchema
object FooSchemaV1 : MappedSchema(schemaFamily = FooSchema.javaClass, version = 1, mappedTypes = listOf(PersistentFoo::class.java)) {
@Entity
@Table(name = "foos")
class PersistentFoo(@Id @Column(name = "foo_id") var fooId: String, @Column(name = "foo_data") var fooData: String) : Serializable
}
val myself = TestIdentity(CordaX500Name("Me", "London", "GB"))
val cordapps = listOf("net.corda.node.services.persistence")
val databaseAndServices = MockServices.makeTestDatabaseAndMockServices(
cordappPackages = cordapps,
identityService = makeTestIdentityService(myself.identity),
initialIdentity = myself,
networkParameters = testNetworkParameters(minimumPlatformVersion = 4)
)
val services: MockServices = databaseAndServices.second
val database: CordaPersistence = databaseAndServices.first
@Test
fun `can persist and query custom entities`() {
val foo = FooSchemaV1.PersistentFoo(UniqueIdentifier().id.toString(), "Bar")
// Persist the foo.
val result: MutableList<FooSchemaV1.PersistentFoo> = database.transaction {
services.withEntityManager {
// Persist.
persist(foo)
// Query.
val query = criteriaBuilder.createQuery(FooSchemaV1.PersistentFoo::class.java)
val type = query.from(FooSchemaV1.PersistentFoo::class.java)
query.select(type)
createQuery(query).resultList
}
}
assertEquals("Bar", result.single().fooData)
}
@Test
fun `can't perform suspendable operations inside withEntityManager`() {
val mockNet = MockNetwork(cordapps)
val mockNode = mockNet.createNode()
assertFailsWith(KryoException::class) {
mockNode.startFlow(object : FlowLogic<Unit>() {
@Suspendable
override fun call() {
serviceHub.withEntityManager {
val session = initiateFlow(myself.party)
session.send("Ooohhh eee oooh ah ah ting tang walla walla bing bang!")
}
}
})
}
mockNet.stopNodes()
}
}

View File

@ -276,7 +276,7 @@ class NodeVaultServiceTest {
assertThat(vaultService.queryBy<Cash.State>(criteriaByLockId1).states).hasSize(3) assertThat(vaultService.queryBy<Cash.State>(criteriaByLockId1).states).hasSize(3)
} }
println("SOFT LOCK STATES #1 succeeded") println("SOFT LOCK STATES #1 succeeded")
} catch (e: Throwable) { } catch (e: Exception) {
println("SOFT LOCK STATES #1 failed") println("SOFT LOCK STATES #1 failed")
} finally { } finally {
countDown.countDown() countDown.countDown()
@ -292,7 +292,7 @@ class NodeVaultServiceTest {
assertThat(vaultService.queryBy<Cash.State>(criteriaByLockId2).states).hasSize(3) assertThat(vaultService.queryBy<Cash.State>(criteriaByLockId2).states).hasSize(3)
} }
println("SOFT LOCK STATES #2 succeeded") println("SOFT LOCK STATES #2 succeeded")
} catch (e: Throwable) { } catch (e: Exception) {
println("SOFT LOCK STATES #2 failed") println("SOFT LOCK STATES #2 failed")
} finally { } finally {
countDown.countDown() countDown.countDown()

View File

@ -327,7 +327,7 @@ class TLSAuthenticationTests {
lock.notifyAll() lock.notifyAll()
} }
sslServerSocket.close() sslServerSocket.close()
} catch (ex: Throwable) { } catch (ex: Exception) {
serverError = true serverError = true
} }
} }

View File

@ -65,7 +65,7 @@ class InterestRateSwapAPI {
return try { return try {
rpc.startFlow(AutoOfferFlow::Requester, newDeal).returnValue.getOrThrow() rpc.startFlow(AutoOfferFlow::Requester, newDeal).returnValue.getOrThrow()
ResponseEntity.created(URI.create(generateDealLink(newDeal))).build() ResponseEntity.created(URI.create(generateDealLink(newDeal))).build()
} catch (ex: Throwable) { } catch (ex: Exception) {
logger.info("Exception when creating deal: $ex", ex) logger.info("Exception when creating deal: $ex", ex)
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.toString()) ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.toString())
} }

View File

@ -1,20 +1,19 @@
package net.corda.notarydemo package net.corda.notarydemo
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.TimeWindow import net.corda.core.flows.FlowLogic
import net.corda.core.contracts.TransactionVerificationException import net.corda.core.flows.FlowSession
import net.corda.core.flows.* import net.corda.core.flows.NotarisationPayload
import net.corda.core.flows.NotaryError
import net.corda.core.internal.ResolveTransactionsFlow import net.corda.core.internal.ResolveTransactionsFlow
import net.corda.core.internal.notary.NotaryInternalException import net.corda.core.internal.notary.NotaryInternalException
import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.core.internal.notary.SinglePartyNotaryService import net.corda.core.internal.notary.SinglePartyNotaryService
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionWithSignatures import net.corda.core.transactions.TransactionWithSignatures
import net.corda.core.transactions.WireTransaction
import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.services.transactions.ValidatingNotaryFlow
import java.security.PublicKey import java.security.PublicKey
import java.security.SignatureException
/** /**
* A custom notary service should provide a constructor that accepts two parameters of types [ServiceHubInternal] and [PublicKey]. * A custom notary service should provide a constructor that accepts two parameters of types [ServiceHubInternal] and [PublicKey].
@ -35,28 +34,15 @@ class MyCustomValidatingNotaryService(override val services: ServiceHubInternal,
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
// START 2 // START 2
class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidatingNotaryService) : NotaryServiceFlow(otherSide, service) { class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidatingNotaryService) : ValidatingNotaryFlow(otherSide, service) {
/** override fun verifyTransaction(requestPayload: NotarisationPayload) {
* The received transaction is checked for contract-validity, for which the caller also has to to reveal the whole
* transaction dependency chain.
*/
@Suspendable
override fun validateRequest(requestPayload: NotarisationPayload): TransactionParts {
try { try {
val stx = requestPayload.signedTransaction val stx = requestPayload.signedTransaction
validateRequestSignature(NotarisationRequest(stx.inputs, stx.id), requestPayload.requestSignature)
val notary = stx.notary
checkNotary(notary)
verifySignatures(stx)
resolveAndContractVerify(stx) resolveAndContractVerify(stx)
val timeWindow: TimeWindow? = if (stx.coreTransaction is WireTransaction) stx.tx.timeWindow else null verifySignatures(stx)
return TransactionParts(stx.id, stx.inputs, timeWindow, notary!!, stx.references) customVerify(stx)
} catch (e: Exception) { } catch (e: Exception) {
throw when (e) { throw NotaryInternalException(NotaryError.TransactionInvalid(e))
is TransactionVerificationException,
is SignatureException -> NotaryInternalException(NotaryError.TransactionInvalid(e))
else -> e
}
} }
} }
@ -64,7 +50,6 @@ class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidating
private fun resolveAndContractVerify(stx: SignedTransaction) { private fun resolveAndContractVerify(stx: SignedTransaction) {
subFlow(ResolveTransactionsFlow(stx, otherSideSession)) subFlow(ResolveTransactionsFlow(stx, otherSideSession))
stx.verify(serviceHub, false) stx.verify(serviceHub, false)
customVerify(stx)
} }
private fun verifySignatures(stx: SignedTransaction) { private fun verifySignatures(stx: SignedTransaction) {
@ -73,11 +58,7 @@ class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidating
} }
private fun checkSignatures(tx: TransactionWithSignatures) { private fun checkSignatures(tx: TransactionWithSignatures) {
try {
tx.verifySignaturesExcept(service.notaryIdentityKey) tx.verifySignaturesExcept(service.notaryIdentityKey)
} catch (e: SignatureException) {
throw NotaryInternalException(NotaryError.TransactionInvalid(e))
}
} }
private fun customVerify(stx: SignedTransaction) { private fun customVerify(stx: SignedTransaction) {

View File

@ -14,6 +14,7 @@ import org.apache.qpid.proton.amqp.UnsignedInteger
import org.apache.qpid.proton.codec.Data import org.apache.qpid.proton.codec.Data
import java.io.InputStream import java.io.InputStream
import java.io.NotSerializableException import java.io.NotSerializableException
import java.lang.Exception
import java.lang.reflect.ParameterizedType import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type import java.lang.reflect.Type
import java.lang.reflect.TypeVariable import java.lang.reflect.TypeVariable
@ -100,8 +101,8 @@ class DeserializationInput constructor(
throw NotSerializableException(amqp.mitigation) throw NotSerializableException(amqp.mitigation)
} catch (nse: NotSerializableException) { } catch (nse: NotSerializableException) {
throw nse throw nse
} catch (t: Throwable) { } catch (e: Exception) {
throw NotSerializableException("Internal deserialization failure: ${t.javaClass.name}: ${t.message}").apply { initCause(t) } throw NotSerializableException("Internal deserialization failure: ${e.javaClass.name}: ${e.message}").apply { initCause(e) }
} finally { } finally {
objectHistory.clear() objectHistory.clear()
} }

View File

@ -23,7 +23,6 @@ include 'experimental'
include 'experimental:avalanche' include 'experimental:avalanche'
include 'experimental:behave' include 'experimental:behave'
include 'experimental:quasar-hook' include 'experimental:quasar-hook'
include 'experimental:kryo-hook'
// include 'experimental:intellij-plugin' // include 'experimental:intellij-plugin'
include 'experimental:flow-hook' include 'experimental:flow-hook'
include 'experimental:flow-worker' include 'experimental:flow-worker'

View File

@ -179,8 +179,8 @@ class DriverTests : IntegrationTest() {
fun `driver waits for in-process nodes to finish`() { fun `driver waits for in-process nodes to finish`() {
fun NodeHandle.stopQuietly() = try { fun NodeHandle.stopQuietly() = try {
stop() stop()
} catch (t: Throwable) { } catch (e: Exception) {
t.printStackTrace() e.printStackTrace()
} }
val handlesFuture = openFuture<List<NodeHandle>>() val handlesFuture = openFuture<List<NodeHandle>>()

View File

@ -28,6 +28,8 @@ import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.NodeVaultService
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.contextTransaction
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.DEV_ROOT_CA import net.corda.testing.internal.DEV_ROOT_CA
@ -39,6 +41,8 @@ import java.security.KeyPair
import java.sql.Connection import java.sql.Connection
import java.time.Clock import java.time.Clock
import java.util.* import java.util.*
import java.util.function.Consumer
import javax.persistence.EntityManager
/** /**
* Returns a simple [InMemoryIdentityService] containing the supplied [identities]. * Returns a simple [InMemoryIdentityService] containing the supplied [identities].
@ -117,6 +121,14 @@ open class MockServices private constructor(
} }
override fun jdbcSession(): Connection = database.createSession() override fun jdbcSession(): Connection = database.createSession()
override fun <T : Any> withEntityManager(block: EntityManager.() -> T): T {
return block(contextTransaction.restrictedEntityManager)
}
override fun withEntityManager(block: Consumer<EntityManager>) {
return block.accept(contextTransaction.restrictedEntityManager)
}
} }
} }
return Pair(database, mockService) return Pair(database, mockService)
@ -264,6 +276,14 @@ open class MockServices private constructor(
override fun jdbcSession(): Connection = throw UnsupportedOperationException() override fun jdbcSession(): Connection = throw UnsupportedOperationException()
override fun <T : Any> withEntityManager(block: EntityManager.() -> T): T {
throw UnsupportedOperationException()
}
override fun withEntityManager(block: Consumer<EntityManager>) {
throw UnsupportedOperationException()
}
override fun registerUnloadHandler(runOnStop: () -> Unit) = throw UnsupportedOperationException() override fun registerUnloadHandler(runOnStop: () -> Unit) = throw UnsupportedOperationException()
/** Add the given package name to the list of packages which will be scanned for cordapp contract verification code */ /** Add the given package name to the list of packages which will be scanned for cordapp contract verification code */

View File

@ -737,7 +737,7 @@ class DriverDSLImpl(
* Keeping [Config] around is needed as the user may specify extra config options not specified in [NodeConfiguration]. * Keeping [Config] around is needed as the user may specify extra config options not specified in [NodeConfiguration].
*/ */
private class NodeConfig(val typesafe: Config) { private class NodeConfig(val typesafe: Config) {
val corda: NodeConfiguration = typesafe.parseAsNodeConfiguration().orThrow() val corda: NodeConfiguration = typesafe.parseAsNodeConfiguration().value()
} }
companion object { companion object {

View File

@ -106,8 +106,8 @@ fun <A> poll(
} else { } else {
executorService.schedule(this, pollInterval.toMillis(), TimeUnit.MILLISECONDS) executorService.schedule(this, pollInterval.toMillis(), TimeUnit.MILLISECONDS)
} }
} catch (t: Throwable) { } catch (e: Exception) {
resultFuture.setException(t) resultFuture.setException(e)
} }
} }
} }

View File

@ -118,7 +118,7 @@ abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyLi
val specificConfig = config.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories.toSet())) val specificConfig = config.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories.toSet()))
val parsedConfig = specificConfig.parseAsNodeConfiguration().orThrow() val parsedConfig = specificConfig.parseAsNodeConfiguration().value()
defaultNetworkParameters.install(baseDirectory) defaultNetworkParameters.install(baseDirectory)
return InProcessNode(parsedConfig, MOCK_VERSION_INFO.copy(platformVersion = platformVersion), flowManager) return InProcessNode(parsedConfig, MOCK_VERSION_INFO.copy(platformVersion = platformVersion), flowManager)

View File

@ -65,6 +65,9 @@ class ShutdownManager(private val executorService: ExecutorService) {
it.value() it.value()
} catch (t: Throwable) { } catch (t: Throwable) {
log.warn("Exception while calling a shutdown action, this might create resource leaks", t) log.warn("Exception while calling a shutdown action, this might create resource leaks", t)
if (t is VirtualMachineError) {
throw t
}
} }
is Try.Failure -> log.warn("Exception while getting shutdown method, disregarding", it.exception) is Try.Failure -> log.warn("Exception while getting shutdown method, disregarding", it.exception)
} }

View File

@ -91,8 +91,8 @@ fun startPublishingFixedRateInjector(
} }
workBoundSemaphore.release() workBoundSemaphore.release()
} }
} catch (throwable: Throwable) { } catch (e: Exception) {
throwable.printStackTrace() e.printStackTrace()
} }
} }
} }

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