Merge branch 'master' into feature-network-parameters

# Conflicts:
#	testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt
This commit is contained in:
Shams Asari 2017-11-21 15:01:52 +00:00
commit 5cf7ecd9ed
215 changed files with 2755 additions and 1882 deletions

View File

@ -1533,7 +1533,6 @@ public final class net.corda.core.identity.IdentityUtils extends java.lang.Objec
@org.jetbrains.annotations.NotNull public abstract List networkMapSnapshot()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NodeInfo nodeInfo()
@org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo nodeInfoFromParty(net.corda.core.identity.AbstractParty)
@net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract rx.Observable nodeStateObservable()
@org.jetbrains.annotations.NotNull public abstract List notaryIdentities()
@org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party notaryPartyFromX500Name(net.corda.core.identity.CordaX500Name)
@org.jetbrains.annotations.NotNull public abstract java.io.InputStream openAttachment(net.corda.core.crypto.SecureHash)
@ -1613,11 +1612,6 @@ public final class net.corda.core.messaging.CordaRPCOpsKt extends java.lang.Obje
##
@net.corda.core.serialization.CordaSerializable public interface net.corda.core.messaging.MessageRecipients
##
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.messaging.NodeState extends java.lang.Enum
protected <init>(String, int)
public static net.corda.core.messaging.NodeState valueOf(String)
public static net.corda.core.messaging.NodeState[] values()
##
@net.corda.core.DoNotImplement public interface net.corda.core.messaging.RPCOps
public abstract int getProtocolVersion()
##
@ -1684,27 +1678,6 @@ public @interface net.corda.core.messaging.RPCReturnsObservables
@org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowHandle startFlow(net.corda.core.flows.FlowLogic)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowProgressHandle startTrackedFlow(net.corda.core.flows.FlowLogic)
##
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NetworkParameters extends java.lang.Object
public <init>(int, List, java.time.Duration, int, int, java.time.Instant, int)
public final int component1()
@org.jetbrains.annotations.NotNull public final List component2()
@org.jetbrains.annotations.NotNull public final java.time.Duration component3()
public final int component4()
public final int component5()
@org.jetbrains.annotations.NotNull public final java.time.Instant component6()
public final int component7()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NetworkParameters copy(int, List, java.time.Duration, int, int, java.time.Instant, int)
public boolean equals(Object)
public final int getEpoch()
@org.jetbrains.annotations.NotNull public final java.time.Duration getEventHorizon()
public final int getMaxMessageSize()
public final int getMaxTransactionSize()
public final int getMinimumPlatformVersion()
@org.jetbrains.annotations.NotNull public final java.time.Instant getModifiedTime()
@org.jetbrains.annotations.NotNull public final List getNotaries()
public int hashCode()
public String toString()
##
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NodeInfo extends java.lang.Object
public <init>(List, List, int, long)
@org.jetbrains.annotations.NotNull public final List component1()
@ -1723,17 +1696,6 @@ public @interface net.corda.core.messaging.RPCReturnsObservables
public final boolean isLegalIdentity(net.corda.core.identity.Party)
public String toString()
##
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NotaryInfo extends java.lang.Object
public <init>(net.corda.core.identity.Party, boolean)
@org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component1()
public final boolean component2()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.NotaryInfo copy(net.corda.core.identity.Party, boolean)
public boolean equals(Object)
@org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party getIdentity()
public final boolean getValidating()
public int hashCode()
public String toString()
##
@net.corda.core.DoNotImplement public interface net.corda.core.node.ServiceHub extends net.corda.core.node.ServicesForResolution
@org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey)
@ -1746,7 +1708,6 @@ public @interface net.corda.core.messaging.RPCReturnsObservables
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.ContractUpgradeService getContractUpgradeService()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.KeyManagementService getKeyManagementService()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NodeInfo getMyInfo()
@org.jetbrains.annotations.NotNull public abstract rx.Observable getMyNodeStateObservable()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.NetworkMapCache getNetworkMapCache()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.TransactionVerifierService getTransactionVerifierService()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.TransactionStorage getValidatedTransactions()

2
.idea/compiler.xml generated
View File

@ -43,6 +43,8 @@
<module name="docs_source_example-code_main" target="1.8" />
<module name="docs_source_example-code_test" target="1.8" />
<module name="docs_test" target="1.8" />
<module name="experimental-kryo-hook_main" target="1.8" />
<module name="experimental-kryo-hook_test" target="1.8" />
<module name="example-code_integrationTest" target="1.8" />
<module name="example-code_main" target="1.8" />
<module name="example-code_test" target="1.8" />

View File

@ -48,6 +48,7 @@ buildscript {
ext.dependency_checker_version = '3.0.1'
ext.commons_collections_version = '4.1'
ext.beanutils_version = '1.9.3'
ext.crash_version = 'faba68332800f21278c5b600bf14ad55cef5989e'
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
ext.java8_minUpdateVersion = '131'

View File

@ -7,7 +7,7 @@ import com.google.common.collect.HashMultimap
import com.google.common.collect.Multimap
import net.corda.client.jackson.StringToMethodCallParser.ParsedMethodCall
import net.corda.core.CordaException
import org.slf4j.LoggerFactory
import net.corda.core.utilities.contextLogger
import java.lang.reflect.Constructor
import java.lang.reflect.Method
import java.util.concurrent.Callable
@ -90,7 +90,7 @@ open class StringToMethodCallParser<in T : Any> @JvmOverloads constructor(
return result
}
private val log = LoggerFactory.getLogger(StringToMethodCallParser::class.java)!!
private val log = contextLogger()
}
/** The methods that can be invoked via this parser. */

View File

@ -64,7 +64,7 @@ class NodeMonitorModelTest {
invokeRpc(CordaRPCOps::stateMachinesFeed),
invokeRpc(CordaRPCOps::networkMapFeed))
)
val aliceNodeHandle = startNode(providedName = ALICE.name, rpcUsers = listOf(cashUser)).getOrThrow()
val aliceNodeHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(cashUser)).getOrThrow()
aliceNode = aliceNodeHandle.nodeInfo
newNode = { nodeName -> startNode(providedName = nodeName).getOrThrow().nodeInfo }
val monitor = NodeMonitorModel()
@ -79,7 +79,7 @@ class NodeMonitorModelTest {
rpc = monitor.proxyObservable.value!!
notaryParty = defaultNotaryIdentity
val bobNodeHandle = startNode(providedName = BOB.name, rpcUsers = listOf(cashUser)).getOrThrow()
val bobNodeHandle = startNode(providedName = BOB_NAME, rpcUsers = listOf(cashUser)).getOrThrow()
bobNode = bobNodeHandle.nodeInfo
val monitorBob = NodeMonitorModel()
stateMachineUpdatesBob = monitorBob.stateMachineUpdates.bufferUntilSubscribed()
@ -91,20 +91,20 @@ class NodeMonitorModelTest {
@Test
fun `network map update`() = setup {
val charlieNode = newNode(CHARLIE.name)
val charlieNode = newNode(CHARLIE_NAME)
val nonServiceIdentities = aliceNode.legalIdentitiesAndCerts + bobNode.legalIdentitiesAndCerts + charlieNode.legalIdentitiesAndCerts
networkMapUpdates.filter { it.node.legalIdentitiesAndCerts.any { it in nonServiceIdentities } }
.expectEvents(isStrict = false) {
sequence(
// TODO : Add test for remove when driver DSL support individual node shutdown.
expect { output: NetworkMapCache.MapChange ->
require(output.node.chooseIdentity().name == ALICE.name) { "Expecting : ${ALICE.name}, Actual : ${output.node.chooseIdentity().name}" }
require(output.node.legalIdentities.any { it.name == ALICE_NAME }) { "Expecting : ${ALICE_NAME}, Actual : ${output.node.legalIdentities.map(Party::name)}" }
},
expect { output: NetworkMapCache.MapChange ->
require(output.node.chooseIdentity().name == BOB.name) { "Expecting : ${BOB.name}, Actual : ${output.node.chooseIdentity().name}" }
require(output.node.legalIdentities.any { it.name == BOB_NAME }) { "Expecting : ${BOB_NAME}, Actual : ${output.node.legalIdentities.map(Party::name)}" }
},
expect { output: NetworkMapCache.MapChange ->
require(output.node.chooseIdentity().name == CHARLIE.name) { "Expecting : ${CHARLIE.name}, Actual : ${output.node.chooseIdentity().name}" }
require(output.node.legalIdentities.any { it.name == CHARLIE_NAME }) { "Expecting : ${CHARLIE_NAME}, Actual : ${output.node.legalIdentities.map(Party::name)}" }
}
)
}

View File

@ -2,6 +2,7 @@ package net.corda.client.rpc
import net.corda.core.context.*
import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.flatMap
import net.corda.core.internal.packageName
import net.corda.core.messaging.*
@ -28,7 +29,6 @@ import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.After
import org.junit.Before
import org.junit.Test
import kotlin.reflect.KClass
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@ -42,6 +42,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
invokeRpc("vaultQueryByCriteria"))
)
private lateinit var node: StartedNode<Node>
private lateinit var identity: Party
private lateinit var client: CordaRPCClient
private var connection: CordaRPCConnection? = null
@ -51,8 +52,9 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
@Before
fun setUp() {
node = startNode(ALICE.name, rpcUsers = listOf(rpcUser))
node = startNode(ALICE_NAME, rpcUsers = listOf(rpcUser))
client = CordaRPCClient(node.internals.configuration.rpcAddress!!)
identity = node.info.identityFromX500Name(ALICE_NAME)
}
@After
@ -86,7 +88,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
println("Creating proxy")
println("Starting flow")
val flowHandle = connection!!.proxy.startTrackedFlow(::CashIssueFlow,
20.DOLLARS, OpaqueBytes.of(0), node.info.chooseIdentity()
20.DOLLARS, OpaqueBytes.of(0), identity
)
println("Started flow, waiting on result")
flowHandle.progress.subscribe {
@ -98,7 +100,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
@Test
fun `sub-type of FlowException thrown by flow`() {
login(rpcUser.username, rpcUser.password)
val handle = connection!!.proxy.startFlow(::CashPaymentFlow, 100.DOLLARS, node.info.chooseIdentity())
val handle = connection!!.proxy.startFlow(::CashPaymentFlow, 100.DOLLARS, identity)
assertThatExceptionOfType(CashException::class.java).isThrownBy {
handle.returnValue.getOrThrow()
}
@ -107,7 +109,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
@Test
fun `check basic flow has no progress`() {
login(rpcUser.username, rpcUser.password)
connection!!.proxy.startFlow(::CashPaymentFlow, 100.DOLLARS, node.info.chooseIdentity()).use {
connection!!.proxy.startFlow(::CashPaymentFlow, 100.DOLLARS, identity).use {
assertFalse(it is FlowProgressHandle<*>)
}
}
@ -120,7 +122,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
assertTrue(startCash.isEmpty(), "Should not start with any cash")
val flowHandle = proxy.startFlow(::CashIssueFlow,
123.DOLLARS, OpaqueBytes.of(0), node.info.chooseIdentity()
123.DOLLARS, OpaqueBytes.of(0), identity
)
println("Started issuing cash, waiting on result")
flowHandle.returnValue.get()
@ -136,13 +138,12 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
val impersonatedActor = Actor(Actor.Id("Mark Dadada"), AuthServiceId("Test"), owningLegalIdentity = BOB.name)
login(rpcUser.username, rpcUser.password, externalTrace, impersonatedActor)
val proxy = connection!!.proxy
val nodeIdentity = node.info.chooseIdentity()
val updates = proxy.stateMachinesFeed().updates
node.services.startFlow(CashIssueFlow(2000.DOLLARS, OpaqueBytes.of(0), nodeIdentity), InvocationContext.shell()).flatMap { it.resultFuture }.getOrThrow()
proxy.startFlow(::CashIssueFlow, 123.DOLLARS, OpaqueBytes.of(0), nodeIdentity).returnValue.getOrThrow()
proxy.startFlowDynamic(CashIssueFlow::class.java, 1000.DOLLARS, OpaqueBytes.of(0), nodeIdentity).returnValue.getOrThrow()
node.services.startFlow(CashIssueFlow(2000.DOLLARS, OpaqueBytes.of(0),identity), InvocationContext.shell()).flatMap { it.resultFuture }.getOrThrow()
proxy.startFlow(::CashIssueFlow, 123.DOLLARS, OpaqueBytes.of(0), identity).returnValue.getOrThrow()
proxy.startFlowDynamic(CashIssueFlow::class.java, 1000.DOLLARS, OpaqueBytes.of(0), identity).returnValue.getOrThrow()
val historicalIds = mutableSetOf<Trace.InvocationId>()
var sessionId: Trace.SessionId? = null

View File

@ -6,16 +6,12 @@ import net.corda.core.context.Trace
import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.concurrent.fork
import net.corda.core.internal.concurrent.transpose
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.NodeState
import net.corda.core.messaging.RPCOps
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.serialize
import net.corda.core.utilities.*
import net.corda.node.services.Permissions.Companion.invokeRpc
import net.corda.node.services.messaging.RPCServerConfiguration
import net.corda.nodeapi.RPCApi
import net.corda.nodeapi.User
import net.corda.testing.driver.poll
import net.corda.testing.internal.*
import org.apache.activemq.artemis.api.core.SimpleString
@ -241,30 +237,6 @@ class RPCStabilityTests {
}
}
@Test
fun `clients receive notifications that node is shutting down`() {
val alice = User("Alice", "Alice", setOf(invokeRpc(CordaRPCOps::nodeStateObservable)))
val bob = User("Bob", "Bob", setOf(invokeRpc(CordaRPCOps::nodeStateObservable)))
val slagathor = User("Slagathor", "Slagathor", setOf(invokeRpc(CordaRPCOps::nodeStateObservable)))
val userList = listOf(alice, bob, slagathor)
val expectedMessages = ArrayList<NodeState>()
rpcDriver(startNodesInProcess = true) {
val node = startNode(rpcUsers = listOf(alice, bob, slagathor)).getOrThrow()
userList.forEach {
val connection = node.rpcClientToNode().start(it.username, it.password)
val nodeStateObservable = connection.proxy.nodeStateObservable()
nodeStateObservable.subscribe { update ->
expectedMessages.add(update)
}
}
node.stop()
}
assertEquals(userList.size, expectedMessages.size)
assertEquals(NodeState.SHUTTING_DOWN, expectedMessages.first())
}
interface TrackSubscriberOps : RPCOps {
fun subscribe(): Observable<Unit>
}

View File

@ -11,10 +11,7 @@ import net.corda.core.messaging.RPCOps
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.minutes
import net.corda.core.utilities.seconds
import net.corda.core.utilities.*
import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport
import net.corda.nodeapi.ConnectionDirection
import net.corda.nodeapi.RPCApi
@ -96,7 +93,7 @@ class RPCClient<I : RPCOps>(
) : this(tcpTransport(ConnectionDirection.Outbound(), hostAndPort, sslConfiguration), configuration, serializationContext)
companion object {
private val log = loggerFor<RPCClient<*>>()
private val log = contextLogger()
}
fun start(

View File

@ -22,10 +22,7 @@ import net.corda.core.internal.ThreadBox
import net.corda.core.messaging.RPCOps
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.serialize
import net.corda.core.utilities.Try
import net.corda.core.utilities.debug
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.*
import net.corda.nodeapi.ArtemisConsumer
import net.corda.nodeapi.ArtemisProducer
import net.corda.nodeapi.RPCApi
@ -91,7 +88,7 @@ class RPCClientProxyHandler(
private val lifeCycle = LifeCycle(State.UNSTARTED)
private companion object {
val log = loggerFor<RPCClientProxyHandler>()
private val log = contextLogger()
// To check whether toString() is being invoked
val toStringMethod: Method = Object::toString.javaMethod!!

View File

@ -11,10 +11,7 @@ import net.corda.core.messaging.*
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.*
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.seconds
import net.corda.core.utilities.*
import net.corda.finance.DOLLARS
import net.corda.finance.POUNDS
import net.corda.finance.SWISS_FRANCS
@ -45,7 +42,7 @@ import kotlin.test.assertTrue
class StandaloneCordaRPClientTest {
private companion object {
val log = loggerFor<StandaloneCordaRPClientTest>()
private val log = contextLogger()
val user = User("user1", "test", permissions = setOf("ALL"))
val port = AtomicInteger(15200)
const val attachmentSize = 2116

View File

@ -3,10 +3,12 @@ package net.corda.confidential
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.utilities.getOrThrow
import net.corda.testing.*
import net.corda.testing.node.MockNetwork
import org.junit.Before
import net.corda.testing.node.MockNodeParameters
import org.junit.Test
import kotlin.test.*
@ -83,28 +85,30 @@ class SwapIdentitiesFlowTests {
val notaryNode = mockNet.defaultNotaryNode
val aliceNode = mockNet.createPartyNode(ALICE.name)
val bobNode = mockNet.createPartyNode(BOB.name)
val bob: Party = bobNode.services.myInfo.singleIdentity()
val alice: PartyAndCertificate = aliceNode.info.singleIdentityAndCert()
val bob: PartyAndCertificate = bobNode.info.singleIdentityAndCert()
val notary: PartyAndCertificate = mockNet.defaultNotaryIdentityAndCert
// Check that the wrong signature is rejected
notaryNode.database.transaction {
notaryNode.services.keyManagementService.freshKeyAndCert(notaryNode.services.myInfo.chooseIdentityAndCert(), false)
notaryNode.services.keyManagementService.freshKeyAndCert(notary, false)
}.let { anonymousNotary ->
val sigData = SwapIdentitiesFlow.buildDataToSign(anonymousNotary)
val signature = notaryNode.services.keyManagementService.sign(sigData, anonymousNotary.owningKey)
assertFailsWith<SwapIdentitiesException>("Signature does not match the given identity and nonce") {
SwapIdentitiesFlow.validateAndRegisterIdentity(aliceNode.services.identityService, bob, anonymousNotary, signature.withoutKey())
SwapIdentitiesFlow.validateAndRegisterIdentity(aliceNode.services.identityService, bob.party, anonymousNotary, signature.withoutKey())
}
}
// Check that the right signing key, but wrong identity is rejected
val anonymousAlice = aliceNode.database.transaction {
aliceNode.services.keyManagementService.freshKeyAndCert(aliceNode.services.myInfo.chooseIdentityAndCert(), false)
val anonymousAlice: PartyAndCertificate = aliceNode.database.transaction {
aliceNode.services.keyManagementService.freshKeyAndCert(alice, false)
}
bobNode.database.transaction {
bobNode.services.keyManagementService.freshKeyAndCert(bobNode.services.myInfo.chooseIdentityAndCert(), false)
bobNode.services.keyManagementService.freshKeyAndCert(bob, false)
}.let { anonymousBob ->
val sigData = SwapIdentitiesFlow.buildDataToSign(anonymousAlice)
val signature = bobNode.services.keyManagementService.sign(sigData, anonymousBob.owningKey)
assertFailsWith<SwapIdentitiesException>("Signature does not match the given identity and nonce.") {
SwapIdentitiesFlow.validateAndRegisterIdentity(aliceNode.services.identityService, bob, anonymousBob, signature.withoutKey())
SwapIdentitiesFlow.validateAndRegisterIdentity(aliceNode.services.identityService, bob.party, anonymousBob, signature.withoutKey())
}
}

View File

@ -1,5 +1,5 @@
gradlePluginsVersion=2.0.8
kotlinVersion=1.1.50
gradlePluginsVersion=2.0.9
kotlinVersion=1.1.60
guavaVersion=21.0
bouncycastleVersion=1.57
typesafeConfigVersion=1.3.1

View File

@ -117,6 +117,9 @@ dependencies {
// JPA 2.1 annotations.
compile "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final"
// required to use @Type annotation
compile "org.hibernate:hibernate-core:$hibernate_version"
}
// TODO Consider moving it to quasar-utils in the future (introduced with PR-1388)

View File

@ -45,9 +45,17 @@ interface NamedByHash {
*/
@CordaSerializable
data class Issued<out P : Any>(val issuer: PartyAndReference, val product: P) {
init {
require(issuer.reference.bytes.size <= MAX_ISSUER_REF_SIZE) { "Maximum issuer reference size is $MAX_ISSUER_REF_SIZE." }
}
override fun toString() = "$product issued by $issuer"
}
/**
* The maximum permissible size of an issuer reference.
*/
const val MAX_ISSUER_REF_SIZE = 512
/**
* Strips the issuer and returns an [Amount] of the raw token directly. This is useful when you are mixing code that
* cares about specific issuers with code that will accept any, or which is imposing issuer constraints via some

View File

@ -103,3 +103,5 @@ fun ByteArray.sha256(): SecureHash.SHA256 = SecureHash.sha256(this)
* Compute the SHA-256 hash for the contents of the [OpaqueBytes].
*/
fun OpaqueBytes.sha256(): SecureHash.SHA256 = SecureHash.sha256(this.bytes)

View File

@ -348,6 +348,20 @@ abstract class FlowLogic<out T> {
}
}
fun trackStepsTreeIndex(): DataFeed<Int, Int>? {
// TODO this is not threadsafe, needs an atomic get-step-and-subscribe
return progressTracker?.let {
DataFeed(it.stepsTreeIndex, it.stepsTreeIndexChanges)
}
}
fun trackStepsTree(): DataFeed<List<Pair<Int,String>>, List<Pair<Int,String>>>? {
// TODO this is not threadsafe, needs an atomic get-step-and-subscribe
return progressTracker?.let {
DataFeed(it.allStepsLabels, it.stepsTreeChanges)
}
}
/**
* Suspends the flow until the transaction with the specified ID is received, successfully verified and
* sent to the vault for processing. Note that this call suspends until the transaction is considered

View File

@ -1,6 +1,6 @@
package net.corda.core.internal
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.contextLogger
import org.slf4j.Logger
import java.util.concurrent.atomic.AtomicReference
import kotlin.reflect.KProperty
@ -45,18 +45,25 @@ class ThreadLocalToggleField<T>(name: String) : ToggleField<T>(name) {
/** The named thread has leaked from a previous test. */
class ThreadLeakException : RuntimeException("Leaked thread detected: ${Thread.currentThread().name}")
/** @param exceptionHandler should throw the exception, or may return normally to suppress inheritance. */
/** @param isAGlobalThreadBeingCreated whether a global thread (that should not inherit any value) is being created. */
class InheritableThreadLocalToggleField<T>(name: String,
private val log: Logger = loggerFor<InheritableThreadLocalToggleField<*>>(),
private val exceptionHandler: (ThreadLeakException) -> Unit = { throw it }) : ToggleField<T>(name) {
private val log: Logger = staticLog,
private val isAGlobalThreadBeingCreated: (Array<StackTraceElement>) -> Boolean) : ToggleField<T>(name) {
companion object {
private val staticLog = contextLogger()
}
private inner class Holder(value: T) : AtomicReference<T?>(value) {
fun valueOrDeclareLeak() = get() ?: throw ThreadLeakException()
fun childValue(): Holder? {
get() != null && return this // Current thread isn't leaked.
val e = ThreadLeakException()
exceptionHandler(e)
log.warn(e.message)
return null
val e = ThreadLeakException() // Expensive, but so is starting the new thread.
return if (isAGlobalThreadBeingCreated(e.stackTrace)) {
get() ?: log.warn(e.message)
null
} else {
get() ?: log.error(e.message)
this
}
}
}

View File

@ -3,8 +3,8 @@ package net.corda.core.internal.concurrent
import net.corda.core.internal.VisibleForTesting
import net.corda.core.concurrent.CordaFuture
import net.corda.core.concurrent.match
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import org.slf4j.Logger
import java.time.Duration
import java.util.concurrent.CompletableFuture
@ -57,6 +57,9 @@ fun <V, W> CordaFuture<out V>.flatMap(transform: (V) -> CordaFuture<out W>): Cor
})
}
/** Wrap a CompletableFuture, for example one that was returned by some API. */
fun <V> CompletableFuture<V>.asCordaFuture(): CordaFuture<V> = CordaFutureImpl(this)
/**
* If all of the given futures succeed, the returned future's outcome is a list of all their values.
* The values are in the same order as the futures in the collection, not the order of completion.
@ -115,7 +118,7 @@ interface OpenFuture<V> : ValueOrException<V>, CordaFuture<V>
@VisibleForTesting
internal class CordaFutureImpl<V>(private val impl: CompletableFuture<V> = CompletableFuture()) : Future<V> by impl, OpenFuture<V> {
companion object {
private val defaultLog = loggerFor<CordaFutureImpl<*>>()
private val defaultLog = contextLogger()
internal val listenerFailedMessage = "Future listener failed:"
}

View File

@ -226,10 +226,6 @@ interface CordaRPCOps : RPCOps {
/** Returns Node's NodeInfo, assuming this will not change while the node is running. */
fun nodeInfo(): NodeInfo
/** Returns and [Observable] object with future states of the node. */
@RPCReturnsObservables
fun nodeStateObservable(): Observable<NodeState>
/**
* Returns network's notary identities, assuming this will not change while the node is running.
*
@ -468,8 +464,3 @@ inline fun <T, A, B, C, D, E, F, reified R : FlowLogic<T>> CordaRPCOps.startTrac
*/
@CordaSerializable
data class DataFeed<out A, B>(val snapshot: A, val updates: Observable<B>)
@CordaSerializable
enum class NodeState {
SHUTTING_DOWN
}

View File

@ -31,6 +31,9 @@ interface FlowHandle<A> : AutoCloseable {
interface FlowProgressHandle<A> : FlowHandle<A> {
val progress: Observable<String>
val stepsTreeIndexFeed: DataFeed<Int, Int>?
val stepsTreeFeed: DataFeed<List<Pair<Int, String>>, List<Pair<Int, String>>>?
/**
* Use this function for flows whose returnValue and progress are not going to be used or tracked, so as to free up
* server resources.
@ -52,10 +55,17 @@ data class FlowHandleImpl<A>(
}
@CordaSerializable
data class FlowProgressHandleImpl<A>(
data class FlowProgressHandleImpl<A> @JvmOverloads constructor(
override val id: StateMachineRunId,
override val returnValue: CordaFuture<A>,
override val progress: Observable<String>) : FlowProgressHandle<A> {
override val progress: Observable<String>,
override val stepsTreeIndexFeed: DataFeed<Int, Int>? = null,
override val stepsTreeFeed: DataFeed<List<Pair<Int, String>>, List<Pair<Int, String>>>? = null) : FlowProgressHandle<A> {
// For API compatibility
fun copy(id: StateMachineRunId, returnValue: CordaFuture<A>, progress: Observable<String>): FlowProgressHandleImpl<A> {
return copy(id = id, returnValue = returnValue, progress = progress, stepsTreeFeed = null, stepsTreeIndexFeed = null)
}
// Remember to add @Throws to FlowProgressHandle.close() if this throws an exception.
override fun close() {

View File

@ -1,40 +0,0 @@
package net.corda.core.node
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import java.time.Duration
import java.time.Instant
/**
* @property minimumPlatformVersion
* @property notaries
* @property eventHorizon
* @property maxMessageSize Maximum P2P message sent over the wire in bytes.
* @property maxTransactionSize Maximum permitted transaction size in bytes.
* @property modifiedTime
* @property epoch Version number of the network parameters. Starting from 1, this will always increment on each new set
* of parameters.
*/
// TODO Wire up the parameters
@CordaSerializable
data class NetworkParameters(
val minimumPlatformVersion: Int,
val notaries: List<NotaryInfo>,
val eventHorizon: Duration,
val maxMessageSize: Int,
val maxTransactionSize: Int,
val modifiedTime: Instant,
val epoch: Int
) {
init {
require(minimumPlatformVersion > 0) { "minimumPlatformVersion must be at least 1" }
require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" }
require(epoch > 0) { "epoch must be at least 1" }
}
}
/**
*
*/
@CordaSerializable
data class NotaryInfo(val identity: Party, val validating: Boolean)

View File

@ -44,8 +44,21 @@ data class NodeInfo(val addresses: List<NetworkHostAndPort>,
/** Returns true if [party] is one of the identities of this node, else false. */
fun isLegalIdentity(party: Party): Boolean = party in legalIdentities
fun identityFromX500Name(name: CordaX500Name): Party {
val identity = legalIdentitiesAndCerts.singleOrNull { it.name == name } ?: throw IllegalArgumentException("Node does not have an identity \"$name\"")
return identity.party
/**
* Get a legal identity of this node from the X.500 name. This is intended for use in cases where the node is
* expected to have a matching identity, and will throw an exception if no match is found.
*
* @throws IllegalArgumentException if the node has no matching identity.
*/
fun identityFromX500Name(name: CordaX500Name): Party = identityAndCertFromX500Name(name).party
/**
* Get a legal identity and certificate of this node from the X.500 name. This is intended for use in cases where
* the node is expected to have a matching identity, and will throw an exception if no match is found.
*
* @throws IllegalArgumentException if the node has no matching identity.
*/
fun identityAndCertFromX500Name(name: CordaX500Name): PartyAndCertificate {
return legalIdentitiesAndCerts.singleOrNull { it.name == name } ?: throw IllegalArgumentException("Node does not have an identity \"$name\"")
}
}

View File

@ -8,13 +8,11 @@ import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.TransactionSignature
import net.corda.core.flows.ContractUpgradeFlow
import net.corda.core.messaging.NodeState
import net.corda.core.node.services.*
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.transactions.FilteredTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import rx.Observable
import java.security.PublicKey
import java.sql.Connection
import java.time.Clock
@ -150,9 +148,6 @@ interface ServiceHub : ServicesForResolution {
/** The [NodeInfo] object corresponding to our own entry in the network map. */
val myInfo: NodeInfo
/** The [Observable] object used to communicate to RPC clients the state of the node. */
val myNodeStateObservable: Observable<NodeState>
/**
* Return the singleton instance of the given Corda service type. This is a class that is annotated with
* [CordaService] and will have automatically been registered by the node.

View File

@ -45,6 +45,13 @@ interface AttachmentStorage {
@Throws(FileAlreadyExistsException::class, IOException::class)
fun importAttachment(jar: InputStream, uploader: String, filename: String): AttachmentId
/**
* Inserts or returns Attachment Id of attachment. Does not throw an exception if already uploaded.
* @param jar [InputStream] of Jar file
* @return [AttachmentId] of uploaded attachment
*/
fun importOrGetAttachment(jar: InputStream): AttachmentId
/**
* Searches attachment using given criteria and optional sort rules
* @param criteria Query criteria to use as a filter
@ -53,5 +60,12 @@ interface AttachmentStorage {
* @return List of AttachmentId of attachment matching criteria, sorted according to given sorting parameter
*/
fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort? = null): List<AttachmentId>
/**
* Searches for an attachment already in the store
* @param attachmentId The attachment Id
* @return true if it's in there
*/
fun hasAttachment(attachmentId: AttachmentId): Boolean
}

View File

@ -53,6 +53,7 @@ interface NetworkMapCacheBase {
*
* Note that the identities are sorted based on legal name, and the ordering might change once new notaries are introduced.
*/
// TODO this list will be taken from NetworkParameters distributed by NetworkMap.
val notaryIdentities: List<Party>
// DOCEND 1
@ -116,7 +117,7 @@ interface NetworkMapCacheBase {
fun getNotary(name: CordaX500Name): Party? = notaryIdentities.firstOrNull { it.name == name }
// DOCEND 2
/** Returns true if and only if the given [Party] is a notary, which is defined by the network parameters. */
/** Checks whether a given party is an advertised notary identity. */
fun isNotary(party: Party): Boolean = party in notaryIdentities
/**

View File

@ -9,7 +9,7 @@ import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.serialize
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.contextLogger
import org.slf4j.Logger
import java.security.PublicKey
@ -45,8 +45,11 @@ abstract class NotaryService : SingletonSerializeAsToken() {
* of the cluster is sufficient for transaction notarisation. For example, a single-node or a Raft notary.
*/
abstract class TrustedAuthorityNotaryService : NotaryService() {
protected open val log: Logger = loggerFor<TrustedAuthorityNotaryService>()
companion object {
private val staticLog = contextLogger()
}
protected open val log: Logger get() = staticLog
// TODO: specify the valid time window in config, and convert TimeWindowChecker to a utility method
protected abstract val timeWindowChecker: TimeWindowChecker
protected abstract val uniquenessProvider: UniquenessProvider

View File

@ -1,12 +1,12 @@
package net.corda.core.schemas
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.FungibleAsset
import net.corda.core.contracts.OwnableState
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.contracts.*
import net.corda.core.identity.AbstractParty
import org.hibernate.annotations.Type
import java.util.*
import javax.persistence.*
import javax.persistence.Column
import javax.persistence.ElementCollection
import javax.persistence.MappedSuperclass
/**
* JPA representation of the common schema entities
@ -74,7 +74,8 @@ object CommonSchemaV1 : MappedSchema(schemaFamily = CommonSchema.javaClass, vers
@Column(name = "issuer_name")
var issuer: AbstractParty,
@Column(name = "issuer_reference")
@Column(name = "issuer_ref", length = MAX_ISSUER_REF_SIZE)
@Type(type = "corda-wrapper-binary")
var issuerRef: ByteArray
) : PersistentState()
}

View File

@ -43,12 +43,12 @@ val _globalSerializationEnv = SimpleToggleField<SerializationEnvironment>("globa
@VisibleForTesting
val _contextSerializationEnv = ThreadLocalToggleField<SerializationEnvironment>("contextSerializationEnv")
@VisibleForTesting
val _inheritableContextSerializationEnv = InheritableThreadLocalToggleField<SerializationEnvironment>("inheritableContextSerializationEnv") suppressInherit@ {
it.stackTrace.forEach {
// A dying Netty thread's death event restarting the Netty global executor:
it.className == "io.netty.util.concurrent.GlobalEventExecutor" && it.methodName == "startThread" && return@suppressInherit
val _inheritableContextSerializationEnv = InheritableThreadLocalToggleField<SerializationEnvironment>("inheritableContextSerializationEnv") { stack ->
stack.fold(false) { isAGlobalThreadBeingCreated, e ->
isAGlobalThreadBeingCreated ||
(e.className == "io.netty.util.concurrent.GlobalEventExecutor" && e.methodName == "startThread") ||
(e.className == "java.util.concurrent.ForkJoinPool\$DefaultForkJoinWorkerThreadFactory" && e.methodName == "newThread")
}
throw it
}
private val serializationEnvProperties = listOf(_nodeSerializationEnv, _globalSerializationEnv, _contextSerializationEnv, _inheritableContextSerializationEnv)
val effectiveSerializationEnv: SerializationEnvironment

View File

@ -14,7 +14,7 @@ import kotlin.reflect.KProperty
// READ ME FIRST:
// This is a collection of public utilities useful only for Kotlin code. Think carefully before adding anything here and
// make sure it's tested and documented. If you're looking to add a public utility that is also relevant to Java then
// don't put it here but in a seperate file called Utils.kt
// don't put it here but in a separate file called Utils.kt
//
/** Like the + operator but throws [ArithmeticException] in case of integer overflow. */
@ -24,12 +24,19 @@ infix fun Int.exactAdd(b: Int): Int = Math.addExact(this, b)
infix fun Long.exactAdd(b: Long): Long = Math.addExact(this, b)
/**
* Get the [Logger] for a class using the syntax
* Usually you won't need this method:
* * If you're in a companion object, use [contextLogger]
* * If you're in an object singleton, use [LoggerFactory.getLogger] directly on javaClass
*
* `val logger = loggerFor<MyClass>()`
* Otherwise, this gets the [Logger] for a class using the syntax
*
* `private val log = loggerFor<MyClass>()`
*/
inline fun <reified T : Any> loggerFor(): Logger = LoggerFactory.getLogger(T::class.java)
/** When called from a companion object, returns the logger for the enclosing class. */
fun Any.contextLogger(): Logger = LoggerFactory.getLogger(javaClass.enclosingClass)
/** Log a TRACE level message produced by evaluating the given lamdba, but only if TRACE logging is enabled. */
inline fun Logger.trace(msg: () -> String) {
if (isTraceEnabled) trace(msg())

View File

@ -6,9 +6,6 @@ import rx.Subscription
import rx.subjects.PublishSubject
import java.util.*
// TODO: Expose the concept of errors.
// TODO: It'd be helpful if this class was at least partly thread safe.
/**
* A progress tracker helps surface information about the progress of an operation to a user interface or API of some
* kind. It lets you define a set of _steps_ that represent an operation. A step is represented by an object (typically
@ -34,16 +31,16 @@ import java.util.*
@CordaSerializable
class ProgressTracker(vararg steps: Step) {
@CordaSerializable
sealed class Change {
data class Position(val tracker: ProgressTracker, val newStep: Step) : Change() {
sealed class Change(val progressTracker: ProgressTracker) {
data class Position(val tracker: ProgressTracker, val newStep: Step) : Change(tracker) {
override fun toString() = newStep.label
}
data class Rendering(val tracker: ProgressTracker, val ofStep: Step) : Change() {
data class Rendering(val tracker: ProgressTracker, val ofStep: Step) : Change(tracker) {
override fun toString() = ofStep.label
}
data class Structural(val tracker: ProgressTracker, val parent: Step) : Change() {
data class Structural(val tracker: ProgressTracker, val parent: Step) : Change(tracker) {
override fun toString() = "Structural step change in child of ${parent.label}"
}
}
@ -70,17 +67,23 @@ class ProgressTracker(vararg steps: Step) {
override fun equals(other: Any?) = other is DONE
}
/** The steps in this tracker, same as the steps passed to the constructor but with UNSTARTED and DONE inserted. */
val steps = arrayOf(UNSTARTED, *steps, DONE)
// This field won't be serialized.
private val _changes by transient { PublishSubject.create<Change>() }
@CordaSerializable
private data class Child(val tracker: ProgressTracker, @Transient val subscription: Subscription?)
private val childProgressTrackers = mutableMapOf<Step, Child>()
/** The steps in this tracker, same as the steps passed to the constructor but with UNSTARTED and DONE inserted. */
val steps = arrayOf(UNSTARTED, *steps, DONE)
private var _allStepsCache: List<Pair<Int, Step>> = _allSteps()
// This field won't be serialized.
private val _changes by transient { PublishSubject.create<Change>() }
private val _stepsTreeChanges by transient { PublishSubject.create<List<Pair<Int, String>>>() }
private val _stepsTreeIndexChanges by transient { PublishSubject.create<Int>() }
init {
steps.forEach {
val childTracker = it.childProgressTracker()
@ -92,7 +95,15 @@ class ProgressTracker(vararg steps: Step) {
/** The zero-based index of the current step in the [steps] array (i.e. with UNSTARTED and DONE) */
var stepIndex: Int = 0
private set
private set(value) {
field = value
}
var stepsTreeIndex: Int = -1
private set(value) {
field = value
_stepsTreeIndexChanges.onNext(value)
}
/**
* Reading returns the value of steps[stepIndex], writing moves the position of the current tracker. Once moved to
@ -118,22 +129,39 @@ class ProgressTracker(vararg steps: Step) {
curChangeSubscription?.unsubscribe()
stepIndex = index
_changes.onNext(Change.Position(this, steps[index]))
curChangeSubscription = currentStep.changes.subscribe({ _changes.onNext(it) }, { _changes.onError(it) })
recalculateStepsTreeIndex()
curChangeSubscription = currentStep.changes.subscribe({
_changes.onNext(it)
if (it is Change.Structural || it is Change.Rendering) rebuildStepsTree() else recalculateStepsTreeIndex()
}, { _changes.onError(it) })
if (currentStep == DONE) _changes.onCompleted()
if (currentStep == DONE) {
_changes.onCompleted()
_stepsTreeIndexChanges.onCompleted()
_stepsTreeChanges.onCompleted()
}
}
/** Returns the current step, descending into children to find the deepest step we are up to. */
val currentStepRecursive: Step
get() = getChildProgressTracker(currentStep)?.currentStepRecursive ?: currentStep
private fun currentStepRecursiveWithoutUnstarted(): Step {
val stepRecursive = getChildProgressTracker(currentStep)?.currentStepRecursive
return if (stepRecursive == null || stepRecursive == UNSTARTED) currentStep else stepRecursive
}
fun getChildProgressTracker(step: Step): ProgressTracker? = childProgressTrackers[step]?.tracker
fun setChildProgressTracker(step: ProgressTracker.Step, childProgressTracker: ProgressTracker) {
val subscription = childProgressTracker.changes.subscribe({ _changes.onNext(it) }, { _changes.onError(it) })
val subscription = childProgressTracker.changes.subscribe({
_changes.onNext(it)
if (it is Change.Structural || it is Change.Rendering) rebuildStepsTree() else recalculateStepsTreeIndex()
}, { _changes.onError(it) })
childProgressTrackers[step] = Child(childProgressTracker, subscription)
childProgressTracker.parent = this
_changes.onNext(Change.Structural(this, step))
rebuildStepsTree()
}
private fun removeChildProgressTracker(step: ProgressTracker.Step) {
@ -142,6 +170,7 @@ class ProgressTracker(vararg steps: Step) {
it.subscription?.unsubscribe()
}
_changes.onNext(Change.Structural(this, step))
rebuildStepsTree()
}
/**
@ -166,6 +195,18 @@ class ProgressTracker(vararg steps: Step) {
return cursor
}
private fun rebuildStepsTree() {
_allStepsCache = _allSteps()
_stepsTreeChanges.onNext(allStepsLabels)
recalculateStepsTreeIndex()
}
private fun recalculateStepsTreeIndex() {
val step = currentStepRecursiveWithoutUnstarted()
stepsTreeIndex = _allStepsCache.indexOfFirst { it.second == step }
}
private fun _allSteps(level: Int = 0): List<Pair<Int, Step>> {
val result = ArrayList<Pair<Int, Step>>()
for (step in steps) {
@ -177,11 +218,15 @@ class ProgressTracker(vararg steps: Step) {
return result
}
private fun _allStepsLabels(level: Int = 0): List<Pair<Int, String>> = _allSteps(level).map { Pair(it.first, it.second.label) }
/**
* A list of all steps in this ProgressTracker and the children, with the indent level provided starting at zero.
* Note that UNSTARTED is never counted, and DONE is only counted at the calling level.
*/
val allSteps: List<Pair<Int, Step>> get() = _allSteps()
val allSteps: List<Pair<Int, Step>> get() = _allStepsCache
val allStepsLabels: List<Pair<Int, String>> get() = _allStepsLabels()
private var curChangeSubscription: Subscription? = null
@ -200,8 +245,15 @@ class ProgressTracker(vararg steps: Step) {
*/
val changes: Observable<Change> get() = _changes
val stepsTreeChanges: Observable<List<Pair<Int,String>>> get() = _stepsTreeChanges
val stepsTreeIndexChanges: Observable<Int> get() = _stepsTreeIndexChanges
/** Returns true if the progress tracker has ended, either by reaching the [DONE] step or prematurely with an error */
val hasEnded: Boolean get() = _changes.hasCompleted() || _changes.hasThrowable()
}
// TODO: Expose the concept of errors.
// TODO: It'd be helpful if this class was at least partly thread safe.

View File

@ -4,8 +4,8 @@ import co.paralleluniverse.fibers.Suspendable;
import com.google.common.primitives.Primitives;
import net.corda.core.identity.Party;
import net.corda.node.internal.StartedNode;
import net.corda.testing.node.MockNetwork;
import net.corda.testing.TestConstants;
import net.corda.testing.node.MockNetwork;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@ -13,7 +13,7 @@ import org.junit.Test;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import static net.corda.testing.CoreTestUtils.chooseIdentity;
import static net.corda.testing.CoreTestUtils.singleIdentity;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.Assert.fail;
import static net.corda.testing.NodeTestUtils.startFlow;
@ -22,11 +22,13 @@ public class FlowsInJavaTest {
private final MockNetwork mockNet = new MockNetwork();
private StartedNode<MockNetwork.MockNode> aliceNode;
private StartedNode<MockNetwork.MockNode> bobNode;
private Party bob;
@Before
public void setUp() throws Exception {
aliceNode = mockNet.createPartyNode(TestConstants.getALICE().getName());
bobNode = mockNet.createPartyNode(TestConstants.getBOB().getName());
bob = singleIdentity(bobNode.getInfo());
}
@After
@ -37,7 +39,7 @@ public class FlowsInJavaTest {
@Test
public void suspendableActionInsideUnwrap() throws Exception {
bobNode.getInternals().registerInitiatedFlow(SendHelloAndThenReceive.class);
Future<String> result = startFlow(aliceNode.getServices(), new SendInUnwrapFlow(chooseIdentity(bobNode.getInfo()))).getResultFuture();
Future<String> result = startFlow(aliceNode.getServices(), new SendInUnwrapFlow(bob)).getResultFuture();
mockNet.runNetwork();
assertThat(result.get()).isEqualTo("Hello");
}
@ -52,7 +54,7 @@ public class FlowsInJavaTest {
}
private void primitiveReceiveTypeTest(Class<?> receiveType) throws InterruptedException {
PrimitiveReceiveFlow flow = new PrimitiveReceiveFlow(chooseIdentity(bobNode.getInfo()), receiveType);
PrimitiveReceiveFlow flow = new PrimitiveReceiveFlow(bob, receiveType);
Future<?> result = startFlow(aliceNode.getServices(), flow).getResultFuture();
mockNet.runNetwork();
try {

View File

@ -0,0 +1,13 @@
package net.corda.core.contracts
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.Test
class PrivacySaltTest {
@Test
fun `all-zero PrivacySalt not allowed`() {
assertThatExceptionOfType(IllegalArgumentException::class.java).isThrownBy {
PrivacySalt(ByteArray(32))
}.withMessage("Privacy salt should not be all zeros.")
}
}

View File

@ -41,13 +41,20 @@ class ContractUpgradeFlowTest {
private lateinit var aliceNode: StartedNode<MockNetwork.MockNode>
private lateinit var bobNode: StartedNode<MockNetwork.MockNode>
private lateinit var notary: Party
private lateinit var alice: Party
private lateinit var bob: Party
@Before
fun setup() {
mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts", "net.corda.finance.contracts.asset", "net.corda.core.flows"))
aliceNode = mockNet.createPartyNode(ALICE.name)
bobNode = mockNet.createPartyNode(BOB.name)
aliceNode = mockNet.createPartyNode(ALICE_NAME)
bobNode = mockNet.createPartyNode(BOB_NAME)
notary = mockNet.defaultNotaryIdentity
alice = aliceNode.info.singleIdentity()
bob = bobNode.info.singleIdentity()
// Process registration
mockNet.runNetwork()
}
@After
@ -58,11 +65,11 @@ class ContractUpgradeFlowTest {
@Test
fun `2 parties contract upgrade`() {
// Create dummy contract.
val twoPartyDummyContract = DummyContract.generateInitial(0, notary, aliceNode.info.chooseIdentity().ref(1), bobNode.info.chooseIdentity().ref(1))
val twoPartyDummyContract = DummyContract.generateInitial(0, notary, alice.ref(1), bob.ref(1))
val signedByA = aliceNode.services.signInitialTransaction(twoPartyDummyContract)
val stx = bobNode.services.addSignature(signedByA)
aliceNode.services.startFlow(FinalityFlow(stx, setOf(bobNode.info.chooseIdentity())))
aliceNode.services.startFlow(FinalityFlow(stx, setOf(bob)))
mockNet.runNetwork()
val atx = aliceNode.database.transaction { aliceNode.services.validatedTransactions.getTransaction(stx.id) }
@ -128,7 +135,7 @@ class ContractUpgradeFlowTest {
fun `2 parties contract upgrade using RPC`() {
rpcDriver(initialiseSerialization = false) {
// Create dummy contract.
val twoPartyDummyContract = DummyContract.generateInitial(0, notary, aliceNode.info.chooseIdentity().ref(1), bobNode.info.chooseIdentity().ref(1))
val twoPartyDummyContract = DummyContract.generateInitial(0, notary, alice.ref(1), bob.ref(1))
val signedByA = aliceNode.services.signInitialTransaction(twoPartyDummyContract)
val stx = bobNode.services.addSignature(signedByA)
@ -140,7 +147,7 @@ class ContractUpgradeFlowTest {
))
val rpcA = startProxy(aliceNode, user)
val rpcB = startProxy(bobNode, user)
val handle = rpcA.startFlow(::FinalityInvoker, stx, setOf(bobNode.info.chooseIdentity()))
val handle = rpcA.startFlow(::FinalityInvoker, stx, setOf(bob))
mockNet.runNetwork()
handle.returnValue.getOrThrow()
@ -202,7 +209,7 @@ class ContractUpgradeFlowTest {
@Test
fun `upgrade Cash to v2`() {
// Create some cash.
val chosenIdentity = aliceNode.info.chooseIdentity()
val chosenIdentity = alice
val result = aliceNode.services.startFlow(CashIssueFlow(Amount(1000, USD), OpaqueBytes.of(1), notary)).resultFuture
mockNet.runNetwork()
val stx = result.getOrThrow().stx

View File

@ -5,46 +5,45 @@ import net.corda.core.identity.Party
import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap
import net.corda.testing.chooseIdentity
import net.corda.testing.node.network
import net.corda.testing.node.MockNetwork
import net.corda.testing.singleIdentity
import net.corda.testing.startFlow
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Test
class ReceiveMultipleFlowTests {
private val mockNet = MockNetwork()
private val nodes = (0..2).map { mockNet.createPartyNode() }
@After
fun stopNodes() {
mockNet.stopNodes()
}
@Test
fun `receive all messages in parallel using map style`() {
network(3) { nodes ->
val doubleValue = 5.0
nodes[1].registerAnswer(AlgorithmDefinition::class, doubleValue)
val stringValue = "Thriller"
nodes[2].registerAnswer(AlgorithmDefinition::class, stringValue)
val flow = nodes[0].services.startFlow(ParallelAlgorithmMap(nodes[1].info.chooseIdentity(), nodes[2].info.chooseIdentity()))
runNetwork()
val result = flow.resultFuture.getOrThrow()
assertThat(result).isEqualTo(doubleValue * stringValue.length)
}
val doubleValue = 5.0
nodes[1].registerAnswer(AlgorithmDefinition::class, doubleValue)
val stringValue = "Thriller"
nodes[2].registerAnswer(AlgorithmDefinition::class, stringValue)
val flow = nodes[0].services.startFlow(ParallelAlgorithmMap(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity()))
mockNet.runNetwork()
val result = flow.resultFuture.getOrThrow()
assertThat(result).isEqualTo(doubleValue * stringValue.length)
}
@Test
fun `receive all messages in parallel using list style`() {
network(3) { nodes ->
val value1 = 5.0
nodes[1].registerAnswer(ParallelAlgorithmList::class, value1)
val value2 = 6.0
nodes[2].registerAnswer(ParallelAlgorithmList::class, value2)
val flow = nodes[0].services.startFlow(ParallelAlgorithmList(nodes[1].info.chooseIdentity(), nodes[2].info.chooseIdentity()))
runNetwork()
val data = flow.resultFuture.getOrThrow()
assertThat(data[0]).isEqualTo(value1)
assertThat(data[1]).isEqualTo(value2)
assertThat(data.fold(1.0) { a, b -> a * b }).isEqualTo(value1 * value2)
}
val value1 = 5.0
nodes[1].registerAnswer(ParallelAlgorithmList::class, value1)
val value2 = 6.0
nodes[2].registerAnswer(ParallelAlgorithmList::class, value2)
val flow = nodes[0].services.startFlow(ParallelAlgorithmList(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity()))
mockNet.runNetwork()
val data = flow.resultFuture.getOrThrow()
assertThat(data[0]).isEqualTo(value1)
assertThat(data[1]).isEqualTo(value2)
assertThat(data.fold(1.0) { a, b -> a * b }).isEqualTo(value1 * value2)
}
class ParallelAlgorithmMap(doubleMember: Party, stringMember: Party) : AlgorithmDefinition(doubleMember, stringMember) {

View File

@ -10,9 +10,9 @@ import net.corda.core.utilities.sequence
import net.corda.node.internal.StartedNode
import net.corda.testing.MEGA_CORP
import net.corda.testing.MINI_CORP
import net.corda.testing.chooseIdentity
import net.corda.testing.contracts.DummyContract
import net.corda.testing.node.MockNetwork
import net.corda.testing.singleIdentity
import net.corda.testing.startFlow
import org.junit.After
import org.junit.Before
@ -45,8 +45,8 @@ class ResolveTransactionsFlowTest {
megaCorpNode.internals.registerInitiatedFlow(TestResponseFlow::class.java)
miniCorpNode.internals.registerInitiatedFlow(TestResponseFlow::class.java)
notary = mockNet.defaultNotaryIdentity
megaCorp = megaCorpNode.info.chooseIdentity()
miniCorp = miniCorpNode.info.chooseIdentity()
megaCorp = megaCorpNode.info.singleIdentity()
miniCorp = miniCorpNode.info.singleIdentity()
}
@After

View File

@ -7,7 +7,10 @@ import com.nhaarman.mockito_kotlin.verifyNoMoreInteractions
import net.corda.core.internal.concurrent.fork
import net.corda.core.utilities.getOrThrow
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestRule
import org.junit.runners.model.Statement
import org.slf4j.Logger
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
@ -28,9 +31,34 @@ private fun <T> withSingleThreadExecutor(callable: ExecutorService.() -> T) = Ex
}
class ToggleFieldTest {
companion object {
@Suppress("JAVA_CLASS_ON_COMPANION")
private val companionName = javaClass.name
private fun <T> globalThreadCreationMethod(task: () -> T) = task()
}
private val log = mock<Logger>()
@Rule
@JvmField
val verifyNoMoreInteractions = TestRule { base, _ ->
object : Statement() {
override fun evaluate() {
base.evaluate()
verifyNoMoreInteractions(log) // Only on success.
}
}
}
private fun <T> inheritableThreadLocalToggleField() = InheritableThreadLocalToggleField<T>("inheritable", log) { stack ->
stack.fold(false) { isAGlobalThreadBeingCreated, e ->
isAGlobalThreadBeingCreated || (e.className == companionName && e.methodName == "globalThreadCreationMethod")
}
}
@Test
fun `toggle is enforced`() {
listOf(SimpleToggleField<String>("simple"), ThreadLocalToggleField<String>("local"), InheritableThreadLocalToggleField("inheritable")).forEach { field ->
listOf(SimpleToggleField<String>("simple"), ThreadLocalToggleField<String>("local"), inheritableThreadLocalToggleField()).forEach { field ->
assertNull(field.get())
assertThatThrownBy { field.set(null) }.isInstanceOf(IllegalStateException::class.java)
field.set("hello")
@ -71,7 +99,7 @@ class ToggleFieldTest {
@Test
fun `inheritable thread local works`() {
val field = InheritableThreadLocalToggleField<String>("field")
val field = inheritableThreadLocalToggleField<String>()
assertNull(field.get())
field.set("hello")
assertEquals("hello", field.get())
@ -84,7 +112,7 @@ class ToggleFieldTest {
@Test
fun `existing threads do not inherit`() {
val field = InheritableThreadLocalToggleField<String>("field")
val field = inheritableThreadLocalToggleField<String>()
withSingleThreadExecutor {
field.set("hello")
assertEquals("hello", field.get())
@ -93,16 +121,8 @@ class ToggleFieldTest {
}
@Test
fun `with default exception handler, inherited values are poisoned on clear`() {
`inherited values are poisoned on clear`(InheritableThreadLocalToggleField("field") { throw it })
}
@Test
fun `with lenient exception handler, inherited values are poisoned on clear`() {
`inherited values are poisoned on clear`(InheritableThreadLocalToggleField("field") {})
}
private fun `inherited values are poisoned on clear`(field: InheritableThreadLocalToggleField<String>) {
fun `inherited values are poisoned on clear`() {
val field = inheritableThreadLocalToggleField<String>()
field.set("hello")
withSingleThreadExecutor {
assertEquals("hello", fork(field::get).getOrThrow())
@ -121,39 +141,70 @@ class ToggleFieldTest {
}
}
/** We log an error rather than failing-fast as the new thread may be an undetected global. */
@Test
fun `with default exception handler, leaked thread is detected as soon as it tries to create another`() {
val field = InheritableThreadLocalToggleField<String>("field") { throw it }
fun `leaked thread propagates holder to non-global thread, with error`() {
val field = inheritableThreadLocalToggleField<String>()
field.set("hello")
withSingleThreadExecutor {
assertEquals("hello", fork(field::get).getOrThrow())
field.set(null) // The executor thread is now considered leaked.
val threadName = fork { Thread.currentThread().name }.getOrThrow()
val future = fork(::Thread)
assertThatThrownBy { future.getOrThrow() }
.isInstanceOf(ThreadLeakException::class.java)
.hasMessageContaining(threadName)
fork {
val leakedThreadName = Thread.currentThread().name
verifyNoMoreInteractions(log)
withSingleThreadExecutor {
// If ThreadLeakException is seen in practice, these errors form a trail of where the holder has been:
verify(log).error(argThat { contains(leakedThreadName) })
val newThreadName = fork { Thread.currentThread().name }.getOrThrow()
val future = fork(field::get)
assertThatThrownBy { future.getOrThrow() }
.isInstanceOf(ThreadLeakException::class.java)
.hasMessageContaining(newThreadName)
fork {
verifyNoMoreInteractions(log)
withSingleThreadExecutor {
verify(log).error(argThat { contains(newThreadName) })
}
}.getOrThrow()
}
}.getOrThrow()
}
}
@Test
fun `with lenient exception handler, leaked thread logs a warning and does not propagate the holder`() {
val log = mock<Logger>()
val field = InheritableThreadLocalToggleField<String>("field", log) {}
fun `leaked thread does not propagate holder to global thread, with warning`() {
val field = inheritableThreadLocalToggleField<String>()
field.set("hello")
withSingleThreadExecutor {
assertEquals("hello", fork(field::get).getOrThrow())
field.set(null) // The executor thread is now considered leaked.
val threadName = fork { Thread.currentThread().name }.getOrThrow()
fork {
verifyNoMoreInteractions(log)
withSingleThreadExecutor {
verify(log).warn(argThat { contains(threadName) })
// In practice the new thread is for example a static thread we can't get rid of:
assertNull(fork(field::get).getOrThrow())
val leakedThreadName = Thread.currentThread().name
globalThreadCreationMethod {
verifyNoMoreInteractions(log)
withSingleThreadExecutor {
verify(log).warn(argThat { contains(leakedThreadName) })
// In practice the new thread is for example a static thread we can't get rid of:
assertNull(fork(field::get).getOrThrow())
}
}
}.getOrThrow()
}
}
@Test
fun `non-leaked thread does not propagate holder to global thread, without warning`() {
val field = inheritableThreadLocalToggleField<String>()
field.set("hello")
withSingleThreadExecutor {
fork {
assertEquals("hello", field.get())
globalThreadCreationMethod {
withSingleThreadExecutor {
assertNull(fork(field::get).getOrThrow())
}
}
}.getOrThrow()
}
verifyNoMoreInteractions(log)
}
}

View File

@ -7,6 +7,7 @@ import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.TestDataVendingFlow
import net.corda.core.identity.Party
import net.corda.core.internal.FetchAttachmentsFlow
import net.corda.core.internal.FetchDataFlow
import net.corda.core.utilities.getOrThrow
@ -15,9 +16,11 @@ import net.corda.node.internal.InitiatedFlowFactory
import net.corda.node.internal.StartedNode
import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.node.utilities.currentDBSession
import net.corda.testing.chooseIdentity
import net.corda.testing.ALICE_NAME
import net.corda.testing.BOB_NAME
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNodeParameters
import net.corda.testing.singleIdentity
import net.corda.testing.startFlow
import org.junit.After
import org.junit.Before
@ -63,13 +66,16 @@ class AttachmentSerializationTest {
private lateinit var mockNet: MockNetwork
private lateinit var server: StartedNode<MockNetwork.MockNode>
private lateinit var client: StartedNode<MockNetwork.MockNode>
private lateinit var serverIdentity: Party
@Before
fun setUp() {
mockNet = MockNetwork()
server = mockNet.createNode()
client = mockNet.createNode()
server = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME))
client = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME))
client.internals.disableDBCloseOnStop() // Otherwise the in-memory database may disappear (taking the checkpoint with it) while we reboot the client.
mockNet.runNetwork()
serverIdentity = server.info.singleIdentity()
}
@After
@ -91,9 +97,7 @@ class AttachmentSerializationTest {
private class ClientResult(internal val attachmentContent: String)
@InitiatingFlow
private abstract class ClientLogic(server: StartedNode<*>) : FlowLogic<ClientResult>() {
internal val server = server.info.chooseIdentity()
private abstract class ClientLogic(val serverIdentity: Party) : FlowLogic<ClientResult>() {
@Suspendable
internal fun communicate(serverSession: FlowSession) {
serverSession.sendAndReceive<String>("ping one").unwrap { assertEquals("pong", it) }
@ -112,30 +116,30 @@ class AttachmentSerializationTest {
override val signers get() = throw UnsupportedOperationException()
}
private class CustomAttachmentLogic(server: StartedNode<*>, private val attachmentId: SecureHash, private val customContent: String) : ClientLogic(server) {
private class CustomAttachmentLogic(serverIdentity: Party, private val attachmentId: SecureHash, private val customContent: String) : ClientLogic(serverIdentity) {
@Suspendable
override fun getAttachmentContent(): String {
val customAttachment = CustomAttachment(attachmentId, customContent)
val session = initiateFlow(server)
val session = initiateFlow(serverIdentity)
communicate(session)
return customAttachment.customContent
}
}
private class OpenAttachmentLogic(server: StartedNode<*>, private val attachmentId: SecureHash) : ClientLogic(server) {
private class OpenAttachmentLogic(serverIdentity: Party, private val attachmentId: SecureHash) : ClientLogic(serverIdentity) {
@Suspendable
override fun getAttachmentContent(): String {
val localAttachment = serviceHub.attachments.openAttachment(attachmentId)!!
val session = initiateFlow(server)
val session = initiateFlow(serverIdentity)
communicate(session)
return localAttachment.extractContent()
}
}
private class FetchAttachmentLogic(server: StartedNode<*>, private val attachmentId: SecureHash) : ClientLogic(server) {
private class FetchAttachmentLogic(serverIdentity: Party, private val attachmentId: SecureHash) : ClientLogic(serverIdentity) {
@Suspendable
override fun getAttachmentContent(): String {
val serverSession = initiateFlow(server)
val serverSession = initiateFlow(serverIdentity)
val (downloadedAttachment) = subFlow(FetchAttachmentsFlow(setOf(attachmentId), serverSession)).downloaded
serverSession.send(FetchDataFlow.Request.End)
communicate(serverSession)
@ -166,14 +170,14 @@ class AttachmentSerializationTest {
@Test
fun `custom (and non-persisted) attachment should be saved in checkpoint`() {
val attachmentId = SecureHash.sha256("any old data")
launchFlow(CustomAttachmentLogic(server, attachmentId, "custom"), 1)
launchFlow(CustomAttachmentLogic(serverIdentity, attachmentId, "custom"), 1)
assertEquals("custom", rebootClientAndGetAttachmentContent())
}
@Test
fun `custom attachment should be saved in checkpoint even if its data was persisted`() {
val attachmentId = client.saveAttachment("genuine")
launchFlow(CustomAttachmentLogic(server, attachmentId, "custom"), 1)
launchFlow(CustomAttachmentLogic(serverIdentity, attachmentId, "custom"), 1)
client.hackAttachment(attachmentId, "hacked") // Should not be reloaded, checkAttachmentsOnLoad may cause next line to blow up if client attempts it.
assertEquals("custom", rebootClientAndGetAttachmentContent())
}
@ -182,7 +186,7 @@ class AttachmentSerializationTest {
fun `only the hash of a regular attachment should be saved in checkpoint`() {
val attachmentId = client.saveAttachment("genuine")
client.attachments.checkAttachmentsOnLoad = false // Cached by AttachmentImpl.
launchFlow(OpenAttachmentLogic(server, attachmentId), 1)
launchFlow(OpenAttachmentLogic(serverIdentity, attachmentId), 1)
client.hackAttachment(attachmentId, "hacked")
assertEquals("hacked", rebootClientAndGetAttachmentContent(false)) // Pass in false to allow non-genuine data to be loaded.
}
@ -190,7 +194,7 @@ class AttachmentSerializationTest {
@Test
fun `only the hash of a FetchAttachmentsFlow attachment should be saved in checkpoint`() {
val attachmentId = server.saveAttachment("genuine")
launchFlow(FetchAttachmentLogic(server, attachmentId), 2, sendData = true)
launchFlow(FetchAttachmentLogic(serverIdentity, attachmentId), 2, sendData = true)
client.hackAttachment(attachmentId, "hacked")
assertEquals("hacked", rebootClientAndGetAttachmentContent(false))
}

View File

@ -2,9 +2,13 @@ package net.corda.core.transactions
import net.corda.core.contracts.*
import net.corda.core.identity.AbstractParty
import net.corda.testing.*
import net.corda.core.identity.Party
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.contracts.DummyContract
import net.corda.testing.dummyCommand
import net.corda.testing.node.MockServices
import net.corda.testing.singleIdentity
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@ -18,6 +22,7 @@ class LedgerTransactionQueryTests {
@JvmField
val testSerialization = SerializationEnvironmentRule()
private val services: MockServices = MockServices()
private val identity: Party = services.myInfo.singleIdentity()
@Before
fun setup() {
@ -66,8 +71,8 @@ class LedgerTransactionQueryTests {
tx.addInputState(makeDummyStateAndRef(i.toString()))
tx.addOutputState(makeDummyState(i), DummyContract.PROGRAM_ID)
tx.addOutputState(makeDummyState(i.toString()), DummyContract.PROGRAM_ID)
tx.addCommand(Commands.Cmd1(i), listOf(services.myInfo.chooseIdentity().owningKey))
tx.addCommand(Commands.Cmd2(i), listOf(services.myInfo.chooseIdentity().owningKey))
tx.addCommand(Commands.Cmd1(i), listOf(identity.owningKey))
tx.addCommand(Commands.Cmd2(i), listOf(identity.owningKey))
}
return tx.toLedgerTransaction(services)
}

View File

@ -5,6 +5,7 @@ import org.junit.Test
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertFails
import org.assertj.core.api.Assertions.*
class ProgressTrackerTest {
object SimpleSteps {
@ -24,13 +25,23 @@ class ProgressTrackerTest {
fun tracker() = ProgressTracker(AYY, BEE, SEA)
}
object BabySteps {
object UNOS : ProgressTracker.Step("unos")
object DOES : ProgressTracker.Step("does")
object TRES : ProgressTracker.Step("tres")
fun tracker() = ProgressTracker(UNOS, DOES, TRES)
}
lateinit var pt: ProgressTracker
lateinit var pt2: ProgressTracker
lateinit var pt3: ProgressTracker
@Before
fun before() {
pt = SimpleSteps.tracker()
pt2 = ChildSteps.tracker()
pt3 = BabySteps.tracker()
}
@Test
@ -81,6 +92,118 @@ class ProgressTrackerTest {
assertEquals(ChildSteps.BEE, pt2.nextStep())
}
@Test
fun `steps tree index counts children steps`() {
pt.setChildProgressTracker(SimpleSteps.TWO, pt2)
val allSteps = pt.allSteps
//capture notifications
val stepsIndexNotifications = LinkedList<Int>()
pt.stepsTreeIndexChanges.subscribe {
stepsIndexNotifications += it
}
val stepsTreeNotification = LinkedList<List<Pair<Int, String>>>()
pt.stepsTreeChanges.subscribe {
stepsTreeNotification += it
}
fun assertCurrentStepsTree(index:Int, step: ProgressTracker.Step) {
assertEquals(index, pt.stepsTreeIndex)
assertEquals(step, allSteps[pt.stepsTreeIndex].second)
}
//travel tree
pt.currentStep = SimpleSteps.ONE
assertCurrentStepsTree(0, SimpleSteps.ONE)
pt.currentStep = SimpleSteps.TWO
assertCurrentStepsTree(1, SimpleSteps.TWO)
pt2.currentStep = ChildSteps.BEE
assertCurrentStepsTree(3, ChildSteps.BEE)
pt.currentStep = SimpleSteps.THREE
assertCurrentStepsTree(5, SimpleSteps.THREE)
//assert no structure changes and proper steps propagation
assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(0, 1, 3, 5))
assertThat(stepsTreeNotification).isEmpty()
}
@Test
fun `structure changes are pushed down when progress trackers are added`() {
pt.setChildProgressTracker(SimpleSteps.TWO, pt2)
//capture notifications
val stepsIndexNotifications = LinkedList<Int>()
pt.stepsTreeIndexChanges.subscribe {
stepsIndexNotifications += it
}
//put current state as a first change for simplicity when asserting
val stepsTreeNotification = mutableListOf(pt.allStepsLabels)
println(pt.allStepsLabels)
pt.stepsTreeChanges.subscribe {
stepsTreeNotification += it
}
fun assertCurrentStepsTree(index:Int, step: ProgressTracker.Step) {
assertEquals(index, pt.stepsTreeIndex)
assertEquals(step.label, stepsTreeNotification.last()[pt.stepsTreeIndex].second)
}
pt.currentStep = SimpleSteps.TWO
assertCurrentStepsTree(1, SimpleSteps.TWO)
pt.currentStep = SimpleSteps.FOUR
assertCurrentStepsTree(6, SimpleSteps.FOUR)
pt.setChildProgressTracker(SimpleSteps.THREE, pt3)
assertCurrentStepsTree(9, SimpleSteps.FOUR)
//assert no structure changes and proper steps propagation
assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(1, 6, 9))
assertThat(stepsTreeNotification).hasSize(2) // 1 change + 1 our initial state
}
@Test
fun `structure changes are pushed down when progress trackers are removed`() {
pt.setChildProgressTracker(SimpleSteps.TWO, pt2)
//capture notifications
val stepsIndexNotifications = LinkedList<Int>()
pt.stepsTreeIndexChanges.subscribe {
stepsIndexNotifications += it
}
//put current state as a first change for simplicity when asserting
val stepsTreeNotification = mutableListOf(pt.allStepsLabels)
pt.stepsTreeChanges.subscribe {
stepsTreeNotification += it
}
fun assertCurrentStepsTree(index:Int, step: ProgressTracker.Step) {
assertEquals(index, pt.stepsTreeIndex)
assertEquals(step.label, stepsTreeNotification.last()[pt.stepsTreeIndex].second)
}
pt.currentStep = SimpleSteps.TWO
pt2.currentStep = ChildSteps.SEA
pt3.currentStep = BabySteps.UNOS
assertCurrentStepsTree(4, ChildSteps.SEA)
pt.setChildProgressTracker(SimpleSteps.TWO, pt3)
assertCurrentStepsTree(2, BabySteps.UNOS)
//assert no structure changes and proper steps propagation
assertThat(stepsIndexNotifications).containsExactlyElementsOf(listOf(1, 4, 2))
assertThat(stepsTreeNotification).hasSize(2) // 1 change + 1 our initial state
}
@Test
fun `can be rewound`() {
pt.setChildProgressTracker(SimpleSteps.TWO, pt2)

View File

@ -9,5 +9,6 @@
"https://docs.corda.net/releases/release-M13.0": "M13.0",
"https://docs.corda.net/releases/release-M14.0": "M14.0",
"https://docs.corda.net/releases/release-V1.0": "V1.0",
"https://docs.corda.net/releases/release-V2.0": "V2.0",
"https://docs.corda.net/head/": "Master"
}

View File

@ -6,11 +6,17 @@ from the previous milestone release.
UNRELEASED
----------
* ``AttachmentStorage`` now allows providing metadata on attachments upload - username and filename, currently as plain
strings. Those can be then used for querying, utilizing ``queryAttachments`` method of the same interface.
* ``SSH Server`` - The node can now expose shell via SSH server with proper authorization and permissioning built in.
* ``CordaRPCOps`` implementation now checks permissions for any function invocation, rather than just when starting flows.
* ``wellKnownPartyFromAnonymous()`` now always resolve the key to a ``Party``, then the party to the well known party.
Previously if it was passed a ``Party`` it would use its name as-is without verifying the key matched that name.
* ``OpaqueBytes.bytes`` now returns a clone of its underlying ``ByteArray``, and has been redeclared as ``final``.
This is a minor change to the public API, but is required to ensure that classes like ``SecureHash`` are immutable.

View File

@ -152,3 +152,8 @@ path to the node's base directory.
:jarDirs: An optional list of file system directories containing JARs to include in the classpath when launching via ``corda.jar`` only.
Each should be a string. Only the JARs in the directories are added, not the directories themselves. This is useful
for including JDBC drivers and the like. e.g. ``jarDirs = [ 'lib' ]``
:sshd: If provided, node will start internal SSH server which will provide a management shell. It uses the same credentials
and permissions as RPC subsystem. It has one required parameter.
:port: - the port to start SSH server on

View File

@ -9,10 +9,7 @@ import net.corda.core.node.services.CordaService
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.unwrap
import net.corda.core.utilities.*
import net.corda.finance.flows.AbstractCashFlow
import net.corda.finance.flows.CashException
import net.corda.finance.flows.CashIssueFlow
@ -25,7 +22,7 @@ object CustomVaultQuery {
@CordaService
class Service(val services: AppServiceHub) : SingletonSerializeAsToken() {
private companion object {
val log = loggerFor<Service>()
private val log = contextLogger()
}
fun rebalanceCurrencyReserves(): List<Amount<Currency>> {
val nativeQuery = """

View File

@ -33,15 +33,16 @@ service.
rpcPort 10006
webPort 10007
cordapps = ["net.corda:corda-finance:$corda_release_version"]
rpcUsers = [[ user: "user1", "password": "test", "permissions": []]]
rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL]]]
}
node {
name "O=PartyB,L=New York,C=US"
p2pPort 10008
rpcPort 10009
webPort 10010
sshdPort 10024
cordapps = ["net.corda:corda-finance:$corda_release_version"]
rpcUsers = [[ user: "user1", "password": "test", "permissions": []]]
rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]]
}
}
@ -101,6 +102,9 @@ node via its built-in CRaSH shell.
Go to the terminal window displaying the CRaSH shell of PartyA. Typing ``help`` will display a list of the available
commands.
.. note:: Local terminal shell is available only in a development mode. In production environment SSH server can be enabled.
More about SSH and how to connect can be found on :doc:`Shell` page.
We want to create an IOU of 100 with PartyB. We start the ``IOUFlow`` by typing:
.. code:: bash

View File

@ -53,6 +53,11 @@ reserve the right to move and rename it as it's not part of the public API as ye
logging name construction. If you can't find what you need to refer to, use the ``--logging-level`` option as above and
then determine the logging module name from the console output.
SSH access
----------
Node can be configured to run SSH server. See :doc:`shell` for details.
Database access
---------------

View File

@ -6,8 +6,24 @@ Here are release notes for each snapshot release from M9 onwards.
Unreleased
----------
Support for observer/regulator nodes has returned. Read :doc:`tutorial-observer-nodes` to learn more or examine the
interest rate swaps demo.
Release 2.0
----------
Following quickly on the heels of the release of Corda 1.0, Corda version 2.0 consolidates
a number of security updates for our dependent libraries alongside the reintroduction of the Observer node functionality.
This was absent from version 1 but based on user feedback its re-introduction removes the need for complicated "isRelevant()" checks.
In addition the fix for a small bug present in the coin selection code of V1.0 is integrated from master.
* **Version Bump**
Due to the introduction of new APIs, Corda 2.0 has a platform version of 2. This will be advertised in the network map structures
and via the versioning APIs.
* **Observer Nodes**
Adds the facility for transparent forwarding of transactions to some third party observer, such as a regulator. By having
that entity simply run an Observer node they can simply recieve a stream of digitally signed, de-duplicated reports that
can be used for reporting.
Release 1.0
-----------

View File

@ -18,11 +18,47 @@ Some of its features include:
* View JMX metrics and monitoring exports.
* UNIX style pipes for both text and objects, an ``egrep`` command and a command for working with columnular data.
.. note:: A future version of Corda will add SSH access to the node.
It is based on the popular `CRaSH`_ shell used in various other projects and supports many of the same features.
The shell may be disabled by passing the ``--no-local-shell`` flag to the node.
Local terminal shell runs only in development mode. It may be disabled by passing the ``--no-local-shell`` flag to the node.
SSH server
----------
Shell can also be accessible via SSH. By default SSH server is *disabled*. To enable it port must be configured - in ``node.conf`` file
.. code:: bash
sshd {
port = 2222
}
Authentication and authorization
--------------------------------
SSH require user to login first - using the same users as RPC system. In fact, shell serves as a proxy to RPC and communicates
with node using RPC calls. This also means that RPC permissions are enforced. No permissions are required to allow the connection
and login in.
Watching flows (``flow watch``) requires ``InvokeRpc.stateMachinesFeed`` while starting flows requires
``InvokeRpc.startTrackedFlowDynamic`` and ``InvokeRpc.registeredFlows`` in addition to a permission for a particular flow.
Host key
--------
The host key is loaded from ``sshkey/hostkey.pem`` file. If the file does not exist, it will be generated randomly, however
in the development mode seed may be tuned to give the same results on the same computer - in order to avoid host checking
errors.
Connecting
----------
Linux and MacOS computers usually come with SSH client preinstalled. On Windows it usually require extra download.
Usual connection syntax is ``ssh user@host -p 2222`` - where ``user`` is a RPC username, and ``-p`` specifies a port parameters -
it's the same as setup in ``node.conf`` file. ``host`` should point to a node hostname, usually ``localhost`` if connecting and
running node on the same computer. Password will be asked after establishing connection.
:note: While developing, checking multiple samples or simply restarting a node frequently host key may be regenerated. SSH usually
saved once trusted hosts and will refuse to connect in case of a change. Then check may be disabled with extra options
``ssh -o StrictHostKeyChecking=no user@host -p2222``. This option should never be used in production environment!
Getting help
------------

View File

@ -4,7 +4,7 @@ import net.corda.core.contracts.*
import net.corda.core.contracts.Amount.Companion.sumOrThrow
import net.corda.core.identity.AbstractParty
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.trace
import java.security.PublicKey
import java.util.*
@ -32,8 +32,7 @@ data class PartyAndAmount<T : Any>(val party: AbstractParty, val amount: Amount<
*/
abstract class OnLedgerAsset<T : Any, C : CommandData, S : FungibleAsset<T>> : Contract {
companion object {
val log = loggerFor<OnLedgerAsset<*, *, *>>()
private val log = contextLogger()
/**
* Generate a transaction that moves an amount of currency to the given pubkey.
*

View File

@ -4,15 +4,12 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Amount
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TransactionState
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.StatesNotAvailableException
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.deserialize
import net.corda.core.utilities.*
import net.corda.finance.contracts.asset.Cash
import java.sql.*
@ -44,7 +41,7 @@ abstract class AbstractCashSelection {
}.invoke()
}
val log = loggerFor<AbstractCashSelection>()
private val log = contextLogger()
}
// coin selection retry loop counter, sleep (msecs) and lock for selecting states

View File

@ -4,17 +4,18 @@ import net.corda.core.contracts.Amount
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.utilities.*
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import java.sql.Connection
import java.sql.DatabaseMetaData
import java.sql.ResultSet
import java.util.*
class CashSelectionH2Impl : AbstractCashSelection() {
companion object {
const val JDBC_DRIVER_NAME = "H2 JDBC Driver"
val log = loggerFor<CashSelectionH2Impl>()
private val log = contextLogger()
}
override fun isCompatible(metadata: DatabaseMetaData): Boolean {
@ -23,7 +24,6 @@ class CashSelectionH2Impl : AbstractCashSelection() {
override fun toString() = "${this::class.java} for $JDBC_DRIVER_NAME"
// We are using an H2 specific means of selecting a minimum set of rows that match a request amount of coins:
// 1) There is no standard SQL mechanism of calculating a cumulative total on a field and restricting row selection on the
// running total of such an accumulator
@ -32,7 +32,7 @@ class CashSelectionH2Impl : AbstractCashSelection() {
// 3) H2 does not support JOIN's in FOR UPDATE (hence we are forced to execute 2 queries)
override fun executeQuery(connection: Connection, amount: Amount<Currency>, lockId: UUID, notary: Party?,
onlyFromIssuerParties: Set<AbstractParty>, withIssuerRefs: Set<OpaqueBytes>) : ResultSet {
connection.createStatement().execute("CALL SET(@t, 0);")
connection.createStatement().execute("CALL SET(@t, CAST(0 AS BIGINT));")
val selectJoin = """
SELECT vs.transaction_id, vs.output_index, ccs.pennies, SET(@t, ifnull(@t,0)+ccs.pennies) total_pennies, vs.lock_id
@ -60,7 +60,7 @@ class CashSelectionH2Impl : AbstractCashSelection() {
if (onlyFromIssuerParties.isNotEmpty())
psSelectJoin.setObject(++pIndex, onlyFromIssuerParties.map { it.owningKey.toStringShort() as Any}.toTypedArray() )
if (withIssuerRefs.isNotEmpty())
psSelectJoin.setObject(++pIndex, withIssuerRefs.map { it.bytes.toHexString() as Any }.toTypedArray())
psSelectJoin.setObject(++pIndex, withIssuerRefs.map { it.bytes as Any }.toTypedArray())
log.debug { psSelectJoin.toString() }
return psSelectJoin.executeQuery()

View File

@ -3,10 +3,7 @@ package net.corda.finance.contracts.asset.cash.selection
import net.corda.core.contracts.Amount
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.debug
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.toBase58String
import net.corda.core.utilities.*
import java.sql.Connection
import java.sql.DatabaseMetaData
import java.sql.ResultSet
@ -16,7 +13,7 @@ class CashSelectionPostgreSQLImpl : AbstractCashSelection() {
companion object {
val JDBC_DRIVER_NAME = "PostgreSQL JDBC Driver"
val log = loggerFor<CashSelectionPostgreSQLImpl>()
private val log = contextLogger()
}
override fun isCompatible(metadata: DatabaseMetaData): Boolean {
@ -79,5 +76,4 @@ class CashSelectionPostgreSQLImpl : AbstractCashSelection() {
return statement.executeQuery()
}
}

View File

@ -27,7 +27,7 @@ class CashIssueAndPaymentFlow(val amount: Amount<Currency>,
val anonymous: Boolean,
val notary: Party,
progressTracker: ProgressTracker) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) {
constructor(amount: Amount<Currency>,
constructor(amount: Amount<Currency>,
issueRef: OpaqueBytes,
recipient: Party,
anonymous: Boolean,

View File

@ -5,6 +5,8 @@ import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
import net.corda.core.contracts.MAX_ISSUER_REF_SIZE
import org.hibernate.annotations.Type
import javax.persistence.*
/**
@ -36,7 +38,8 @@ object CashSchemaV1 : MappedSchema(schemaFamily = CashSchema.javaClass, version
@Column(name = "issuer_key_hash", length = MAX_HASH_HEX_SIZE)
var issuerPartyHash: String,
@Column(name = "issuer_ref")
@Column(name = "issuer_ref", length = MAX_ISSUER_REF_SIZE)
@Type(type = "corda-wrapper-binary")
var issuerRef: ByteArray
) : PersistentState()
}

View File

@ -1,9 +1,11 @@
package net.corda.finance.schemas
import net.corda.core.contracts.MAX_ISSUER_REF_SIZE
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
import org.hibernate.annotations.Type
import java.time.Instant
import javax.persistence.Column
import javax.persistence.Entity
@ -48,7 +50,8 @@ object CommercialPaperSchemaV1 : MappedSchema(schemaFamily = CommercialPaperSche
@Column(name = "face_value_issuer_key_hash", length = MAX_HASH_HEX_SIZE)
var faceValueIssuerPartyHash: String,
@Column(name = "face_value_issuer_ref")
@Column(name = "face_value_issuer_ref", length = MAX_ISSUER_REF_SIZE)
@Type(type = "corda-wrapper-binary")
var faceValueIssuerRef: ByteArray
) : PersistentState()
}

View File

@ -54,8 +54,7 @@ class CashTests {
lateinit var database: CordaPersistence
private lateinit var vaultStatesUnconsumed: List<StateAndRef<Cash.State>>
private lateinit var OUR_IDENTITY_1: AbstractParty
private lateinit var OUR_IDENTITY_AND_CERT: PartyAndCertificate
private lateinit var ourIdentity: AbstractParty
private lateinit var miniCorpAnonymised: AnonymousParty
private val CHARLIE_ANONYMISED = CHARLIE_IDENTITY.party.anonymise()
@ -65,28 +64,33 @@ class CashTests {
fun setUp() {
LogHelper.setLevel(NodeVaultService::class)
megaCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), MEGA_CORP.name, MEGA_CORP_KEY)
miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), MINI_CORP.name, MINI_CORP_KEY)
val notaryServices = MockServices(listOf("net.corda.finance.contracts.asset"), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
val databaseAndServices = makeTestDatabaseAndMockServices(
cordappPackages = listOf("net.corda.finance.contracts.asset"),
initialIdentityName = CordaX500Name(organisation = "Me", locality = "London", country = "GB"),
keys = listOf(generateKeyPair()))
database = databaseAndServices.first
miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), MINI_CORP.name, MINI_CORP_KEY)
ourServices = databaseAndServices.second
OUR_IDENTITY_AND_CERT = ourServices.myInfo.singleIdentityAndCert()
OUR_IDENTITY_1 = ourServices.myInfo.singleIdentity()
// Set up and register identities
ourIdentity = ourServices.myInfo.singleIdentity()
miniCorpAnonymised = miniCorpServices.myInfo.singleIdentityAndCert().party.anonymise()
(miniCorpServices.myInfo.legalIdentitiesAndCerts + megaCorpServices.myInfo.legalIdentitiesAndCerts + notaryServices.myInfo.legalIdentitiesAndCerts).forEach { identity ->
ourServices.identityService.verifyAndRegisterIdentity(identity)
}
// Create some cash. Any attempt to spend >$500 will require multiple issuers to be involved.
database.transaction {
database.transaction {
ourServices.fillWithSomeTestCash(howMuch = 100.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1,
ownedBy = OUR_IDENTITY_1, issuedBy = MEGA_CORP.ref(1), issuerServices = megaCorpServices)
owner = ourIdentity, issuedBy = MEGA_CORP.ref(1), issuerServices = megaCorpServices)
ourServices.fillWithSomeTestCash(howMuch = 400.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1,
ownedBy = OUR_IDENTITY_1, issuedBy = MEGA_CORP.ref(1), issuerServices = megaCorpServices)
owner = ourIdentity, issuedBy = MEGA_CORP.ref(1), issuerServices = megaCorpServices)
ourServices.fillWithSomeTestCash(howMuch = 80.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1,
ownedBy = OUR_IDENTITY_1, issuedBy = MINI_CORP.ref(1), issuerServices = miniCorpServices)
owner = ourIdentity, issuedBy = MINI_CORP.ref(1), issuerServices = miniCorpServices)
ourServices.fillWithSomeTestCash(howMuch = 80.SWISS_FRANCS, atLeastThisManyStates = 1, atMostThisManyStates = 1,
ownedBy = OUR_IDENTITY_1, issuedBy = MINI_CORP.ref(1), issuerServices = miniCorpServices)
owner = ourIdentity, issuedBy = MINI_CORP.ref(1), issuerServices = miniCorpServices)
}
miniCorpAnonymised = miniCorpServices.myInfo.singleIdentityAndCert().party.anonymise()
database.transaction {
vaultStatesUnconsumed = ourServices.vaultService.queryBy<Cash.State>().states
}
@ -495,7 +499,7 @@ class CashTests {
private fun makeCash(amount: Amount<Currency>, issuer: AbstractParty, depositRef: Byte = 1) =
StateAndRef(
TransactionState(Cash.State(amount `issued by` issuer.ref(depositRef), OUR_IDENTITY_1), Cash.PROGRAM_ID, DUMMY_NOTARY),
TransactionState(Cash.State(amount `issued by` issuer.ref(depositRef), ourIdentity), Cash.PROGRAM_ID, DUMMY_NOTARY),
StateRef(SecureHash.randomSHA256(), Random().nextInt(32))
)
@ -509,12 +513,14 @@ class CashTests {
return tx.toWireTransaction(serviceHub)
}
private fun makeSpend(amount: Amount<Currency>, dest: AbstractParty): WireTransaction {
private fun makeSpend(services: ServiceHub, amount: Amount<Currency>, dest: AbstractParty): WireTransaction {
val ourIdentity = services.myInfo.singleIdentityAndCert()
val changeIdentity = services.keyManagementService.freshKeyAndCert(ourIdentity, false)
val tx = TransactionBuilder(DUMMY_NOTARY)
database.transaction {
Cash.generateSpend(ourServices, tx, amount, OUR_IDENTITY_AND_CERT, dest)
Cash.generateSpend(services, tx, amount, changeIdentity, dest)
}
return tx.toWireTransaction(miniCorpServices)
return tx.toWireTransaction(services)
}
/**
@ -588,7 +594,7 @@ class CashTests {
fun generateExitWithEmptyVault() {
assertFailsWith<IllegalArgumentException> {
val tx = TransactionBuilder(DUMMY_NOTARY)
Cash().generateExit(tx, Amount(100, Issued(CHARLIE.ref(1), GBP)), emptyList(), OUR_IDENTITY_1)
Cash().generateExit(tx, Amount(100, Issued(CHARLIE.ref(1), GBP)), emptyList(), ourIdentity)
}
}
@ -596,22 +602,23 @@ class CashTests {
fun generateSimpleDirectSpend() {
val wtx =
database.transaction {
makeSpend(100.DOLLARS, miniCorpAnonymised)
makeSpend(ourServices, 100.DOLLARS, miniCorpAnonymised)
}
database.transaction {
val vaultState = vaultStatesUnconsumed.elementAt(0)
assertEquals(vaultState.ref, wtx.inputs[0])
assertEquals(vaultState.state.data.copy(owner = miniCorpAnonymised), wtx.getOutput(0))
assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
assertEquals(ourIdentity.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
}
}
@Test
fun generateSimpleSpendWithParties() {
val changeIdentity = ourServices.keyManagementService.freshKeyAndCert(ourServices.myInfo.singleIdentityAndCert(), false)
database.transaction {
val tx = TransactionBuilder(DUMMY_NOTARY)
Cash.generateSpend(ourServices, tx, 80.DOLLARS, OUR_IDENTITY_AND_CERT, ALICE, setOf(MINI_CORP))
Cash.generateSpend(ourServices, tx, 80.DOLLARS, changeIdentity, ALICE, setOf(MINI_CORP))
assertEquals(vaultStatesUnconsumed.elementAt(2).ref, tx.inputStates()[0])
}
@ -621,7 +628,7 @@ class CashTests {
fun generateSimpleSpendWithChange() {
val wtx =
database.transaction {
makeSpend(10.DOLLARS, miniCorpAnonymised)
makeSpend(ourServices, 10.DOLLARS, miniCorpAnonymised)
}
database.transaction {
val vaultState = vaultStatesUnconsumed.elementAt(0)
@ -638,7 +645,7 @@ class CashTests {
assertEquals(vaultState.ref, wtx.inputs[0])
assertEquals(vaultState.state.data.copy(owner = miniCorpAnonymised, amount = 10.DOLLARS `issued by` defaultIssuer), wtx.outputs[0].data)
assertEquals(vaultState.state.data.copy(amount = changeAmount, owner = changeOwner), wtx.outputs[1].data)
assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
assertEquals(ourIdentity.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
}
}
@ -646,7 +653,7 @@ class CashTests {
fun generateSpendWithTwoInputs() {
val wtx =
database.transaction {
makeSpend(500.DOLLARS, miniCorpAnonymised)
makeSpend(ourServices, 500.DOLLARS, miniCorpAnonymised)
}
database.transaction {
val vaultState0 = vaultStatesUnconsumed.elementAt(0)
@ -654,7 +661,7 @@ class CashTests {
assertEquals(vaultState0.ref, wtx.inputs[0])
assertEquals(vaultState1.ref, wtx.inputs[1])
assertEquals(vaultState0.state.data.copy(owner = miniCorpAnonymised, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.getOutput(0))
assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
assertEquals(ourIdentity.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
}
}
@ -662,7 +669,7 @@ class CashTests {
fun generateSpendMixedDeposits() {
val wtx =
database.transaction {
val wtx = makeSpend(580.DOLLARS, miniCorpAnonymised)
val wtx = makeSpend(ourServices, 580.DOLLARS, miniCorpAnonymised)
assertEquals(3, wtx.inputs.size)
wtx
}
@ -675,7 +682,7 @@ class CashTests {
assertEquals(vaultState2.ref, wtx.inputs[2])
assertEquals(vaultState0.state.data.copy(owner = miniCorpAnonymised, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[1].data)
assertEquals(vaultState2.state.data.copy(owner = miniCorpAnonymised), wtx.outputs[0].data)
assertEquals(OUR_IDENTITY_1.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
assertEquals(ourIdentity.owningKey, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
}
}
@ -684,12 +691,12 @@ class CashTests {
database.transaction {
val e: InsufficientBalanceException = assertFailsWith("balance") {
makeSpend(1000.DOLLARS, miniCorpAnonymised)
makeSpend(ourServices, 1000.DOLLARS, miniCorpAnonymised)
}
assertEquals((1000 - 580).DOLLARS, e.amountMissing)
assertFailsWith(InsufficientBalanceException::class) {
makeSpend(81.SWISS_FRANCS, miniCorpAnonymised)
makeSpend(ourServices, 81.SWISS_FRANCS, miniCorpAnonymised)
}
}
}
@ -821,7 +828,7 @@ class CashTests {
fun multiSpend() {
val tx = TransactionBuilder(DUMMY_NOTARY)
database.transaction {
val changeIdentity = ourServices.keyManagementService.freshKeyAndCert(OUR_IDENTITY_AND_CERT, false)
val changeIdentity = ourServices.keyManagementService.freshKeyAndCert(ourServices.myInfo.singleIdentityAndCert(), false)
val payments = listOf(
PartyAndAmount(miniCorpAnonymised, 400.DOLLARS),
PartyAndAmount(CHARLIE_ANONYMISED, 150.DOLLARS)

View File

@ -55,7 +55,7 @@ class DummyFungibleContract : OnLedgerAsset<Currency, DummyFungibleContract.Comm
_quantity = this.amount.quantity,
currency = this.amount.token.product.currencyCode,
_issuerParty = this.amount.token.issuer.party,
_issuerRef = this.amount.token.issuer.reference.bytes
_issuerRef = this.amount.token.issuer.reference
)
is SampleCashSchemaV3 -> SampleCashSchemaV3.PersistentCashState(
participants = this.participants.toMutableSet(),

View File

@ -1,8 +1,12 @@
package net.corda.finance.contracts.asset
package net.corda.finance.contracts.asset.cash.selection
import net.corda.core.internal.concurrent.transpose
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS
import net.corda.finance.POUNDS
import net.corda.finance.flows.CashException
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNodeParameters
@ -10,8 +14,9 @@ import net.corda.testing.startFlow
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.After
import org.junit.Test
import java.util.Collections.nCopies
class CashSelectionH2Test {
class CashSelectionH2ImplTest {
private val mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance"))
@After
@ -19,6 +24,19 @@ class CashSelectionH2Test {
mockNet.stopNodes()
}
@Test
fun `selecting pennies amount larger than max int, which is split across multiple cash states`() {
val node = mockNet.createNode()
// The amount has to split across at least two states, probably to trigger the H2 accumulator variable during the
// spend operation below.
// Issuing Integer.MAX_VALUE will not cause an exception since PersistentCashState.pennies is a long
nCopies(2, Integer.MAX_VALUE).map { issueAmount ->
node.services.startFlow(CashIssueFlow(issueAmount.POUNDS, OpaqueBytes.of(1), mockNet.defaultNotaryIdentity)).resultFuture
}.transpose().getOrThrow()
// The spend must be more than the size of a single cash state to force the accumulator onto the second state.
node.services.startFlow(CashPaymentFlow((Integer.MAX_VALUE + 1L).POUNDS, node.info.legalIdentities[0])).resultFuture.getOrThrow()
}
@Test
fun `check does not hold connection over retries`() {
val bankA = mockNet.createNode(MockNodeParameters(configOverrides = {

View File

@ -7,8 +7,7 @@ import net.corda.finance.DOLLARS
import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.Cash
import net.corda.node.internal.StartedNode
import net.corda.testing.BOC
import net.corda.testing.chooseIdentity
import net.corda.testing.BOC_NAME
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNetwork.MockNode
@ -31,8 +30,8 @@ class CashExitFlowTests {
fun start() {
mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(),
cordappPackages = listOf("net.corda.finance.contracts.asset"))
bankOfCordaNode = mockNet.createPartyNode(BOC.name)
bankOfCorda = bankOfCordaNode.info.chooseIdentity()
bankOfCordaNode = mockNet.createPartyNode(BOC_NAME)
bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME)
notary = mockNet.defaultNotaryIdentity
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, notary)).resultFuture
mockNet.runNetwork()

View File

@ -7,8 +7,7 @@ import net.corda.finance.DOLLARS
import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.Cash
import net.corda.node.internal.StartedNode
import net.corda.testing.BOC
import net.corda.testing.chooseIdentity
import net.corda.testing.BOC_NAME
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNetwork.MockNode
@ -28,8 +27,8 @@ class CashIssueFlowTests {
@Before
fun start() {
mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts.asset"))
bankOfCordaNode = mockNet.createPartyNode(BOC.name)
bankOfCorda = bankOfCordaNode.info.chooseIdentity()
bankOfCordaNode = mockNet.createPartyNode(BOC_NAME)
bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME)
notary = mockNet.defaultNotaryIdentity
}

View File

@ -31,9 +31,9 @@ class CashPaymentFlowTests {
@Before
fun start() {
mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts.asset"))
bankOfCordaNode = mockNet.createPartyNode(BOC.name)
aliceNode = mockNet.createPartyNode(ALICE.name)
bankOfCorda = bankOfCordaNode.info.chooseIdentity()
bankOfCordaNode = mockNet.createPartyNode(BOC_NAME)
bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME)
aliceNode = mockNet.createPartyNode(ALICE_NAME)
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, mockNet.defaultNotaryIdentity)).resultFuture
future.getOrThrow()
}

View File

@ -1,8 +1,10 @@
package net.corda.finance.schemas
import net.corda.core.contracts.MAX_ISSUER_REF_SIZE
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
import org.hibernate.annotations.Type
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Index
@ -35,7 +37,8 @@ object SampleCashSchemaV1 : MappedSchema(schemaFamily = CashSchema.javaClass, ve
@Column(name = "issuer_key_hash", length = MAX_HASH_HEX_SIZE)
var issuerPartyHash: String,
@Column(name = "issuer_ref")
@Column(name = "issuer_ref", length = MAX_ISSUER_REF_SIZE)
@Type(type = "corda-wrapper-binary")
var issuerRef: ByteArray
) : PersistentState()
}

View File

@ -3,6 +3,7 @@ package net.corda.finance.schemas
import net.corda.core.identity.AbstractParty
import net.corda.core.schemas.CommonSchemaV1
import net.corda.core.schemas.MappedSchema
import net.corda.core.utilities.OpaqueBytes
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Index
@ -32,6 +33,6 @@ object SampleCashSchemaV2 : MappedSchema(schemaFamily = CashSchema.javaClass, ve
@Transient
val _issuerParty: AbstractParty,
@Transient
val _issuerRef: ByteArray
) : CommonSchemaV1.FungibleState(_participants.toMutableSet(), _owner, _quantity, _issuerParty, _issuerRef)
val _issuerRef: OpaqueBytes
) : CommonSchemaV1.FungibleState(_participants.toMutableSet(), _owner, _quantity, _issuerParty, _issuerRef.bytes)
}

View File

@ -1,8 +1,10 @@
package net.corda.finance.schemas
import net.corda.core.contracts.MAX_ISSUER_REF_SIZE
import net.corda.core.identity.AbstractParty
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import org.hibernate.annotations.Type
import javax.persistence.Column
import javax.persistence.ElementCollection
import javax.persistence.Entity
@ -37,7 +39,8 @@ object SampleCashSchemaV3 : MappedSchema(schemaFamily = CashSchema.javaClass, ve
@Column(name = "issuer_name")
var issuer: AbstractParty,
@Column(name = "issuer_ref")
@Column(name = "issuer_ref", length = MAX_ISSUER_REF_SIZE)
@Type(type = "corda-wrapper-binary")
var issuerRef: ByteArray
) : PersistentState()
}

View File

@ -1,8 +1,10 @@
package net.corda.finance.schemas
import net.corda.core.contracts.MAX_ISSUER_REF_SIZE
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
import org.hibernate.annotations.Type
import java.time.Instant
import javax.persistence.Column
import javax.persistence.Entity
@ -46,7 +48,8 @@ object SampleCommercialPaperSchemaV1 : MappedSchema(schemaFamily = CommercialPap
@Column(name = "face_value_issuer_key_hash", length = MAX_HASH_HEX_SIZE)
var faceValueIssuerPartyHash: String,
@Column(name = "face_value_issuer_ref")
@Column(name = "face_value_issuer_ref", length = MAX_ISSUER_REF_SIZE)
@Type(type = "corda-wrapper-binary")
var faceValueIssuerRef: ByteArray
) : PersistentState()
}

View File

@ -1,9 +1,12 @@
package net.corda.finance.schemas
import net.corda.core.contracts.MAX_ISSUER_REF_SIZE
import net.corda.core.identity.AbstractParty
import net.corda.core.schemas.CommonSchemaV1
import net.corda.core.schemas.MappedSchema
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
import net.corda.core.utilities.OpaqueBytes
import org.hibernate.annotations.Type
import java.time.Instant
import javax.persistence.Column
import javax.persistence.Entity
@ -30,7 +33,8 @@ object SampleCommercialPaperSchemaV2 : MappedSchema(schemaFamily = CommercialPap
@Column(name = "face_value_issuer_key_hash", length = MAX_HASH_HEX_SIZE)
var faceValueIssuerPartyHash: String,
@Column(name = "face_value_issuer_ref")
@Column(name = "face_value_issuer_ref", length = MAX_ISSUER_REF_SIZE)
@Type(type = "corda-wrapper-binary")
var faceValueIssuerRef: ByteArray,
/** parent attributes */
@ -44,6 +48,6 @@ object SampleCommercialPaperSchemaV2 : MappedSchema(schemaFamily = CommercialPap
@Transient
val _issuerParty: AbstractParty,
@Transient
val _issuerRef: ByteArray
) : CommonSchemaV1.FungibleState(_participants.toMutableSet(), _owner, _quantity, _issuerParty, _issuerRef)
val _issuerRef: OpaqueBytes
) : CommonSchemaV1.FungibleState(_participants.toMutableSet(), _owner, _quantity, _issuerParty, _issuerRef.bytes)
}

View File

@ -96,15 +96,15 @@ class Node(private val project: Project) : CordformNode() {
}
/**
* Set the SSHD port for this node.
* Enables SSH access on given port
*
* @param sshdPort The SSHD port.
* @param sshdPort The port for SSH server to listen on
*/
fun sshdPort(sshdPort: Int) {
config = config.withValue("sshdAddress",
ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$sshdPort"))
fun sshdPort(sshdPort: Int?) {
config = config.withValue("sshd.port", ConfigValueFactory.fromAnyRef(sshdPort))
}
internal fun build() {
configureProperties()
installCordaJar()

View File

@ -5,7 +5,7 @@ import net.corda.core.internal.ThreadBox
import net.corda.core.internal.createDirectories
import net.corda.core.internal.isRegularFile
import net.corda.core.internal.list
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.contextLogger
import rx.Observable
import rx.Scheduler
import rx.Subscription
@ -28,7 +28,7 @@ import java.util.concurrent.TimeUnit
class NodeInfoFilesCopier(scheduler: Scheduler = Schedulers.io()) : AutoCloseable {
companion object {
private val log = loggerFor<NodeInfoFilesCopier>()
private val log = contextLogger()
const val NODE_INFO_FILE_NAME_PREFIX = "nodeInfo-"
}

View File

@ -1,19 +1,17 @@
package net.corda.nodeapi
package net.corda.nodeapi.internal
import net.corda.core.messaging.MessageRecipientGroup
import net.corda.core.messaging.MessageRecipients
import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.toBase58String
import net.corda.nodeapi.config.SSLConfiguration
import java.security.PublicKey
/**
* The base class for Artemis services that defines shared data structures and SSL transport configuration.
*/
abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() {
class ArtemisMessagingComponent {
companion object {
init {
System.setProperty("org.jboss.logging.provider", "slf4j")
@ -66,7 +64,4 @@ abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() {
data class ServiceAddress(val identity: PublicKey) : ArtemisAddress, MessageRecipientGroup {
override val queueName: String = "$PEERS_PREFIX${identity.toBase58String()}"
}
/** The config object is used to pass in the passwords for the certificate KeyStore and TrustStore */
abstract val config: SSLConfiguration?
}

View File

@ -10,7 +10,7 @@ import net.corda.nodeapi.internal.AttachmentsClassLoader
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializationContext
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.serialization.amqp.hasAnnotationInHierarchy
import net.corda.nodeapi.internal.serialization.kryo.ThrowableSerializer
import java.io.PrintWriter
@ -187,7 +187,7 @@ class TransientClassWhiteList(delegate: ClassWhitelist) : AbstractMutableClassWh
@Suppress("unused")
class LoggingWhitelist(val delegate: ClassWhitelist, val global: Boolean = true) : MutableClassWhitelist {
companion object {
val log = loggerFor<LoggingWhitelist>()
private val log = contextLogger()
val globallySeen: MutableSet<String> = Collections.synchronizedSet(mutableSetOf())
val journalWriter: PrintWriter? = openOptionalDynamicWhitelistJournal()

View File

@ -36,7 +36,6 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme {
register(publicKeySerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.PrivateKeySerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.ThrowableSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.X500NameSerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.BigDecimalSerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.CurrencySerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.OpaqueBytesSubSequenceSerializer(this))
@ -54,8 +53,8 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme {
register(net.corda.nodeapi.internal.serialization.amqp.custom.MonthDaySerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.PeriodSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.ClassSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateHolderSerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.PartyAndCertificateSerializer(factory))
register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateSerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.CertPathSerializer(this))
register(net.corda.nodeapi.internal.serialization.amqp.custom.StringBufferSerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.SimpleStringSerializer)
register(net.corda.nodeapi.internal.serialization.amqp.custom.InputStreamSerializer)

View File

@ -1,7 +1,7 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.core.utilities.loggerFor
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.nameForType
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
@ -17,7 +17,9 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
open val kotlinConstructor = constructorForDeserialization(clazz)
val javaConstructor by lazy { kotlinConstructor?.javaConstructor }
private val logger = loggerFor<ObjectSerializer>()
companion object {
private val logger = contextLogger()
}
open internal val propertySerializers: Collection<PropertySerializer> by lazy {
propertiesForSerialization(kotlinConstructor, clazz, factory)

View File

@ -1,6 +1,6 @@
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.contextLogger
import org.apache.qpid.proton.amqp.Binary
import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Method
@ -61,8 +61,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
}
companion object {
private val logger = loggerFor<PropertySerializer>()
private val logger = contextLogger()
fun make(name: String, readMethod: Method?, resolvedType: Type, factory: SerializerFactory): PropertySerializer {
readMethod?.isAccessible = true
if (SerializerFactory.isPrimitive(resolvedType)) {

View File

@ -0,0 +1,27 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import java.io.NotSerializableException
import java.security.cert.CertPath
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
class CertPathSerializer(factory: SerializerFactory)
: CustomSerializer.Proxy<CertPath, CertPathSerializer.CertPathProxy>(CertPath::class.java, CertPathProxy::class.java, factory) {
override fun toProxy(obj: CertPath): CertPathProxy = CertPathProxy(obj.type, obj.encoded)
override fun fromProxy(proxy: CertPathProxy): CertPath {
try {
val cf = CertificateFactory.getInstance(proxy.type)
return cf.generateCertPath(proxy.encoded.inputStream())
} catch (ce: CertificateException) {
val nse = NotSerializableException("java.security.cert.CertPath: $type")
nse.initCause(ce)
throw nse
}
}
@Suppress("ArrayInDataClass")
data class CertPathProxy(val type: String, val encoded: ByteArray)
}

View File

@ -1,30 +0,0 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.core.crypto.Crypto
import net.corda.core.identity.PartyAndCertificate
import net.corda.nodeapi.internal.serialization.amqp.*
import java.io.ByteArrayInputStream
import java.io.NotSerializableException
import java.security.cert.CertPath
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
/**
* A serializer that writes out a party and certificate in encoded format.
*/
class PartyAndCertificateSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<PartyAndCertificate, PartyAndCertificateSerializer.PartyAndCertificateProxy>(PartyAndCertificate::class.java, PartyAndCertificateProxy::class.java, factory) {
override fun toProxy(obj: PartyAndCertificate): PartyAndCertificateProxy = PartyAndCertificateProxy(obj.certPath.type, obj.certPath.encoded)
override fun fromProxy(proxy: PartyAndCertificateProxy): PartyAndCertificate {
try {
val cf = CertificateFactory.getInstance(proxy.type)
return PartyAndCertificate(cf.generateCertPath(ByteArrayInputStream(proxy.encoded)))
} catch (ce: CertificateException) {
val nse = NotSerializableException("java.security.cert.CertPath: " + type)
nse.initCause(ce)
throw nse
}
}
data class PartyAndCertificateProxy(val type: String, val encoded: ByteArray)
}

View File

@ -3,14 +3,14 @@ package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.core.CordaRuntimeException
import net.corda.core.CordaThrowable
import net.corda.core.serialization.SerializationFactory
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.serialization.amqp.*
import java.io.NotSerializableException
class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<Throwable, ThrowableSerializer.ThrowableProxy>(Throwable::class.java, ThrowableProxy::class.java, factory) {
companion object {
private val logger = loggerFor<ThrowableSerializer>()
private val logger = contextLogger()
}
override val revealSubclassesInSchema: Boolean = true

View File

@ -1,23 +0,0 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.serialization.amqp.*
import org.apache.qpid.proton.codec.Data
import org.bouncycastle.asn1.ASN1InputStream
import org.bouncycastle.asn1.x500.X500Name
import java.lang.reflect.Type
/**
* Custom serializer for X500 names that utilizes their ASN.1 encoding on the wire.
*/
object X500NameSerializer : CustomSerializer.Implements<X500Name>(X500Name::class.java) {
override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), SerializerFactory.primitiveTypeName(ByteArray::class.java)!!, descriptor, emptyList())))
override fun writeDescribedObject(obj: X500Name, data: Data, type: Type, output: SerializationOutput) {
output.writeObject(obj.encoded, data, clazz)
}
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): X500Name {
val binary = input.readObject(obj, schema, ByteArray::class.java) as ByteArray
return X500Name.getInstance(ASN1InputStream(binary).readObject())
}
}

View File

@ -1,23 +0,0 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.core.crypto.Crypto
import net.corda.nodeapi.internal.serialization.amqp.*
import org.apache.qpid.proton.codec.Data
import org.bouncycastle.cert.X509CertificateHolder
import java.lang.reflect.Type
/**
* A serializer that writes out a certificate in X.509 format.
*/
object X509CertificateHolderSerializer : CustomSerializer.Implements<X509CertificateHolder>(X509CertificateHolder::class.java) {
override val schemaForDocumentation = Schema(listOf(RestrictedType(type.toString(), "", listOf(type.toString()), SerializerFactory.primitiveTypeName(ByteArray::class.java)!!, descriptor, emptyList())))
override fun writeDescribedObject(obj: X509CertificateHolder, data: Data, type: Type, output: SerializationOutput) {
output.writeObject(obj.encoded, data, clazz)
}
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): X509CertificateHolder {
val bits = input.readObject(obj, schema, ByteArray::class.java) as ByteArray
return X509CertificateHolder(bits)
}
}

View File

@ -0,0 +1,27 @@
package net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.serialization.amqp.*
import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Type
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
object X509CertificateSerializer : CustomSerializer.Implements<X509Certificate>(X509Certificate::class.java) {
override val schemaForDocumentation = Schema(listOf(RestrictedType(
type.toString(),
"",
listOf(type.toString()),
SerializerFactory.primitiveTypeName(ByteArray::class.java)!!,
descriptor,
emptyList()
)))
override fun writeDescribedObject(obj: X509Certificate, data: Data, type: Type, output: SerializationOutput) {
output.writeObject(obj.encoded, data, clazz)
}
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): X509Certificate {
val bits = input.readObject(obj, schema, ByteArray::class.java) as ByteArray
return CertificateFactory.getInstance("X.509").generateCertificate(bits.inputStream()) as X509Certificate
}
}

View File

@ -32,8 +32,6 @@ import net.corda.nodeapi.internal.serialization.GeneratedAttachment
import net.corda.nodeapi.internal.serialization.MutableClassWhitelist
import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey
@ -53,6 +51,7 @@ import java.io.InputStream
import java.lang.reflect.Modifier.isPublic
import java.security.PublicKey
import java.security.cert.CertPath
import java.security.cert.X509Certificate
import java.util.*
import kotlin.collections.ArrayList
@ -109,8 +108,7 @@ object DefaultKryoCustomizer {
register(FileInputStream::class.java, InputStreamSerializer)
register(CertPath::class.java, CertPathSerializer)
register(X509CertPath::class.java, CertPathSerializer)
register(X500Name::class.java, X500NameSerializer)
register(X509CertificateHolder::class.java, X509CertificateSerializer)
register(X509Certificate::class.java, X509CertificateSerializer)
register(BCECPrivateKey::class.java, PrivateKeySerializer)
register(BCECPublicKey::class.java, publicKeySerializer)
register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer)

View File

@ -15,7 +15,8 @@ import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.Party
import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationContext.UseCase.*
import net.corda.core.serialization.SerializationContext.UseCase.Checkpoint
import net.corda.core.serialization.SerializationContext.UseCase.Storage
import net.corda.core.serialization.SerializeAsTokenContext
import net.corda.core.serialization.SerializedBytes
import net.corda.core.toFuture
@ -23,9 +24,6 @@ import net.corda.core.toObservable
import net.corda.core.transactions.*
import net.corda.nodeapi.internal.serialization.CordaClassResolver
import net.corda.nodeapi.internal.serialization.serializationContextKey
import org.bouncycastle.asn1.ASN1InputStream
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import rx.Observable
@ -36,6 +34,7 @@ import java.security.PrivateKey
import java.security.PublicKey
import java.security.cert.CertPath
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.*
import javax.annotation.concurrent.ThreadSafe
import kotlin.reflect.KClass
@ -471,46 +470,28 @@ object ClassSerializer : Serializer<Class<*>>() {
}
}
/**
* For serialising an [X500Name] without touching Sun internal classes.
*/
@ThreadSafe
object X500NameSerializer : Serializer<X500Name>() {
override fun read(kryo: Kryo, input: Input, type: Class<X500Name>): X500Name {
return X500Name.getInstance(ASN1InputStream(input.readBytes()).readObject())
}
override fun write(kryo: Kryo, output: Output, obj: X500Name) {
output.writeBytes(obj.encoded)
}
}
/**
* For serialising an [CertPath] in an X.500 standard format.
*/
@ThreadSafe
object CertPathSerializer : Serializer<CertPath>() {
val factory: CertificateFactory = CertificateFactory.getInstance("X.509")
override fun read(kryo: Kryo, input: Input, type: Class<CertPath>): CertPath {
return factory.generateCertPath(input)
val factory = CertificateFactory.getInstance(input.readString())
return factory.generateCertPath(input.readBytesWithLength().inputStream())
}
override fun write(kryo: Kryo, output: Output, obj: CertPath) {
output.writeBytes(obj.encoded)
output.writeString(obj.type)
output.writeBytesWithLength(obj.encoded)
}
}
/**
* For serialising an [X509CertificateHolder] in an X.500 standard format.
*/
@ThreadSafe
object X509CertificateSerializer : Serializer<X509CertificateHolder>() {
override fun read(kryo: Kryo, input: Input, type: Class<X509CertificateHolder>): X509CertificateHolder {
return X509CertificateHolder(input.readBytes())
object X509CertificateSerializer : Serializer<X509Certificate>() {
override fun read(kryo: Kryo, input: Input, type: Class<X509Certificate>): X509Certificate {
val factory = CertificateFactory.getInstance("X.509")
return factory.generateCertificate(input.readBytesWithLength().inputStream()) as X509Certificate
}
override fun write(kryo: Kryo, output: Output, obj: X509CertificateHolder) {
output.writeBytes(obj.encoded)
override fun write(kryo: Kryo, output: Output, obj: X509Certificate) {
output.writeBytesWithLength(obj.encoded)
}
}

View File

@ -21,13 +21,14 @@ import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.time.Instant
import java.util.Collections
import kotlin.test.*
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class KryoTests {
@Rule
@ -36,9 +37,6 @@ class KryoTests {
private lateinit var factory: SerializationFactory
private lateinit var context: SerializationContext
@get:Rule
val expectedEx: ExpectedException = ExpectedException.none()
@Before
fun setup() {
factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) }
@ -51,7 +49,7 @@ class KryoTests {
}
@Test
fun ok() {
fun `simple data class`() {
val birthday = Instant.parse("1984-04-17T00:30:00.00Z")
val mike = Person("mike", birthday)
val bits = mike.serialize(factory, context)
@ -59,7 +57,7 @@ class KryoTests {
}
@Test
fun nullables() {
fun `null values`() {
val bob = Person("bob", null)
val bits = bob.serialize(factory, context)
assertThat(bits.deserialize(factory, context)).isEqualTo(Person("bob", null))
@ -202,13 +200,6 @@ class KryoTests {
assertEquals(expected, actual)
}
@Test
fun `all-zero PrivacySalt not allowed`() {
expectedEx.expect(IllegalArgumentException::class.java)
expectedEx.expectMessage("Privacy salt should not be all zeros.")
PrivacySalt(ByteArray(32))
}
@CordaSerializable
private object TestSingleton

View File

@ -2,16 +2,16 @@ package net.corda.nodeapi.internal.serialization
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.util.DefaultClassResolver
import net.corda.core.identity.CordaX500Name
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.node.services.statemachine.SessionData
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.amqpSpecific
import net.corda.testing.kryoSpecific
import net.corda.testing.SerializationEnvironmentRule
import org.assertj.core.api.Assertions
import org.bouncycastle.asn1.x500.X500Name
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Assert.assertArrayEquals
import org.junit.Rule
import org.junit.Test
@ -53,7 +53,7 @@ class MapsSerializationTest {
fun `check throws for forbidden declared type`() = amqpSpecific("Such exceptions are not expected in Kryo mode.") {
val payload = HashMap<String, String>(smallMap)
val wrongPayloadType = WrongPayloadType(payload)
Assertions.assertThatThrownBy { wrongPayloadType.serialize() }
assertThatThrownBy { wrongPayloadType.serialize() }
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining(
"Map type class java.util.HashMap is unstable under iteration. Suggested fix: use java.util.LinkedHashMap instead.")
}
@ -62,27 +62,29 @@ class MapsSerializationTest {
data class MyKey(val keyContent: Double)
@CordaSerializable
data class MyValue(val valueContent: X500Name)
data class MyValue(val valueContent: CordaX500Name)
@Test
fun `check map serialization works with custom types`() {
val myMap = mapOf(
MyKey(1.0) to MyValue(X500Name("CN=one")),
MyKey(10.0) to MyValue(X500Name("CN=ten")))
MyKey(1.0) to MyValue(CordaX500Name("OOO", "LLL", "CC")),
MyKey(10.0) to MyValue(CordaX500Name("OO", "LL", "CC")))
assertEqualAfterRoundTripSerialization(myMap)
}
@Test
fun `check empty map serialises as Java emptyMap`() = kryoSpecific("Specifically checks Kryo serialization") {
val nameID = 0
val serializedForm = emptyMap<Int, Int>().serialize()
val output = ByteArrayOutputStream().apply {
write(KryoHeaderV0_1.bytes)
write(DefaultClassResolver.NAME + 2)
write(nameID)
write(javaEmptyMapClass.name.toAscii())
write(Kryo.NOT_NULL.toInt())
fun `check empty map serialises as Java emptyMap`() {
kryoSpecific("Specifically checks Kryo serialization") {
val nameID = 0
val serializedForm = emptyMap<Int, Int>().serialize()
val output = ByteArrayOutputStream().apply {
write(KryoHeaderV0_1.bytes)
write(DefaultClassResolver.NAME + 2)
write(nameID)
write(javaEmptyMapClass.name.toAscii())
write(Kryo.NOT_NULL.toInt())
}
assertArrayEquals(output.toByteArray(), serializedForm.bytes)
}
assertArrayEquals(output.toByteArray(), serializedForm.bytes)
}
}

View File

@ -1,17 +1,20 @@
@file:Suppress("unused", "MemberVisibilityCanPrivate")
package net.corda.nodeapi.internal.serialization.amqp
import net.corda.client.rpc.RPCException
import net.corda.core.CordaRuntimeException
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.secureRandomBytes
import net.corda.core.flows.FlowException
import net.corda.core.identity.AbstractParty
import net.corda.core.internal.toX509CertHolder
import net.corda.core.internal.AbstractAttachment
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.MissingAttachmentsException
import net.corda.core.serialization.SerializationFactory
import net.corda.core.transactions.LedgerTransaction
import net.corda.client.rpc.RPCException
import net.corda.core.contracts.*
import net.corda.core.internal.AbstractAttachment
import net.corda.core.serialization.MissingAttachmentsException
import net.corda.core.utilities.OpaqueBytes
import net.corda.nodeapi.internal.serialization.AllWhitelist
import net.corda.nodeapi.internal.serialization.EmptyWhitelist
import net.corda.nodeapi.internal.serialization.GeneratedAttachment
@ -25,10 +28,8 @@ import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.qpid.proton.amqp.*
import org.apache.qpid.proton.codec.DecoderImpl
import org.apache.qpid.proton.codec.EncoderImpl
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertNotSame
import org.junit.Assert.assertSame
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.assertj.core.api.Assertions.*
import org.junit.Assert.*
import org.junit.Ignore
import org.junit.Test
import java.io.ByteArrayInputStream
@ -39,10 +40,7 @@ import java.nio.ByteBuffer
import java.time.*
import java.time.temporal.ChronoUnit
import java.util.*
import kotlin.reflect.full.declaredFunctions
import kotlin.reflect.full.declaredMemberFunctions
import kotlin.reflect.full.superclasses
import kotlin.reflect.jvm.javaMethod
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
@ -79,7 +77,6 @@ class SerializationOutputTests {
}
data class Woo(val fred: Int) {
@Suppress("unused")
val bob = "Bob"
}
@ -89,7 +86,6 @@ class SerializationOutputTests {
@CordaSerializable
data class AnnotatedWoo(val fred: Int) {
@Suppress("unused")
val bob = "Bob"
}
@ -151,6 +147,13 @@ class SerializationOutputTests {
data class PolymorphicProperty(val foo: FooInterface?)
@CordaSerializable
class NonZeroByte(val value: Byte) {
init {
require(value.toInt() != 0) { "Zero not allowed" }
}
}
private inline fun <reified T : Any> serdes(obj: T,
factory: SerializerFactory = SerializerFactory(
AllWhitelist, ClassLoader.getSystemClassLoader()),
@ -406,6 +409,32 @@ class SerializationOutputTests {
serdes(obj)
}
@Test
fun `class constructor is invoked on deserialisation`() {
val ser = SerializationOutput(SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()))
val des = DeserializationInput(ser.serializerFactory)
val serialisedOne = ser.serialize(NonZeroByte(1)).bytes
val serialisedTwo = ser.serialize(NonZeroByte(2)).bytes
// Find the index that holds the value byte
val valueIndex = serialisedOne.zip(serialisedTwo).mapIndexedNotNull { index, (oneByte, twoByte) ->
if (oneByte.toInt() == 1 && twoByte.toInt() == 2) index else null
}.single()
val copy = serialisedTwo.clone()
// Double check
copy[valueIndex] = 0x03
assertThat(des.deserialize(OpaqueBytes(copy), NonZeroByte::class.java).value).isEqualTo(3)
// Now use the forbidden value
copy[valueIndex] = 0x00
assertThatExceptionOfType(NotSerializableException::class.java).isThrownBy {
des.deserialize(OpaqueBytes(copy), NonZeroByte::class.java)
}.withMessageContaining("Zero not allowed")
}
@Test
fun `test custom serializers on public key`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
@ -762,26 +791,32 @@ class SerializationOutputTests {
}
@Test
fun `test certificate holder serialize`() {
fun `test privacy salt serialize`() {
serdes(PrivacySalt())
serdes(PrivacySalt(secureRandomBytes(32)))
}
@Test
fun `test X509 certificate serialize`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateHolderSerializer)
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateSerializer)
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateHolderSerializer)
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateSerializer)
val obj = BOB_IDENTITY.certificate.toX509CertHolder()
val obj = BOB_IDENTITY.certificate
serdes(obj, factory, factory2)
}
@Test
fun `test party and certificate serialize`() {
fun `test cert path serialize`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.PartyAndCertificateSerializer(factory))
factory.register(net.corda.nodeapi.internal.serialization.amqp.custom.CertPathSerializer(factory))
val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.PartyAndCertificateSerializer(factory2))
factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.CertPathSerializer(factory2))
val obj = BOB_IDENTITY
val obj = BOB_IDENTITY.certPath
serdes(obj, factory, factory2)
}

View File

@ -139,8 +139,14 @@ dependencies {
compile "io.netty:netty-all:$netty_version"
// CRaSH: An embeddable monitoring and admin shell with support for adding new commands written in Groovy.
compile("com.github.corda.crash:crash.shell:d5da86ba1b38e9c33af2a621dd15ba286307bec4") {
compile("com.github.corda.crash:crash.shell:$crash_version") {
exclude group: "org.slf4j", module: "slf4j-jdk14"
exclude group: "org.bouncycastle"
}
compile("com.github.corda.crash:crash.connectors.ssh:$crash_version") {
exclude group: "org.slf4j", module: "slf4j-jdk14"
exclude group: "org.bouncycastle"
}
// OkHTTP: Simple HTTP library.
@ -157,6 +163,9 @@ dependencies {
integrationTestCompile "junit:junit:$junit_version"
integrationTestCompile "org.assertj:assertj-core:${assertj_version}"
// Jsh: Testing SSH server
integrationTestCompile group: 'com.jcraft', name: 'jsch', version: '0.1.54'
// Jetty dependencies for NetworkMapClient test.
// Web stuff: for HTTP[S] servlets
testCompile "org.eclipse.jetty:jetty-servlet:${jetty_version}"

View File

@ -0,0 +1,170 @@
package net.corda.node
import co.paralleluniverse.fibers.Suspendable
import com.jcraft.jsch.ChannelExec
import com.jcraft.jsch.JSch
import com.jcraft.jsch.JSchException
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap
import net.corda.nodeapi.User
import net.corda.testing.ALICE
import net.corda.testing.driver.driver
import org.bouncycastle.util.io.Streams
import org.junit.Test
import net.corda.node.services.Permissions.Companion.startFlow
import java.net.ConnectException
import kotlin.test.assertTrue
import kotlin.test.fail
import org.assertj.core.api.Assertions.assertThat
import java.util.regex.Pattern
class SSHServerTest {
@Test()
fun `ssh server does not start be default`() {
val user = User("u", "p", setOf())
// The driver will automatically pick up the annotated flows below
driver() {
val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user))
node.getOrThrow()
val session = JSch().getSession("u", "localhost", 2222)
session.setConfig("StrictHostKeyChecking", "no")
session.setPassword("p")
try {
session.connect()
fail()
} catch (e:JSchException) {
assertTrue(e.cause is ConnectException)
}
}
}
@Test
fun `ssh server starts when configured`() {
val user = User("u", "p", setOf())
// The driver will automatically pick up the annotated flows below
driver {
val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user),
customOverrides = mapOf("sshd" to mapOf("port" to 2222)))
node.getOrThrow()
val session = JSch().getSession("u", "localhost", 2222)
session.setConfig("StrictHostKeyChecking", "no")
session.setPassword("p")
session.connect()
assertTrue(session.isConnected)
}
}
@Test
fun `ssh server verify credentials`() {
val user = User("u", "p", setOf())
// The driver will automatically pick up the annotated flows below
driver {
val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user),
customOverrides = mapOf("sshd" to mapOf("port" to 2222)))
node.getOrThrow()
val session = JSch().getSession("u", "localhost", 2222)
session.setConfig("StrictHostKeyChecking", "no")
session.setPassword("p_is_bad_password")
try {
session.connect()
fail("Server should reject invalid credentials")
} catch (e: JSchException) {
//There is no specialized exception for this
assertTrue(e.message == "Auth fail")
}
}
}
@Test
fun `ssh respects permissions`() {
val user = User("u", "p", setOf(startFlow<FlowICanRun>()))
// The driver will automatically pick up the annotated flows below
driver(isDebug = true) {
val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user),
customOverrides = mapOf("sshd" to mapOf("port" to 2222)))
node.getOrThrow()
val session = JSch().getSession("u", "localhost", 2222)
session.setConfig("StrictHostKeyChecking", "no")
session.setPassword("p")
session.connect()
assertTrue(session.isConnected)
val channel = session.openChannel("exec") as ChannelExec
channel.setCommand("start FlowICannotRun otherParty: \"O=Alice Corp,L=Madrid,C=ES\"")
channel.connect()
val response = String(Streams.readAll(channel.inputStream))
val flowNameEscaped = Pattern.quote("StartFlow.${SSHServerTest::class.qualifiedName}$${FlowICannotRun::class.simpleName}")
channel.disconnect()
session.disconnect()
assertThat(response).matches("(?s)User not permissioned with any of \\[[^]]*${flowNameEscaped}.*")
}
}
@Test
fun `ssh runs flows`() {
val user = User("u", "p", setOf(startFlow<FlowICanRun>()))
// The driver will automatically pick up the annotated flows below
driver(isDebug = true) {
val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user),
customOverrides = mapOf("sshd" to mapOf("port" to 2222)))
node.getOrThrow()
val session = JSch().getSession("u", "localhost", 2222)
session.setConfig("StrictHostKeyChecking", "no")
session.setPassword("p")
session.connect()
assertTrue(session.isConnected)
val channel = session.openChannel("exec") as ChannelExec
channel.setCommand("start FlowICanRun")
channel.connect()
val response = String(Streams.readAll(channel.inputStream))
//There are ANSI control characters involved, so we want to avoid direct byte to byte matching
assertThat(response.lines()).filteredOn( { it.contains("") && it.contains("Done")}).hasSize(1)
}
}
@StartableByRPC
@InitiatingFlow
class FlowICanRun : FlowLogic<String>() {
private val HELLO_STEP = ProgressTracker.Step("Hello")
@Suspendable
override fun call(): String {
progressTracker?.currentStep = HELLO_STEP
return "bambam"
}
override val progressTracker: ProgressTracker? = ProgressTracker(HELLO_STEP)
}
@StartableByRPC
@InitiatingFlow
class FlowICannotRun(val otherParty: Party) : FlowLogic<String>() {
@Suspendable
override fun call(): String = initiateFlow(otherParty).receive<String>().unwrap { it }
override val progressTracker: ProgressTracker? = ProgressTracker()
}
}

View File

@ -13,8 +13,8 @@ import net.corda.core.internal.div
import net.corda.core.internal.toLedgerTransaction
import net.corda.core.serialization.SerializationFactory
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.testing.*
@ -41,7 +41,7 @@ class AttachmentLoadingTests {
}
private companion object {
val logger = loggerFor<AttachmentLoadingTests>()
private val logger = contextLogger()
val isolatedJAR = AttachmentLoadingTests::class.java.getResource("isolated.jar")!!
val ISOLATED_CONTRACT_ID = "net.corda.finance.contracts.isolated.AnotherDummyContract"

View File

@ -13,8 +13,6 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.deleteIfExists
import net.corda.core.internal.div
import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.NotaryService
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.NetworkHostAndPort
@ -23,12 +21,11 @@ import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.StartedNode
import net.corda.node.services.config.BFTSMaRtConfiguration
import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.transactions.BFTNonValidatingNotaryService
import net.corda.node.services.transactions.minClusterSize
import net.corda.node.services.transactions.minCorrectReplicas
import net.corda.node.utilities.ServiceIdentityGenerator
import net.corda.testing.chooseIdentity
import net.corda.testing.common.internal.NetworkParametersCopier
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract
import net.corda.testing.dummyCommand
import net.corda.testing.node.MockNetwork
@ -57,26 +54,19 @@ class BFTNotaryServiceTests {
notary = ServiceIdentityGenerator.generateToDisk(
replicaIds.map { mockNet.baseDirectory(mockNet.nextNodeId + it) },
CordaX500Name("BFT", "Zurich", "CH"),
NotaryService.constructId(validating = false, bft = true))
val networkParameters = NetworkParametersCopier(testNetworkParameters(listOf(NotaryInfo(notary, false))))
CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH")
)
val clusterAddresses = replicaIds.map { NetworkHostAndPort("localhost", 11000 + it * 10) }
val nodes = replicaIds.map { replicaId ->
mockNet.createUnstartedNode(MockNodeParameters(configOverrides = {
replicaIds.forEach { replicaId ->
mockNet.createNode(MockNodeParameters(configOverrides = {
val notary = NotaryConfig(validating = false, bftSMaRt = BFTSMaRtConfiguration(replicaId, clusterAddresses, exposeRaces = exposeRaces))
doReturn(notary).whenever(it).notary
}))
} + mockNet.createUnstartedNode()
}
// MockNetwork doesn't support BFT clusters, so we create all the nodes we need unstarted, and then install the
// network-parameters in their directories before they're started.
node = nodes.map { node ->
networkParameters.install(mockNet.baseDirectory(node.id))
node.start()
}.last()
node = mockNet.createNode()
}
/** Failure mode is the redundant replica gets stuck in startup, so we can't dispose it cleanly at the end. */

View File

@ -13,6 +13,7 @@ import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.Permissions.Companion.invokeRpc
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.nodeapi.User
import net.corda.testing.*
import net.corda.testing.driver.NodeHandle
@ -41,7 +42,7 @@ class DistributedServiceTests {
driver(
extraCordappPackagesToScan = listOf("net.corda.finance.contracts"),
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name, rpcUsers = listOf(testUser), cluster = ClusterSpec.Raft(clusterSize = 3))))
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name.copy(commonName = RaftValidatingNotaryService.id), rpcUsers = listOf(testUser), cluster = ClusterSpec.Raft(clusterSize = 3))))
{
alice = startNode(providedName = ALICE.name, rpcUsers = listOf(testUser)).getOrThrow()
raftNotaryIdentity = defaultNotaryIdentity

View File

@ -3,8 +3,8 @@ package net.corda.services.messaging
import net.corda.core.crypto.Crypto
import net.corda.core.internal.*
import net.corda.node.utilities.*
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEER_USER
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
import net.corda.nodeapi.RPCApi
import net.corda.nodeapi.config.SSLConfiguration
import net.corda.testing.MEGA_CORP

View File

@ -17,10 +17,10 @@ import net.corda.core.utilities.toBase58String
import net.corda.core.utilities.unwrap
import net.corda.node.internal.Node
import net.corda.node.internal.StartedNode
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NOTIFICATIONS_ADDRESS
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.P2P_QUEUE
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEERS_PREFIX
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NOTIFICATIONS_ADDRESS
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX
import net.corda.nodeapi.RPCApi
import net.corda.nodeapi.User
import net.corda.nodeapi.config.SSLConfiguration

View File

@ -40,26 +40,30 @@ class NodeStatePersistenceTests {
val user = User("mark", "dadada", setOf(startFlow<SendMessageFlow>(), invokeRpc("vaultQuery")))
val message = Message("Hello world!")
driver(isDebug = true, startNodesInProcess = isQuasarAgentSpecified()) {
val stateAndRef: StateAndRef<MessageState>? = driver(isDebug = true, startNodesInProcess = isQuasarAgentSpecified()) {
val nodeName = {
val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow()
val nodeName = nodeHandle.nodeInfo.chooseIdentity().name
// Ensure the notary node has finished starting up, before starting a flow that needs a notary
defaultNotaryNode.getOrThrow()
nodeHandle.rpcClientToNode().start(user.username, user.password).use {
it.proxy.startFlow(::SendMessageFlow, message).returnValue.getOrThrow()
it.proxy.startFlow(::SendMessageFlow, message, defaultNotaryIdentity).returnValue.getOrThrow()
}
nodeHandle.stop()
nodeName
}()
val nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user)).getOrThrow()
nodeHandle.rpcClientToNode().start(user.username, user.password).use {
val result = nodeHandle.rpcClientToNode().start(user.username, user.password).use {
val page = it.proxy.vaultQuery(MessageState::class.java)
val stateAndRef = page.states.singleOrNull()
assertNotNull(stateAndRef)
val retrievedMessage = stateAndRef!!.state.data.message
assertEquals(message, retrievedMessage)
page.states.singleOrNull()
}
nodeHandle.stop()
result
}
assertNotNull(stateAndRef)
val retrievedMessage = stateAndRef!!.state.data.message
assertEquals(message, retrievedMessage)
}
}
@ -126,7 +130,7 @@ open class MessageContract : Contract {
}
@StartableByRPC
class SendMessageFlow(private val message: Message) : FlowLogic<SignedTransaction>() {
class SendMessageFlow(private val message: Message, private val notary: Party) : FlowLogic<SignedTransaction>() {
companion object {
object GENERATING_TRANSACTION : ProgressTracker.Step("Generating transaction based on the message.")
object VERIFYING_TRANSACTION : ProgressTracker.Step("Verifying contract constraints.")
@ -142,8 +146,6 @@ class SendMessageFlow(private val message: Message) : FlowLogic<SignedTransactio
@Suspendable
override fun call(): SignedTransaction {
val notary = serviceHub.networkMapCache.notaryIdentities.first()
progressTracker.currentStep = GENERATING_TRANSACTION
val messageState = MessageState(message = message, by = ourIdentity)

View File

@ -2,6 +2,9 @@ package net.corda.node.shell;
// See the comments at the top of run.java
import net.corda.core.messaging.CordaRPCOps;
import net.corda.node.utilities.ANSIProgressRenderer;
import net.corda.node.utilities.CRaSHNSIProgressRenderer;
import org.crsh.cli.*;
import org.crsh.command.*;
import org.crsh.text.*;
@ -9,6 +12,7 @@ import org.crsh.text.ui.TableElement;
import java.util.*;
import static net.corda.node.services.messaging.RPCServerKt.CURRENT_RPC_CONTEXT;
import static net.corda.node.shell.InteractiveShell.*;
@Man(
@ -25,25 +29,27 @@ public class FlowShellCommand extends InteractiveShellCommand {
@Usage("The class name of the flow to run, or an unambiguous substring") @Argument String name,
@Usage("The data to pass as input") @Argument(unquote = false) List<String> input
) {
startFlow(name, input, out);
startFlow(name, input, out, ops(), ansiProgressRenderer());
}
// TODO Limit number of flows shown option?
@Command
@Usage("watch information about state machines running on the node with result information")
public void watch(InvocationContext<TableElement> context) throws Exception {
runStateMachinesView(out);
runStateMachinesView(out, ops());
}
static void startFlow(@Usage("The class name of the flow to run, or an unambiguous substring") @Argument String name,
@Usage("The data to pass as input") @Argument(unquote = false) List<String> input,
RenderPrintWriter out) {
RenderPrintWriter out,
CordaRPCOps rpcOps,
ANSIProgressRenderer ansiProgressRenderer) {
if (name == null) {
out.println("You must pass a name for the flow, see 'man flow'", Color.red);
return;
}
String inp = input == null ? "" : String.join(" ", input).trim();
runFlowByNameFragment(name, inp, out);
runFlowByNameFragment(name, inp, out, rpcOps, ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHNSIProgressRenderer(out) );
}
@Command

View File

@ -2,6 +2,8 @@ package net.corda.node.shell;
// A simple forwarder to the "flow start" command, for easier typing.
import net.corda.node.utilities.ANSIProgressRenderer;
import net.corda.node.utilities.CRaSHNSIProgressRenderer;
import org.crsh.cli.*;
import java.util.*;
@ -11,6 +13,7 @@ public class StartShellCommand extends InteractiveShellCommand {
@Man("An alias for 'flow start'. Example: \"start Yo target: Some other company\"")
public void main(@Usage("The class name of the flow to run, or an unambiguous substring") @Argument String name,
@Usage("The data to pass as input") @Argument(unquote = false) List<String> input) {
FlowShellCommand.startFlow(name, input, out);
ANSIProgressRenderer ansiProgressRenderer = ansiProgressRenderer();
FlowShellCommand.startFlow(name, input, out, ops(), ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHNSIProgressRenderer(out));
}
}

View File

@ -20,7 +20,10 @@ import net.corda.core.internal.concurrent.openFuture
import net.corda.core.messaging.*
import net.corda.core.node.*
import net.corda.core.node.services.*
import net.corda.core.serialization.*
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.serialize
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.debug
@ -33,6 +36,7 @@ import net.corda.node.internal.cordapp.CordappProviderInternal
import net.corda.node.services.ContractUpgradeHandler
import net.corda.node.services.FinalityHandler
import net.corda.node.services.NotaryChangeHandler
import net.corda.node.services.RPCUserService
import net.corda.node.services.api.*
import net.corda.node.services.config.BFTSMaRtConfiguration
import net.corda.node.services.config.NodeConfiguration
@ -43,15 +47,8 @@ import net.corda.node.services.events.ScheduledActivityObserver
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.services.keys.PersistentKeyManagementService
import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.network.NetworkMapCacheImpl
import net.corda.node.services.network.NodeInfoWatcher
import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.persistence.*
import net.corda.node.services.network.*
import net.corda.node.services.persistence.DBCheckpointStorage
import net.corda.node.services.persistence.DBTransactionMappingStorage
import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.node.services.persistence.*
import net.corda.node.services.schema.HibernateObserver
import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.statemachine.*
@ -59,11 +56,11 @@ import net.corda.node.services.transactions.*
import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
import net.corda.node.services.vault.NodeVaultService
import net.corda.node.services.vault.VaultSoftLockManager
import net.corda.node.shell.InteractiveShell
import net.corda.node.utilities.*
import org.apache.activemq.artemis.utils.ReusableLatch
import org.slf4j.Logger
import rx.Observable
import rx.subjects.PublishSubject
import java.io.IOException
import java.lang.reflect.InvocationTargetException
import java.security.KeyPair
@ -89,8 +86,6 @@ import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair
* Marked as SingletonSerializeAsToken to prevent the invisible reference to AbstractNode in the ServiceHub accidentally
* sweeping up the Node into the Kryo checkpoint serialization via any flows holding a reference to ServiceHub.
*/
// TODO Log warning if this node is a notary but not one of the ones specified in the network parameters, both for core and custom
// In theory the NodeInfo for the node should be passed in, instead, however currently this is constructed by the
// AbstractNode. It should be possible to generate the NodeInfo outside of AbstractNode, so it can be passed in.
abstract class AbstractNode(val configuration: NodeConfiguration,
@ -120,14 +115,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
// low-performance prototyping period.
protected abstract val serverThread: AffinityExecutor
protected lateinit var networkParameters: NetworkParameters
private val cordappServices = MutableClassToInstanceMap.create<SerializeAsToken>()
private val flowFactories = ConcurrentHashMap<Class<out FlowLogic<*>>, InitiatedFlowFactory<*>>()
protected val services: ServiceHubInternal get() = _services
private lateinit var _services: ServiceHubInternalImpl
protected lateinit var info: NodeInfo
protected val nodeStateObservable: PublishSubject<NodeState> = PublishSubject.create<NodeState>()
protected var myNotaryIdentity: PartyAndCertificate? = null
protected lateinit var checkpointStorage: CheckpointStorage
protected lateinit var smm: StateMachineManager
@ -138,6 +130,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
protected val _nodeReadyFuture = openFuture<Unit>()
protected val networkMapClient: NetworkMapClient? by lazy { configuration.compatibilityZoneURL?.let(::NetworkMapClient) }
lateinit var userService: RPCUserService get
/** Completes once the node has successfully registered with the network map service
* or has loaded network map data from local database */
val nodeReadyFuture: CordaFuture<Unit>
@ -173,7 +167,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
check(started == null) { "Node has already been started" }
log.info("Generating nodeInfo ...")
initCertificate()
val keyPairs = initNodeInfo()
val (keyPairs, info) = initNodeInfo()
val identityKeypair = keyPairs.first { it.public == info.legalIdentities.first().owningKey }
val serialisedNodeInfo = info.serialize()
val signature = identityKeypair.sign(serialisedNodeInfo)
@ -185,14 +179,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
check(started == null) { "Node has already been started" }
log.info("Node starting up ...")
initCertificate()
val keyPairs = initNodeInfo()
readNetworkParameters()
val (keyPairs, info) = initNodeInfo()
val schemaService = NodeSchemaService(cordappLoader)
// Do all of this in a database transaction so anything that might need a connection has one.
val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService) { database ->
val transactionStorage = makeTransactionStorage(database)
val stateLoader = StateLoaderImpl(transactionStorage)
val nodeServices = makeServices(keyPairs, schemaService, transactionStorage, stateLoader, database)
val nodeServices = makeServices(keyPairs, schemaService, transactionStorage, stateLoader, database, info)
val notaryService = makeNotaryService(nodeServices, database)
smm = makeStateMachineManager(database)
val flowStarter = FlowStarterImpl(serverThread, smm)
@ -220,8 +213,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
registerCordappFlows()
_services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows }
FlowLogicRefFactoryImpl.classloader = cordappLoader.appClassLoader
runOnStop += network::stop
startShell(rpcOps)
Pair(StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, network, database, rpcOps, flowStarter, notaryService), schedulerService)
}
@ -252,27 +244,26 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
}
}
private fun initNodeInfo(): Set<KeyPair> {
open fun startShell(rpcOps: CordaRPCOps) {
InteractiveShell.startShell(configuration, rpcOps, userService, _services.identityService, _services.database)
}
private fun initNodeInfo(): Pair<Set<KeyPair>, NodeInfo> {
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
val keyPairs = mutableSetOf(identityKeyPair)
myNotaryIdentity = configuration.notary?.let {
if (it.isClusterConfig) {
val (notaryIdentity, notaryIdentityKeyPair) = obtainIdentity(it)
keyPairs += notaryIdentityKeyPair
notaryIdentity
} else {
// In case of a single notary service myNotaryIdentity will be the node's single identity.
identity
}
val (notaryIdentity, notaryIdentityKeyPair) = obtainIdentity(it)
keyPairs += notaryIdentityKeyPair
notaryIdentity
}
info = NodeInfo(
val info = NodeInfo(
myAddresses(),
setOf(identity, myNotaryIdentity).filterNotNull(),
listOf(identity, myNotaryIdentity).filterNotNull(),
versionInfo.platformVersion,
platformClock.instant().toEpochMilli()
)
return keyPairs
return Pair(keyPairs, info)
}
protected abstract fun myAddresses(): List<NetworkHostAndPort>
@ -498,12 +489,12 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
* Builds node internal, advertised, and plugin services.
* Returns a list of tokenizable services to be added to the serialisation context.
*/
private fun makeServices(keyPairs: Set<KeyPair>, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, stateLoader: StateLoader, database: CordaPersistence): MutableList<Any> {
private fun makeServices(keyPairs: Set<KeyPair>, schemaService: SchemaService, transactionStorage: WritableTransactionStorage, stateLoader: StateLoader, database: CordaPersistence, info: NodeInfo): MutableList<Any> {
checkpointStorage = DBCheckpointStorage()
val metrics = MetricRegistry()
attachments = NodeAttachmentService(metrics)
val cordappProvider = CordappProviderImpl(cordappLoader, attachments)
val identityService = makeIdentityService()
val identityService = makeIdentityService(info)
val keyManagementService = makeKeyManagementService(identityService, keyPairs)
_services = ServiceHubInternalImpl(
identityService,
@ -513,8 +504,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
stateLoader,
MonitoringService(metrics),
cordappProvider,
database)
network = makeMessagingService(database)
database,
info)
network = makeMessagingService(database, info)
val tokenizableServices = mutableListOf(attachments, network, services.vaultService,
services.keyManagementService, services.identityService, platformClock,
services.auditService, services.monitoringService, services.networkMapCache, services.schemaService,
@ -596,13 +588,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
return PersistentKeyManagementService(identityService, keyPairs)
}
private fun readNetworkParameters() {
val file = configuration.baseDirectory / "network-parameters"
networkParameters = file.readAll().deserialize<SignedData<NetworkParameters>>().verified()
log.info(networkParameters.toString())
check(networkParameters.minimumPlatformVersion <= versionInfo.platformVersion) { "Node is too old for the network" }
}
private fun makeCoreNotaryService(notaryConfig: NotaryConfig, database: CordaPersistence): NotaryService {
val notaryKey = myNotaryIdentity?.owningKey ?: throw IllegalArgumentException("No notary identity initialized when creating a notary service")
return notaryConfig.run {
@ -626,7 +611,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
}
}
private fun makeIdentityService(): IdentityService {
private fun makeIdentityService(info: NodeInfo): IdentityService {
val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword)
val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
val trustRoot = trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA)
@ -644,9 +629,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
// Meanwhile, we let the remote service send us updates until the acknowledgment buffer overflows and it
// unsubscribes us forcibly, rather than blocking the shutdown process.
// Notify observers that the node is shutting down
nodeStateObservable.onNext(NodeState.SHUTTING_DOWN)
// Run shutdown hooks in opposite order to starting
for (toRun in runOnStop.reversed()) {
toRun()
@ -655,22 +637,28 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
_started = null
}
protected abstract fun makeMessagingService(database: CordaPersistence): MessagingService
protected abstract fun makeMessagingService(database: CordaPersistence, info: NodeInfo): MessagingService
protected abstract fun startMessagingService(rpcOps: RPCOps)
private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair<PartyAndCertificate, KeyPair> {
val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) {
// Node's main identity or if it's a single node notary
val (id, singleName) = if (notaryConfig == null) {
// Node's main identity
Pair("identity", myLegalName)
} else {
val notaryId = notaryConfig.run {
NotaryService.constructId(validating, raft != null, bftSMaRt != null, custom)
}
// The node is part of a distributed notary whose identity must already be generated beforehand.
Pair(notaryId, null)
if (!notaryConfig.isClusterConfig) {
// Node's notary identity
Pair(notaryId, myLegalName.copy(commonName = notaryId))
} else {
// The node is part of a distributed notary whose identity must already be generated beforehand
Pair(notaryId, null)
}
}
// TODO: Integrate with Key management service?
val privateKeyAlias = "$id-private-key"
@ -728,26 +716,19 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
private val stateLoader: StateLoader,
override val monitoringService: MonitoringService,
override val cordappProvider: CordappProviderInternal,
override val database: CordaPersistence
override val database: CordaPersistence,
override val myInfo: NodeInfo
) : SingletonSerializeAsToken(), ServiceHubInternal, StateLoader by stateLoader {
override val rpcFlows = ArrayList<Class<out FlowLogic<*>>>()
override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage()
override val auditService = DummyAuditService()
override val transactionVerifierService by lazy { makeTransactionVerifierService() }
override val networkMapCache by lazy {
NetworkMapCacheImpl(
PersistentNetworkMapCache(
database,
networkParameters.notaries),
identityService)
}
override val networkMapCache by lazy { NetworkMapCacheImpl(PersistentNetworkMapCache(database), identityService) }
override val vaultService by lazy { makeVaultService(keyManagementService, stateLoader, database.hibernateConfig) }
override val contractUpgradeService by lazy { ContractUpgradeServiceImpl() }
override val attachments: AttachmentStorage get() = this@AbstractNode.attachments
override val networkService: MessagingService get() = network
override val clock: Clock get() = platformClock
override val myInfo: NodeInfo get() = info
override val myNodeStateObservable: Observable<NodeState> get() = nodeStateObservable
override val configuration: NodeConfiguration get() = this@AbstractNode.configuration
override fun <T : SerializeAsToken> cordaService(type: Class<T>): T {
require(type.isAnnotationPresent(CordaService::class.java)) { "${type.name} is not a Corda service" }

View File

@ -20,8 +20,8 @@ import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.*
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.node.services.api.FlowStarter
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.messaging.context
@ -117,10 +117,6 @@ internal class CordaRPCOpsImpl(
return services.myInfo
}
override fun nodeStateObservable(): Observable<NodeState> {
return services.myNodeStateObservable
}
override fun notaryIdentities(): List<Party> {
return services.networkMapCache.notaryIdentities
}
@ -142,7 +138,9 @@ internal class CordaRPCOpsImpl(
return FlowProgressHandleImpl(
id = stateMachine.id,
returnValue = stateMachine.resultFuture,
progress = stateMachine.logic.track()?.updates ?: Observable.empty()
progress = stateMachine.logic.track()?.updates ?: Observable.empty(),
stepsTreeIndexFeed = stateMachine.logic.trackStepsTreeIndex(),
stepsTreeFeed = stateMachine.logic.trackStepsTree()
)
}
@ -296,6 +294,6 @@ internal class CordaRPCOpsImpl(
}
companion object {
private val log = loggerFor<CordaRPCOpsImpl>()
private val log = contextLogger()
}
}

View File

@ -6,21 +6,22 @@ import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.concurrent.thenMatch
import net.corda.core.internal.uncheckedCast
import net.corda.core.messaging.RPCOps
import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.TransactionVerifierService
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.core.utilities.contextLogger
import net.corda.node.VersionInfo
import net.corda.node.internal.cordapp.CordappLoader
import net.corda.node.serialization.KryoServerSerializationScheme
import net.corda.node.services.RPCUserService
import net.corda.node.services.RPCUserServiceImpl
import net.corda.node.services.api.SchemaService
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.messaging.ArtemisMessagingServer
import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.messaging.NodeMessagingClient
import net.corda.node.services.config.VerifierType
import net.corda.node.services.messaging.*
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.utilities.AddressUtils
import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.CordaPersistence
@ -48,7 +49,7 @@ open class Node(configuration: NodeConfiguration,
cordappLoader: CordappLoader = makeCordappLoader(configuration)
) : AbstractNode(configuration, createClock(configuration), versionInfo, cordappLoader) {
companion object {
private val logger = loggerFor<Node>()
private val staticLog = contextLogger()
var renderBasicInfoToConsole = true
/** Used for useful info that we always want to show, even when not logging to the console */
@ -78,8 +79,11 @@ open class Node(configuration: NodeConfiguration,
}
}
override val log: Logger get() = logger
override fun makeTransactionVerifierService() = (network as NodeMessagingClient).verifierService
override val log: Logger get() = staticLog
override fun makeTransactionVerifierService(): TransactionVerifierService = when (configuration.verifierType) {
VerifierType.OutOfProcess -> verifierMessagingClient!!.verifierService
VerifierType.InMemory -> InMemoryTransactionVerifierService(numberOfWorkers = 4)
}
private val sameVmNodeNumber = sameVmNodeCounter.incrementAndGet() // Under normal (non-test execution) it will always be "1"
@ -126,23 +130,25 @@ open class Node(configuration: NodeConfiguration,
private var shutdownHook: ShutdownHook? = null
private lateinit var userService: RPCUserService
override fun makeMessagingService(database: CordaPersistence): MessagingService {
override fun makeMessagingService(database: CordaPersistence, info: NodeInfo): MessagingService {
userService = RPCUserServiceImpl(configuration.rpcUsers)
val serverAddress = configuration.messagingServerAddress ?: makeLocalMessageBroker()
val advertisedAddress = info.addresses.single()
printBasicNodeInfo("Incoming connection address", advertisedAddress.toString())
return NodeMessagingClient(
rpcMessagingClient = RPCMessagingClient(configuration, serverAddress)
verifierMessagingClient = when (configuration.verifierType) {
VerifierType.OutOfProcess -> VerifierMessagingClient(configuration, serverAddress, services.monitoringService.metrics)
VerifierType.InMemory -> null
}
return P2PMessagingClient(
configuration,
versionInfo,
serverAddress,
info.legalIdentities[0].owningKey,
serverThread,
database,
services.monitoringService,
advertisedAddress)
}
@ -197,9 +203,19 @@ open class Node(configuration: NodeConfiguration,
runOnStop += this::stop
start()
}
// Start up the MQ client.
(network as NodeMessagingClient).start(rpcOps, userService)
// Start up the MQ clients.
rpcMessagingClient.run {
runOnStop += this::stop
start(rpcOps, userService)
}
verifierMessagingClient?.run {
runOnStop += this::stop
start()
}
(network as P2PMessagingClient).apply {
runOnStop += this::stop
start()
}
}
/**
@ -268,7 +284,7 @@ open class Node(configuration: NodeConfiguration,
_startupComplete.set(Unit)
}
},
{ th -> logger.error("Unexpected exception", th) }
{ th -> staticLog.error("Unexpected exception", th) } // XXX: Why not use log?
)
shutdownHook = addShutdownHook {
stop()
@ -289,9 +305,13 @@ open class Node(configuration: NodeConfiguration,
checkpointContext = KRYO_CHECKPOINT_CONTEXT.withClassLoader(classloader))
}
private lateinit var rpcMessagingClient: RPCMessagingClient
private var verifierMessagingClient: VerifierMessagingClient? = null
/** Starts a blocking event loop for message dispatch. */
fun run() {
(network as NodeMessagingClient).run(messageBroker!!.serverControl)
rpcMessagingClient.start2(messageBroker!!.serverControl)
verifierMessagingClient?.start2()
(network as P2PMessagingClient).run()
}
private var shutdown = false

View File

@ -23,14 +23,13 @@ import java.lang.management.ManagementFactory
import java.net.InetAddress
import java.nio.file.Path
import java.nio.file.Paths
import java.time.LocalDate
import java.util.*
import kotlin.system.exitProcess
/** This class is responsible for starting a Node from command line arguments. */
open class NodeStartup(val args: Array<String>) {
companion object {
private val logger by lazy { loggerFor<Node>() }
private val logger by lazy { loggerFor<Node>() } // I guess this is lazy to allow for logging init, but why Node?
val LOGS_DIRECTORY_NAME = "logs"
val LOGS_CAN_BE_FOUND_IN_STRING = "Logs can be found in"
}
@ -118,12 +117,13 @@ open class NodeStartup(val args: Array<String>) {
Node.printBasicNodeInfo("Node for \"$name\" started up and registered in $elapsed sec")
// Don't start the shell if there's no console attached.
val runShell = !cmdlineOptions.noLocalShell && System.console() != null
startedNode.internals.startupComplete.then {
try {
InteractiveShell.startShell(cmdlineOptions.baseDirectory, runShell, cmdlineOptions.sshdServer, startedNode)
} catch (e: Throwable) {
logger.error("Shell failed to start", e)
if (!cmdlineOptions.noLocalShell && System.console() != null && conf.devMode) {
startedNode.internals.startupComplete.then {
try {
InteractiveShell.runLocalShell(startedNode)
} catch (e: Throwable) {
logger.error("Shell failed to start", e)
}
}
}
},
@ -305,11 +305,6 @@ open class NodeStartup(val args: Array<String>) {
"Computers are useless. They can only\ngive you answers. -- Picasso"
)
// TODO: Delete this after CordaCon.
val cordaCon2017date = LocalDate.of(2017, 9, 12)
val cordaConBanner = if (LocalDate.now() < cordaCon2017date)
"${Emoji.soon} Register for our Free CordaCon event : see https://goo.gl/Z15S8W" else ""
if (Emoji.hasEmojiTerminal)
messages += "Kind of like a regular database but\nwith emojis, colours and ascii art. ${Emoji.coolGuy}"
val (msg1, msg2) = messages.randomOrNull()!!.split('\n')
@ -323,8 +318,6 @@ open class NodeStartup(val args: Array<String>) {
a("--- ${versionInfo.vendor} ${versionInfo.releaseVersion} (${versionInfo.revision.take(7)}) -----------------------------------------------").
newline().
newline().
a(cordaConBanner).
newline().
reset())
}
}

View File

@ -8,14 +8,12 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.NodeState
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.*
import net.corda.node.services.messaging.RpcAuthContext
import rx.Observable
import java.io.InputStream
import java.security.PublicKey
@ -68,8 +66,6 @@ class RpcAuthorisationProxy(private val implementation: CordaRPCOps, private val
override fun nodeInfo(): NodeInfo = guard("nodeInfo", implementation::nodeInfo)
override fun nodeStateObservable(): Observable<NodeState> = guard("nodeStateObservable", implementation::nodeStateObservable)
override fun notaryIdentities(): List<Party> = guard("notaryIdentities", implementation::notaryIdentities)
override fun addVaultTransactionNote(txnId: SecureHash, txnNote: String) = guard("addVaultTransactionNote") {

View File

@ -12,10 +12,11 @@ import net.corda.core.node.services.CordaService
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.contextLogger
import net.corda.node.internal.classloading.requireAnnotation
import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.internal.serialization.DefaultWhitelist
import org.apache.commons.collections4.map.LRUMap
import java.io.File
import java.io.FileOutputStream
import java.lang.reflect.Modifier
@ -52,8 +53,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
}
companion object {
private val logger = loggerFor<CordappLoader>()
private val logger = contextLogger()
/**
* Default cordapp dir name
*/
@ -67,6 +67,9 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
*/
fun createDefault(baseDir: Path) = CordappLoader(getCordappsInDirectory(getCordappsPath(baseDir)))
// Cache for CordappLoaders to avoid costly classpath scanning
private val cordappLoadersCache = LRUMap<List<*>, CordappLoader>(1000)
/**
* Create a dev mode CordappLoader for test environments that creates and loads cordapps from the classpath
* and cordapps directory. This is intended mostly for use by the driver.
@ -77,7 +80,8 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
@VisibleForTesting
fun createDefaultWithTestPackages(configuration: NodeConfiguration, testPackages: List<String>): CordappLoader {
check(configuration.devMode) { "Package scanning can only occur in dev mode" }
return CordappLoader(getCordappsInDirectory(getCordappsPath(configuration.baseDirectory)) + testPackages.flatMap(this::createScanPackage))
val paths = getCordappsInDirectory(getCordappsPath(configuration.baseDirectory)) + testPackages.flatMap(this::createScanPackage)
return cordappLoadersCache.computeIfAbsent(paths, { CordappLoader(paths) })
}
/**
@ -89,7 +93,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
*/
@VisibleForTesting
fun createWithTestPackages(testPackages: List<String>)
= CordappLoader(testPackages.flatMap(this::createScanPackage))
= cordappLoadersCache.computeIfAbsent(testPackages, { CordappLoader(testPackages.flatMap(this::createScanPackage)) })
/**
* Creates a dev mode CordappLoader intended only to be used in test environments
@ -242,9 +246,12 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
return scanResult.getClassesWithSuperclass(MappedSchema::class).toSet()
}
private val cachedScanResult = LRUMap<RestrictedURL, RestrictedScanResult>(1000)
private fun scanCordapp(cordappJarPath: RestrictedURL): RestrictedScanResult {
logger.info("Scanning CorDapp in $cordappJarPath")
return RestrictedScanResult(FastClasspathScanner().addClassLoader(appClassLoader).overrideClasspath(cordappJarPath.url).scan(), cordappJarPath.qualifiedNamePrefix)
return cachedScanResult.computeIfAbsent(cordappJarPath, {
RestrictedScanResult(FastClasspathScanner().addClassLoader(appClassLoader).overrideClasspath(cordappJarPath.url).scan(), cordappJarPath.qualifiedNamePrefix)
})
}
private class FlowTypeHierarchyComparator(val initiatingFlow: Class<out FlowLogic<*>>) : Comparator<Class<out FlowLogic<*>>> {

View File

@ -2,18 +2,24 @@ package net.corda.node.internal.cordapp
import com.google.common.collect.HashBiMap
import net.corda.core.contracts.ContractClassName
import net.corda.core.crypto.SecureHash
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.cordapp.Cordapp
import net.corda.core.cordapp.CordappContext
import net.corda.core.crypto.SecureHash
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.loggerFor
import java.net.URL
/**
* Cordapp provider and store. For querying CorDapps for their attachment and vice versa.
*/
open class CordappProviderImpl(private val cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage) : SingletonSerializeAsToken(), CordappProviderInternal {
companion object {
private val log = loggerFor<CordappProviderImpl>()
}
override fun getAppContext(): CordappContext {
// TODO: Use better supported APIs in Java 9
Exception().stackTrace.forEach { stackFrame ->
@ -45,7 +51,7 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader, attachm
private fun loadContractsIntoAttachmentStore(attachmentStorage: AttachmentStorage): Map<SecureHash, URL> {
val cordappsWithAttachments = cordapps.filter { !it.contractClassNames.isEmpty() }.map { it.jarPath }
val attachmentIds = cordappsWithAttachments.map { it.openStream().use { attachmentStorage.importAttachment(it) } }
val attachmentIds = cordappsWithAttachments.map { it.openStream().use { attachmentStorage.importOrGetAttachment(it) }}
return attachmentIds.zip(cordappsWithAttachments).toMap()
}

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