Merge branch 'release/os/4.3' into rog-os-merge

This commit is contained in:
RogerWillis 2019-10-14 10:37:21 +01:00
commit c261a8447a
29 changed files with 612 additions and 221 deletions

View File

@ -2464,7 +2464,6 @@ public interface net.corda.core.flows.IdentifiableException
@Nullable @Nullable
public Long getErrorId() public Long getErrorId()
## ##
@CordaSerializable
public final class net.corda.core.flows.IllegalFlowLogicException extends java.lang.IllegalArgumentException public final class net.corda.core.flows.IllegalFlowLogicException extends java.lang.IllegalArgumentException
public <init>(Class<?>, String) public <init>(Class<?>, String)
public <init>(String, String) public <init>(String, String)

9
Jenkinsfile vendored
View File

@ -39,15 +39,6 @@ pipeline {
" allParallelIntegrationTest" " allParallelIntegrationTest"
} }
} }
stage('Unit Tests') {
steps {
sh "./gradlew " +
"-DbuildId=\"\${BUILD_ID}\" " +
"-Dkubenetize=true " +
"-Ddocker.tag=\"\${DOCKER_TAG_TO_USE}\"" +
" allParallelUnitTest"
}
}
} }
} }

View File

@ -23,15 +23,7 @@ allprojects {
} }
} }
configurations {
runtime
}
dependencies { dependencies {
// Add the top-level projects ONLY to the host project.
runtime project.childProjects.collect { n, p ->
project(p.path)
}
compile gradleApi() compile gradleApi()
compile "io.fabric8:kubernetes-client:4.4.1" compile "io.fabric8:kubernetes-client:4.4.1"
compile 'org.apache.commons:commons-compress:1.19' compile 'org.apache.commons:commons-compress:1.19'

View File

@ -1,10 +1,10 @@
package net.corda.client.rpc.internal package net.corda.client.rpc.internal
import co.paralleluniverse.common.util.SameThreadExecutor
import com.github.benmanes.caffeine.cache.Cache import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.RemovalCause import com.github.benmanes.caffeine.cache.RemovalCause
import com.github.benmanes.caffeine.cache.RemovalListener import com.github.benmanes.caffeine.cache.RemovalListener
import com.google.common.util.concurrent.MoreExecutors
import com.google.common.util.concurrent.SettableFuture import com.google.common.util.concurrent.SettableFuture
import com.google.common.util.concurrent.ThreadFactoryBuilder import com.google.common.util.concurrent.ThreadFactoryBuilder
import net.corda.client.rpc.ConnectionFailureException import net.corda.client.rpc.ConnectionFailureException
@ -132,7 +132,10 @@ class RPCClientProxyHandler(
private var sendExecutor: ExecutorService? = null private var sendExecutor: ExecutorService? = null
// A sticky pool for running Observable.onNext()s. We need the stickiness to preserve the observation ordering. // A sticky pool for running Observable.onNext()s. We need the stickiness to preserve the observation ordering.
private val observationExecutorThreadFactory = ThreadFactoryBuilder().setNameFormat("rpc-client-observation-pool-%d").setDaemon(true).build() private val observationExecutorThreadFactory = ThreadFactoryBuilder()
.setNameFormat("rpc-client-observation-pool-%d")
.setDaemon(true)
.build()
private val observationExecutorPool = LazyStickyPool(rpcConfiguration.observationExecutorPoolSize) { private val observationExecutorPool = LazyStickyPool(rpcConfiguration.observationExecutorPoolSize) {
Executors.newFixedThreadPool(1, observationExecutorThreadFactory) Executors.newFixedThreadPool(1, observationExecutorThreadFactory)
} }
@ -156,12 +159,14 @@ class RPCClientProxyHandler(
private val observablesToReap = ThreadBox(object { private val observablesToReap = ThreadBox(object {
var observables = ArrayList<InvocationId>() var observables = ArrayList<InvocationId>()
}) })
private val serializationContextWithObservableContext = RpcClientObservableDeSerializer.createContext(serializationContext, observableContext) private val serializationContextWithObservableContext = RpcClientObservableDeSerializer
.createContext(serializationContext, observableContext)
private fun createRpcObservableMap(): RpcObservableMap { private fun createRpcObservableMap(): RpcObservableMap {
val onObservableRemove = RemovalListener<InvocationId, UnicastSubject<Notification<*>>> { key, _, cause -> val onObservableRemove = RemovalListener<InvocationId, UnicastSubject<Notification<*>>> { key, _, cause ->
val observableId = key!! val observableId = key!!
val rpcCallSite: CallSite? = callSiteMap?.remove(observableId) val rpcCallSite: CallSite? = callSiteMap?.remove(observableId)
if (cause == RemovalCause.COLLECTED) { if (cause == RemovalCause.COLLECTED) {
log.warn(listOf( log.warn(listOf(
"A hot observable returned from an RPC was never subscribed to.", "A hot observable returned from an RPC was never subscribed to.",
@ -175,7 +180,13 @@ class RPCClientProxyHandler(
} }
observablesToReap.locked { observables.add(observableId) } observablesToReap.locked { observables.add(observableId) }
} }
return cacheFactory.buildNamed(Caffeine.newBuilder().weakValues().removalListener(onObservableRemove).executor(SameThreadExecutor.getExecutor()), "RpcClientProxyHandler_rpcObservable") return cacheFactory.buildNamed(
Caffeine.newBuilder()
.weakValues()
.removalListener(onObservableRemove)
.executor(MoreExecutors.directExecutor()),
"RpcClientProxyHandler_rpcObservable"
)
} }
private var sessionFactory: ClientSessionFactory? = null private var sessionFactory: ClientSessionFactory? = null

View File

@ -32,3 +32,5 @@ metricsVersion=4.1.0
metricsNewRelicVersion=1.1.1 metricsNewRelicVersion=1.1.1
openSourceBranch=https://github.com/corda/corda/blob/master openSourceBranch=https://github.com/corda/corda/blob/master
openSourceSamplesBranch=https://github.com/corda/samples/blob/master openSourceSamplesBranch=https://github.com/corda/samples/blob/master
jolokiaAgentVersion=1.6.1

View File

@ -1,8 +1,13 @@
package net.corda.coretests.contracts package net.corda.coretests.contracts
import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint
import net.corda.core.contracts.Contract import net.corda.core.contracts.Contract
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TransactionState
import net.corda.core.contracts.TransactionVerificationException import net.corda.core.contracts.TransactionVerificationException
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.Party
import net.corda.core.internal.createContractCreationError import net.corda.core.internal.createContractCreationError
import net.corda.core.internal.createContractRejection import net.corda.core.internal.createContractRejection
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
@ -13,6 +18,9 @@ import net.corda.serialization.internal.amqp.SerializationOutput
import net.corda.serialization.internal.amqp.SerializerFactoryBuilder import net.corda.serialization.internal.amqp.SerializerFactoryBuilder
import net.corda.serialization.internal.amqp.custom.PublicKeySerializer import net.corda.serialization.internal.amqp.custom.PublicKeySerializer
import net.corda.serialization.internal.amqp.custom.ThrowableSerializer import net.corda.serialization.internal.amqp.custom.ThrowableSerializer
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.DUMMY_BANK_A_NAME import net.corda.testing.core.DUMMY_BANK_A_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.TestIdentity import net.corda.testing.core.TestIdentity
@ -23,7 +31,9 @@ class TransactionVerificationExceptionSerialisationTests {
private fun defaultFactory() = SerializerFactoryBuilder.build( private fun defaultFactory() = SerializerFactoryBuilder.build(
AllWhitelist, AllWhitelist,
ClassLoader.getSystemClassLoader() ClassLoader.getSystemClassLoader()
).apply { register(ThrowableSerializer(this)) } ).apply {
register(ThrowableSerializer(this))
}
private val context get() = AMQP_RPC_CLIENT_CONTEXT private val context get() = AMQP_RPC_CLIENT_CONTEXT
@ -179,4 +189,125 @@ class TransactionVerificationExceptionSerialisationTests {
assertEquals(exc.message, exc2.message) assertEquals(exc.message, exc2.message)
} }
@Test
fun transactionNetworkParameterOrderingExceptionTest() {
val exception = TransactionVerificationException.TransactionNetworkParameterOrderingException(
txid,
StateRef(SecureHash.zeroHash, 1),
testNetworkParameters(),
testNetworkParameters())
val exception2 = DeserializationInput(factory)
.deserialize(
SerializationOutput(factory)
.serialize(exception, context),
context)
assertEquals(exception.message, exception2.message)
assertEquals(exception.cause?.message, exception2.cause?.message)
assertEquals(exception.txId, exception2.txId)
}
@Test
fun missingNetworkParametersExceptionTest() {
val exception = TransactionVerificationException.MissingNetworkParametersException(txid, SecureHash.zeroHash)
val exception2 = DeserializationInput(factory)
.deserialize(
SerializationOutput(factory)
.serialize(exception, context),
context)
assertEquals(exception.message, exception2.message)
assertEquals(exception.cause?.message, exception2.cause?.message)
assertEquals(exception.txId, exception2.txId)
}
@Test
fun constraintPropagationRejectionTest() {
val exception = TransactionVerificationException.ConstraintPropagationRejection(txid, "com.test.Contract",
AlwaysAcceptAttachmentConstraint, AlwaysAcceptAttachmentConstraint)
val exception2 = DeserializationInput(factory)
.deserialize(
SerializationOutput(factory)
.serialize(exception, context),
context)
assertEquals(exception.message, exception2.message)
assertEquals(exception.cause?.message, exception2.cause?.message)
assertEquals(exception.txId, exception2.txId)
assertEquals("com.test.Contract", exception2.contractClass)
}
@Test
fun transactionDuplicateEncumbranceExceptionTest() {
val exception = TransactionVerificationException.TransactionDuplicateEncumbranceException(txid, 1)
val exception2 = DeserializationInput(factory)
.deserialize(
SerializationOutput(factory)
.serialize(exception, context),
context)
assertEquals(exception.message, exception2.message)
assertEquals(exception.cause?.message, exception2.cause?.message)
assertEquals(exception.txId, exception2.txId)
}
@Test
fun transactionNonMatchingEncumbranceExceptionTest() {
val exception = TransactionVerificationException.TransactionNonMatchingEncumbranceException(txid, listOf(1, 2, 3))
val exception2 = DeserializationInput(factory)
.deserialize(
SerializationOutput(factory)
.serialize(exception, context),
context)
assertEquals(exception.message, exception2.message)
assertEquals(exception.cause?.message, exception2.cause?.message)
assertEquals(exception.txId, exception2.txId)
}
@Test
fun transactionNotaryMismatchEncumbranceExceptionTest() {
val exception = TransactionVerificationException.TransactionNotaryMismatchEncumbranceException(
txid, 1, 2, Party(ALICE_NAME, generateKeyPair().public), Party(BOB_NAME, generateKeyPair().public))
val exception2 = DeserializationInput(factory)
.deserialize(
SerializationOutput(factory)
.serialize(exception, context),
context)
assertEquals(exception.message, exception2.message)
assertEquals(exception.cause?.message, exception2.cause?.message)
assertEquals(exception.txId, exception2.txId)
}
@Test
fun transactionContractConflictExceptionTest() {
val exception = TransactionVerificationException.TransactionContractConflictException(
txid, TransactionState(DummyContractState(), notary = Party(BOB_NAME, generateKeyPair().public)), "aa")
val exception2 = DeserializationInput(factory)
.deserialize(
SerializationOutput(factory)
.serialize(exception, context),
context)
assertEquals(exception.message, exception2.message)
assertEquals(exception.cause?.message, exception2.cause?.message)
assertEquals(exception.txId, exception2.txId)
}
@Test
fun transactionRequiredContractUnspecifiedExceptionTest() {
val exception = TransactionVerificationException.TransactionRequiredContractUnspecifiedException(
txid, TransactionState(DummyContractState(), notary = Party(BOB_NAME, generateKeyPair().public)))
val exception2 = DeserializationInput(factory)
.deserialize(
SerializationOutput(factory)
.serialize(exception, context),
context)
assertEquals(exception.message, exception2.message)
assertEquals(exception.cause?.message, exception2.cause?.message)
assertEquals(exception.txId, exception2.txId)
}
} }

View File

@ -70,8 +70,17 @@ abstract class TransactionVerificationException(val txId: SecureHash, message: S
* @property outputConstraint The constraint of the outputs state. * @property outputConstraint The constraint of the outputs state.
*/ */
@KeepForDJVM @KeepForDJVM
class ConstraintPropagationRejection(txId: SecureHash, val contractClass: String, inputConstraint: AttachmentConstraint, outputConstraint: AttachmentConstraint) class ConstraintPropagationRejection(txId: SecureHash, message: String) : TransactionVerificationException(txId, message, null) {
: TransactionVerificationException(txId, "Contract constraints for $contractClass are not propagated correctly. The outputConstraint: $outputConstraint is not a valid transition from the input constraint: $inputConstraint.", null) constructor(txId: SecureHash,
contractClass: String,
inputConstraint: AttachmentConstraint,
outputConstraint: AttachmentConstraint) :
this(txId, "Contract constraints for $contractClass are not propagated correctly. " +
"The outputConstraint: $outputConstraint is not a valid transition from the input constraint: $inputConstraint.")
// This is only required for backwards compatibility. In case the message format changes, update the index.
val contractClass: String = message.split(" ")[3]
}
/** /**
* The transaction attachment that contains the [contractClass] class didn't meet the constraints specified by * The transaction attachment that contains the [contractClass] class didn't meet the constraints specified by
@ -153,19 +162,24 @@ abstract class TransactionVerificationException(val txId: SecureHash, message: S
* be satisfied. * be satisfied.
*/ */
@KeepForDJVM @KeepForDJVM
class TransactionDuplicateEncumbranceException(txId: SecureHash, index: Int) class TransactionDuplicateEncumbranceException(txId: SecureHash, message: String)
: TransactionVerificationException(txId, "The bi-directionality property of encumbered output states " + : TransactionVerificationException(txId, message, null) {
"is not satisfied. Index $index is referenced more than once", null) constructor(txId: SecureHash, index: Int) : this(txId, "The bi-directionality property of encumbered output states " +
"is not satisfied. Index $index is referenced more than once")
}
/** /**
* An encumbered state should also be referenced as the encumbrance of another state in order to satisfy the * An encumbered state should also be referenced as the encumbrance of another state in order to satisfy the
* bi-directionality property (a full cycle should be present). * bi-directionality property (a full cycle should be present).
*/ */
@KeepForDJVM @KeepForDJVM
class TransactionNonMatchingEncumbranceException(txId: SecureHash, nonMatching: Collection<Int>) class TransactionNonMatchingEncumbranceException(txId: SecureHash, message: String)
: TransactionVerificationException(txId, "The bi-directionality property of encumbered output states " + : TransactionVerificationException(txId, message, null) {
"is not satisfied. Encumbered states should also be referenced as an encumbrance of another state to form " + constructor(txId: SecureHash, nonMatching: Collection<Int>) : this(txId,
"a full cycle. Offending indices $nonMatching", null) "The bi-directionality property of encumbered output states " +
"is not satisfied. Encumbered states should also be referenced as an encumbrance of another state to form " +
"a full cycle. Offending indices $nonMatching")
}
/** /**
* All encumbered states should be assigned to the same notary. This is due to the fact that multi-notary * All encumbered states should be assigned to the same notary. This is due to the fact that multi-notary
@ -173,9 +187,13 @@ abstract class TransactionVerificationException(val txId: SecureHash, message: S
* in the same transaction. * in the same transaction.
*/ */
@KeepForDJVM @KeepForDJVM
class TransactionNotaryMismatchEncumbranceException(txId: SecureHash, encumberedIndex: Int, encumbranceIndex: Int, encumberedNotary: Party, encumbranceNotary: Party) class TransactionNotaryMismatchEncumbranceException(txId: SecureHash, message: String)
: TransactionVerificationException(txId, "Encumbered output states assigned to different notaries found. " + : TransactionVerificationException(txId, message, null) {
"Output state with index $encumberedIndex is assigned to notary [$encumberedNotary], while its encumbrance with index $encumbranceIndex is assigned to notary [$encumbranceNotary]", null) constructor(txId: SecureHash, encumberedIndex: Int, encumbranceIndex: Int, encumberedNotary: Party, encumbranceNotary: Party) :
this(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]")
}
/** /**
* If a state is identified as belonging to a contract, either because the state class is defined as an inner class * If a state is identified as belonging to a contract, either because the state class is defined as an inner class
@ -186,35 +204,44 @@ abstract class TransactionVerificationException(val txId: SecureHash, message: S
* @param requiredContractClassName The class name of the contract to which the state belongs. * @param requiredContractClassName The class name of the contract to which the state belongs.
*/ */
@KeepForDJVM @KeepForDJVM
class TransactionContractConflictException(txId: SecureHash, state: TransactionState<ContractState>, requiredContractClassName: String) class TransactionContractConflictException(txId: SecureHash, message: String)
: TransactionVerificationException(txId, : TransactionVerificationException(txId, message, null) {
""" constructor(txId: SecureHash, state: TransactionState<ContractState>, requiredContractClassName: String): this(txId,
State of class ${state.data::class.java.typeName} belongs to contract $requiredContractClassName, but """
State of class ${state.data ::class.java.typeName} belongs to contract $requiredContractClassName, but
is bundled in TransactionState with ${state.contract}. is bundled in TransactionState with ${state.contract}.
For details see: https://docs.corda.net/api-contract-constraints.html#contract-state-agreement For details see: https://docs.corda.net/api-contract-constraints.html#contract-state-agreement
""".trimIndent().replace('\n', ' '), null) """.trimIndent().replace('\n', ' '))
}
// TODO: add reference to documentation // TODO: add reference to documentation
@KeepForDJVM @KeepForDJVM
class TransactionRequiredContractUnspecifiedException(txId: SecureHash, state: TransactionState<ContractState>) class TransactionRequiredContractUnspecifiedException(txId: SecureHash, message: String)
: TransactionVerificationException(txId, : TransactionVerificationException(txId, message, null) {
""" constructor(txId: SecureHash, state: TransactionState<ContractState>) : this(txId,
"""
State of class ${state.data::class.java.typeName} does not have a specified owning contract. State of class ${state.data::class.java.typeName} does not have a specified owning contract.
Add the @BelongsToContract annotation to this class to ensure that it can only be bundled in a TransactionState Add the @BelongsToContract annotation to this class to ensure that it can only be bundled in a TransactionState
with the correct contract. with the correct contract.
For details see: https://docs.corda.net/api-contract-constraints.html#contract-state-agreement For details see: https://docs.corda.net/api-contract-constraints.html#contract-state-agreement
""".trimIndent(), null) """.trimIndent())
}
/** /**
* If the network parameters associated with an input or reference state in a transaction are more recent than the network parameters of the new transaction itself. * If the network parameters associated with an input or reference state in a transaction are more recent than the network parameters of the new transaction itself.
*/ */
@KeepForDJVM @KeepForDJVM
class TransactionNetworkParameterOrderingException(txId: SecureHash, inputStateRef: StateRef, txnNetworkParameters: NetworkParameters, inputNetworkParameters: NetworkParameters) class TransactionNetworkParameterOrderingException(txId: SecureHash, message: String) :
: TransactionVerificationException(txId, "The network parameters epoch (${txnNetworkParameters.epoch}) of this transaction " + TransactionVerificationException(txId, message, null) {
"is older than the epoch (${inputNetworkParameters.epoch}) of input state: $inputStateRef", null) constructor(txId: SecureHash,
inputStateRef: StateRef,
txnNetworkParameters: NetworkParameters,
inputNetworkParameters: NetworkParameters)
: this(txId, "The network parameters epoch (${txnNetworkParameters.epoch}) of this transaction " +
"is older than the epoch (${inputNetworkParameters.epoch}) of input state: $inputStateRef")
}
/** /**
* Thrown when the network parameters with hash: missingNetworkParametersHash is not available at this node. Usually all the parameters * Thrown when the network parameters with hash: missingNetworkParametersHash is not available at this node. Usually all the parameters
@ -224,9 +251,11 @@ abstract class TransactionVerificationException(val txId: SecureHash, message: S
* @param missingNetworkParametersHash Missing hash of the network parameters associated to this transaction * @param missingNetworkParametersHash Missing hash of the network parameters associated to this transaction
*/ */
@KeepForDJVM @KeepForDJVM
class MissingNetworkParametersException(txId: SecureHash, missingNetworkParametersHash: SecureHash) class MissingNetworkParametersException(txId: SecureHash, message: String)
: TransactionVerificationException(txId, "Couldn't find network parameters with hash: $missingNetworkParametersHash related to this transaction: $txId", null) : TransactionVerificationException(txId, message, null) {
constructor(txId: SecureHash, missingNetworkParametersHash: SecureHash) :
this(txId, "Couldn't find network parameters with hash: $missingNetworkParametersHash related to this transaction: $txId")
}
/** 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

View File

@ -45,7 +45,6 @@ interface FlowLogicRefFactory {
* *
* @property type the fully qualified name of the class that failed checks. * @property type the fully qualified name of the class that failed checks.
*/ */
@CordaSerializable
class IllegalFlowLogicException(val type: String, msg: String) : class IllegalFlowLogicException(val type: String, msg: String) :
IllegalArgumentException("A FlowLogicRef cannot be constructed for FlowLogic of type $type: $msg") { IllegalArgumentException("A FlowLogicRef cannot be constructed for FlowLogic of type $type: $msg") {
constructor(type: Class<*>, msg: String) : this(type.name, msg) constructor(type: Class<*>, msg: String) : this(type.name, msg)

View File

@ -80,9 +80,7 @@ interface IdentityService {
* @param key The owning [PublicKey] of the [Party]. * @param key The owning [PublicKey] of the [Party].
* @return Returns a [Party] with a matching owningKey if known, else returns null. * @return Returns a [Party] with a matching owningKey if known, else returns null.
*/ */
fun partyFromKey(key: PublicKey): Party? = fun partyFromKey(key: PublicKey): Party?
@Suppress("DEPRECATION")
certificateFromKey(key)?.party
/** /**
* Resolves a party name to the well known identity [Party] instance for this name. Where possible well known identity * Resolves a party name to the well known identity [Party] instance for this name. Where possible well known identity

File diff suppressed because one or more lines are too long

View File

@ -10,29 +10,33 @@ complexity:
active: true active: true
ComplexCondition: ComplexCondition:
active: true active: true
excludes: "**/buildSrc/**"
threshold: 4 threshold: 4
ComplexMethod: ComplexMethod:
active: true active: true
excludes: "**/buildSrc/**"
threshold: 10 threshold: 10
ignoreSingleWhenExpression: true ignoreSingleWhenExpression: true
LargeClass: LargeClass:
active: true active: true
excludes: "**/test/**,**/integration-test/**,**/integration-test-slow/**,**/*Test.kt,**/*Tests.kt" excludes: "**/test/**,**/integration-test/**,**/integration-test-slow/**,**/*Test.kt,**/*Tests.kt,**/buildSrc/**"
threshold: 600 threshold: 600
LongMethod: LongMethod:
active: true active: true
excludes: "**/test/**,**/integration-test/**,**/integration-test-slow/**,**/*Test.kt,**/*Tests.kt" excludes: "**/test/**,**/integration-test/**,**/integration-test-slow/**,**/*Test.kt,**/*Tests.kt,**/buildSrc/**"
threshold: 120 threshold: 120
LongParameterList: LongParameterList:
active: true active: true
excludes: "**/buildSrc/**"
threshold: 6 threshold: 6
ignoreDefaultParameters: false ignoreDefaultParameters: false
NestedBlockDepth: NestedBlockDepth:
active: true active: true
excludes: "**/buildSrc/**"
threshold: 4 threshold: 4
TooManyFunctions: TooManyFunctions:
active: true active: true
excludes: "**/test/**,**/integration-test/**,**/integration-test-slow/**,**/*Test.kt,**/*Tests.kt" excludes: "**/test/**,**/integration-test/**,**/integration-test-slow/**,**/*Test.kt,**/*Tests.kt,**/buildSrc/**"
thresholdInFiles: 15 thresholdInFiles: 15
thresholdInClasses: 15 thresholdInClasses: 15
thresholdInInterfaces: 15 thresholdInInterfaces: 15
@ -41,6 +45,7 @@ complexity:
empty-blocks: empty-blocks:
active: true active: true
excludes: "**/buildSrc/**"
EmptyCatchBlock: EmptyCatchBlock:
active: true active: true
allowedExceptionNameRegex: "^(_|(ignore|expected).*)" allowedExceptionNameRegex: "^(_|(ignore|expected).*)"
@ -71,6 +76,7 @@ empty-blocks:
exceptions: exceptions:
active: true active: true
excludes: "**/buildSrc/**"
TooGenericExceptionCaught: TooGenericExceptionCaught:
active: true active: true
exceptionNames: exceptionNames:
@ -92,6 +98,7 @@ exceptions:
naming: naming:
active: true active: true
excludes: "**/buildSrc/**"
ClassNaming: ClassNaming:
active: true active: true
classPattern: '[A-Z$][a-zA-Z0-9$]*' classPattern: '[A-Z$][a-zA-Z0-9$]*'
@ -127,6 +134,7 @@ naming:
performance: performance:
active: true active: true
excludes: "**/buildSrc/**"
ForEachOnRange: ForEachOnRange:
active: true active: true
SpreadOperator: SpreadOperator:
@ -136,6 +144,7 @@ performance:
potential-bugs: potential-bugs:
active: true active: true
excludes: "**/buildSrc/**"
DuplicateCaseInWhenExpression: DuplicateCaseInWhenExpression:
active: true active: true
EqualsWithHashCodeExist: EqualsWithHashCodeExist:
@ -149,10 +158,11 @@ style:
active: true active: true
ForbiddenComment: ForbiddenComment:
active: true active: true
excludes: "**/buildSrc/**"
values: 'TODO:,FIXME:,STOPSHIP:' values: 'TODO:,FIXME:,STOPSHIP:'
MagicNumber: MagicNumber:
active: true active: true
excludes: "**/test/**,**/integration-test/**,**/integration-test-slow/**,**/*Test.kt,**/*Tests.kt" excludes: "**/test/**,**/integration-test/**,**/integration-test-slow/**,**/*Test.kt,**/*Tests.kt,**/buildSrc/**"
ignoreNumbers: '-1,0,1,2' ignoreNumbers: '-1,0,1,2'
ignoreHashCodeFunction: true ignoreHashCodeFunction: true
ignorePropertyDeclaration: false ignorePropertyDeclaration: false
@ -163,23 +173,30 @@ style:
ignoreEnums: false ignoreEnums: false
MaxLineLength: MaxLineLength:
active: true active: true
excludes: "**/buildSrc/**"
maxLineLength: 140 maxLineLength: 140
excludePackageStatements: true excludePackageStatements: true
excludeImportStatements: true excludeImportStatements: true
ModifierOrder: ModifierOrder:
active: true active: true
excludes: "**/buildSrc/**"
OptionalAbstractKeyword: OptionalAbstractKeyword:
active: true active: true
excludes: "**/buildSrc/**"
ReturnCount: ReturnCount:
active: true active: true
excludes: "**/buildSrc/**"
max: 2 max: 2
excludedFunctions: "equals" excludedFunctions: "equals"
excludeReturnFromLambda: true excludeReturnFromLambda: true
SafeCast: SafeCast:
active: true active: true
excludes: "**/buildSrc/**"
ThrowsCount: ThrowsCount:
active: true active: true
excludes: "**/buildSrc/**"
max: 2 max: 2
WildcardImport: WildcardImport:
active: true active: true
excludes: "**/buildSrc/**"
excludeImports: 'java.util.*,kotlinx.android.synthetic.*' excludeImports: 'java.util.*,kotlinx.android.synthetic.*'

View File

@ -25,7 +25,8 @@ corda_substitutions = {
"|quasar_version|" : constants_properties_dict["quasarVersion"], "|quasar_version|" : constants_properties_dict["quasarVersion"],
"|platform_version|" : constants_properties_dict["platformVersion"], "|platform_version|" : constants_properties_dict["platformVersion"],
"|os_branch|" : constants_properties_dict["openSourceBranch"], "|os_branch|" : constants_properties_dict["openSourceBranch"],
"|os_samples_branch|" : constants_properties_dict["openSourceSamplesBranch"] "|os_samples_branch|" : constants_properties_dict["openSourceSamplesBranch"],
"|jolokia_version|" : constants_properties_dict["jolokiaAgentVersion"]
} }
def setup(app): def setup(app):

View File

@ -81,6 +81,12 @@ Note that in production, exposing the database via the node is not recommended.
Monitoring your node Monitoring your node
-------------------- --------------------
This section covers monitoring performance and health of a node in Corda Enterprise with Jolokia and Graphite. General best practices for monitoring (e.g. setting up TCP checks for the ports the node communicates on, database health checks etc.) are not covered here but should be followed.
Monitoring via Jolokia
++++++++++++++++++++++
Like most Java servers, the node can be configured to export various useful metrics and management operations via the industry-standard Like most Java servers, the node can be configured to export various useful metrics and management operations via the industry-standard
`JMX infrastructure <https://en.wikipedia.org/wiki/Java_Management_Extensions>`_. JMX is a standard API `JMX infrastructure <https://en.wikipedia.org/wiki/Java_Management_Extensions>`_. JMX is a standard API
for registering so-called *MBeans* ... objects whose properties and methods are intended for server management. As Java for registering so-called *MBeans* ... objects whose properties and methods are intended for server management. As Java
@ -106,8 +112,12 @@ Here are a few ways to build dashboards and extract monitoring data for a node:
It can bridge any data input to any output using their plugin system, for example, Telegraf can It can bridge any data input to any output using their plugin system, for example, Telegraf can
be configured to collect data from Jolokia and write to DataDog web api. be configured to collect data from Jolokia and write to DataDog web api.
The Node configuration parameter `jmxMonitoringHttpPort` has to be present in order to ensure a Jolokia agent is instrumented with In order to ensure that a Jolokia agent is instrumented with the JVM run-time, you can choose one of these options:
the JVM run-time.
* Specify the Node configuration parameter ``jmxMonitoringHttpPort`` which will attempt to load the jolokia driver from the ``drivers`` folder.
The format of the driver name needs to be ``jolokia-jvm-{VERSION}-agent.jar`` where VERSION is the version required by Corda, currently |jolokia_version|.
* Start the node with ``java -Dcapsule.jvm.args="-javaagent:drivers/jolokia-jvm-1.6.0-agent.jar=port=7777,host=localhost" -jar corda.jar``.
The following JMX statistics are exported: The following JMX statistics are exported:
@ -126,6 +136,8 @@ via a file called ``jolokia-access.xml``.
Several Jolokia policy based security configuration files (``jolokia-access.xml``) are available for dev, test, and prod Several Jolokia policy based security configuration files (``jolokia-access.xml``) are available for dev, test, and prod
environments under ``/config/<env>``. environments under ``/config/<env>``.
To pass a security policy use ``java -Dcapsule.jvm.args=-javaagent:./drivers/jolokia-jvm-1.6.0-agent.jar,policyLocation=file:./config-path/jolokia-access.xml -jar corda.jar``
Notes for development use Notes for development use
+++++++++++++++++++++++++ +++++++++++++++++++++++++

View File

@ -33,6 +33,7 @@ import java.io.File
import java.net.URL import java.net.URL
import java.nio.file.FileAlreadyExistsException import java.nio.file.FileAlreadyExistsException
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption.REPLACE_EXISTING import java.nio.file.StandardCopyOption.REPLACE_EXISTING
import java.security.PublicKey import java.security.PublicKey
import java.time.Duration import java.time.Duration
@ -68,7 +69,7 @@ internal constructor(private val initSerEnv: Boolean,
companion object { companion object {
// TODO This will probably need to change once we start using a bundled JVM // TODO This will probably need to change once we start using a bundled JVM
private val nodeInfoGenCmd = listOf( private val nodeInfoGenCmd = listOf(
"java", Paths.get(System.getProperty("java.home"), "bin", "java").toString(),
"-jar", "-jar",
"corda.jar", "corda.jar",
"generate-node-info" "generate-node-info"

View File

@ -25,9 +25,6 @@ class DatabaseTransaction(
) { ) {
val id: UUID = UUID.randomUUID() val id: UUID = UUID.randomUUID()
val flushing: Boolean get() = _flushingCount > 0
private var _flushingCount = 0
val connection: Connection by lazy(LazyThreadSafetyMode.NONE) { val connection: Connection by lazy(LazyThreadSafetyMode.NONE) {
database.dataSource.connection.apply { database.dataSource.connection.apply {
autoCommit = false autoCommit = false
@ -37,27 +34,6 @@ class DatabaseTransaction(
private val sessionDelegate = lazy { private val sessionDelegate = lazy {
val session = database.entityManagerFactory.withOptions().connection(connection).openSession() val session = database.entityManagerFactory.withOptions().connection(connection).openSession()
session.addEventListeners(object : BaseSessionEventListener() {
override fun flushStart() {
_flushingCount++
super.flushStart()
}
override fun flushEnd(numberOfEntities: Int, numberOfCollections: Int) {
super.flushEnd(numberOfEntities, numberOfCollections)
_flushingCount--
}
override fun partialFlushStart() {
_flushingCount++
super.partialFlushStart()
}
override fun partialFlushEnd(numberOfEntities: Int, numberOfCollections: Int) {
super.partialFlushEnd(numberOfEntities, numberOfCollections)
_flushingCount--
}
})
hibernateTransaction = session.beginTransaction() hibernateTransaction = session.beginTransaction()
session session
} }

View File

@ -3,7 +3,12 @@ buildscript {
def properties = new Properties() def properties = new Properties()
file("$projectDir/src/main/resources/build.properties").withInputStream { properties.load(it) } file("$projectDir/src/main/resources/build.properties").withInputStream { properties.load(it) }
ext.jolokia_version = properties.getProperty('jolokiaAgentVersion')
Properties constants = new Properties()
file("$rootDir/constants.properties").withInputStream { constants.load(it) }
ext.jolokia_version = constants.getProperty('jolokiaAgentVersion')
dependencies { dependencies {
classpath group: 'com.github.docker-java', name: 'docker-java', version: '3.1.5' classpath group: 'com.github.docker-java', name: 'docker-java', version: '3.1.5'

View File

@ -150,6 +150,7 @@ internal class CordaRPCOpsImpl(
override fun killFlow(id: StateMachineRunId): Boolean = if (smm.killFlow(id)) true else smm.flowHospital.dropSessionInit(id.uuid) override fun killFlow(id: StateMachineRunId): Boolean = if (smm.killFlow(id)) true else smm.flowHospital.dropSessionInit(id.uuid)
override fun stateMachinesFeed(): DataFeed<List<StateMachineInfo>, StateMachineUpdate> { override fun stateMachinesFeed(): DataFeed<List<StateMachineInfo>, StateMachineUpdate> {
val (allStateMachines, changes) = smm.track() val (allStateMachines, changes) = smm.track()
return DataFeed( return DataFeed(
allStateMachines.map { stateMachineInfoFromFlowLogic(it) }, allStateMachines.map { stateMachineInfoFromFlowLogic(it) },

View File

@ -6,13 +6,10 @@ import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.identity.x500Matches import net.corda.core.identity.x500Matches
import net.corda.core.internal.CertRole import net.corda.core.internal.CertRole
import net.corda.core.internal.hash
import net.corda.core.node.services.IdentityService
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.trace import net.corda.core.utilities.trace
import net.corda.node.services.api.IdentityServiceInternal import net.corda.node.services.api.IdentityServiceInternal
import net.corda.node.services.persistence.WritablePublicKeyToOwningIdentityCache
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.x509Certificates import net.corda.nodeapi.internal.crypto.x509Certificates
import java.security.InvalidAlgorithmParameterException import java.security.InvalidAlgorithmParameterException
@ -101,6 +98,10 @@ class InMemoryIdentityService(
return keyToPartyAndCerts[identityCertChain[1].publicKey] return keyToPartyAndCerts[identityCertChain[1].publicKey]
} }
override fun partyFromKey(key: PublicKey): Party? {
return certificateFromKey(key)?.party ?: keyToName[key.toStringShort()]?.let { wellKnownPartyFromX500Name(it) }
}
override fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate? = keyToPartyAndCerts[owningKey] override fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate? = keyToPartyAndCerts[owningKey]
// We give the caller a copy of the data set to avoid any locking problems // We give the caller a copy of the data set to avoid any locking problems

View File

@ -296,6 +296,12 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
keyToPartyAndCert[owningKey.toStringShort()] keyToPartyAndCert[owningKey.toStringShort()]
} }
override fun partyFromKey(key: PublicKey): Party? {
return certificateFromKey(key)?.party ?: database.transaction {
keyToName[key.toStringShort()]
}?.let { wellKnownPartyFromX500Name(it) }
}
private fun certificateFromCordaX500Name(name: CordaX500Name): PartyAndCertificate? { private fun certificateFromCordaX500Name(name: CordaX500Name): PartyAndCertificate? {
return database.transaction { return database.transaction {
val partyId = nameToKey[name] val partyId = nameToKey[name]

View File

@ -5,6 +5,8 @@ import net.corda.core.flows.*
import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.VisibleForTesting
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.contextLogger
import org.slf4j.Logger
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
@ -33,7 +35,12 @@ data class FlowLogicRefImpl internal constructor(val flowLogicClassName: String,
* in response to a potential malicious use or buggy update to an app etc. * in response to a potential malicious use or buggy update to an app etc.
*/ */
// TODO: Replace with a per app classloader/cordapp provider/cordapp loader - this will do for now // TODO: Replace with a per app classloader/cordapp provider/cordapp loader - this will do for now
@Suppress("ReturnCount", "TooManyFunctions")
open class FlowLogicRefFactoryImpl(private val classloader: ClassLoader) : SingletonSerializeAsToken(), FlowLogicRefFactory { open class FlowLogicRefFactoryImpl(private val classloader: ClassLoader) : SingletonSerializeAsToken(), FlowLogicRefFactory {
companion object {
private val log: Logger = contextLogger()
}
override fun create(flowClass: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef { override fun create(flowClass: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef {
if (!flowClass.isAnnotationPresent(SchedulableFlow::class.java)) { if (!flowClass.isAnnotationPresent(SchedulableFlow::class.java)) {
throw IllegalFlowLogicException(flowClass, "because it's not a schedulable flow") throw IllegalFlowLogicException(flowClass, "because it's not a schedulable flow")
@ -76,20 +83,100 @@ open class FlowLogicRefFactoryImpl(private val classloader: ClassLoader) : Singl
return createKotlin(flowClass, argsMap) return createKotlin(flowClass, argsMap)
} }
protected open fun findConstructor(flowClass: Class<out FlowLogic<*>>, argTypes: List<Class<Any>?>): KFunction<FlowLogic<*>> { private fun matchConstructorArgs(ctorTypes: List<Class<out Any>>, optional: List<Boolean>,
argTypes: List<Class<Any>?>): Pair<Boolean, Int> {
// There must be at least as many constructor arguments as supplied arguments
if (argTypes.size > ctorTypes.size) {
return Pair(false, 0)
}
// Check if all constructor arguments are assignable for all supplied arguments, then for remaining arguments in constructor
// check that they are optional. If they are it's still a match. Return if matched and the number of default args consumed.
var numDefaultsUsed = 0
var index = 0
for (conArg in ctorTypes) {
if (index < argTypes.size) {
val argType = argTypes[index]
if (argType != null && !conArg.isAssignableFrom(argType)) {
return Pair(false, 0)
}
} else {
if (index >= optional.size || !optional[index]) {
return Pair(false, 0)
}
numDefaultsUsed++
}
index++
}
return Pair(true, numDefaultsUsed)
}
private fun handleNoMatchingConstructor(flowClass: Class<out FlowLogic<*>>, argTypes: List<Class<Any>?>) {
log.error("Cannot find Constructor to match arguments: ${argTypes.joinToString()}")
log.info("Candidate constructors are:")
for (ctor in flowClass.kotlin.constructors) {
log.info("${ctor}")
}
}
private fun findConstructorCheckDefaultParams(flowClass: Class<out FlowLogic<*>>, argTypes: List<Class<Any>?>):
KFunction<FlowLogic<*>> {
// There may be multiple matches. If there are, we will use the one with the least number of default parameter matches.
var ctorMatch: KFunction<FlowLogic<*>>? = null
var matchNumDefArgs = 0
for (ctor in flowClass.kotlin.constructors) {
// Get the types of the arguments, always boxed (as that's what we get in the invocation).
val ctorTypes = ctor.javaConstructor!!.parameterTypes.map {
if (it == null) { it } else { Primitives.wrap(it) }
}
val optional = ctor.parameters.map { it.isOptional }
val (matched, numDefaultsUsed) = matchConstructorArgs(ctorTypes, optional, argTypes)
if (matched) {
if (ctorMatch == null || numDefaultsUsed < matchNumDefArgs) {
ctorMatch = ctor
matchNumDefArgs = numDefaultsUsed
}
}
}
if (ctorMatch == null) {
handleNoMatchingConstructor(flowClass, argTypes)
// Must do the throw here, not in handleNoMatchingConstructor(added for Detekt) else we can't return ctorMatch as non-null
throw IllegalFlowLogicException(flowClass, "No constructor found that matches arguments (${argTypes.joinToString()}), "
+ "see log for more information.")
}
log.info("Matched constructor: ${ctorMatch} (num_default_args_used=$matchNumDefArgs)")
return ctorMatch
}
private fun findConstructorDirectMatch(flowClass: Class<out FlowLogic<*>>, argTypes: List<Class<Any>?>): KFunction<FlowLogic<*>> {
return flowClass.kotlin.constructors.single { ctor -> return flowClass.kotlin.constructors.single { ctor ->
// Get the types of the arguments, always boxed (as that's what we get in the invocation). // Get the types of the arguments, always boxed (as that's what we get in the invocation).
val ctorTypes = ctor.javaConstructor!!.parameterTypes.map { Primitives.wrap(it) } val ctorTypes = ctor.javaConstructor!!.parameterTypes.map { Primitives.wrap(it) }
if (argTypes.size != ctorTypes.size) if (argTypes.size != ctorTypes.size)
return@single false return@single false
for ((argType, ctorType) in argTypes.zip(ctorTypes)) { for ((argType, ctorType) in argTypes.zip(ctorTypes)) {
if (argType == null) continue // Try and find a match based on the other arguments. if (argType == null) continue // Try and find a match based on the other arguments.
if (!ctorType.isAssignableFrom(argType)) return@single false if (!ctorType.isAssignableFrom(argType)) return@single false
} }
true true
} }
} }
protected open fun findConstructor(flowClass: Class<out FlowLogic<*>>, argTypes: List<Class<Any>?>): KFunction<FlowLogic<*>> {
try {
return findConstructorDirectMatch(flowClass, argTypes)
} catch(e: java.lang.IllegalArgumentException) {
log.trace("findConstructorDirectMatch threw IllegalArgumentException (more than 1 matches).")
} catch (e: NoSuchElementException) {
log.trace("findConstructorDirectMatch threw NoSuchElementException (no matches).")
}
return findConstructorCheckDefaultParams(flowClass, argTypes)
}
/** /**
* Create a [FlowLogicRef] by trying to find a Kotlin constructor that matches the given args. * Create a [FlowLogicRef] by trying to find a Kotlin constructor that matches the given args.
* *

View File

@ -7,6 +7,8 @@ import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.persistence.DatabaseTransaction import net.corda.nodeapi.internal.persistence.DatabaseTransaction
import net.corda.nodeapi.internal.persistence.contextTransaction import net.corda.nodeapi.internal.persistence.contextTransaction
import net.corda.nodeapi.internal.persistence.currentDBSession import net.corda.nodeapi.internal.persistence.currentDBSession
import org.hibernate.Session
import org.hibernate.internal.SessionImpl
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -191,14 +193,23 @@ abstract class AppendOnlyPersistentMapBase<K, V, E, out EK>(
private fun loadValue(key: K): V? { private fun loadValue(key: K): V? {
val session = currentDBSession() val session = currentDBSession()
val flushing = contextTransaction.flushing val isSafeToDetach = isSafeToFlushAndDetach(session)
if (!flushing) { if (isSafeToDetach) {
// IMPORTANT: The flush is needed because detach() makes the queue of unflushed entries invalid w.r.t. Hibernate internal state if the found entity is unflushed. // IMPORTANT: The flush is needed because detach() makes the queue of unflushed entries invalid w.r.t. Hibernate internal state if the found entity is unflushed.
// We want the detach() so that we rely on our cache memory management and don't retain strong references in the Hibernate session. // We want the detach() so that we rely on our cache memory management and don't retain strong references in the Hibernate session.
session.flush() session.flush()
} }
val result = session.find(persistentEntityClass, toPersistentEntityKey(key)) val result = session.find(persistentEntityClass, toPersistentEntityKey(key))
return result?.apply { if (!flushing) session.detach(result) }?.let(fromPersistentEntity)?.second return result?.apply { if (isSafeToDetach) session.detach(result) }?.let(fromPersistentEntity)?.second
}
private fun isSafeToFlushAndDetach(session: Session): Boolean {
if (session !is SessionImpl)
return true
val flushInProgress = session.persistenceContext.isFlushing
val cascadeInProgress = session.persistenceContext.cascadeLevel > 0
return !flushInProgress && !cascadeInProgress
} }
protected fun transactionalLoadValue(key: K): Transactional<V> { protected fun transactionalLoadValue(key: K): Transactional<V> {

View File

@ -1,4 +1,6 @@
# Build constants exported as resource file to make them visible in Node program # Build constants exported as resource file to make them visible in Node program
# Note: sadly, due to present limitation of IntelliJ-IDEA in processing resource files, these constants cannot be # Note: sadly, due to present limitation of IntelliJ-IDEA in processing resource files, these constants cannot be
# imported from top-level 'constants.properties' file # imported from top-level 'constants.properties' file
jolokiaAgentVersion=1.6.1 #jolokiaAgentVersion=1.6.1

View File

@ -261,6 +261,17 @@ class PersistentIdentityServiceTests {
} }
} }
@Test
fun `resolve key to party for key without certificate`() {
// Register Alice's PartyAndCert as if it was done so via the network map cache.
identityService.verifyAndRegisterIdentity(alice.identity)
// Use a key which is not tied to a cert.
val publicKey = Crypto.generateKeyPair().public
// Register the PublicKey to Alice's CordaX500Name.
identityService.registerKey(publicKey, alice.party)
assertEquals(alice.party, identityService.partyFromKey(publicKey))
}
@Test @Test
fun `register incorrect party to public key `(){ fun `register incorrect party to public key `(){
database.transaction { identityService.verifyAndRegisterIdentity(ALICE_IDENTITY) } database.transaction { identityService.verifyAndRegisterIdentity(ALICE_IDENTITY) }

View File

@ -1,82 +0,0 @@
package net.corda.node.services.persistence
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes
import net.corda.finance.DOLLARS
import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.issuedBy
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.E2ETestKeyManagementService
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.testing.node.MockServices
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import kotlin.test.assertEquals
class HibernateColumnConverterTests {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
private val cordapps = listOf("net.corda.finance")
private val myself = TestIdentity(CordaX500Name("Me", "London", "GB"))
private val notary = TestIdentity(CordaX500Name("NotaryService", "London", "GB"), 1337L)
lateinit var services: MockServices
lateinit var database: CordaPersistence
@Before
fun setUp() {
val (db, mockServices) = MockServices.makeTestDatabaseAndPersistentServices(
cordappPackages = cordapps,
initialIdentity = myself,
networkParameters = testNetworkParameters(minimumPlatformVersion = 4),
moreIdentities = setOf(notary.identity),
moreKeys = emptySet()
)
services = mockServices
database = db
}
// AbstractPartyToX500NameAsStringConverter could cause circular flush of Hibernate session because it is invoked during flush, and a
// cache miss was doing a flush. This also checks that loading during flush does actually work.
@Test
fun `issue some cash on a notary that exists only in the database to check cache loading works in our identity column converters during flush of vault update`() {
val expected = 500.DOLLARS
val ref = OpaqueBytes.of(0x01)
// Create parallel set of key and identity services so that the values are not cached, forcing the node caches to do a lookup.
val cacheFactory = TestingNamedCacheFactory()
val identityService = PersistentIdentityService(cacheFactory)
val originalIdentityService: PersistentIdentityService = services.identityService as PersistentIdentityService
identityService.database = originalIdentityService.database
identityService.start(originalIdentityService.trustRoot, pkToIdCache = PublicKeyToOwningIdentityCacheImpl(database, cacheFactory))
val keyService = E2ETestKeyManagementService(identityService)
keyService.start(setOf(myself.keyPair))
// New identity for a notary (doesn't matter that it's for Bank Of Corda... since not going to use it as an actual notary etc).
val newKeyAndCert = keyService.freshKeyAndCert(services.myInfo.legalIdentitiesAndCerts[0], false)
val randomNotary = Party(myself.name, newKeyAndCert.owningKey)
val ourIdentity = services.myInfo.legalIdentities.first()
val builder = TransactionBuilder(notary.party)
val issuer = services.myInfo.legalIdentities.first().ref(ref)
val signers = Cash().generateIssue(builder, expected.issuedBy(issuer), ourIdentity, randomNotary)
val tx: SignedTransaction = services.signInitialTransaction(builder, signers)
services.recordTransactions(tx)
val output = tx.tx.outputsOfType<Cash.State>().single()
assertEquals(expected.`issued by`(ourIdentity.ref(ref)), output.amount)
}
}

View File

@ -0,0 +1,168 @@
package net.corda.node.services.persistence
import net.corda.core.contracts.BelongsToContract
import net.corda.core.contracts.Contract
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.TransactionState
import net.corda.core.contracts.TypeOnlyCommandData
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import net.corda.core.schemas.QueryableState
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes
import net.corda.finance.DOLLARS
import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.issuedBy
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.testing.node.MockServices
import org.assertj.core.api.Assertions.assertThat
import org.hibernate.annotations.Cascade
import org.hibernate.annotations.CascadeType
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.lang.IllegalArgumentException
import javax.persistence.Entity
import javax.persistence.Id
import javax.persistence.OneToMany
import javax.persistence.Table
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import kotlin.test.assertEquals
/**
* These tests cover the interactions between Corda and Hibernate with regards to flushing/detaching/cascading.
*/
class HibernateInteractionTests {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
private val cordapps = listOf("net.corda.finance", "net.corda.node.services.persistence")
private val myself = TestIdentity(CordaX500Name("Me", "London", "GB"))
private val notary = TestIdentity(CordaX500Name("NotaryService", "London", "GB"), 1337L)
lateinit var services: MockServices
lateinit var database: CordaPersistence
@Before
fun setUp() {
val (db, mockServices) = MockServices.makeTestDatabaseAndPersistentServices(
cordappPackages = cordapps,
initialIdentity = myself,
networkParameters = testNetworkParameters(minimumPlatformVersion = 4),
moreIdentities = setOf(notary.identity),
moreKeys = emptySet(),
// forcing a cache size of zero, so that all requests lead to a cache miss and end up hitting the database
cacheFactory = TestingNamedCacheFactory(0)
)
services = mockServices
database = db
}
// AbstractPartyToX500NameAsStringConverter could cause circular flush of Hibernate session because it is invoked during flush, and a
// cache miss was doing a flush. This also checks that loading during flush does actually work.
@Test
fun `issue some cash on a notary that exists only in the database to check cache loading works in our identity column converters during flush of vault update`() {
val expected = 500.DOLLARS
val ref = OpaqueBytes.of(0x01)
val ourIdentity = services.myInfo.legalIdentities.first()
val builder = TransactionBuilder(notary.party)
val issuer = services.myInfo.legalIdentities.first().ref(ref)
val signers = Cash().generateIssue(builder, expected.issuedBy(issuer), ourIdentity, notary.party)
val tx: SignedTransaction = services.signInitialTransaction(builder, signers)
services.recordTransactions(tx)
val output = tx.tx.outputsOfType<Cash.State>().single()
assertEquals(expected.`issued by`(ourIdentity.ref(ref)), output.amount)
}
@Test
fun `when a cascade is in progress (because of nested entities), the node avoids to flush & detach entities, since it's not allowed by Hibernate`() {
val ourIdentity = services.myInfo.legalIdentities.first()
val childEntities = listOf(SimpleContract.ChildState(ourIdentity))
val parentEntity = SimpleContract.ParentState(childEntities)
val builder = TransactionBuilder(notary.party)
.addOutputState(TransactionState(parentEntity, SimpleContract::class.java.name, notary.party))
.addCommand(SimpleContract.Issue(), listOf(ourIdentity.owningKey))
val tx: SignedTransaction = services.signInitialTransaction(builder, listOf(ourIdentity.owningKey))
services.recordTransactions(tx)
val output = tx.tx.outputsOfType<SimpleContract.ParentState>().single()
assertThat(output.children.single().member).isEqualTo(ourIdentity)
}
object PersistenceSchema: MappedSchema(PersistenceSchema::class.java, 1, listOf(Parent::class.java, Child::class.java)) {
@Entity(name = "parents")
@Table
class Parent: PersistentState() {
@Cascade(CascadeType.ALL)
@OneToMany(targetEntity = Child::class)
val children: MutableCollection<Child> = mutableSetOf()
fun addChild(child: Child) {
children.add(child)
}
}
@Entity(name = "children")
class Child(
@Id
// Do not change this: this generation type is required in order to trigger the proper cascade ordering.
@GeneratedValue(strategy = GenerationType.IDENTITY)
val identifier: Int?,
val member: AbstractParty?
) {
constructor(member: AbstractParty): this(null, member)
}
}
class SimpleContract: Contract {
@BelongsToContract(SimpleContract::class)
@CordaSerializable
data class ParentState(val children: List<ChildState>): ContractState, QueryableState {
override fun supportedSchemas(): Iterable<MappedSchema> = listOf(PersistenceSchema)
override fun generateMappedObject(schema: MappedSchema): PersistentState {
return when(schema) {
is PersistenceSchema -> {
val parent = PersistenceSchema.Parent()
children.forEach { parent.addChild(PersistenceSchema.Child(it.member)) }
parent
}
else -> throw IllegalArgumentException("Unrecognised schema $schema")
}
}
override val participants: List<AbstractParty> = children.map { it.member }
}
@CordaSerializable
data class ChildState(val member: AbstractParty)
override fun verify(tx: LedgerTransaction) {}
class Issue: TypeOnlyCommandData()
}
}

View File

@ -139,10 +139,12 @@ open class MockServices private constructor(
* Makes database and persistent services appropriate for unit tests which require persistence across the vault, identity service * Makes database and persistent services appropriate for unit tests which require persistence across the vault, identity service
* and key managment service. * and key managment service.
* *
* @param cordappPackages A [List] of cordapp packages to scan for any cordapp code, e.g. contract verification code, flows and services. * @param cordappPackages A [List] of cordapp packages to scan for any cordapp code, e.g. contract verification code,
* flows and services.
* @param initialIdentity The first (typically sole) identity the services will represent. * @param initialIdentity The first (typically sole) identity the services will represent.
* @param moreKeys A list of additional [KeyPair] instances to be used by [MockServices]. * @param moreKeys A list of additional [KeyPair] instances to be used by [MockServices].
* @param moreIdentities A list of additional [KeyPair] instances to be used by [MockServices]. * @param moreIdentities A list of additional [KeyPair] instances to be used by [MockServices].
* @param cacheFactory A custom cache factory to be used by the created [IdentityService]
* @return A pair where the first element is the instance of [CordaPersistence] and the second is [MockServices]. * @return A pair where the first element is the instance of [CordaPersistence] and the second is [MockServices].
*/ */
@JvmStatic @JvmStatic
@ -152,12 +154,13 @@ open class MockServices private constructor(
initialIdentity: TestIdentity, initialIdentity: TestIdentity,
networkParameters: NetworkParameters = testNetworkParameters(modifiedTime = Instant.MIN), networkParameters: NetworkParameters = testNetworkParameters(modifiedTime = Instant.MIN),
moreKeys: Set<KeyPair>, moreKeys: Set<KeyPair>,
moreIdentities: Set<PartyAndCertificate> moreIdentities: Set<PartyAndCertificate>,
cacheFactory: TestingNamedCacheFactory = TestingNamedCacheFactory()
): Pair<CordaPersistence, MockServices> { ): Pair<CordaPersistence, MockServices> {
val cordappLoader = cordappLoaderForPackages(cordappPackages) val cordappLoader = cordappLoaderForPackages(cordappPackages)
val dataSourceProps = makeTestDataSourceProperties() val dataSourceProps = makeTestDataSourceProperties()
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas) val schemaService = NodeSchemaService(cordappLoader.cordappSchemas)
val identityService = PersistentIdentityService(TestingNamedCacheFactory()) val identityService = PersistentIdentityService(cacheFactory)
val persistence = configureDatabase( val persistence = configureDatabase(
hikariProperties = dataSourceProps, hikariProperties = dataSourceProps,
databaseConfig = DatabaseConfig(), databaseConfig = DatabaseConfig(),
@ -167,7 +170,7 @@ open class MockServices private constructor(
internalSchemas = schemaService.internalSchemas() internalSchemas = schemaService.internalSchemas()
) )
val pkToIdCache = PublicKeyToOwningIdentityCacheImpl(persistence, TestingNamedCacheFactory()) val pkToIdCache = PublicKeyToOwningIdentityCacheImpl(persistence, cacheFactory)
// Create a persistent identity service and add all the supplied identities. // Create a persistent identity service and add all the supplied identities.
identityService.apply { identityService.apply {

View File

@ -6,6 +6,7 @@ import net.corda.networkbuilder.nodes.FoundNode
import net.corda.networkbuilder.nodes.NodeCopier import net.corda.networkbuilder.nodes.NodeCopier
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.File import java.io.File
import java.nio.file.Paths
class NotaryCopier(private val cacheDir: File) : NodeCopier(cacheDir) { class NotaryCopier(private val cacheDir: File) : NodeCopier(cacheDir) {
@ -28,7 +29,9 @@ class NotaryCopier(private val cacheDir: File) : NodeCopier(cacheDir) {
fun generateNodeInfo(dirToGenerateFrom: File): File { fun generateNodeInfo(dirToGenerateFrom: File): File {
val nodeInfoGeneratorProcess = ProcessBuilder() val nodeInfoGeneratorProcess = ProcessBuilder()
.command(listOf("java", "-jar", "corda.jar", "generate-node-info")) .command(listOf(
Paths.get(System.getProperty("java.home"), "bin", "java").toString(),
"-jar", "corda.jar", "generate-node-info"))
.directory(dirToGenerateFrom) .directory(dirToGenerateFrom)
.inheritIO() .inheritIO()
.start() .start()

View File

@ -13,6 +13,7 @@ import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.CordaRPCClientConfiguration import net.corda.client.rpc.CordaRPCClientConfiguration
import net.corda.client.rpc.GracefulReconnect import net.corda.client.rpc.GracefulReconnect
import net.corda.client.rpc.PermissionException import net.corda.client.rpc.PermissionException
import net.corda.client.rpc.notUsed
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.contracts.UniqueIdentifier import net.corda.core.contracts.UniqueIdentifier
@ -70,6 +71,7 @@ import kotlin.concurrent.thread
// TODO: Resurrect or reimplement the mail plugin. // TODO: Resurrect or reimplement the mail plugin.
// TODO: Make it notice new shell commands added after the node started. // TODO: Make it notice new shell commands added after the node started.
@Suppress("MaxLineLength")
object InteractiveShell { object InteractiveShell {
private val log = LoggerFactory.getLogger(javaClass) private val log = LoggerFactory.getLogger(javaClass)
private lateinit var rpcOps: (username: String, password: String) -> InternalCordaRPCOps private lateinit var rpcOps: (username: String, password: String) -> InternalCordaRPCOps
@ -521,8 +523,11 @@ object InteractiveShell {
val parser = StringToMethodCallParser(CordaRPCOps::class.java, inputObjectMapper) val parser = StringToMethodCallParser(CordaRPCOps::class.java, inputObjectMapper)
val call = parser.parse(cordaRPCOps, cmd) val call = parser.parse(cordaRPCOps, cmd)
result = call.call() result = call.call()
var subscription : Subscriber<*>? = null
if (result != null && result !== kotlin.Unit && result !is Void) { if (result != null && result !== kotlin.Unit && result !is Void) {
result = printAndFollowRPCResponse(result, out, outputFormat) val (subs, future) = printAndFollowRPCResponse(result, out, outputFormat)
subscription = subs
result = future
} }
if (result is Future<*>) { if (result is Future<*>) {
if (!result.isDone) { if (!result.isDone) {
@ -532,6 +537,7 @@ object InteractiveShell {
try { try {
result = result.get() result = result.get()
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
subscription?.unsubscribe()
Thread.currentThread().interrupt() Thread.currentThread().interrupt()
} catch (e: ExecutionException) { } catch (e: ExecutionException) {
throw e.rootCause throw e.rootCause
@ -621,7 +627,11 @@ object InteractiveShell {
} }
} }
private fun printAndFollowRPCResponse(response: Any?, out: PrintWriter, outputFormat: OutputFormat): CordaFuture<Unit> { private fun printAndFollowRPCResponse(
response: Any?,
out: PrintWriter,
outputFormat: OutputFormat
): Pair<PrintingSubscriber?, CordaFuture<Unit>> {
val outputMapper = createOutputMapper(outputFormat) val outputMapper = createOutputMapper(outputFormat)
val mapElement: (Any?) -> String = { element -> outputMapper.writerWithDefaultPrettyPrinter().writeValueAsString(element) } val mapElement: (Any?) -> String = { element -> outputMapper.writerWithDefaultPrettyPrinter().writeValueAsString(element) }
@ -659,34 +669,52 @@ object InteractiveShell {
} }
} }
private fun maybeFollow(response: Any?, printerFun: (Any?) -> String, out: PrintWriter): CordaFuture<Unit> { private fun maybeFollow(
response: Any?,
printerFun: (Any?) -> String,
out: PrintWriter
): Pair<PrintingSubscriber?, CordaFuture<Unit>> {
// Match on a couple of common patterns for "important" observables. It's tough to do this in a generic // Match on a couple of common patterns for "important" observables. It's tough to do this in a generic
// way because observables can be embedded anywhere in the object graph, and can emit other arbitrary // way because observables can be embedded anywhere in the object graph, and can emit other arbitrary
// object graphs that contain yet more observables. So we just look for top level responses that follow // object graphs that contain yet more observables. So we just look for top level responses that follow
// the standard "track" pattern, and print them until the user presses Ctrl-C // the standard "track" pattern, and print them until the user presses Ctrl-C
if (response == null) return doneFuture(Unit) var result = Pair<PrintingSubscriber?, CordaFuture<Unit>>(null, doneFuture(Unit))
if (response is DataFeed<*, *>) {
out.println("Snapshot:") when {
out.println(printerFun(response.snapshot)) response is DataFeed<*, *> -> {
out.flush() out.println("Snapshot:")
out.println("Updates:") out.println(printerFun(response.snapshot))
return printNextElements(response.updates, printerFun, out) out.flush()
out.println("Updates:")
val unsubscribeAndPrint: (Any?) -> String = { resp ->
if (resp is StateMachineUpdate.Added) {
resp.stateMachineInfo.progressTrackerStepAndUpdates?.updates?.notUsed()
}
printerFun(resp)
}
result = printNextElements(response.updates, unsubscribeAndPrint, out)
}
response is Observable<*> -> {
result = printNextElements(response, printerFun, out)
}
response != null -> {
out.println(printerFun(response))
}
} }
if (response is Observable<*>) { return result
return printNextElements(response, printerFun, out)
}
out.println(printerFun(response))
return doneFuture(Unit)
} }
private fun printNextElements(elements: Observable<*>, printerFun: (Any?) -> String, out: PrintWriter): CordaFuture<Unit> { private fun printNextElements(
elements: Observable<*>,
printerFun: (Any?) -> String,
out: PrintWriter
): Pair<PrintingSubscriber?, CordaFuture<Unit>> {
val subscriber = PrintingSubscriber(printerFun, out) val subscriber = PrintingSubscriber(printerFun, out)
uncheckedCast(elements).subscribe(subscriber) uncheckedCast(elements).subscribe(subscriber)
return subscriber.future return Pair(subscriber, subscriber.future)
} }
} }

View File

@ -177,7 +177,7 @@ abstract class ANSIProgressRenderer {
ansi.fgRed() ansi.fgRed()
ansi.a("${IntStream.range(indent, indent).mapToObj { "\t" }.toList().joinToString(separator = "") { s -> s }} $errorIcon ${error.message}") ansi.a("${IntStream.range(indent, indent).mapToObj { "\t" }.toList().joinToString(separator = "") { s -> s }} $errorIcon ${error.message}")
ansi.reset() ansi.reset()
errorToPrint = error.cause errorToPrint = errorToPrint.cause
indent++ indent++
} }
ansi.eraseLine(Ansi.Erase.FORWARD) ansi.eraseLine(Ansi.Erase.FORWARD)