mirror of
https://github.com/corda/corda.git
synced 2025-05-04 09:43:05 +00:00
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:
commit
5cf7ecd9ed
@ -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 List networkMapSnapshot()
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NodeInfo nodeInfo()
|
@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)
|
@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.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.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)
|
@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 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
|
@net.corda.core.DoNotImplement public interface net.corda.core.messaging.RPCOps
|
||||||
public abstract int getProtocolVersion()
|
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.FlowHandle startFlow(net.corda.core.flows.FlowLogic)
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowProgressHandle startTrackedFlow(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
|
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NodeInfo extends java.lang.Object
|
||||||
public <init>(List, List, int, long)
|
public <init>(List, List, int, long)
|
||||||
@org.jetbrains.annotations.NotNull public final List component1()
|
@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 final boolean isLegalIdentity(net.corda.core.identity.Party)
|
||||||
public String toString()
|
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
|
@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)
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey)
|
@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.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.services.KeyManagementService getKeyManagementService()
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NodeInfo getMyInfo()
|
@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.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.TransactionVerifierService getTransactionVerifierService()
|
||||||
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.TransactionStorage getValidatedTransactions()
|
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.TransactionStorage getValidatedTransactions()
|
||||||
|
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@ -43,6 +43,8 @@
|
|||||||
<module name="docs_source_example-code_main" target="1.8" />
|
<module name="docs_source_example-code_main" target="1.8" />
|
||||||
<module name="docs_source_example-code_test" target="1.8" />
|
<module name="docs_source_example-code_test" target="1.8" />
|
||||||
<module name="docs_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_integrationTest" target="1.8" />
|
||||||
<module name="example-code_main" target="1.8" />
|
<module name="example-code_main" target="1.8" />
|
||||||
<module name="example-code_test" target="1.8" />
|
<module name="example-code_test" target="1.8" />
|
||||||
|
@ -48,6 +48,7 @@ buildscript {
|
|||||||
ext.dependency_checker_version = '3.0.1'
|
ext.dependency_checker_version = '3.0.1'
|
||||||
ext.commons_collections_version = '4.1'
|
ext.commons_collections_version = '4.1'
|
||||||
ext.beanutils_version = '1.9.3'
|
ext.beanutils_version = '1.9.3'
|
||||||
|
ext.crash_version = 'faba68332800f21278c5b600bf14ad55cef5989e'
|
||||||
|
|
||||||
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
|
// Update 121 is required for ObjectInputFilter and at time of writing 131 was latest:
|
||||||
ext.java8_minUpdateVersion = '131'
|
ext.java8_minUpdateVersion = '131'
|
||||||
|
@ -7,7 +7,7 @@ import com.google.common.collect.HashMultimap
|
|||||||
import com.google.common.collect.Multimap
|
import com.google.common.collect.Multimap
|
||||||
import net.corda.client.jackson.StringToMethodCallParser.ParsedMethodCall
|
import net.corda.client.jackson.StringToMethodCallParser.ParsedMethodCall
|
||||||
import net.corda.core.CordaException
|
import net.corda.core.CordaException
|
||||||
import org.slf4j.LoggerFactory
|
import net.corda.core.utilities.contextLogger
|
||||||
import java.lang.reflect.Constructor
|
import java.lang.reflect.Constructor
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import java.util.concurrent.Callable
|
import java.util.concurrent.Callable
|
||||||
@ -90,7 +90,7 @@ open class StringToMethodCallParser<in T : Any> @JvmOverloads constructor(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private val log = LoggerFactory.getLogger(StringToMethodCallParser::class.java)!!
|
private val log = contextLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The methods that can be invoked via this parser. */
|
/** The methods that can be invoked via this parser. */
|
||||||
|
@ -64,7 +64,7 @@ class NodeMonitorModelTest {
|
|||||||
invokeRpc(CordaRPCOps::stateMachinesFeed),
|
invokeRpc(CordaRPCOps::stateMachinesFeed),
|
||||||
invokeRpc(CordaRPCOps::networkMapFeed))
|
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
|
aliceNode = aliceNodeHandle.nodeInfo
|
||||||
newNode = { nodeName -> startNode(providedName = nodeName).getOrThrow().nodeInfo }
|
newNode = { nodeName -> startNode(providedName = nodeName).getOrThrow().nodeInfo }
|
||||||
val monitor = NodeMonitorModel()
|
val monitor = NodeMonitorModel()
|
||||||
@ -79,7 +79,7 @@ class NodeMonitorModelTest {
|
|||||||
rpc = monitor.proxyObservable.value!!
|
rpc = monitor.proxyObservable.value!!
|
||||||
notaryParty = defaultNotaryIdentity
|
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
|
bobNode = bobNodeHandle.nodeInfo
|
||||||
val monitorBob = NodeMonitorModel()
|
val monitorBob = NodeMonitorModel()
|
||||||
stateMachineUpdatesBob = monitorBob.stateMachineUpdates.bufferUntilSubscribed()
|
stateMachineUpdatesBob = monitorBob.stateMachineUpdates.bufferUntilSubscribed()
|
||||||
@ -91,20 +91,20 @@ class NodeMonitorModelTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `network map update`() = setup {
|
fun `network map update`() = setup {
|
||||||
val charlieNode = newNode(CHARLIE.name)
|
val charlieNode = newNode(CHARLIE_NAME)
|
||||||
val nonServiceIdentities = aliceNode.legalIdentitiesAndCerts + bobNode.legalIdentitiesAndCerts + charlieNode.legalIdentitiesAndCerts
|
val nonServiceIdentities = aliceNode.legalIdentitiesAndCerts + bobNode.legalIdentitiesAndCerts + charlieNode.legalIdentitiesAndCerts
|
||||||
networkMapUpdates.filter { it.node.legalIdentitiesAndCerts.any { it in nonServiceIdentities } }
|
networkMapUpdates.filter { it.node.legalIdentitiesAndCerts.any { it in nonServiceIdentities } }
|
||||||
.expectEvents(isStrict = false) {
|
.expectEvents(isStrict = false) {
|
||||||
sequence(
|
sequence(
|
||||||
// TODO : Add test for remove when driver DSL support individual node shutdown.
|
// TODO : Add test for remove when driver DSL support individual node shutdown.
|
||||||
expect { output: NetworkMapCache.MapChange ->
|
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 ->
|
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 ->
|
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)}" }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package net.corda.client.rpc
|
|||||||
|
|
||||||
import net.corda.core.context.*
|
import net.corda.core.context.*
|
||||||
import net.corda.core.crypto.random63BitValue
|
import net.corda.core.crypto.random63BitValue
|
||||||
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.concurrent.flatMap
|
import net.corda.core.internal.concurrent.flatMap
|
||||||
import net.corda.core.internal.packageName
|
import net.corda.core.internal.packageName
|
||||||
import net.corda.core.messaging.*
|
import net.corda.core.messaging.*
|
||||||
@ -28,7 +29,6 @@ import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
|||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.reflect.KClass
|
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
@ -42,6 +42,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
|
|||||||
invokeRpc("vaultQueryByCriteria"))
|
invokeRpc("vaultQueryByCriteria"))
|
||||||
)
|
)
|
||||||
private lateinit var node: StartedNode<Node>
|
private lateinit var node: StartedNode<Node>
|
||||||
|
private lateinit var identity: Party
|
||||||
private lateinit var client: CordaRPCClient
|
private lateinit var client: CordaRPCClient
|
||||||
private var connection: CordaRPCConnection? = null
|
private var connection: CordaRPCConnection? = null
|
||||||
|
|
||||||
@ -51,8 +52,9 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
node = startNode(ALICE.name, rpcUsers = listOf(rpcUser))
|
node = startNode(ALICE_NAME, rpcUsers = listOf(rpcUser))
|
||||||
client = CordaRPCClient(node.internals.configuration.rpcAddress!!)
|
client = CordaRPCClient(node.internals.configuration.rpcAddress!!)
|
||||||
|
identity = node.info.identityFromX500Name(ALICE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -86,7 +88,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
|
|||||||
println("Creating proxy")
|
println("Creating proxy")
|
||||||
println("Starting flow")
|
println("Starting flow")
|
||||||
val flowHandle = connection!!.proxy.startTrackedFlow(::CashIssueFlow,
|
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")
|
println("Started flow, waiting on result")
|
||||||
flowHandle.progress.subscribe {
|
flowHandle.progress.subscribe {
|
||||||
@ -98,7 +100,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
|
|||||||
@Test
|
@Test
|
||||||
fun `sub-type of FlowException thrown by flow`() {
|
fun `sub-type of FlowException thrown by flow`() {
|
||||||
login(rpcUser.username, rpcUser.password)
|
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 {
|
assertThatExceptionOfType(CashException::class.java).isThrownBy {
|
||||||
handle.returnValue.getOrThrow()
|
handle.returnValue.getOrThrow()
|
||||||
}
|
}
|
||||||
@ -107,7 +109,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C
|
|||||||
@Test
|
@Test
|
||||||
fun `check basic flow has no progress`() {
|
fun `check basic flow has no progress`() {
|
||||||
login(rpcUser.username, rpcUser.password)
|
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<*>)
|
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")
|
assertTrue(startCash.isEmpty(), "Should not start with any cash")
|
||||||
|
|
||||||
val flowHandle = proxy.startFlow(::CashIssueFlow,
|
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")
|
println("Started issuing cash, waiting on result")
|
||||||
flowHandle.returnValue.get()
|
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)
|
val impersonatedActor = Actor(Actor.Id("Mark Dadada"), AuthServiceId("Test"), owningLegalIdentity = BOB.name)
|
||||||
login(rpcUser.username, rpcUser.password, externalTrace, impersonatedActor)
|
login(rpcUser.username, rpcUser.password, externalTrace, impersonatedActor)
|
||||||
val proxy = connection!!.proxy
|
val proxy = connection!!.proxy
|
||||||
val nodeIdentity = node.info.chooseIdentity()
|
|
||||||
|
|
||||||
val updates = proxy.stateMachinesFeed().updates
|
val updates = proxy.stateMachinesFeed().updates
|
||||||
|
|
||||||
node.services.startFlow(CashIssueFlow(2000.DOLLARS, OpaqueBytes.of(0), nodeIdentity), InvocationContext.shell()).flatMap { it.resultFuture }.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), nodeIdentity).returnValue.getOrThrow()
|
proxy.startFlow(::CashIssueFlow, 123.DOLLARS, OpaqueBytes.of(0), identity).returnValue.getOrThrow()
|
||||||
proxy.startFlowDynamic(CashIssueFlow::class.java, 1000.DOLLARS, OpaqueBytes.of(0), nodeIdentity).returnValue.getOrThrow()
|
proxy.startFlowDynamic(CashIssueFlow::class.java, 1000.DOLLARS, OpaqueBytes.of(0), identity).returnValue.getOrThrow()
|
||||||
|
|
||||||
val historicalIds = mutableSetOf<Trace.InvocationId>()
|
val historicalIds = mutableSetOf<Trace.InvocationId>()
|
||||||
var sessionId: Trace.SessionId? = null
|
var sessionId: Trace.SessionId? = null
|
||||||
|
@ -6,16 +6,12 @@ import net.corda.core.context.Trace
|
|||||||
import net.corda.core.crypto.random63BitValue
|
import net.corda.core.crypto.random63BitValue
|
||||||
import net.corda.core.internal.concurrent.fork
|
import net.corda.core.internal.concurrent.fork
|
||||||
import net.corda.core.internal.concurrent.transpose
|
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.messaging.RPCOps
|
||||||
import net.corda.core.serialization.SerializationDefaults
|
import net.corda.core.serialization.SerializationDefaults
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.utilities.*
|
import net.corda.core.utilities.*
|
||||||
import net.corda.node.services.Permissions.Companion.invokeRpc
|
|
||||||
import net.corda.node.services.messaging.RPCServerConfiguration
|
import net.corda.node.services.messaging.RPCServerConfiguration
|
||||||
import net.corda.nodeapi.RPCApi
|
import net.corda.nodeapi.RPCApi
|
||||||
import net.corda.nodeapi.User
|
|
||||||
import net.corda.testing.driver.poll
|
import net.corda.testing.driver.poll
|
||||||
import net.corda.testing.internal.*
|
import net.corda.testing.internal.*
|
||||||
import org.apache.activemq.artemis.api.core.SimpleString
|
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 {
|
interface TrackSubscriberOps : RPCOps {
|
||||||
fun subscribe(): Observable<Unit>
|
fun subscribe(): Observable<Unit>
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,7 @@ import net.corda.core.messaging.RPCOps
|
|||||||
import net.corda.core.serialization.SerializationContext
|
import net.corda.core.serialization.SerializationContext
|
||||||
import net.corda.core.serialization.SerializationDefaults
|
import net.corda.core.serialization.SerializationDefaults
|
||||||
import net.corda.core.serialization.internal.nodeSerializationEnv
|
import net.corda.core.serialization.internal.nodeSerializationEnv
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.*
|
||||||
import net.corda.core.utilities.loggerFor
|
|
||||||
import net.corda.core.utilities.minutes
|
|
||||||
import net.corda.core.utilities.seconds
|
|
||||||
import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport
|
import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport
|
||||||
import net.corda.nodeapi.ConnectionDirection
|
import net.corda.nodeapi.ConnectionDirection
|
||||||
import net.corda.nodeapi.RPCApi
|
import net.corda.nodeapi.RPCApi
|
||||||
@ -96,7 +93,7 @@ class RPCClient<I : RPCOps>(
|
|||||||
) : this(tcpTransport(ConnectionDirection.Outbound(), hostAndPort, sslConfiguration), configuration, serializationContext)
|
) : this(tcpTransport(ConnectionDirection.Outbound(), hostAndPort, sslConfiguration), configuration, serializationContext)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = loggerFor<RPCClient<*>>()
|
private val log = contextLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(
|
fun start(
|
||||||
|
@ -22,10 +22,7 @@ import net.corda.core.internal.ThreadBox
|
|||||||
import net.corda.core.messaging.RPCOps
|
import net.corda.core.messaging.RPCOps
|
||||||
import net.corda.core.serialization.SerializationContext
|
import net.corda.core.serialization.SerializationContext
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.utilities.Try
|
import net.corda.core.utilities.*
|
||||||
import net.corda.core.utilities.debug
|
|
||||||
import net.corda.core.utilities.getOrThrow
|
|
||||||
import net.corda.core.utilities.loggerFor
|
|
||||||
import net.corda.nodeapi.ArtemisConsumer
|
import net.corda.nodeapi.ArtemisConsumer
|
||||||
import net.corda.nodeapi.ArtemisProducer
|
import net.corda.nodeapi.ArtemisProducer
|
||||||
import net.corda.nodeapi.RPCApi
|
import net.corda.nodeapi.RPCApi
|
||||||
@ -91,7 +88,7 @@ class RPCClientProxyHandler(
|
|||||||
private val lifeCycle = LifeCycle(State.UNSTARTED)
|
private val lifeCycle = LifeCycle(State.UNSTARTED)
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
val log = loggerFor<RPCClientProxyHandler>()
|
private val log = contextLogger()
|
||||||
// To check whether toString() is being invoked
|
// To check whether toString() is being invoked
|
||||||
val toStringMethod: Method = Object::toString.javaMethod!!
|
val toStringMethod: Method = Object::toString.javaMethod!!
|
||||||
|
|
||||||
|
@ -11,10 +11,7 @@ import net.corda.core.messaging.*
|
|||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.services.Vault
|
import net.corda.core.node.services.Vault
|
||||||
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.*
|
||||||
import net.corda.core.utilities.getOrThrow
|
|
||||||
import net.corda.core.utilities.loggerFor
|
|
||||||
import net.corda.core.utilities.seconds
|
|
||||||
import net.corda.finance.DOLLARS
|
import net.corda.finance.DOLLARS
|
||||||
import net.corda.finance.POUNDS
|
import net.corda.finance.POUNDS
|
||||||
import net.corda.finance.SWISS_FRANCS
|
import net.corda.finance.SWISS_FRANCS
|
||||||
@ -45,7 +42,7 @@ import kotlin.test.assertTrue
|
|||||||
|
|
||||||
class StandaloneCordaRPClientTest {
|
class StandaloneCordaRPClientTest {
|
||||||
private companion object {
|
private companion object {
|
||||||
val log = loggerFor<StandaloneCordaRPClientTest>()
|
private val log = contextLogger()
|
||||||
val user = User("user1", "test", permissions = setOf("ALL"))
|
val user = User("user1", "test", permissions = setOf("ALL"))
|
||||||
val port = AtomicInteger(15200)
|
val port = AtomicInteger(15200)
|
||||||
const val attachmentSize = 2116
|
const val attachmentSize = 2116
|
||||||
|
@ -3,10 +3,12 @@ package net.corda.confidential
|
|||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.AnonymousParty
|
import net.corda.core.identity.AnonymousParty
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.identity.PartyAndCertificate
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.testing.*
|
import net.corda.testing.*
|
||||||
import net.corda.testing.node.MockNetwork
|
import net.corda.testing.node.MockNetwork
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
|
import net.corda.testing.node.MockNodeParameters
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
|
|
||||||
@ -83,28 +85,30 @@ class SwapIdentitiesFlowTests {
|
|||||||
val notaryNode = mockNet.defaultNotaryNode
|
val notaryNode = mockNet.defaultNotaryNode
|
||||||
val aliceNode = mockNet.createPartyNode(ALICE.name)
|
val aliceNode = mockNet.createPartyNode(ALICE.name)
|
||||||
val bobNode = mockNet.createPartyNode(BOB.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
|
// Check that the wrong signature is rejected
|
||||||
notaryNode.database.transaction {
|
notaryNode.database.transaction {
|
||||||
notaryNode.services.keyManagementService.freshKeyAndCert(notaryNode.services.myInfo.chooseIdentityAndCert(), false)
|
notaryNode.services.keyManagementService.freshKeyAndCert(notary, false)
|
||||||
}.let { anonymousNotary ->
|
}.let { anonymousNotary ->
|
||||||
val sigData = SwapIdentitiesFlow.buildDataToSign(anonymousNotary)
|
val sigData = SwapIdentitiesFlow.buildDataToSign(anonymousNotary)
|
||||||
val signature = notaryNode.services.keyManagementService.sign(sigData, anonymousNotary.owningKey)
|
val signature = notaryNode.services.keyManagementService.sign(sigData, anonymousNotary.owningKey)
|
||||||
assertFailsWith<SwapIdentitiesException>("Signature does not match the given identity and nonce") {
|
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
|
// Check that the right signing key, but wrong identity is rejected
|
||||||
val anonymousAlice = aliceNode.database.transaction {
|
val anonymousAlice: PartyAndCertificate = aliceNode.database.transaction {
|
||||||
aliceNode.services.keyManagementService.freshKeyAndCert(aliceNode.services.myInfo.chooseIdentityAndCert(), false)
|
aliceNode.services.keyManagementService.freshKeyAndCert(alice, false)
|
||||||
}
|
}
|
||||||
bobNode.database.transaction {
|
bobNode.database.transaction {
|
||||||
bobNode.services.keyManagementService.freshKeyAndCert(bobNode.services.myInfo.chooseIdentityAndCert(), false)
|
bobNode.services.keyManagementService.freshKeyAndCert(bob, false)
|
||||||
}.let { anonymousBob ->
|
}.let { anonymousBob ->
|
||||||
val sigData = SwapIdentitiesFlow.buildDataToSign(anonymousAlice)
|
val sigData = SwapIdentitiesFlow.buildDataToSign(anonymousAlice)
|
||||||
val signature = bobNode.services.keyManagementService.sign(sigData, anonymousBob.owningKey)
|
val signature = bobNode.services.keyManagementService.sign(sigData, anonymousBob.owningKey)
|
||||||
assertFailsWith<SwapIdentitiesException>("Signature does not match the given identity and nonce.") {
|
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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
gradlePluginsVersion=2.0.8
|
gradlePluginsVersion=2.0.9
|
||||||
kotlinVersion=1.1.50
|
kotlinVersion=1.1.60
|
||||||
guavaVersion=21.0
|
guavaVersion=21.0
|
||||||
bouncycastleVersion=1.57
|
bouncycastleVersion=1.57
|
||||||
typesafeConfigVersion=1.3.1
|
typesafeConfigVersion=1.3.1
|
@ -117,6 +117,9 @@ dependencies {
|
|||||||
|
|
||||||
// JPA 2.1 annotations.
|
// JPA 2.1 annotations.
|
||||||
compile "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final"
|
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)
|
// TODO Consider moving it to quasar-utils in the future (introduced with PR-1388)
|
||||||
|
@ -45,9 +45,17 @@ interface NamedByHash {
|
|||||||
*/
|
*/
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class Issued<out P : Any>(val issuer: PartyAndReference, val product: P) {
|
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"
|
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
|
* 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
|
* cares about specific issuers with code that will accept any, or which is imposing issuer constraints via some
|
||||||
|
@ -103,3 +103,5 @@ fun ByteArray.sha256(): SecureHash.SHA256 = SecureHash.sha256(this)
|
|||||||
* Compute the SHA-256 hash for the contents of the [OpaqueBytes].
|
* Compute the SHA-256 hash for the contents of the [OpaqueBytes].
|
||||||
*/
|
*/
|
||||||
fun OpaqueBytes.sha256(): SecureHash.SHA256 = SecureHash.sha256(this.bytes)
|
fun OpaqueBytes.sha256(): SecureHash.SHA256 = SecureHash.sha256(this.bytes)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
* 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
|
* sent to the vault for processing. Note that this call suspends until the transaction is considered
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package net.corda.core.internal
|
package net.corda.core.internal
|
||||||
|
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.contextLogger
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
import kotlin.reflect.KProperty
|
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. */
|
/** The named thread has leaked from a previous test. */
|
||||||
class ThreadLeakException : RuntimeException("Leaked thread detected: ${Thread.currentThread().name}")
|
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,
|
class InheritableThreadLocalToggleField<T>(name: String,
|
||||||
private val log: Logger = loggerFor<InheritableThreadLocalToggleField<*>>(),
|
private val log: Logger = staticLog,
|
||||||
private val exceptionHandler: (ThreadLeakException) -> Unit = { throw it }) : ToggleField<T>(name) {
|
private val isAGlobalThreadBeingCreated: (Array<StackTraceElement>) -> Boolean) : ToggleField<T>(name) {
|
||||||
|
companion object {
|
||||||
|
private val staticLog = contextLogger()
|
||||||
|
}
|
||||||
|
|
||||||
private inner class Holder(value: T) : AtomicReference<T?>(value) {
|
private inner class Holder(value: T) : AtomicReference<T?>(value) {
|
||||||
fun valueOrDeclareLeak() = get() ?: throw ThreadLeakException()
|
fun valueOrDeclareLeak() = get() ?: throw ThreadLeakException()
|
||||||
fun childValue(): Holder? {
|
fun childValue(): Holder? {
|
||||||
get() != null && return this // Current thread isn't leaked.
|
val e = ThreadLeakException() // Expensive, but so is starting the new thread.
|
||||||
val e = ThreadLeakException()
|
return if (isAGlobalThreadBeingCreated(e.stackTrace)) {
|
||||||
exceptionHandler(e)
|
get() ?: log.warn(e.message)
|
||||||
log.warn(e.message)
|
null
|
||||||
return null
|
} else {
|
||||||
|
get() ?: log.error(e.message)
|
||||||
|
this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,8 +3,8 @@ package net.corda.core.internal.concurrent
|
|||||||
import net.corda.core.internal.VisibleForTesting
|
import net.corda.core.internal.VisibleForTesting
|
||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
import net.corda.core.concurrent.match
|
import net.corda.core.concurrent.match
|
||||||
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.core.utilities.loggerFor
|
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.concurrent.CompletableFuture
|
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.
|
* 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.
|
* 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
|
@VisibleForTesting
|
||||||
internal class CordaFutureImpl<V>(private val impl: CompletableFuture<V> = CompletableFuture()) : Future<V> by impl, OpenFuture<V> {
|
internal class CordaFutureImpl<V>(private val impl: CompletableFuture<V> = CompletableFuture()) : Future<V> by impl, OpenFuture<V> {
|
||||||
companion object {
|
companion object {
|
||||||
private val defaultLog = loggerFor<CordaFutureImpl<*>>()
|
private val defaultLog = contextLogger()
|
||||||
internal val listenerFailedMessage = "Future listener failed:"
|
internal val listenerFailedMessage = "Future listener failed:"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,10 +226,6 @@ interface CordaRPCOps : RPCOps {
|
|||||||
/** Returns Node's NodeInfo, assuming this will not change while the node is running. */
|
/** Returns Node's NodeInfo, assuming this will not change while the node is running. */
|
||||||
fun nodeInfo(): NodeInfo
|
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.
|
* 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
|
@CordaSerializable
|
||||||
data class DataFeed<out A, B>(val snapshot: A, val updates: Observable<B>)
|
data class DataFeed<out A, B>(val snapshot: A, val updates: Observable<B>)
|
||||||
|
|
||||||
@CordaSerializable
|
|
||||||
enum class NodeState {
|
|
||||||
SHUTTING_DOWN
|
|
||||||
}
|
|
@ -31,6 +31,9 @@ interface FlowHandle<A> : AutoCloseable {
|
|||||||
interface FlowProgressHandle<A> : FlowHandle<A> {
|
interface FlowProgressHandle<A> : FlowHandle<A> {
|
||||||
val progress: Observable<String>
|
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
|
* Use this function for flows whose returnValue and progress are not going to be used or tracked, so as to free up
|
||||||
* server resources.
|
* server resources.
|
||||||
@ -52,10 +55,17 @@ data class FlowHandleImpl<A>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class FlowProgressHandleImpl<A>(
|
data class FlowProgressHandleImpl<A> @JvmOverloads constructor(
|
||||||
override val id: StateMachineRunId,
|
override val id: StateMachineRunId,
|
||||||
override val returnValue: CordaFuture<A>,
|
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.
|
// Remember to add @Throws to FlowProgressHandle.close() if this throws an exception.
|
||||||
override fun close() {
|
override fun close() {
|
||||||
|
@ -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)
|
|
@ -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. */
|
/** Returns true if [party] is one of the identities of this node, else false. */
|
||||||
fun isLegalIdentity(party: Party): Boolean = party in legalIdentities
|
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\"")
|
* Get a legal identity of this node from the X.500 name. This is intended for use in cases where the node is
|
||||||
return identity.party
|
* 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\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,13 +8,11 @@ import net.corda.core.crypto.SignableData
|
|||||||
import net.corda.core.crypto.SignatureMetadata
|
import net.corda.core.crypto.SignatureMetadata
|
||||||
import net.corda.core.crypto.TransactionSignature
|
import net.corda.core.crypto.TransactionSignature
|
||||||
import net.corda.core.flows.ContractUpgradeFlow
|
import net.corda.core.flows.ContractUpgradeFlow
|
||||||
import net.corda.core.messaging.NodeState
|
|
||||||
import net.corda.core.node.services.*
|
import net.corda.core.node.services.*
|
||||||
import net.corda.core.serialization.SerializeAsToken
|
import net.corda.core.serialization.SerializeAsToken
|
||||||
import net.corda.core.transactions.FilteredTransaction
|
import net.corda.core.transactions.FilteredTransaction
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import rx.Observable
|
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
@ -150,9 +148,6 @@ interface ServiceHub : ServicesForResolution {
|
|||||||
/** The [NodeInfo] object corresponding to our own entry in the network map. */
|
/** The [NodeInfo] object corresponding to our own entry in the network map. */
|
||||||
val myInfo: NodeInfo
|
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
|
* 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.
|
* [CordaService] and will have automatically been registered by the node.
|
||||||
|
@ -45,6 +45,13 @@ interface AttachmentStorage {
|
|||||||
@Throws(FileAlreadyExistsException::class, IOException::class)
|
@Throws(FileAlreadyExistsException::class, IOException::class)
|
||||||
fun importAttachment(jar: InputStream, uploader: String, filename: String): AttachmentId
|
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
|
* Searches attachment using given criteria and optional sort rules
|
||||||
* @param criteria Query criteria to use as a filter
|
* @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
|
* @return List of AttachmentId of attachment matching criteria, sorted according to given sorting parameter
|
||||||
*/
|
*/
|
||||||
fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort? = null): List<AttachmentId>
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
* 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>
|
val notaryIdentities: List<Party>
|
||||||
// DOCEND 1
|
// DOCEND 1
|
||||||
|
|
||||||
@ -116,7 +117,7 @@ interface NetworkMapCacheBase {
|
|||||||
fun getNotary(name: CordaX500Name): Party? = notaryIdentities.firstOrNull { it.name == name }
|
fun getNotary(name: CordaX500Name): Party? = notaryIdentities.firstOrNull { it.name == name }
|
||||||
// DOCEND 2
|
// 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
|
fun isNotary(party: Party): Boolean = party in notaryIdentities
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -9,7 +9,7 @@ import net.corda.core.identity.Party
|
|||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.contextLogger
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import java.security.PublicKey
|
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.
|
* of the cluster is sufficient for transaction notarisation. For example, a single-node or a Raft notary.
|
||||||
*/
|
*/
|
||||||
abstract class TrustedAuthorityNotaryService : NotaryService() {
|
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
|
// TODO: specify the valid time window in config, and convert TimeWindowChecker to a utility method
|
||||||
protected abstract val timeWindowChecker: TimeWindowChecker
|
protected abstract val timeWindowChecker: TimeWindowChecker
|
||||||
protected abstract val uniquenessProvider: UniquenessProvider
|
protected abstract val uniquenessProvider: UniquenessProvider
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package net.corda.core.schemas
|
package net.corda.core.schemas
|
||||||
|
|
||||||
import net.corda.core.contracts.ContractState
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.contracts.FungibleAsset
|
|
||||||
import net.corda.core.contracts.OwnableState
|
|
||||||
import net.corda.core.contracts.UniqueIdentifier
|
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
|
import org.hibernate.annotations.Type
|
||||||
import java.util.*
|
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
|
* JPA representation of the common schema entities
|
||||||
@ -74,7 +74,8 @@ object CommonSchemaV1 : MappedSchema(schemaFamily = CommonSchema.javaClass, vers
|
|||||||
@Column(name = "issuer_name")
|
@Column(name = "issuer_name")
|
||||||
var issuer: AbstractParty,
|
var issuer: AbstractParty,
|
||||||
|
|
||||||
@Column(name = "issuer_reference")
|
@Column(name = "issuer_ref", length = MAX_ISSUER_REF_SIZE)
|
||||||
|
@Type(type = "corda-wrapper-binary")
|
||||||
var issuerRef: ByteArray
|
var issuerRef: ByteArray
|
||||||
) : PersistentState()
|
) : PersistentState()
|
||||||
}
|
}
|
@ -43,12 +43,12 @@ val _globalSerializationEnv = SimpleToggleField<SerializationEnvironment>("globa
|
|||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
val _contextSerializationEnv = ThreadLocalToggleField<SerializationEnvironment>("contextSerializationEnv")
|
val _contextSerializationEnv = ThreadLocalToggleField<SerializationEnvironment>("contextSerializationEnv")
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
val _inheritableContextSerializationEnv = InheritableThreadLocalToggleField<SerializationEnvironment>("inheritableContextSerializationEnv") suppressInherit@ {
|
val _inheritableContextSerializationEnv = InheritableThreadLocalToggleField<SerializationEnvironment>("inheritableContextSerializationEnv") { stack ->
|
||||||
it.stackTrace.forEach {
|
stack.fold(false) { isAGlobalThreadBeingCreated, e ->
|
||||||
// A dying Netty thread's death event restarting the Netty global executor:
|
isAGlobalThreadBeingCreated ||
|
||||||
it.className == "io.netty.util.concurrent.GlobalEventExecutor" && it.methodName == "startThread" && return@suppressInherit
|
(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)
|
private val serializationEnvProperties = listOf(_nodeSerializationEnv, _globalSerializationEnv, _contextSerializationEnv, _inheritableContextSerializationEnv)
|
||||||
val effectiveSerializationEnv: SerializationEnvironment
|
val effectiveSerializationEnv: SerializationEnvironment
|
||||||
|
@ -14,7 +14,7 @@ import kotlin.reflect.KProperty
|
|||||||
// READ ME FIRST:
|
// READ ME FIRST:
|
||||||
// This is a collection of public utilities useful only for Kotlin code. Think carefully before adding anything here and
|
// 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
|
// 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. */
|
/** 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)
|
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)
|
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. */
|
/** Log a TRACE level message produced by evaluating the given lamdba, but only if TRACE logging is enabled. */
|
||||||
inline fun Logger.trace(msg: () -> String) {
|
inline fun Logger.trace(msg: () -> String) {
|
||||||
if (isTraceEnabled) trace(msg())
|
if (isTraceEnabled) trace(msg())
|
||||||
|
@ -6,9 +6,6 @@ import rx.Subscription
|
|||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
import java.util.*
|
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
|
* 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
|
* 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
|
@CordaSerializable
|
||||||
class ProgressTracker(vararg steps: Step) {
|
class ProgressTracker(vararg steps: Step) {
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
sealed class Change {
|
sealed class Change(val progressTracker: ProgressTracker) {
|
||||||
data class Position(val tracker: ProgressTracker, val newStep: Step) : Change() {
|
data class Position(val tracker: ProgressTracker, val newStep: Step) : Change(tracker) {
|
||||||
override fun toString() = newStep.label
|
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
|
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}"
|
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
|
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
|
@CordaSerializable
|
||||||
private data class Child(val tracker: ProgressTracker, @Transient val subscription: Subscription?)
|
private data class Child(val tracker: ProgressTracker, @Transient val subscription: Subscription?)
|
||||||
|
|
||||||
private val childProgressTrackers = mutableMapOf<Step, Child>()
|
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 {
|
init {
|
||||||
steps.forEach {
|
steps.forEach {
|
||||||
val childTracker = it.childProgressTracker()
|
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) */
|
/** The zero-based index of the current step in the [steps] array (i.e. with UNSTARTED and DONE) */
|
||||||
var stepIndex: Int = 0
|
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
|
* 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()
|
curChangeSubscription?.unsubscribe()
|
||||||
stepIndex = index
|
stepIndex = index
|
||||||
_changes.onNext(Change.Position(this, steps[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. */
|
/** Returns the current step, descending into children to find the deepest step we are up to. */
|
||||||
val currentStepRecursive: Step
|
val currentStepRecursive: Step
|
||||||
get() = getChildProgressTracker(currentStep)?.currentStepRecursive ?: currentStep
|
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 getChildProgressTracker(step: Step): ProgressTracker? = childProgressTrackers[step]?.tracker
|
||||||
|
|
||||||
fun setChildProgressTracker(step: ProgressTracker.Step, childProgressTracker: ProgressTracker) {
|
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)
|
childProgressTrackers[step] = Child(childProgressTracker, subscription)
|
||||||
childProgressTracker.parent = this
|
childProgressTracker.parent = this
|
||||||
_changes.onNext(Change.Structural(this, step))
|
_changes.onNext(Change.Structural(this, step))
|
||||||
|
rebuildStepsTree()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeChildProgressTracker(step: ProgressTracker.Step) {
|
private fun removeChildProgressTracker(step: ProgressTracker.Step) {
|
||||||
@ -142,6 +170,7 @@ class ProgressTracker(vararg steps: Step) {
|
|||||||
it.subscription?.unsubscribe()
|
it.subscription?.unsubscribe()
|
||||||
}
|
}
|
||||||
_changes.onNext(Change.Structural(this, step))
|
_changes.onNext(Change.Structural(this, step))
|
||||||
|
rebuildStepsTree()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -166,6 +195,18 @@ class ProgressTracker(vararg steps: Step) {
|
|||||||
return cursor
|
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>> {
|
private fun _allSteps(level: Int = 0): List<Pair<Int, Step>> {
|
||||||
val result = ArrayList<Pair<Int, Step>>()
|
val result = ArrayList<Pair<Int, Step>>()
|
||||||
for (step in steps) {
|
for (step in steps) {
|
||||||
@ -177,11 +218,15 @@ class ProgressTracker(vararg steps: Step) {
|
|||||||
return result
|
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.
|
* 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.
|
* 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
|
private var curChangeSubscription: Subscription? = null
|
||||||
|
|
||||||
@ -200,8 +245,15 @@ class ProgressTracker(vararg steps: Step) {
|
|||||||
*/
|
*/
|
||||||
val changes: Observable<Change> get() = _changes
|
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 */
|
/** 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()
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,8 +4,8 @@ import co.paralleluniverse.fibers.Suspendable;
|
|||||||
import com.google.common.primitives.Primitives;
|
import com.google.common.primitives.Primitives;
|
||||||
import net.corda.core.identity.Party;
|
import net.corda.core.identity.Party;
|
||||||
import net.corda.node.internal.StartedNode;
|
import net.corda.node.internal.StartedNode;
|
||||||
import net.corda.testing.node.MockNetwork;
|
|
||||||
import net.corda.testing.TestConstants;
|
import net.corda.testing.TestConstants;
|
||||||
|
import net.corda.testing.node.MockNetwork;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -13,7 +13,7 @@ import org.junit.Test;
|
|||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Future;
|
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.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
import static net.corda.testing.NodeTestUtils.startFlow;
|
import static net.corda.testing.NodeTestUtils.startFlow;
|
||||||
@ -22,11 +22,13 @@ public class FlowsInJavaTest {
|
|||||||
private final MockNetwork mockNet = new MockNetwork();
|
private final MockNetwork mockNet = new MockNetwork();
|
||||||
private StartedNode<MockNetwork.MockNode> aliceNode;
|
private StartedNode<MockNetwork.MockNode> aliceNode;
|
||||||
private StartedNode<MockNetwork.MockNode> bobNode;
|
private StartedNode<MockNetwork.MockNode> bobNode;
|
||||||
|
private Party bob;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
aliceNode = mockNet.createPartyNode(TestConstants.getALICE().getName());
|
aliceNode = mockNet.createPartyNode(TestConstants.getALICE().getName());
|
||||||
bobNode = mockNet.createPartyNode(TestConstants.getBOB().getName());
|
bobNode = mockNet.createPartyNode(TestConstants.getBOB().getName());
|
||||||
|
bob = singleIdentity(bobNode.getInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -37,7 +39,7 @@ public class FlowsInJavaTest {
|
|||||||
@Test
|
@Test
|
||||||
public void suspendableActionInsideUnwrap() throws Exception {
|
public void suspendableActionInsideUnwrap() throws Exception {
|
||||||
bobNode.getInternals().registerInitiatedFlow(SendHelloAndThenReceive.class);
|
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();
|
mockNet.runNetwork();
|
||||||
assertThat(result.get()).isEqualTo("Hello");
|
assertThat(result.get()).isEqualTo("Hello");
|
||||||
}
|
}
|
||||||
@ -52,7 +54,7 @@ public class FlowsInJavaTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void primitiveReceiveTypeTest(Class<?> receiveType) throws InterruptedException {
|
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();
|
Future<?> result = startFlow(aliceNode.getServices(), flow).getResultFuture();
|
||||||
mockNet.runNetwork();
|
mockNet.runNetwork();
|
||||||
try {
|
try {
|
||||||
|
@ -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.")
|
||||||
|
}
|
||||||
|
}
|
@ -41,13 +41,20 @@ class ContractUpgradeFlowTest {
|
|||||||
private lateinit var aliceNode: StartedNode<MockNetwork.MockNode>
|
private lateinit var aliceNode: StartedNode<MockNetwork.MockNode>
|
||||||
private lateinit var bobNode: StartedNode<MockNetwork.MockNode>
|
private lateinit var bobNode: StartedNode<MockNetwork.MockNode>
|
||||||
private lateinit var notary: Party
|
private lateinit var notary: Party
|
||||||
|
private lateinit var alice: Party
|
||||||
|
private lateinit var bob: Party
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts", "net.corda.finance.contracts.asset", "net.corda.core.flows"))
|
mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts", "net.corda.finance.contracts.asset", "net.corda.core.flows"))
|
||||||
aliceNode = mockNet.createPartyNode(ALICE.name)
|
aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
||||||
bobNode = mockNet.createPartyNode(BOB.name)
|
bobNode = mockNet.createPartyNode(BOB_NAME)
|
||||||
notary = mockNet.defaultNotaryIdentity
|
notary = mockNet.defaultNotaryIdentity
|
||||||
|
alice = aliceNode.info.singleIdentity()
|
||||||
|
bob = bobNode.info.singleIdentity()
|
||||||
|
|
||||||
|
// Process registration
|
||||||
|
mockNet.runNetwork()
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -58,11 +65,11 @@ class ContractUpgradeFlowTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `2 parties contract upgrade`() {
|
fun `2 parties contract upgrade`() {
|
||||||
// Create dummy contract.
|
// 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 signedByA = aliceNode.services.signInitialTransaction(twoPartyDummyContract)
|
||||||
val stx = bobNode.services.addSignature(signedByA)
|
val stx = bobNode.services.addSignature(signedByA)
|
||||||
|
|
||||||
aliceNode.services.startFlow(FinalityFlow(stx, setOf(bobNode.info.chooseIdentity())))
|
aliceNode.services.startFlow(FinalityFlow(stx, setOf(bob)))
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
|
|
||||||
val atx = aliceNode.database.transaction { aliceNode.services.validatedTransactions.getTransaction(stx.id) }
|
val atx = aliceNode.database.transaction { aliceNode.services.validatedTransactions.getTransaction(stx.id) }
|
||||||
@ -128,7 +135,7 @@ class ContractUpgradeFlowTest {
|
|||||||
fun `2 parties contract upgrade using RPC`() {
|
fun `2 parties contract upgrade using RPC`() {
|
||||||
rpcDriver(initialiseSerialization = false) {
|
rpcDriver(initialiseSerialization = false) {
|
||||||
// Create dummy contract.
|
// 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 signedByA = aliceNode.services.signInitialTransaction(twoPartyDummyContract)
|
||||||
val stx = bobNode.services.addSignature(signedByA)
|
val stx = bobNode.services.addSignature(signedByA)
|
||||||
|
|
||||||
@ -140,7 +147,7 @@ class ContractUpgradeFlowTest {
|
|||||||
))
|
))
|
||||||
val rpcA = startProxy(aliceNode, user)
|
val rpcA = startProxy(aliceNode, user)
|
||||||
val rpcB = startProxy(bobNode, 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()
|
mockNet.runNetwork()
|
||||||
handle.returnValue.getOrThrow()
|
handle.returnValue.getOrThrow()
|
||||||
|
|
||||||
@ -202,7 +209,7 @@ class ContractUpgradeFlowTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `upgrade Cash to v2`() {
|
fun `upgrade Cash to v2`() {
|
||||||
// Create some cash.
|
// 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
|
val result = aliceNode.services.startFlow(CashIssueFlow(Amount(1000, USD), OpaqueBytes.of(1), notary)).resultFuture
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
val stx = result.getOrThrow().stx
|
val stx = result.getOrThrow().stx
|
||||||
|
@ -5,46 +5,45 @@ import net.corda.core.identity.Party
|
|||||||
import net.corda.core.utilities.UntrustworthyData
|
import net.corda.core.utilities.UntrustworthyData
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
import net.corda.testing.chooseIdentity
|
import net.corda.testing.node.MockNetwork
|
||||||
import net.corda.testing.node.network
|
import net.corda.testing.singleIdentity
|
||||||
import net.corda.testing.startFlow
|
import net.corda.testing.startFlow
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.After
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class ReceiveMultipleFlowTests {
|
class ReceiveMultipleFlowTests {
|
||||||
|
private val mockNet = MockNetwork()
|
||||||
|
private val nodes = (0..2).map { mockNet.createPartyNode() }
|
||||||
|
@After
|
||||||
|
fun stopNodes() {
|
||||||
|
mockNet.stopNodes()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `receive all messages in parallel using map style`() {
|
fun `receive all messages in parallel using map style`() {
|
||||||
network(3) { nodes ->
|
val doubleValue = 5.0
|
||||||
val doubleValue = 5.0
|
nodes[1].registerAnswer(AlgorithmDefinition::class, doubleValue)
|
||||||
nodes[1].registerAnswer(AlgorithmDefinition::class, doubleValue)
|
val stringValue = "Thriller"
|
||||||
val stringValue = "Thriller"
|
nodes[2].registerAnswer(AlgorithmDefinition::class, stringValue)
|
||||||
nodes[2].registerAnswer(AlgorithmDefinition::class, stringValue)
|
val flow = nodes[0].services.startFlow(ParallelAlgorithmMap(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity()))
|
||||||
|
mockNet.runNetwork()
|
||||||
val flow = nodes[0].services.startFlow(ParallelAlgorithmMap(nodes[1].info.chooseIdentity(), nodes[2].info.chooseIdentity()))
|
val result = flow.resultFuture.getOrThrow()
|
||||||
runNetwork()
|
assertThat(result).isEqualTo(doubleValue * stringValue.length)
|
||||||
|
|
||||||
val result = flow.resultFuture.getOrThrow()
|
|
||||||
|
|
||||||
assertThat(result).isEqualTo(doubleValue * stringValue.length)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `receive all messages in parallel using list style`() {
|
fun `receive all messages in parallel using list style`() {
|
||||||
network(3) { nodes ->
|
val value1 = 5.0
|
||||||
val value1 = 5.0
|
nodes[1].registerAnswer(ParallelAlgorithmList::class, value1)
|
||||||
nodes[1].registerAnswer(ParallelAlgorithmList::class, value1)
|
val value2 = 6.0
|
||||||
val value2 = 6.0
|
nodes[2].registerAnswer(ParallelAlgorithmList::class, value2)
|
||||||
nodes[2].registerAnswer(ParallelAlgorithmList::class, value2)
|
val flow = nodes[0].services.startFlow(ParallelAlgorithmList(nodes[1].info.singleIdentity(), nodes[2].info.singleIdentity()))
|
||||||
|
mockNet.runNetwork()
|
||||||
val flow = nodes[0].services.startFlow(ParallelAlgorithmList(nodes[1].info.chooseIdentity(), nodes[2].info.chooseIdentity()))
|
val data = flow.resultFuture.getOrThrow()
|
||||||
runNetwork()
|
assertThat(data[0]).isEqualTo(value1)
|
||||||
val data = flow.resultFuture.getOrThrow()
|
assertThat(data[1]).isEqualTo(value2)
|
||||||
|
assertThat(data.fold(1.0) { a, b -> a * b }).isEqualTo(value1 * value2)
|
||||||
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) {
|
class ParallelAlgorithmMap(doubleMember: Party, stringMember: Party) : AlgorithmDefinition(doubleMember, stringMember) {
|
||||||
|
@ -10,9 +10,9 @@ import net.corda.core.utilities.sequence
|
|||||||
import net.corda.node.internal.StartedNode
|
import net.corda.node.internal.StartedNode
|
||||||
import net.corda.testing.MEGA_CORP
|
import net.corda.testing.MEGA_CORP
|
||||||
import net.corda.testing.MINI_CORP
|
import net.corda.testing.MINI_CORP
|
||||||
import net.corda.testing.chooseIdentity
|
|
||||||
import net.corda.testing.contracts.DummyContract
|
import net.corda.testing.contracts.DummyContract
|
||||||
import net.corda.testing.node.MockNetwork
|
import net.corda.testing.node.MockNetwork
|
||||||
|
import net.corda.testing.singleIdentity
|
||||||
import net.corda.testing.startFlow
|
import net.corda.testing.startFlow
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@ -45,8 +45,8 @@ class ResolveTransactionsFlowTest {
|
|||||||
megaCorpNode.internals.registerInitiatedFlow(TestResponseFlow::class.java)
|
megaCorpNode.internals.registerInitiatedFlow(TestResponseFlow::class.java)
|
||||||
miniCorpNode.internals.registerInitiatedFlow(TestResponseFlow::class.java)
|
miniCorpNode.internals.registerInitiatedFlow(TestResponseFlow::class.java)
|
||||||
notary = mockNet.defaultNotaryIdentity
|
notary = mockNet.defaultNotaryIdentity
|
||||||
megaCorp = megaCorpNode.info.chooseIdentity()
|
megaCorp = megaCorpNode.info.singleIdentity()
|
||||||
miniCorp = miniCorpNode.info.chooseIdentity()
|
miniCorp = miniCorpNode.info.singleIdentity()
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -7,7 +7,10 @@ import com.nhaarman.mockito_kotlin.verifyNoMoreInteractions
|
|||||||
import net.corda.core.internal.concurrent.fork
|
import net.corda.core.internal.concurrent.fork
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TestRule
|
||||||
|
import org.junit.runners.model.Statement
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import java.util.concurrent.ExecutorService
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
@ -28,9 +31,34 @@ private fun <T> withSingleThreadExecutor(callable: ExecutorService.() -> T) = Ex
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ToggleFieldTest {
|
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
|
@Test
|
||||||
fun `toggle is enforced`() {
|
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())
|
assertNull(field.get())
|
||||||
assertThatThrownBy { field.set(null) }.isInstanceOf(IllegalStateException::class.java)
|
assertThatThrownBy { field.set(null) }.isInstanceOf(IllegalStateException::class.java)
|
||||||
field.set("hello")
|
field.set("hello")
|
||||||
@ -71,7 +99,7 @@ class ToggleFieldTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `inheritable thread local works`() {
|
fun `inheritable thread local works`() {
|
||||||
val field = InheritableThreadLocalToggleField<String>("field")
|
val field = inheritableThreadLocalToggleField<String>()
|
||||||
assertNull(field.get())
|
assertNull(field.get())
|
||||||
field.set("hello")
|
field.set("hello")
|
||||||
assertEquals("hello", field.get())
|
assertEquals("hello", field.get())
|
||||||
@ -84,7 +112,7 @@ class ToggleFieldTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `existing threads do not inherit`() {
|
fun `existing threads do not inherit`() {
|
||||||
val field = InheritableThreadLocalToggleField<String>("field")
|
val field = inheritableThreadLocalToggleField<String>()
|
||||||
withSingleThreadExecutor {
|
withSingleThreadExecutor {
|
||||||
field.set("hello")
|
field.set("hello")
|
||||||
assertEquals("hello", field.get())
|
assertEquals("hello", field.get())
|
||||||
@ -93,16 +121,8 @@ class ToggleFieldTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `with default exception handler, inherited values are poisoned on clear`() {
|
fun `inherited values are poisoned on clear`() {
|
||||||
`inherited values are poisoned on clear`(InheritableThreadLocalToggleField("field") { throw it })
|
val field = inheritableThreadLocalToggleField<String>()
|
||||||
}
|
|
||||||
|
|
||||||
@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>) {
|
|
||||||
field.set("hello")
|
field.set("hello")
|
||||||
withSingleThreadExecutor {
|
withSingleThreadExecutor {
|
||||||
assertEquals("hello", fork(field::get).getOrThrow())
|
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
|
@Test
|
||||||
fun `with default exception handler, leaked thread is detected as soon as it tries to create another`() {
|
fun `leaked thread propagates holder to non-global thread, with error`() {
|
||||||
val field = InheritableThreadLocalToggleField<String>("field") { throw it }
|
val field = inheritableThreadLocalToggleField<String>()
|
||||||
field.set("hello")
|
field.set("hello")
|
||||||
withSingleThreadExecutor {
|
withSingleThreadExecutor {
|
||||||
assertEquals("hello", fork(field::get).getOrThrow())
|
assertEquals("hello", fork(field::get).getOrThrow())
|
||||||
field.set(null) // The executor thread is now considered leaked.
|
field.set(null) // The executor thread is now considered leaked.
|
||||||
val threadName = fork { Thread.currentThread().name }.getOrThrow()
|
fork {
|
||||||
val future = fork(::Thread)
|
val leakedThreadName = Thread.currentThread().name
|
||||||
assertThatThrownBy { future.getOrThrow() }
|
verifyNoMoreInteractions(log)
|
||||||
.isInstanceOf(ThreadLeakException::class.java)
|
withSingleThreadExecutor {
|
||||||
.hasMessageContaining(threadName)
|
// 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
|
@Test
|
||||||
fun `with lenient exception handler, leaked thread logs a warning and does not propagate the holder`() {
|
fun `leaked thread does not propagate holder to global thread, with warning`() {
|
||||||
val log = mock<Logger>()
|
val field = inheritableThreadLocalToggleField<String>()
|
||||||
val field = InheritableThreadLocalToggleField<String>("field", log) {}
|
|
||||||
field.set("hello")
|
field.set("hello")
|
||||||
withSingleThreadExecutor {
|
withSingleThreadExecutor {
|
||||||
assertEquals("hello", fork(field::get).getOrThrow())
|
assertEquals("hello", fork(field::get).getOrThrow())
|
||||||
field.set(null) // The executor thread is now considered leaked.
|
field.set(null) // The executor thread is now considered leaked.
|
||||||
val threadName = fork { Thread.currentThread().name }.getOrThrow()
|
|
||||||
fork {
|
fork {
|
||||||
verifyNoMoreInteractions(log)
|
val leakedThreadName = Thread.currentThread().name
|
||||||
withSingleThreadExecutor {
|
globalThreadCreationMethod {
|
||||||
verify(log).warn(argThat { contains(threadName) })
|
verifyNoMoreInteractions(log)
|
||||||
// In practice the new thread is for example a static thread we can't get rid of:
|
withSingleThreadExecutor {
|
||||||
assertNull(fork(field::get).getOrThrow())
|
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()
|
}.getOrThrow()
|
||||||
}
|
}
|
||||||
verifyNoMoreInteractions(log)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import net.corda.core.flows.FlowLogic
|
|||||||
import net.corda.core.flows.FlowSession
|
import net.corda.core.flows.FlowSession
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
import net.corda.core.flows.TestDataVendingFlow
|
import net.corda.core.flows.TestDataVendingFlow
|
||||||
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.FetchAttachmentsFlow
|
import net.corda.core.internal.FetchAttachmentsFlow
|
||||||
import net.corda.core.internal.FetchDataFlow
|
import net.corda.core.internal.FetchDataFlow
|
||||||
import net.corda.core.utilities.getOrThrow
|
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.internal.StartedNode
|
||||||
import net.corda.node.services.persistence.NodeAttachmentService
|
import net.corda.node.services.persistence.NodeAttachmentService
|
||||||
import net.corda.node.utilities.currentDBSession
|
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.MockNetwork
|
||||||
import net.corda.testing.node.MockNodeParameters
|
import net.corda.testing.node.MockNodeParameters
|
||||||
|
import net.corda.testing.singleIdentity
|
||||||
import net.corda.testing.startFlow
|
import net.corda.testing.startFlow
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@ -63,13 +66,16 @@ class AttachmentSerializationTest {
|
|||||||
private lateinit var mockNet: MockNetwork
|
private lateinit var mockNet: MockNetwork
|
||||||
private lateinit var server: StartedNode<MockNetwork.MockNode>
|
private lateinit var server: StartedNode<MockNetwork.MockNode>
|
||||||
private lateinit var client: StartedNode<MockNetwork.MockNode>
|
private lateinit var client: StartedNode<MockNetwork.MockNode>
|
||||||
|
private lateinit var serverIdentity: Party
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
mockNet = MockNetwork()
|
mockNet = MockNetwork()
|
||||||
server = mockNet.createNode()
|
server = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME))
|
||||||
client = mockNet.createNode()
|
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.
|
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
|
@After
|
||||||
@ -91,9 +97,7 @@ class AttachmentSerializationTest {
|
|||||||
private class ClientResult(internal val attachmentContent: String)
|
private class ClientResult(internal val attachmentContent: String)
|
||||||
|
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
private abstract class ClientLogic(server: StartedNode<*>) : FlowLogic<ClientResult>() {
|
private abstract class ClientLogic(val serverIdentity: Party) : FlowLogic<ClientResult>() {
|
||||||
internal val server = server.info.chooseIdentity()
|
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
internal fun communicate(serverSession: FlowSession) {
|
internal fun communicate(serverSession: FlowSession) {
|
||||||
serverSession.sendAndReceive<String>("ping one").unwrap { assertEquals("pong", it) }
|
serverSession.sendAndReceive<String>("ping one").unwrap { assertEquals("pong", it) }
|
||||||
@ -112,30 +116,30 @@ class AttachmentSerializationTest {
|
|||||||
override val signers get() = throw UnsupportedOperationException()
|
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
|
@Suspendable
|
||||||
override fun getAttachmentContent(): String {
|
override fun getAttachmentContent(): String {
|
||||||
val customAttachment = CustomAttachment(attachmentId, customContent)
|
val customAttachment = CustomAttachment(attachmentId, customContent)
|
||||||
val session = initiateFlow(server)
|
val session = initiateFlow(serverIdentity)
|
||||||
communicate(session)
|
communicate(session)
|
||||||
return customAttachment.customContent
|
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
|
@Suspendable
|
||||||
override fun getAttachmentContent(): String {
|
override fun getAttachmentContent(): String {
|
||||||
val localAttachment = serviceHub.attachments.openAttachment(attachmentId)!!
|
val localAttachment = serviceHub.attachments.openAttachment(attachmentId)!!
|
||||||
val session = initiateFlow(server)
|
val session = initiateFlow(serverIdentity)
|
||||||
communicate(session)
|
communicate(session)
|
||||||
return localAttachment.extractContent()
|
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
|
@Suspendable
|
||||||
override fun getAttachmentContent(): String {
|
override fun getAttachmentContent(): String {
|
||||||
val serverSession = initiateFlow(server)
|
val serverSession = initiateFlow(serverIdentity)
|
||||||
val (downloadedAttachment) = subFlow(FetchAttachmentsFlow(setOf(attachmentId), serverSession)).downloaded
|
val (downloadedAttachment) = subFlow(FetchAttachmentsFlow(setOf(attachmentId), serverSession)).downloaded
|
||||||
serverSession.send(FetchDataFlow.Request.End)
|
serverSession.send(FetchDataFlow.Request.End)
|
||||||
communicate(serverSession)
|
communicate(serverSession)
|
||||||
@ -166,14 +170,14 @@ class AttachmentSerializationTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `custom (and non-persisted) attachment should be saved in checkpoint`() {
|
fun `custom (and non-persisted) attachment should be saved in checkpoint`() {
|
||||||
val attachmentId = SecureHash.sha256("any old data")
|
val attachmentId = SecureHash.sha256("any old data")
|
||||||
launchFlow(CustomAttachmentLogic(server, attachmentId, "custom"), 1)
|
launchFlow(CustomAttachmentLogic(serverIdentity, attachmentId, "custom"), 1)
|
||||||
assertEquals("custom", rebootClientAndGetAttachmentContent())
|
assertEquals("custom", rebootClientAndGetAttachmentContent())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `custom attachment should be saved in checkpoint even if its data was persisted`() {
|
fun `custom attachment should be saved in checkpoint even if its data was persisted`() {
|
||||||
val attachmentId = client.saveAttachment("genuine")
|
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.
|
client.hackAttachment(attachmentId, "hacked") // Should not be reloaded, checkAttachmentsOnLoad may cause next line to blow up if client attempts it.
|
||||||
assertEquals("custom", rebootClientAndGetAttachmentContent())
|
assertEquals("custom", rebootClientAndGetAttachmentContent())
|
||||||
}
|
}
|
||||||
@ -182,7 +186,7 @@ class AttachmentSerializationTest {
|
|||||||
fun `only the hash of a regular attachment should be saved in checkpoint`() {
|
fun `only the hash of a regular attachment should be saved in checkpoint`() {
|
||||||
val attachmentId = client.saveAttachment("genuine")
|
val attachmentId = client.saveAttachment("genuine")
|
||||||
client.attachments.checkAttachmentsOnLoad = false // Cached by AttachmentImpl.
|
client.attachments.checkAttachmentsOnLoad = false // Cached by AttachmentImpl.
|
||||||
launchFlow(OpenAttachmentLogic(server, attachmentId), 1)
|
launchFlow(OpenAttachmentLogic(serverIdentity, attachmentId), 1)
|
||||||
client.hackAttachment(attachmentId, "hacked")
|
client.hackAttachment(attachmentId, "hacked")
|
||||||
assertEquals("hacked", rebootClientAndGetAttachmentContent(false)) // Pass in false to allow non-genuine data to be loaded.
|
assertEquals("hacked", rebootClientAndGetAttachmentContent(false)) // Pass in false to allow non-genuine data to be loaded.
|
||||||
}
|
}
|
||||||
@ -190,7 +194,7 @@ class AttachmentSerializationTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `only the hash of a FetchAttachmentsFlow attachment should be saved in checkpoint`() {
|
fun `only the hash of a FetchAttachmentsFlow attachment should be saved in checkpoint`() {
|
||||||
val attachmentId = server.saveAttachment("genuine")
|
val attachmentId = server.saveAttachment("genuine")
|
||||||
launchFlow(FetchAttachmentLogic(server, attachmentId), 2, sendData = true)
|
launchFlow(FetchAttachmentLogic(serverIdentity, attachmentId), 2, sendData = true)
|
||||||
client.hackAttachment(attachmentId, "hacked")
|
client.hackAttachment(attachmentId, "hacked")
|
||||||
assertEquals("hacked", rebootClientAndGetAttachmentContent(false))
|
assertEquals("hacked", rebootClientAndGetAttachmentContent(false))
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,13 @@ package net.corda.core.transactions
|
|||||||
|
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.identity.AbstractParty
|
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.contracts.DummyContract
|
||||||
|
import net.corda.testing.dummyCommand
|
||||||
import net.corda.testing.node.MockServices
|
import net.corda.testing.node.MockServices
|
||||||
|
import net.corda.testing.singleIdentity
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@ -18,6 +22,7 @@ class LedgerTransactionQueryTests {
|
|||||||
@JvmField
|
@JvmField
|
||||||
val testSerialization = SerializationEnvironmentRule()
|
val testSerialization = SerializationEnvironmentRule()
|
||||||
private val services: MockServices = MockServices()
|
private val services: MockServices = MockServices()
|
||||||
|
private val identity: Party = services.myInfo.singleIdentity()
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
@ -66,8 +71,8 @@ class LedgerTransactionQueryTests {
|
|||||||
tx.addInputState(makeDummyStateAndRef(i.toString()))
|
tx.addInputState(makeDummyStateAndRef(i.toString()))
|
||||||
tx.addOutputState(makeDummyState(i), DummyContract.PROGRAM_ID)
|
tx.addOutputState(makeDummyState(i), DummyContract.PROGRAM_ID)
|
||||||
tx.addOutputState(makeDummyState(i.toString()), DummyContract.PROGRAM_ID)
|
tx.addOutputState(makeDummyState(i.toString()), DummyContract.PROGRAM_ID)
|
||||||
tx.addCommand(Commands.Cmd1(i), listOf(services.myInfo.chooseIdentity().owningKey))
|
tx.addCommand(Commands.Cmd1(i), listOf(identity.owningKey))
|
||||||
tx.addCommand(Commands.Cmd2(i), listOf(services.myInfo.chooseIdentity().owningKey))
|
tx.addCommand(Commands.Cmd2(i), listOf(identity.owningKey))
|
||||||
}
|
}
|
||||||
return tx.toLedgerTransaction(services)
|
return tx.toLedgerTransaction(services)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import org.junit.Test
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFails
|
import kotlin.test.assertFails
|
||||||
|
import org.assertj.core.api.Assertions.*
|
||||||
|
|
||||||
class ProgressTrackerTest {
|
class ProgressTrackerTest {
|
||||||
object SimpleSteps {
|
object SimpleSteps {
|
||||||
@ -24,13 +25,23 @@ class ProgressTrackerTest {
|
|||||||
fun tracker() = ProgressTracker(AYY, BEE, SEA)
|
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 pt: ProgressTracker
|
||||||
lateinit var pt2: ProgressTracker
|
lateinit var pt2: ProgressTracker
|
||||||
|
lateinit var pt3: ProgressTracker
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun before() {
|
fun before() {
|
||||||
pt = SimpleSteps.tracker()
|
pt = SimpleSteps.tracker()
|
||||||
pt2 = ChildSteps.tracker()
|
pt2 = ChildSteps.tracker()
|
||||||
|
pt3 = BabySteps.tracker()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -81,6 +92,118 @@ class ProgressTrackerTest {
|
|||||||
assertEquals(ChildSteps.BEE, pt2.nextStep())
|
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
|
@Test
|
||||||
fun `can be rewound`() {
|
fun `can be rewound`() {
|
||||||
pt.setChildProgressTracker(SimpleSteps.TWO, pt2)
|
pt.setChildProgressTracker(SimpleSteps.TWO, pt2)
|
||||||
|
@ -9,5 +9,6 @@
|
|||||||
"https://docs.corda.net/releases/release-M13.0": "M13.0",
|
"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-M14.0": "M14.0",
|
||||||
"https://docs.corda.net/releases/release-V1.0": "V1.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"
|
"https://docs.corda.net/head/": "Master"
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,17 @@ from the previous milestone release.
|
|||||||
|
|
||||||
UNRELEASED
|
UNRELEASED
|
||||||
----------
|
----------
|
||||||
|
|
||||||
* ``AttachmentStorage`` now allows providing metadata on attachments upload - username and filename, currently as plain
|
* ``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.
|
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.
|
* ``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``.
|
* ``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.
|
This is a minor change to the public API, but is required to ensure that classes like ``SecureHash`` are immutable.
|
||||||
|
|
||||||
|
@ -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.
|
: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
|
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' ]``
|
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
|
||||||
|
@ -9,10 +9,7 @@ import net.corda.core.node.services.CordaService
|
|||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.*
|
||||||
import net.corda.core.utilities.ProgressTracker
|
|
||||||
import net.corda.core.utilities.loggerFor
|
|
||||||
import net.corda.core.utilities.unwrap
|
|
||||||
import net.corda.finance.flows.AbstractCashFlow
|
import net.corda.finance.flows.AbstractCashFlow
|
||||||
import net.corda.finance.flows.CashException
|
import net.corda.finance.flows.CashException
|
||||||
import net.corda.finance.flows.CashIssueFlow
|
import net.corda.finance.flows.CashIssueFlow
|
||||||
@ -25,7 +22,7 @@ object CustomVaultQuery {
|
|||||||
@CordaService
|
@CordaService
|
||||||
class Service(val services: AppServiceHub) : SingletonSerializeAsToken() {
|
class Service(val services: AppServiceHub) : SingletonSerializeAsToken() {
|
||||||
private companion object {
|
private companion object {
|
||||||
val log = loggerFor<Service>()
|
private val log = contextLogger()
|
||||||
}
|
}
|
||||||
fun rebalanceCurrencyReserves(): List<Amount<Currency>> {
|
fun rebalanceCurrencyReserves(): List<Amount<Currency>> {
|
||||||
val nativeQuery = """
|
val nativeQuery = """
|
||||||
|
@ -33,15 +33,16 @@ service.
|
|||||||
rpcPort 10006
|
rpcPort 10006
|
||||||
webPort 10007
|
webPort 10007
|
||||||
cordapps = ["net.corda:corda-finance:$corda_release_version"]
|
cordapps = ["net.corda:corda-finance:$corda_release_version"]
|
||||||
rpcUsers = [[ user: "user1", "password": "test", "permissions": []]]
|
rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL]]]
|
||||||
}
|
}
|
||||||
node {
|
node {
|
||||||
name "O=PartyB,L=New York,C=US"
|
name "O=PartyB,L=New York,C=US"
|
||||||
p2pPort 10008
|
p2pPort 10008
|
||||||
rpcPort 10009
|
rpcPort 10009
|
||||||
webPort 10010
|
webPort 10010
|
||||||
|
sshdPort 10024
|
||||||
cordapps = ["net.corda:corda-finance:$corda_release_version"]
|
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
|
Go to the terminal window displaying the CRaSH shell of PartyA. Typing ``help`` will display a list of the available
|
||||||
commands.
|
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:
|
We want to create an IOU of 100 with PartyB. We start the ``IOUFlow`` by typing:
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
@ -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
|
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.
|
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
|
Database access
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -6,8 +6,24 @@ Here are release notes for each snapshot release from M9 onwards.
|
|||||||
Unreleased
|
Unreleased
|
||||||
----------
|
----------
|
||||||
|
|
||||||
Support for observer/regulator nodes has returned. Read :doc:`tutorial-observer-nodes` to learn more or examine the
|
Release 2.0
|
||||||
interest rate swaps demo.
|
----------
|
||||||
|
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
|
Release 1.0
|
||||||
-----------
|
-----------
|
||||||
|
@ -18,11 +18,47 @@ Some of its features include:
|
|||||||
* View JMX metrics and monitoring exports.
|
* 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.
|
* 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.
|
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
|
Getting help
|
||||||
------------
|
------------
|
||||||
|
@ -4,7 +4,7 @@ import net.corda.core.contracts.*
|
|||||||
import net.corda.core.contracts.Amount.Companion.sumOrThrow
|
import net.corda.core.contracts.Amount.Companion.sumOrThrow
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
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 net.corda.core.utilities.trace
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.util.*
|
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 {
|
abstract class OnLedgerAsset<T : Any, C : CommandData, S : FungibleAsset<T>> : Contract {
|
||||||
companion object {
|
companion object {
|
||||||
val log = loggerFor<OnLedgerAsset<*, *, *>>()
|
private val log = contextLogger()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a transaction that moves an amount of currency to the given pubkey.
|
* Generate a transaction that moves an amount of currency to the given pubkey.
|
||||||
*
|
*
|
||||||
|
@ -4,15 +4,12 @@ import co.paralleluniverse.fibers.Suspendable
|
|||||||
import net.corda.core.contracts.Amount
|
import net.corda.core.contracts.Amount
|
||||||
import net.corda.core.contracts.StateAndRef
|
import net.corda.core.contracts.StateAndRef
|
||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.contracts.TransactionState
|
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.services.StatesNotAvailableException
|
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.core.utilities.*
|
||||||
import net.corda.finance.contracts.asset.Cash
|
import net.corda.finance.contracts.asset.Cash
|
||||||
import java.sql.*
|
import java.sql.*
|
||||||
@ -44,7 +41,7 @@ abstract class AbstractCashSelection {
|
|||||||
}.invoke()
|
}.invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
val log = loggerFor<AbstractCashSelection>()
|
private val log = contextLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
// coin selection retry loop counter, sleep (msecs) and lock for selecting states
|
// coin selection retry loop counter, sleep (msecs) and lock for selecting states
|
||||||
|
@ -4,17 +4,18 @@ import net.corda.core.contracts.Amount
|
|||||||
import net.corda.core.crypto.toStringShort
|
import net.corda.core.crypto.toStringShort
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import 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.Connection
|
||||||
import java.sql.DatabaseMetaData
|
import java.sql.DatabaseMetaData
|
||||||
import java.sql.ResultSet
|
import java.sql.ResultSet
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class CashSelectionH2Impl : AbstractCashSelection() {
|
class CashSelectionH2Impl : AbstractCashSelection() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val JDBC_DRIVER_NAME = "H2 JDBC Driver"
|
const val JDBC_DRIVER_NAME = "H2 JDBC Driver"
|
||||||
val log = loggerFor<CashSelectionH2Impl>()
|
private val log = contextLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isCompatible(metadata: DatabaseMetaData): Boolean {
|
override fun isCompatible(metadata: DatabaseMetaData): Boolean {
|
||||||
@ -23,7 +24,6 @@ class CashSelectionH2Impl : AbstractCashSelection() {
|
|||||||
|
|
||||||
override fun toString() = "${this::class.java} for $JDBC_DRIVER_NAME"
|
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:
|
// 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
|
// 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
|
// 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)
|
// 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?,
|
override fun executeQuery(connection: Connection, amount: Amount<Currency>, lockId: UUID, notary: Party?,
|
||||||
onlyFromIssuerParties: Set<AbstractParty>, withIssuerRefs: Set<OpaqueBytes>) : ResultSet {
|
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 = """
|
val selectJoin = """
|
||||||
SELECT vs.transaction_id, vs.output_index, ccs.pennies, SET(@t, ifnull(@t,0)+ccs.pennies) total_pennies, vs.lock_id
|
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())
|
if (onlyFromIssuerParties.isNotEmpty())
|
||||||
psSelectJoin.setObject(++pIndex, onlyFromIssuerParties.map { it.owningKey.toStringShort() as Any}.toTypedArray() )
|
psSelectJoin.setObject(++pIndex, onlyFromIssuerParties.map { it.owningKey.toStringShort() as Any}.toTypedArray() )
|
||||||
if (withIssuerRefs.isNotEmpty())
|
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() }
|
log.debug { psSelectJoin.toString() }
|
||||||
|
|
||||||
return psSelectJoin.executeQuery()
|
return psSelectJoin.executeQuery()
|
||||||
|
@ -3,10 +3,7 @@ package net.corda.finance.contracts.asset.cash.selection
|
|||||||
import net.corda.core.contracts.Amount
|
import net.corda.core.contracts.Amount
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.*
|
||||||
import net.corda.core.utilities.debug
|
|
||||||
import net.corda.core.utilities.loggerFor
|
|
||||||
import net.corda.core.utilities.toBase58String
|
|
||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
import java.sql.DatabaseMetaData
|
import java.sql.DatabaseMetaData
|
||||||
import java.sql.ResultSet
|
import java.sql.ResultSet
|
||||||
@ -16,7 +13,7 @@ class CashSelectionPostgreSQLImpl : AbstractCashSelection() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val JDBC_DRIVER_NAME = "PostgreSQL JDBC Driver"
|
val JDBC_DRIVER_NAME = "PostgreSQL JDBC Driver"
|
||||||
val log = loggerFor<CashSelectionPostgreSQLImpl>()
|
private val log = contextLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isCompatible(metadata: DatabaseMetaData): Boolean {
|
override fun isCompatible(metadata: DatabaseMetaData): Boolean {
|
||||||
@ -79,5 +76,4 @@ class CashSelectionPostgreSQLImpl : AbstractCashSelection() {
|
|||||||
|
|
||||||
return statement.executeQuery()
|
return statement.executeQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ class CashIssueAndPaymentFlow(val amount: Amount<Currency>,
|
|||||||
val anonymous: Boolean,
|
val anonymous: Boolean,
|
||||||
val notary: Party,
|
val notary: Party,
|
||||||
progressTracker: ProgressTracker) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) {
|
progressTracker: ProgressTracker) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) {
|
||||||
constructor(amount: Amount<Currency>,
|
constructor(amount: Amount<Currency>,
|
||||||
issueRef: OpaqueBytes,
|
issueRef: OpaqueBytes,
|
||||||
recipient: Party,
|
recipient: Party,
|
||||||
anonymous: Boolean,
|
anonymous: Boolean,
|
||||||
|
@ -5,6 +5,8 @@ import net.corda.core.schemas.MappedSchema
|
|||||||
import net.corda.core.schemas.PersistentState
|
import net.corda.core.schemas.PersistentState
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
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.*
|
import javax.persistence.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,7 +38,8 @@ object CashSchemaV1 : MappedSchema(schemaFamily = CashSchema.javaClass, version
|
|||||||
@Column(name = "issuer_key_hash", length = MAX_HASH_HEX_SIZE)
|
@Column(name = "issuer_key_hash", length = MAX_HASH_HEX_SIZE)
|
||||||
var issuerPartyHash: String,
|
var issuerPartyHash: String,
|
||||||
|
|
||||||
@Column(name = "issuer_ref")
|
@Column(name = "issuer_ref", length = MAX_ISSUER_REF_SIZE)
|
||||||
|
@Type(type = "corda-wrapper-binary")
|
||||||
var issuerRef: ByteArray
|
var issuerRef: ByteArray
|
||||||
) : PersistentState()
|
) : PersistentState()
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package net.corda.finance.schemas
|
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.MappedSchema
|
||||||
import net.corda.core.schemas.PersistentState
|
import net.corda.core.schemas.PersistentState
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
||||||
|
import org.hibernate.annotations.Type
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import javax.persistence.Column
|
import javax.persistence.Column
|
||||||
import javax.persistence.Entity
|
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)
|
@Column(name = "face_value_issuer_key_hash", length = MAX_HASH_HEX_SIZE)
|
||||||
var faceValueIssuerPartyHash: String,
|
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
|
var faceValueIssuerRef: ByteArray
|
||||||
) : PersistentState()
|
) : PersistentState()
|
||||||
}
|
}
|
||||||
|
@ -54,8 +54,7 @@ class CashTests {
|
|||||||
lateinit var database: CordaPersistence
|
lateinit var database: CordaPersistence
|
||||||
private lateinit var vaultStatesUnconsumed: List<StateAndRef<Cash.State>>
|
private lateinit var vaultStatesUnconsumed: List<StateAndRef<Cash.State>>
|
||||||
|
|
||||||
private lateinit var OUR_IDENTITY_1: AbstractParty
|
private lateinit var ourIdentity: AbstractParty
|
||||||
private lateinit var OUR_IDENTITY_AND_CERT: PartyAndCertificate
|
|
||||||
private lateinit var miniCorpAnonymised: AnonymousParty
|
private lateinit var miniCorpAnonymised: AnonymousParty
|
||||||
private val CHARLIE_ANONYMISED = CHARLIE_IDENTITY.party.anonymise()
|
private val CHARLIE_ANONYMISED = CHARLIE_IDENTITY.party.anonymise()
|
||||||
|
|
||||||
@ -65,28 +64,33 @@ class CashTests {
|
|||||||
fun setUp() {
|
fun setUp() {
|
||||||
LogHelper.setLevel(NodeVaultService::class)
|
LogHelper.setLevel(NodeVaultService::class)
|
||||||
megaCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), MEGA_CORP.name, MEGA_CORP_KEY)
|
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(
|
val databaseAndServices = makeTestDatabaseAndMockServices(
|
||||||
cordappPackages = listOf("net.corda.finance.contracts.asset"),
|
cordappPackages = listOf("net.corda.finance.contracts.asset"),
|
||||||
initialIdentityName = CordaX500Name(organisation = "Me", locality = "London", country = "GB"),
|
initialIdentityName = CordaX500Name(organisation = "Me", locality = "London", country = "GB"),
|
||||||
keys = listOf(generateKeyPair()))
|
keys = listOf(generateKeyPair()))
|
||||||
database = databaseAndServices.first
|
database = databaseAndServices.first
|
||||||
miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), MINI_CORP.name, MINI_CORP_KEY)
|
|
||||||
ourServices = databaseAndServices.second
|
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.
|
// 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,
|
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,
|
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,
|
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,
|
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 {
|
database.transaction {
|
||||||
vaultStatesUnconsumed = ourServices.vaultService.queryBy<Cash.State>().states
|
vaultStatesUnconsumed = ourServices.vaultService.queryBy<Cash.State>().states
|
||||||
}
|
}
|
||||||
@ -495,7 +499,7 @@ class CashTests {
|
|||||||
|
|
||||||
private fun makeCash(amount: Amount<Currency>, issuer: AbstractParty, depositRef: Byte = 1) =
|
private fun makeCash(amount: Amount<Currency>, issuer: AbstractParty, depositRef: Byte = 1) =
|
||||||
StateAndRef(
|
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))
|
StateRef(SecureHash.randomSHA256(), Random().nextInt(32))
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -509,12 +513,14 @@ class CashTests {
|
|||||||
return tx.toWireTransaction(serviceHub)
|
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)
|
val tx = TransactionBuilder(DUMMY_NOTARY)
|
||||||
database.transaction {
|
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() {
|
fun generateExitWithEmptyVault() {
|
||||||
assertFailsWith<IllegalArgumentException> {
|
assertFailsWith<IllegalArgumentException> {
|
||||||
val tx = TransactionBuilder(DUMMY_NOTARY)
|
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() {
|
fun generateSimpleDirectSpend() {
|
||||||
val wtx =
|
val wtx =
|
||||||
database.transaction {
|
database.transaction {
|
||||||
makeSpend(100.DOLLARS, miniCorpAnonymised)
|
makeSpend(ourServices, 100.DOLLARS, miniCorpAnonymised)
|
||||||
}
|
}
|
||||||
database.transaction {
|
database.transaction {
|
||||||
val vaultState = vaultStatesUnconsumed.elementAt(0)
|
val vaultState = vaultStatesUnconsumed.elementAt(0)
|
||||||
assertEquals(vaultState.ref, wtx.inputs[0])
|
assertEquals(vaultState.ref, wtx.inputs[0])
|
||||||
assertEquals(vaultState.state.data.copy(owner = miniCorpAnonymised), wtx.getOutput(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
|
@Test
|
||||||
fun generateSimpleSpendWithParties() {
|
fun generateSimpleSpendWithParties() {
|
||||||
|
val changeIdentity = ourServices.keyManagementService.freshKeyAndCert(ourServices.myInfo.singleIdentityAndCert(), false)
|
||||||
database.transaction {
|
database.transaction {
|
||||||
|
|
||||||
val tx = TransactionBuilder(DUMMY_NOTARY)
|
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])
|
assertEquals(vaultStatesUnconsumed.elementAt(2).ref, tx.inputStates()[0])
|
||||||
}
|
}
|
||||||
@ -621,7 +628,7 @@ class CashTests {
|
|||||||
fun generateSimpleSpendWithChange() {
|
fun generateSimpleSpendWithChange() {
|
||||||
val wtx =
|
val wtx =
|
||||||
database.transaction {
|
database.transaction {
|
||||||
makeSpend(10.DOLLARS, miniCorpAnonymised)
|
makeSpend(ourServices, 10.DOLLARS, miniCorpAnonymised)
|
||||||
}
|
}
|
||||||
database.transaction {
|
database.transaction {
|
||||||
val vaultState = vaultStatesUnconsumed.elementAt(0)
|
val vaultState = vaultStatesUnconsumed.elementAt(0)
|
||||||
@ -638,7 +645,7 @@ class CashTests {
|
|||||||
assertEquals(vaultState.ref, wtx.inputs[0])
|
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(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(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() {
|
fun generateSpendWithTwoInputs() {
|
||||||
val wtx =
|
val wtx =
|
||||||
database.transaction {
|
database.transaction {
|
||||||
makeSpend(500.DOLLARS, miniCorpAnonymised)
|
makeSpend(ourServices, 500.DOLLARS, miniCorpAnonymised)
|
||||||
}
|
}
|
||||||
database.transaction {
|
database.transaction {
|
||||||
val vaultState0 = vaultStatesUnconsumed.elementAt(0)
|
val vaultState0 = vaultStatesUnconsumed.elementAt(0)
|
||||||
@ -654,7 +661,7 @@ class CashTests {
|
|||||||
assertEquals(vaultState0.ref, wtx.inputs[0])
|
assertEquals(vaultState0.ref, wtx.inputs[0])
|
||||||
assertEquals(vaultState1.ref, wtx.inputs[1])
|
assertEquals(vaultState1.ref, wtx.inputs[1])
|
||||||
assertEquals(vaultState0.state.data.copy(owner = miniCorpAnonymised, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.getOutput(0))
|
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() {
|
fun generateSpendMixedDeposits() {
|
||||||
val wtx =
|
val wtx =
|
||||||
database.transaction {
|
database.transaction {
|
||||||
val wtx = makeSpend(580.DOLLARS, miniCorpAnonymised)
|
val wtx = makeSpend(ourServices, 580.DOLLARS, miniCorpAnonymised)
|
||||||
assertEquals(3, wtx.inputs.size)
|
assertEquals(3, wtx.inputs.size)
|
||||||
wtx
|
wtx
|
||||||
}
|
}
|
||||||
@ -675,7 +682,7 @@ class CashTests {
|
|||||||
assertEquals(vaultState2.ref, wtx.inputs[2])
|
assertEquals(vaultState2.ref, wtx.inputs[2])
|
||||||
assertEquals(vaultState0.state.data.copy(owner = miniCorpAnonymised, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[1].data)
|
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(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 {
|
database.transaction {
|
||||||
|
|
||||||
val e: InsufficientBalanceException = assertFailsWith("balance") {
|
val e: InsufficientBalanceException = assertFailsWith("balance") {
|
||||||
makeSpend(1000.DOLLARS, miniCorpAnonymised)
|
makeSpend(ourServices, 1000.DOLLARS, miniCorpAnonymised)
|
||||||
}
|
}
|
||||||
assertEquals((1000 - 580).DOLLARS, e.amountMissing)
|
assertEquals((1000 - 580).DOLLARS, e.amountMissing)
|
||||||
|
|
||||||
assertFailsWith(InsufficientBalanceException::class) {
|
assertFailsWith(InsufficientBalanceException::class) {
|
||||||
makeSpend(81.SWISS_FRANCS, miniCorpAnonymised)
|
makeSpend(ourServices, 81.SWISS_FRANCS, miniCorpAnonymised)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -821,7 +828,7 @@ class CashTests {
|
|||||||
fun multiSpend() {
|
fun multiSpend() {
|
||||||
val tx = TransactionBuilder(DUMMY_NOTARY)
|
val tx = TransactionBuilder(DUMMY_NOTARY)
|
||||||
database.transaction {
|
database.transaction {
|
||||||
val changeIdentity = ourServices.keyManagementService.freshKeyAndCert(OUR_IDENTITY_AND_CERT, false)
|
val changeIdentity = ourServices.keyManagementService.freshKeyAndCert(ourServices.myInfo.singleIdentityAndCert(), false)
|
||||||
val payments = listOf(
|
val payments = listOf(
|
||||||
PartyAndAmount(miniCorpAnonymised, 400.DOLLARS),
|
PartyAndAmount(miniCorpAnonymised, 400.DOLLARS),
|
||||||
PartyAndAmount(CHARLIE_ANONYMISED, 150.DOLLARS)
|
PartyAndAmount(CHARLIE_ANONYMISED, 150.DOLLARS)
|
||||||
|
@ -55,7 +55,7 @@ class DummyFungibleContract : OnLedgerAsset<Currency, DummyFungibleContract.Comm
|
|||||||
_quantity = this.amount.quantity,
|
_quantity = this.amount.quantity,
|
||||||
currency = this.amount.token.product.currencyCode,
|
currency = this.amount.token.product.currencyCode,
|
||||||
_issuerParty = this.amount.token.issuer.party,
|
_issuerParty = this.amount.token.issuer.party,
|
||||||
_issuerRef = this.amount.token.issuer.reference.bytes
|
_issuerRef = this.amount.token.issuer.reference
|
||||||
)
|
)
|
||||||
is SampleCashSchemaV3 -> SampleCashSchemaV3.PersistentCashState(
|
is SampleCashSchemaV3 -> SampleCashSchemaV3.PersistentCashState(
|
||||||
participants = this.participants.toMutableSet(),
|
participants = this.participants.toMutableSet(),
|
||||||
|
@ -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.core.utilities.getOrThrow
|
||||||
import net.corda.finance.DOLLARS
|
import net.corda.finance.DOLLARS
|
||||||
|
import net.corda.finance.POUNDS
|
||||||
import net.corda.finance.flows.CashException
|
import net.corda.finance.flows.CashException
|
||||||
|
import net.corda.finance.flows.CashIssueFlow
|
||||||
import net.corda.finance.flows.CashPaymentFlow
|
import net.corda.finance.flows.CashPaymentFlow
|
||||||
import net.corda.testing.node.MockNetwork
|
import net.corda.testing.node.MockNetwork
|
||||||
import net.corda.testing.node.MockNodeParameters
|
import net.corda.testing.node.MockNodeParameters
|
||||||
@ -10,8 +14,9 @@ import net.corda.testing.startFlow
|
|||||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.util.Collections.nCopies
|
||||||
|
|
||||||
class CashSelectionH2Test {
|
class CashSelectionH2ImplTest {
|
||||||
private val mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance"))
|
private val mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance"))
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -19,6 +24,19 @@ class CashSelectionH2Test {
|
|||||||
mockNet.stopNodes()
|
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
|
@Test
|
||||||
fun `check does not hold connection over retries`() {
|
fun `check does not hold connection over retries`() {
|
||||||
val bankA = mockNet.createNode(MockNodeParameters(configOverrides = {
|
val bankA = mockNet.createNode(MockNodeParameters(configOverrides = {
|
@ -7,8 +7,7 @@ import net.corda.finance.DOLLARS
|
|||||||
import net.corda.finance.`issued by`
|
import net.corda.finance.`issued by`
|
||||||
import net.corda.finance.contracts.asset.Cash
|
import net.corda.finance.contracts.asset.Cash
|
||||||
import net.corda.node.internal.StartedNode
|
import net.corda.node.internal.StartedNode
|
||||||
import net.corda.testing.BOC
|
import net.corda.testing.BOC_NAME
|
||||||
import net.corda.testing.chooseIdentity
|
|
||||||
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
|
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
|
||||||
import net.corda.testing.node.MockNetwork
|
import net.corda.testing.node.MockNetwork
|
||||||
import net.corda.testing.node.MockNetwork.MockNode
|
import net.corda.testing.node.MockNetwork.MockNode
|
||||||
@ -31,8 +30,8 @@ class CashExitFlowTests {
|
|||||||
fun start() {
|
fun start() {
|
||||||
mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(),
|
mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(),
|
||||||
cordappPackages = listOf("net.corda.finance.contracts.asset"))
|
cordappPackages = listOf("net.corda.finance.contracts.asset"))
|
||||||
bankOfCordaNode = mockNet.createPartyNode(BOC.name)
|
bankOfCordaNode = mockNet.createPartyNode(BOC_NAME)
|
||||||
bankOfCorda = bankOfCordaNode.info.chooseIdentity()
|
bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME)
|
||||||
notary = mockNet.defaultNotaryIdentity
|
notary = mockNet.defaultNotaryIdentity
|
||||||
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, notary)).resultFuture
|
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, notary)).resultFuture
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
|
@ -7,8 +7,7 @@ import net.corda.finance.DOLLARS
|
|||||||
import net.corda.finance.`issued by`
|
import net.corda.finance.`issued by`
|
||||||
import net.corda.finance.contracts.asset.Cash
|
import net.corda.finance.contracts.asset.Cash
|
||||||
import net.corda.node.internal.StartedNode
|
import net.corda.node.internal.StartedNode
|
||||||
import net.corda.testing.BOC
|
import net.corda.testing.BOC_NAME
|
||||||
import net.corda.testing.chooseIdentity
|
|
||||||
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
|
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
|
||||||
import net.corda.testing.node.MockNetwork
|
import net.corda.testing.node.MockNetwork
|
||||||
import net.corda.testing.node.MockNetwork.MockNode
|
import net.corda.testing.node.MockNetwork.MockNode
|
||||||
@ -28,8 +27,8 @@ class CashIssueFlowTests {
|
|||||||
@Before
|
@Before
|
||||||
fun start() {
|
fun start() {
|
||||||
mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts.asset"))
|
mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts.asset"))
|
||||||
bankOfCordaNode = mockNet.createPartyNode(BOC.name)
|
bankOfCordaNode = mockNet.createPartyNode(BOC_NAME)
|
||||||
bankOfCorda = bankOfCordaNode.info.chooseIdentity()
|
bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME)
|
||||||
notary = mockNet.defaultNotaryIdentity
|
notary = mockNet.defaultNotaryIdentity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,9 +31,9 @@ class CashPaymentFlowTests {
|
|||||||
@Before
|
@Before
|
||||||
fun start() {
|
fun start() {
|
||||||
mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts.asset"))
|
mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts.asset"))
|
||||||
bankOfCordaNode = mockNet.createPartyNode(BOC.name)
|
bankOfCordaNode = mockNet.createPartyNode(BOC_NAME)
|
||||||
aliceNode = mockNet.createPartyNode(ALICE.name)
|
bankOfCorda = bankOfCordaNode.info.identityFromX500Name(BOC_NAME)
|
||||||
bankOfCorda = bankOfCordaNode.info.chooseIdentity()
|
aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
||||||
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, mockNet.defaultNotaryIdentity)).resultFuture
|
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, mockNet.defaultNotaryIdentity)).resultFuture
|
||||||
future.getOrThrow()
|
future.getOrThrow()
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package net.corda.finance.schemas
|
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.MappedSchema
|
||||||
import net.corda.core.schemas.PersistentState
|
import net.corda.core.schemas.PersistentState
|
||||||
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
||||||
|
import org.hibernate.annotations.Type
|
||||||
import javax.persistence.Column
|
import javax.persistence.Column
|
||||||
import javax.persistence.Entity
|
import javax.persistence.Entity
|
||||||
import javax.persistence.Index
|
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)
|
@Column(name = "issuer_key_hash", length = MAX_HASH_HEX_SIZE)
|
||||||
var issuerPartyHash: String,
|
var issuerPartyHash: String,
|
||||||
|
|
||||||
@Column(name = "issuer_ref")
|
@Column(name = "issuer_ref", length = MAX_ISSUER_REF_SIZE)
|
||||||
|
@Type(type = "corda-wrapper-binary")
|
||||||
var issuerRef: ByteArray
|
var issuerRef: ByteArray
|
||||||
) : PersistentState()
|
) : PersistentState()
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package net.corda.finance.schemas
|
|||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.schemas.CommonSchemaV1
|
import net.corda.core.schemas.CommonSchemaV1
|
||||||
import net.corda.core.schemas.MappedSchema
|
import net.corda.core.schemas.MappedSchema
|
||||||
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import javax.persistence.Column
|
import javax.persistence.Column
|
||||||
import javax.persistence.Entity
|
import javax.persistence.Entity
|
||||||
import javax.persistence.Index
|
import javax.persistence.Index
|
||||||
@ -32,6 +33,6 @@ object SampleCashSchemaV2 : MappedSchema(schemaFamily = CashSchema.javaClass, ve
|
|||||||
@Transient
|
@Transient
|
||||||
val _issuerParty: AbstractParty,
|
val _issuerParty: AbstractParty,
|
||||||
@Transient
|
@Transient
|
||||||
val _issuerRef: ByteArray
|
val _issuerRef: OpaqueBytes
|
||||||
) : CommonSchemaV1.FungibleState(_participants.toMutableSet(), _owner, _quantity, _issuerParty, _issuerRef)
|
) : CommonSchemaV1.FungibleState(_participants.toMutableSet(), _owner, _quantity, _issuerParty, _issuerRef.bytes)
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package net.corda.finance.schemas
|
package net.corda.finance.schemas
|
||||||
|
|
||||||
|
import net.corda.core.contracts.MAX_ISSUER_REF_SIZE
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.schemas.MappedSchema
|
import net.corda.core.schemas.MappedSchema
|
||||||
import net.corda.core.schemas.PersistentState
|
import net.corda.core.schemas.PersistentState
|
||||||
|
import org.hibernate.annotations.Type
|
||||||
import javax.persistence.Column
|
import javax.persistence.Column
|
||||||
import javax.persistence.ElementCollection
|
import javax.persistence.ElementCollection
|
||||||
import javax.persistence.Entity
|
import javax.persistence.Entity
|
||||||
@ -37,7 +39,8 @@ object SampleCashSchemaV3 : MappedSchema(schemaFamily = CashSchema.javaClass, ve
|
|||||||
@Column(name = "issuer_name")
|
@Column(name = "issuer_name")
|
||||||
var issuer: AbstractParty,
|
var issuer: AbstractParty,
|
||||||
|
|
||||||
@Column(name = "issuer_ref")
|
@Column(name = "issuer_ref", length = MAX_ISSUER_REF_SIZE)
|
||||||
|
@Type(type = "corda-wrapper-binary")
|
||||||
var issuerRef: ByteArray
|
var issuerRef: ByteArray
|
||||||
) : PersistentState()
|
) : PersistentState()
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package net.corda.finance.schemas
|
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.MappedSchema
|
||||||
import net.corda.core.schemas.PersistentState
|
import net.corda.core.schemas.PersistentState
|
||||||
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
||||||
|
import org.hibernate.annotations.Type
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import javax.persistence.Column
|
import javax.persistence.Column
|
||||||
import javax.persistence.Entity
|
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)
|
@Column(name = "face_value_issuer_key_hash", length = MAX_HASH_HEX_SIZE)
|
||||||
var faceValueIssuerPartyHash: String,
|
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
|
var faceValueIssuerRef: ByteArray
|
||||||
) : PersistentState()
|
) : PersistentState()
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package net.corda.finance.schemas
|
package net.corda.finance.schemas
|
||||||
|
|
||||||
|
import net.corda.core.contracts.MAX_ISSUER_REF_SIZE
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.schemas.CommonSchemaV1
|
import net.corda.core.schemas.CommonSchemaV1
|
||||||
import net.corda.core.schemas.MappedSchema
|
import net.corda.core.schemas.MappedSchema
|
||||||
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
|
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 java.time.Instant
|
||||||
import javax.persistence.Column
|
import javax.persistence.Column
|
||||||
import javax.persistence.Entity
|
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)
|
@Column(name = "face_value_issuer_key_hash", length = MAX_HASH_HEX_SIZE)
|
||||||
var faceValueIssuerPartyHash: String,
|
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,
|
var faceValueIssuerRef: ByteArray,
|
||||||
|
|
||||||
/** parent attributes */
|
/** parent attributes */
|
||||||
@ -44,6 +48,6 @@ object SampleCommercialPaperSchemaV2 : MappedSchema(schemaFamily = CommercialPap
|
|||||||
@Transient
|
@Transient
|
||||||
val _issuerParty: AbstractParty,
|
val _issuerParty: AbstractParty,
|
||||||
@Transient
|
@Transient
|
||||||
val _issuerRef: ByteArray
|
val _issuerRef: OpaqueBytes
|
||||||
) : CommonSchemaV1.FungibleState(_participants.toMutableSet(), _owner, _quantity, _issuerParty, _issuerRef)
|
) : CommonSchemaV1.FungibleState(_participants.toMutableSet(), _owner, _quantity, _issuerParty, _issuerRef.bytes)
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
fun sshdPort(sshdPort: Int?) {
|
||||||
config = config.withValue("sshdAddress",
|
config = config.withValue("sshd.port", ConfigValueFactory.fromAnyRef(sshdPort))
|
||||||
ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$sshdPort"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun build() {
|
internal fun build() {
|
||||||
configureProperties()
|
configureProperties()
|
||||||
installCordaJar()
|
installCordaJar()
|
||||||
|
@ -5,7 +5,7 @@ import net.corda.core.internal.ThreadBox
|
|||||||
import net.corda.core.internal.createDirectories
|
import net.corda.core.internal.createDirectories
|
||||||
import net.corda.core.internal.isRegularFile
|
import net.corda.core.internal.isRegularFile
|
||||||
import net.corda.core.internal.list
|
import net.corda.core.internal.list
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.contextLogger
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Scheduler
|
import rx.Scheduler
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
@ -28,7 +28,7 @@ import java.util.concurrent.TimeUnit
|
|||||||
class NodeInfoFilesCopier(scheduler: Scheduler = Schedulers.io()) : AutoCloseable {
|
class NodeInfoFilesCopier(scheduler: Scheduler = Schedulers.io()) : AutoCloseable {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = loggerFor<NodeInfoFilesCopier>()
|
private val log = contextLogger()
|
||||||
const val NODE_INFO_FILE_NAME_PREFIX = "nodeInfo-"
|
const val NODE_INFO_FILE_NAME_PREFIX = "nodeInfo-"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,17 @@
|
|||||||
package net.corda.nodeapi
|
package net.corda.nodeapi.internal
|
||||||
|
|
||||||
import net.corda.core.messaging.MessageRecipientGroup
|
import net.corda.core.messaging.MessageRecipientGroup
|
||||||
import net.corda.core.messaging.MessageRecipients
|
import net.corda.core.messaging.MessageRecipients
|
||||||
import net.corda.core.messaging.SingleMessageRecipient
|
import net.corda.core.messaging.SingleMessageRecipient
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.toBase58String
|
import net.corda.core.utilities.toBase58String
|
||||||
import net.corda.nodeapi.config.SSLConfiguration
|
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base class for Artemis services that defines shared data structures and SSL transport configuration.
|
* The base class for Artemis services that defines shared data structures and SSL transport configuration.
|
||||||
*/
|
*/
|
||||||
abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() {
|
class ArtemisMessagingComponent {
|
||||||
companion object {
|
companion object {
|
||||||
init {
|
init {
|
||||||
System.setProperty("org.jboss.logging.provider", "slf4j")
|
System.setProperty("org.jboss.logging.provider", "slf4j")
|
||||||
@ -66,7 +64,4 @@ abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() {
|
|||||||
data class ServiceAddress(val identity: PublicKey) : ArtemisAddress, MessageRecipientGroup {
|
data class ServiceAddress(val identity: PublicKey) : ArtemisAddress, MessageRecipientGroup {
|
||||||
override val queueName: String = "$PEERS_PREFIX${identity.toBase58String()}"
|
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?
|
|
||||||
}
|
}
|
@ -10,7 +10,7 @@ import net.corda.nodeapi.internal.AttachmentsClassLoader
|
|||||||
import net.corda.core.serialization.ClassWhitelist
|
import net.corda.core.serialization.ClassWhitelist
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.serialization.SerializationContext
|
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.amqp.hasAnnotationInHierarchy
|
||||||
import net.corda.nodeapi.internal.serialization.kryo.ThrowableSerializer
|
import net.corda.nodeapi.internal.serialization.kryo.ThrowableSerializer
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
@ -187,7 +187,7 @@ class TransientClassWhiteList(delegate: ClassWhitelist) : AbstractMutableClassWh
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
class LoggingWhitelist(val delegate: ClassWhitelist, val global: Boolean = true) : MutableClassWhitelist {
|
class LoggingWhitelist(val delegate: ClassWhitelist, val global: Boolean = true) : MutableClassWhitelist {
|
||||||
companion object {
|
companion object {
|
||||||
val log = loggerFor<LoggingWhitelist>()
|
private val log = contextLogger()
|
||||||
val globallySeen: MutableSet<String> = Collections.synchronizedSet(mutableSetOf())
|
val globallySeen: MutableSet<String> = Collections.synchronizedSet(mutableSetOf())
|
||||||
val journalWriter: PrintWriter? = openOptionalDynamicWhitelistJournal()
|
val journalWriter: PrintWriter? = openOptionalDynamicWhitelistJournal()
|
||||||
|
|
||||||
|
@ -36,7 +36,6 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme {
|
|||||||
register(publicKeySerializer)
|
register(publicKeySerializer)
|
||||||
register(net.corda.nodeapi.internal.serialization.amqp.custom.PrivateKeySerializer)
|
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.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.BigDecimalSerializer)
|
||||||
register(net.corda.nodeapi.internal.serialization.amqp.custom.CurrencySerializer)
|
register(net.corda.nodeapi.internal.serialization.amqp.custom.CurrencySerializer)
|
||||||
register(net.corda.nodeapi.internal.serialization.amqp.custom.OpaqueBytesSubSequenceSerializer(this))
|
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.MonthDaySerializer(this))
|
||||||
register(net.corda.nodeapi.internal.serialization.amqp.custom.PeriodSerializer(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.ClassSerializer(this))
|
||||||
register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateHolderSerializer)
|
register(net.corda.nodeapi.internal.serialization.amqp.custom.X509CertificateSerializer)
|
||||||
register(net.corda.nodeapi.internal.serialization.amqp.custom.PartyAndCertificateSerializer(factory))
|
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.StringBufferSerializer)
|
||||||
register(net.corda.nodeapi.internal.serialization.amqp.custom.SimpleStringSerializer)
|
register(net.corda.nodeapi.internal.serialization.amqp.custom.SimpleStringSerializer)
|
||||||
register(net.corda.nodeapi.internal.serialization.amqp.custom.InputStreamSerializer)
|
register(net.corda.nodeapi.internal.serialization.amqp.custom.InputStreamSerializer)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.corda.nodeapi.internal.serialization.amqp
|
package net.corda.nodeapi.internal.serialization.amqp
|
||||||
|
|
||||||
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
import net.corda.core.utilities.loggerFor
|
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.nameForType
|
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.nameForType
|
||||||
import org.apache.qpid.proton.amqp.Symbol
|
import org.apache.qpid.proton.amqp.Symbol
|
||||||
import org.apache.qpid.proton.codec.Data
|
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)
|
open val kotlinConstructor = constructorForDeserialization(clazz)
|
||||||
val javaConstructor by lazy { kotlinConstructor?.javaConstructor }
|
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 {
|
open internal val propertySerializers: Collection<PropertySerializer> by lazy {
|
||||||
propertiesForSerialization(kotlinConstructor, clazz, factory)
|
propertiesForSerialization(kotlinConstructor, clazz, factory)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package net.corda.nodeapi.internal.serialization.amqp
|
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.amqp.Binary
|
||||||
import org.apache.qpid.proton.codec.Data
|
import org.apache.qpid.proton.codec.Data
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
@ -61,8 +61,7 @@ sealed class PropertySerializer(val name: String, val readMethod: Method?, val r
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = loggerFor<PropertySerializer>()
|
private val logger = contextLogger()
|
||||||
|
|
||||||
fun make(name: String, readMethod: Method?, resolvedType: Type, factory: SerializerFactory): PropertySerializer {
|
fun make(name: String, readMethod: Method?, resolvedType: Type, factory: SerializerFactory): PropertySerializer {
|
||||||
readMethod?.isAccessible = true
|
readMethod?.isAccessible = true
|
||||||
if (SerializerFactory.isPrimitive(resolvedType)) {
|
if (SerializerFactory.isPrimitive(resolvedType)) {
|
||||||
|
@ -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)
|
||||||
|
}
|
@ -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)
|
|
||||||
}
|
|
@ -3,14 +3,14 @@ package net.corda.nodeapi.internal.serialization.amqp.custom
|
|||||||
import net.corda.core.CordaRuntimeException
|
import net.corda.core.CordaRuntimeException
|
||||||
import net.corda.core.CordaThrowable
|
import net.corda.core.CordaThrowable
|
||||||
import net.corda.core.serialization.SerializationFactory
|
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 net.corda.nodeapi.internal.serialization.amqp.*
|
||||||
import java.io.NotSerializableException
|
import java.io.NotSerializableException
|
||||||
|
|
||||||
class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<Throwable, ThrowableSerializer.ThrowableProxy>(Throwable::class.java, ThrowableProxy::class.java, factory) {
|
class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<Throwable, ThrowableSerializer.ThrowableProxy>(Throwable::class.java, ThrowableProxy::class.java, factory) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = loggerFor<ThrowableSerializer>()
|
private val logger = contextLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
override val revealSubclassesInSchema: Boolean = true
|
override val revealSubclassesInSchema: Boolean = true
|
||||||
|
@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -32,8 +32,6 @@ import net.corda.nodeapi.internal.serialization.GeneratedAttachment
|
|||||||
import net.corda.nodeapi.internal.serialization.MutableClassWhitelist
|
import net.corda.nodeapi.internal.serialization.MutableClassWhitelist
|
||||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
||||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
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.BCECPrivateKey
|
||||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey
|
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey
|
||||||
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey
|
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey
|
||||||
@ -53,6 +51,7 @@ import java.io.InputStream
|
|||||||
import java.lang.reflect.Modifier.isPublic
|
import java.lang.reflect.Modifier.isPublic
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.security.cert.CertPath
|
import java.security.cert.CertPath
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
@ -109,8 +108,7 @@ object DefaultKryoCustomizer {
|
|||||||
register(FileInputStream::class.java, InputStreamSerializer)
|
register(FileInputStream::class.java, InputStreamSerializer)
|
||||||
register(CertPath::class.java, CertPathSerializer)
|
register(CertPath::class.java, CertPathSerializer)
|
||||||
register(X509CertPath::class.java, CertPathSerializer)
|
register(X509CertPath::class.java, CertPathSerializer)
|
||||||
register(X500Name::class.java, X500NameSerializer)
|
register(X509Certificate::class.java, X509CertificateSerializer)
|
||||||
register(X509CertificateHolder::class.java, X509CertificateSerializer)
|
|
||||||
register(BCECPrivateKey::class.java, PrivateKeySerializer)
|
register(BCECPrivateKey::class.java, PrivateKeySerializer)
|
||||||
register(BCECPublicKey::class.java, publicKeySerializer)
|
register(BCECPublicKey::class.java, publicKeySerializer)
|
||||||
register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer)
|
register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer)
|
||||||
|
@ -15,7 +15,8 @@ import net.corda.core.crypto.TransactionSignature
|
|||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.serialization.SerializationContext
|
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.SerializeAsTokenContext
|
||||||
import net.corda.core.serialization.SerializedBytes
|
import net.corda.core.serialization.SerializedBytes
|
||||||
import net.corda.core.toFuture
|
import net.corda.core.toFuture
|
||||||
@ -23,9 +24,6 @@ import net.corda.core.toObservable
|
|||||||
import net.corda.core.transactions.*
|
import net.corda.core.transactions.*
|
||||||
import net.corda.nodeapi.internal.serialization.CordaClassResolver
|
import net.corda.nodeapi.internal.serialization.CordaClassResolver
|
||||||
import net.corda.nodeapi.internal.serialization.serializationContextKey
|
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.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
@ -36,6 +34,7 @@ import java.security.PrivateKey
|
|||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.security.cert.CertPath
|
import java.security.cert.CertPath
|
||||||
import java.security.cert.CertificateFactory
|
import java.security.cert.CertificateFactory
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.annotation.concurrent.ThreadSafe
|
import javax.annotation.concurrent.ThreadSafe
|
||||||
import kotlin.reflect.KClass
|
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
|
@ThreadSafe
|
||||||
object CertPathSerializer : Serializer<CertPath>() {
|
object CertPathSerializer : Serializer<CertPath>() {
|
||||||
val factory: CertificateFactory = CertificateFactory.getInstance("X.509")
|
|
||||||
override fun read(kryo: Kryo, input: Input, type: Class<CertPath>): CertPath {
|
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) {
|
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
|
@ThreadSafe
|
||||||
object X509CertificateSerializer : Serializer<X509CertificateHolder>() {
|
object X509CertificateSerializer : Serializer<X509Certificate>() {
|
||||||
override fun read(kryo: Kryo, input: Input, type: Class<X509CertificateHolder>): X509CertificateHolder {
|
override fun read(kryo: Kryo, input: Input, type: Class<X509Certificate>): X509Certificate {
|
||||||
return X509CertificateHolder(input.readBytes())
|
val factory = CertificateFactory.getInstance("X.509")
|
||||||
|
return factory.generateCertificate(input.readBytesWithLength().inputStream()) as X509Certificate
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(kryo: Kryo, output: Output, obj: X509CertificateHolder) {
|
override fun write(kryo: Kryo, output: Output, obj: X509Certificate) {
|
||||||
output.writeBytes(obj.encoded)
|
output.writeBytesWithLength(obj.encoded)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,13 +21,14 @@ import org.assertj.core.api.Assertions.assertThatThrownBy
|
|||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.ExpectedException
|
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.Collections
|
import java.util.*
|
||||||
import kotlin.test.*
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class KryoTests {
|
class KryoTests {
|
||||||
@Rule
|
@Rule
|
||||||
@ -36,9 +37,6 @@ class KryoTests {
|
|||||||
private lateinit var factory: SerializationFactory
|
private lateinit var factory: SerializationFactory
|
||||||
private lateinit var context: SerializationContext
|
private lateinit var context: SerializationContext
|
||||||
|
|
||||||
@get:Rule
|
|
||||||
val expectedEx: ExpectedException = ExpectedException.none()
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) }
|
factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) }
|
||||||
@ -51,7 +49,7 @@ class KryoTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun ok() {
|
fun `simple data class`() {
|
||||||
val birthday = Instant.parse("1984-04-17T00:30:00.00Z")
|
val birthday = Instant.parse("1984-04-17T00:30:00.00Z")
|
||||||
val mike = Person("mike", birthday)
|
val mike = Person("mike", birthday)
|
||||||
val bits = mike.serialize(factory, context)
|
val bits = mike.serialize(factory, context)
|
||||||
@ -59,7 +57,7 @@ class KryoTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun nullables() {
|
fun `null values`() {
|
||||||
val bob = Person("bob", null)
|
val bob = Person("bob", null)
|
||||||
val bits = bob.serialize(factory, context)
|
val bits = bob.serialize(factory, context)
|
||||||
assertThat(bits.deserialize(factory, context)).isEqualTo(Person("bob", null))
|
assertThat(bits.deserialize(factory, context)).isEqualTo(Person("bob", null))
|
||||||
@ -202,13 +200,6 @@ class KryoTests {
|
|||||||
assertEquals(expected, actual)
|
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
|
@CordaSerializable
|
||||||
private object TestSingleton
|
private object TestSingleton
|
||||||
|
|
||||||
|
@ -2,16 +2,16 @@ package net.corda.nodeapi.internal.serialization
|
|||||||
|
|
||||||
import com.esotericsoftware.kryo.Kryo
|
import com.esotericsoftware.kryo.Kryo
|
||||||
import com.esotericsoftware.kryo.util.DefaultClassResolver
|
import com.esotericsoftware.kryo.util.DefaultClassResolver
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.node.services.statemachine.SessionData
|
import net.corda.node.services.statemachine.SessionData
|
||||||
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
|
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
|
||||||
|
import net.corda.testing.SerializationEnvironmentRule
|
||||||
import net.corda.testing.amqpSpecific
|
import net.corda.testing.amqpSpecific
|
||||||
import net.corda.testing.kryoSpecific
|
import net.corda.testing.kryoSpecific
|
||||||
import net.corda.testing.SerializationEnvironmentRule
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
import org.assertj.core.api.Assertions
|
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
|
||||||
import org.junit.Assert.assertArrayEquals
|
import org.junit.Assert.assertArrayEquals
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
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.") {
|
fun `check throws for forbidden declared type`() = amqpSpecific("Such exceptions are not expected in Kryo mode.") {
|
||||||
val payload = HashMap<String, String>(smallMap)
|
val payload = HashMap<String, String>(smallMap)
|
||||||
val wrongPayloadType = WrongPayloadType(payload)
|
val wrongPayloadType = WrongPayloadType(payload)
|
||||||
Assertions.assertThatThrownBy { wrongPayloadType.serialize() }
|
assertThatThrownBy { wrongPayloadType.serialize() }
|
||||||
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining(
|
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining(
|
||||||
"Map type class java.util.HashMap is unstable under iteration. Suggested fix: use java.util.LinkedHashMap instead.")
|
"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)
|
data class MyKey(val keyContent: Double)
|
||||||
|
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class MyValue(val valueContent: X500Name)
|
data class MyValue(val valueContent: CordaX500Name)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `check map serialization works with custom types`() {
|
fun `check map serialization works with custom types`() {
|
||||||
val myMap = mapOf(
|
val myMap = mapOf(
|
||||||
MyKey(1.0) to MyValue(X500Name("CN=one")),
|
MyKey(1.0) to MyValue(CordaX500Name("OOO", "LLL", "CC")),
|
||||||
MyKey(10.0) to MyValue(X500Name("CN=ten")))
|
MyKey(10.0) to MyValue(CordaX500Name("OO", "LL", "CC")))
|
||||||
assertEqualAfterRoundTripSerialization(myMap)
|
assertEqualAfterRoundTripSerialization(myMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `check empty map serialises as Java emptyMap`() = kryoSpecific("Specifically checks Kryo serialization") {
|
fun `check empty map serialises as Java emptyMap`() {
|
||||||
val nameID = 0
|
kryoSpecific("Specifically checks Kryo serialization") {
|
||||||
val serializedForm = emptyMap<Int, Int>().serialize()
|
val nameID = 0
|
||||||
val output = ByteArrayOutputStream().apply {
|
val serializedForm = emptyMap<Int, Int>().serialize()
|
||||||
write(KryoHeaderV0_1.bytes)
|
val output = ByteArrayOutputStream().apply {
|
||||||
write(DefaultClassResolver.NAME + 2)
|
write(KryoHeaderV0_1.bytes)
|
||||||
write(nameID)
|
write(DefaultClassResolver.NAME + 2)
|
||||||
write(javaEmptyMapClass.name.toAscii())
|
write(nameID)
|
||||||
write(Kryo.NOT_NULL.toInt())
|
write(javaEmptyMapClass.name.toAscii())
|
||||||
|
write(Kryo.NOT_NULL.toInt())
|
||||||
|
}
|
||||||
|
assertArrayEquals(output.toByteArray(), serializedForm.bytes)
|
||||||
}
|
}
|
||||||
assertArrayEquals(output.toByteArray(), serializedForm.bytes)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
|
@file:Suppress("unused", "MemberVisibilityCanPrivate")
|
||||||
|
|
||||||
package net.corda.nodeapi.internal.serialization.amqp
|
package net.corda.nodeapi.internal.serialization.amqp
|
||||||
|
|
||||||
|
import net.corda.client.rpc.RPCException
|
||||||
import net.corda.core.CordaRuntimeException
|
import net.corda.core.CordaRuntimeException
|
||||||
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.crypto.secureRandomBytes
|
||||||
import net.corda.core.flows.FlowException
|
import net.corda.core.flows.FlowException
|
||||||
import net.corda.core.identity.AbstractParty
|
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.CordaSerializable
|
||||||
|
import net.corda.core.serialization.MissingAttachmentsException
|
||||||
import net.corda.core.serialization.SerializationFactory
|
import net.corda.core.serialization.SerializationFactory
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
import net.corda.client.rpc.RPCException
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import net.corda.core.contracts.*
|
|
||||||
import net.corda.core.internal.AbstractAttachment
|
|
||||||
import net.corda.core.serialization.MissingAttachmentsException
|
|
||||||
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||||
import net.corda.nodeapi.internal.serialization.EmptyWhitelist
|
import net.corda.nodeapi.internal.serialization.EmptyWhitelist
|
||||||
import net.corda.nodeapi.internal.serialization.GeneratedAttachment
|
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.amqp.*
|
||||||
import org.apache.qpid.proton.codec.DecoderImpl
|
import org.apache.qpid.proton.codec.DecoderImpl
|
||||||
import org.apache.qpid.proton.codec.EncoderImpl
|
import org.apache.qpid.proton.codec.EncoderImpl
|
||||||
import org.junit.Assert.assertArrayEquals
|
import org.assertj.core.api.Assertions.*
|
||||||
import org.junit.Assert.assertNotSame
|
import org.junit.Assert.*
|
||||||
import org.junit.Assert.assertSame
|
|
||||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
@ -39,10 +40,7 @@ import java.nio.ByteBuffer
|
|||||||
import java.time.*
|
import java.time.*
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.reflect.full.declaredFunctions
|
|
||||||
import kotlin.reflect.full.declaredMemberFunctions
|
|
||||||
import kotlin.reflect.full.superclasses
|
import kotlin.reflect.full.superclasses
|
||||||
import kotlin.reflect.jvm.javaMethod
|
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertNotNull
|
import kotlin.test.assertNotNull
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
@ -79,7 +77,6 @@ class SerializationOutputTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class Woo(val fred: Int) {
|
data class Woo(val fred: Int) {
|
||||||
@Suppress("unused")
|
|
||||||
val bob = "Bob"
|
val bob = "Bob"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +86,6 @@ class SerializationOutputTests {
|
|||||||
|
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class AnnotatedWoo(val fred: Int) {
|
data class AnnotatedWoo(val fred: Int) {
|
||||||
@Suppress("unused")
|
|
||||||
val bob = "Bob"
|
val bob = "Bob"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,6 +147,13 @@ class SerializationOutputTests {
|
|||||||
|
|
||||||
data class PolymorphicProperty(val foo: FooInterface?)
|
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,
|
private inline fun <reified T : Any> serdes(obj: T,
|
||||||
factory: SerializerFactory = SerializerFactory(
|
factory: SerializerFactory = SerializerFactory(
|
||||||
AllWhitelist, ClassLoader.getSystemClassLoader()),
|
AllWhitelist, ClassLoader.getSystemClassLoader()),
|
||||||
@ -406,6 +409,32 @@ class SerializationOutputTests {
|
|||||||
serdes(obj)
|
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
|
@Test
|
||||||
fun `test custom serializers on public key`() {
|
fun `test custom serializers on public key`() {
|
||||||
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
||||||
@ -762,26 +791,32 @@ class SerializationOutputTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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())
|
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())
|
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)
|
serdes(obj, factory, factory2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test party and certificate serialize`() {
|
fun `test cert path serialize`() {
|
||||||
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
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())
|
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)
|
serdes(obj, factory, factory2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,8 +139,14 @@ dependencies {
|
|||||||
compile "io.netty:netty-all:$netty_version"
|
compile "io.netty:netty-all:$netty_version"
|
||||||
|
|
||||||
// CRaSH: An embeddable monitoring and admin shell with support for adding new commands written in Groovy.
|
// 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.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.
|
// OkHTTP: Simple HTTP library.
|
||||||
@ -157,6 +163,9 @@ dependencies {
|
|||||||
integrationTestCompile "junit:junit:$junit_version"
|
integrationTestCompile "junit:junit:$junit_version"
|
||||||
integrationTestCompile "org.assertj:assertj-core:${assertj_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.
|
// Jetty dependencies for NetworkMapClient test.
|
||||||
// Web stuff: for HTTP[S] servlets
|
// Web stuff: for HTTP[S] servlets
|
||||||
testCompile "org.eclipse.jetty:jetty-servlet:${jetty_version}"
|
testCompile "org.eclipse.jetty:jetty-servlet:${jetty_version}"
|
||||||
|
170
node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt
Normal file
170
node/src/integration-test/kotlin/net/corda/node/SSHServerTest.kt
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
@ -13,8 +13,8 @@ import net.corda.core.internal.div
|
|||||||
import net.corda.core.internal.toLedgerTransaction
|
import net.corda.core.internal.toLedgerTransaction
|
||||||
import net.corda.core.serialization.SerializationFactory
|
import net.corda.core.serialization.SerializationFactory
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.getOrThrow
|
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.CordappLoader
|
||||||
import net.corda.node.internal.cordapp.CordappProviderImpl
|
import net.corda.node.internal.cordapp.CordappProviderImpl
|
||||||
import net.corda.testing.*
|
import net.corda.testing.*
|
||||||
@ -41,7 +41,7 @@ class AttachmentLoadingTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
val logger = loggerFor<AttachmentLoadingTests>()
|
private val logger = contextLogger()
|
||||||
val isolatedJAR = AttachmentLoadingTests::class.java.getResource("isolated.jar")!!
|
val isolatedJAR = AttachmentLoadingTests::class.java.getResource("isolated.jar")!!
|
||||||
val ISOLATED_CONTRACT_ID = "net.corda.finance.contracts.isolated.AnotherDummyContract"
|
val ISOLATED_CONTRACT_ID = "net.corda.finance.contracts.isolated.AnotherDummyContract"
|
||||||
|
|
||||||
|
@ -13,8 +13,6 @@ import net.corda.core.identity.CordaX500Name
|
|||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.deleteIfExists
|
import net.corda.core.internal.deleteIfExists
|
||||||
import net.corda.core.internal.div
|
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.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
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.internal.StartedNode
|
||||||
import net.corda.node.services.config.BFTSMaRtConfiguration
|
import net.corda.node.services.config.BFTSMaRtConfiguration
|
||||||
import net.corda.node.services.config.NotaryConfig
|
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.minClusterSize
|
||||||
import net.corda.node.services.transactions.minCorrectReplicas
|
import net.corda.node.services.transactions.minCorrectReplicas
|
||||||
import net.corda.node.utilities.ServiceIdentityGenerator
|
import net.corda.node.utilities.ServiceIdentityGenerator
|
||||||
import net.corda.testing.chooseIdentity
|
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.contracts.DummyContract
|
||||||
import net.corda.testing.dummyCommand
|
import net.corda.testing.dummyCommand
|
||||||
import net.corda.testing.node.MockNetwork
|
import net.corda.testing.node.MockNetwork
|
||||||
@ -57,26 +54,19 @@ class BFTNotaryServiceTests {
|
|||||||
|
|
||||||
notary = ServiceIdentityGenerator.generateToDisk(
|
notary = ServiceIdentityGenerator.generateToDisk(
|
||||||
replicaIds.map { mockNet.baseDirectory(mockNet.nextNodeId + it) },
|
replicaIds.map { mockNet.baseDirectory(mockNet.nextNodeId + it) },
|
||||||
CordaX500Name("BFT", "Zurich", "CH"),
|
CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH")
|
||||||
NotaryService.constructId(validating = false, bft = true))
|
)
|
||||||
|
|
||||||
val networkParameters = NetworkParametersCopier(testNetworkParameters(listOf(NotaryInfo(notary, false))))
|
|
||||||
|
|
||||||
val clusterAddresses = replicaIds.map { NetworkHostAndPort("localhost", 11000 + it * 10) }
|
val clusterAddresses = replicaIds.map { NetworkHostAndPort("localhost", 11000 + it * 10) }
|
||||||
|
|
||||||
val nodes = replicaIds.map { replicaId ->
|
replicaIds.forEach { replicaId ->
|
||||||
mockNet.createUnstartedNode(MockNodeParameters(configOverrides = {
|
mockNet.createNode(MockNodeParameters(configOverrides = {
|
||||||
val notary = NotaryConfig(validating = false, bftSMaRt = BFTSMaRtConfiguration(replicaId, clusterAddresses, exposeRaces = exposeRaces))
|
val notary = NotaryConfig(validating = false, bftSMaRt = BFTSMaRtConfiguration(replicaId, clusterAddresses, exposeRaces = exposeRaces))
|
||||||
doReturn(notary).whenever(it).notary
|
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
|
node = mockNet.createNode()
|
||||||
// network-parameters in their directories before they're started.
|
|
||||||
node = nodes.map { node ->
|
|
||||||
networkParameters.install(mockNet.baseDirectory(node.id))
|
|
||||||
node.start()
|
|
||||||
}.last()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Failure mode is the redundant replica gets stuck in startup, so we can't dispose it cleanly at the end. */
|
/** Failure mode is the redundant replica gets stuck in startup, so we can't dispose it cleanly at the end. */
|
||||||
|
@ -13,6 +13,7 @@ import net.corda.finance.flows.CashIssueFlow
|
|||||||
import net.corda.finance.flows.CashPaymentFlow
|
import net.corda.finance.flows.CashPaymentFlow
|
||||||
import net.corda.node.services.Permissions.Companion.invokeRpc
|
import net.corda.node.services.Permissions.Companion.invokeRpc
|
||||||
import net.corda.node.services.Permissions.Companion.startFlow
|
import net.corda.node.services.Permissions.Companion.startFlow
|
||||||
|
import net.corda.node.services.transactions.RaftValidatingNotaryService
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
import net.corda.testing.*
|
import net.corda.testing.*
|
||||||
import net.corda.testing.driver.NodeHandle
|
import net.corda.testing.driver.NodeHandle
|
||||||
@ -41,7 +42,7 @@ class DistributedServiceTests {
|
|||||||
|
|
||||||
driver(
|
driver(
|
||||||
extraCordappPackagesToScan = listOf("net.corda.finance.contracts"),
|
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()
|
alice = startNode(providedName = ALICE.name, rpcUsers = listOf(testUser)).getOrThrow()
|
||||||
raftNotaryIdentity = defaultNotaryIdentity
|
raftNotaryIdentity = defaultNotaryIdentity
|
||||||
|
@ -3,8 +3,8 @@ package net.corda.services.messaging
|
|||||||
import net.corda.core.crypto.Crypto
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.internal.*
|
import net.corda.core.internal.*
|
||||||
import net.corda.node.utilities.*
|
import net.corda.node.utilities.*
|
||||||
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
||||||
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEER_USER
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
||||||
import net.corda.nodeapi.RPCApi
|
import net.corda.nodeapi.RPCApi
|
||||||
import net.corda.nodeapi.config.SSLConfiguration
|
import net.corda.nodeapi.config.SSLConfiguration
|
||||||
import net.corda.testing.MEGA_CORP
|
import net.corda.testing.MEGA_CORP
|
||||||
|
@ -17,10 +17,10 @@ import net.corda.core.utilities.toBase58String
|
|||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
import net.corda.node.internal.Node
|
import net.corda.node.internal.Node
|
||||||
import net.corda.node.internal.StartedNode
|
import net.corda.node.internal.StartedNode
|
||||||
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX
|
||||||
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NOTIFICATIONS_ADDRESS
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NOTIFICATIONS_ADDRESS
|
||||||
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.P2P_QUEUE
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE
|
||||||
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEERS_PREFIX
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX
|
||||||
import net.corda.nodeapi.RPCApi
|
import net.corda.nodeapi.RPCApi
|
||||||
import net.corda.nodeapi.User
|
import net.corda.nodeapi.User
|
||||||
import net.corda.nodeapi.config.SSLConfiguration
|
import net.corda.nodeapi.config.SSLConfiguration
|
||||||
|
@ -40,26 +40,30 @@ class NodeStatePersistenceTests {
|
|||||||
|
|
||||||
val user = User("mark", "dadada", setOf(startFlow<SendMessageFlow>(), invokeRpc("vaultQuery")))
|
val user = User("mark", "dadada", setOf(startFlow<SendMessageFlow>(), invokeRpc("vaultQuery")))
|
||||||
val message = Message("Hello world!")
|
val message = Message("Hello world!")
|
||||||
driver(isDebug = true, startNodesInProcess = isQuasarAgentSpecified()) {
|
val stateAndRef: StateAndRef<MessageState>? = driver(isDebug = true, startNodesInProcess = isQuasarAgentSpecified()) {
|
||||||
val nodeName = {
|
val nodeName = {
|
||||||
val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow()
|
val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow()
|
||||||
val nodeName = nodeHandle.nodeInfo.chooseIdentity().name
|
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 {
|
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()
|
nodeHandle.stop()
|
||||||
nodeName
|
nodeName
|
||||||
}()
|
}()
|
||||||
|
|
||||||
val nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user)).getOrThrow()
|
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 page = it.proxy.vaultQuery(MessageState::class.java)
|
||||||
val stateAndRef = page.states.singleOrNull()
|
page.states.singleOrNull()
|
||||||
assertNotNull(stateAndRef)
|
|
||||||
val retrievedMessage = stateAndRef!!.state.data.message
|
|
||||||
assertEquals(message, retrievedMessage)
|
|
||||||
}
|
}
|
||||||
|
nodeHandle.stop()
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
assertNotNull(stateAndRef)
|
||||||
|
val retrievedMessage = stateAndRef!!.state.data.message
|
||||||
|
assertEquals(message, retrievedMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +130,7 @@ open class MessageContract : Contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
class SendMessageFlow(private val message: Message) : FlowLogic<SignedTransaction>() {
|
class SendMessageFlow(private val message: Message, private val notary: Party) : FlowLogic<SignedTransaction>() {
|
||||||
companion object {
|
companion object {
|
||||||
object GENERATING_TRANSACTION : ProgressTracker.Step("Generating transaction based on the message.")
|
object GENERATING_TRANSACTION : ProgressTracker.Step("Generating transaction based on the message.")
|
||||||
object VERIFYING_TRANSACTION : ProgressTracker.Step("Verifying contract constraints.")
|
object VERIFYING_TRANSACTION : ProgressTracker.Step("Verifying contract constraints.")
|
||||||
@ -142,8 +146,6 @@ class SendMessageFlow(private val message: Message) : FlowLogic<SignedTransactio
|
|||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): SignedTransaction {
|
override fun call(): SignedTransaction {
|
||||||
val notary = serviceHub.networkMapCache.notaryIdentities.first()
|
|
||||||
|
|
||||||
progressTracker.currentStep = GENERATING_TRANSACTION
|
progressTracker.currentStep = GENERATING_TRANSACTION
|
||||||
|
|
||||||
val messageState = MessageState(message = message, by = ourIdentity)
|
val messageState = MessageState(message = message, by = ourIdentity)
|
||||||
|
@ -2,6 +2,9 @@ package net.corda.node.shell;
|
|||||||
|
|
||||||
// See the comments at the top of run.java
|
// 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.cli.*;
|
||||||
import org.crsh.command.*;
|
import org.crsh.command.*;
|
||||||
import org.crsh.text.*;
|
import org.crsh.text.*;
|
||||||
@ -9,6 +12,7 @@ import org.crsh.text.ui.TableElement;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import static net.corda.node.services.messaging.RPCServerKt.CURRENT_RPC_CONTEXT;
|
||||||
import static net.corda.node.shell.InteractiveShell.*;
|
import static net.corda.node.shell.InteractiveShell.*;
|
||||||
|
|
||||||
@Man(
|
@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 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
|
@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?
|
// TODO Limit number of flows shown option?
|
||||||
@Command
|
@Command
|
||||||
@Usage("watch information about state machines running on the node with result information")
|
@Usage("watch information about state machines running on the node with result information")
|
||||||
public void watch(InvocationContext<TableElement> context) throws Exception {
|
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,
|
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,
|
@Usage("The data to pass as input") @Argument(unquote = false) List<String> input,
|
||||||
RenderPrintWriter out) {
|
RenderPrintWriter out,
|
||||||
|
CordaRPCOps rpcOps,
|
||||||
|
ANSIProgressRenderer ansiProgressRenderer) {
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
out.println("You must pass a name for the flow, see 'man flow'", Color.red);
|
out.println("You must pass a name for the flow, see 'man flow'", Color.red);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String inp = input == null ? "" : String.join(" ", input).trim();
|
String inp = input == null ? "" : String.join(" ", input).trim();
|
||||||
runFlowByNameFragment(name, inp, out);
|
runFlowByNameFragment(name, inp, out, rpcOps, ansiProgressRenderer != null ? ansiProgressRenderer : new CRaSHNSIProgressRenderer(out) );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command
|
@Command
|
||||||
|
@ -2,6 +2,8 @@ package net.corda.node.shell;
|
|||||||
|
|
||||||
// A simple forwarder to the "flow start" command, for easier typing.
|
// 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 org.crsh.cli.*;
|
||||||
|
|
||||||
import java.util.*;
|
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\"")
|
@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,
|
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) {
|
@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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,10 @@ import net.corda.core.internal.concurrent.openFuture
|
|||||||
import net.corda.core.messaging.*
|
import net.corda.core.messaging.*
|
||||||
import net.corda.core.node.*
|
import net.corda.core.node.*
|
||||||
import net.corda.core.node.services.*
|
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.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.debug
|
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.ContractUpgradeHandler
|
||||||
import net.corda.node.services.FinalityHandler
|
import net.corda.node.services.FinalityHandler
|
||||||
import net.corda.node.services.NotaryChangeHandler
|
import net.corda.node.services.NotaryChangeHandler
|
||||||
|
import net.corda.node.services.RPCUserService
|
||||||
import net.corda.node.services.api.*
|
import net.corda.node.services.api.*
|
||||||
import net.corda.node.services.config.BFTSMaRtConfiguration
|
import net.corda.node.services.config.BFTSMaRtConfiguration
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
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.identity.PersistentIdentityService
|
||||||
import net.corda.node.services.keys.PersistentKeyManagementService
|
import net.corda.node.services.keys.PersistentKeyManagementService
|
||||||
import net.corda.node.services.messaging.MessagingService
|
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.network.*
|
||||||
import net.corda.node.services.persistence.DBCheckpointStorage
|
import net.corda.node.services.persistence.*
|
||||||
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.schema.HibernateObserver
|
import net.corda.node.services.schema.HibernateObserver
|
||||||
import net.corda.node.services.schema.NodeSchemaService
|
import net.corda.node.services.schema.NodeSchemaService
|
||||||
import net.corda.node.services.statemachine.*
|
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.upgrade.ContractUpgradeServiceImpl
|
||||||
import net.corda.node.services.vault.NodeVaultService
|
import net.corda.node.services.vault.NodeVaultService
|
||||||
import net.corda.node.services.vault.VaultSoftLockManager
|
import net.corda.node.services.vault.VaultSoftLockManager
|
||||||
|
import net.corda.node.shell.InteractiveShell
|
||||||
import net.corda.node.utilities.*
|
import net.corda.node.utilities.*
|
||||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.subjects.PublishSubject
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
import java.security.KeyPair
|
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
|
* 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.
|
* 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
|
// 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.
|
// AbstractNode. It should be possible to generate the NodeInfo outside of AbstractNode, so it can be passed in.
|
||||||
abstract class AbstractNode(val configuration: NodeConfiguration,
|
abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||||
@ -120,14 +115,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
// low-performance prototyping period.
|
// low-performance prototyping period.
|
||||||
protected abstract val serverThread: AffinityExecutor
|
protected abstract val serverThread: AffinityExecutor
|
||||||
|
|
||||||
protected lateinit var networkParameters: NetworkParameters
|
|
||||||
private val cordappServices = MutableClassToInstanceMap.create<SerializeAsToken>()
|
private val cordappServices = MutableClassToInstanceMap.create<SerializeAsToken>()
|
||||||
private val flowFactories = ConcurrentHashMap<Class<out FlowLogic<*>>, InitiatedFlowFactory<*>>()
|
private val flowFactories = ConcurrentHashMap<Class<out FlowLogic<*>>, InitiatedFlowFactory<*>>()
|
||||||
|
|
||||||
protected val services: ServiceHubInternal get() = _services
|
protected val services: ServiceHubInternal get() = _services
|
||||||
private lateinit var _services: ServiceHubInternalImpl
|
private lateinit var _services: ServiceHubInternalImpl
|
||||||
protected lateinit var info: NodeInfo
|
|
||||||
protected val nodeStateObservable: PublishSubject<NodeState> = PublishSubject.create<NodeState>()
|
|
||||||
protected var myNotaryIdentity: PartyAndCertificate? = null
|
protected var myNotaryIdentity: PartyAndCertificate? = null
|
||||||
protected lateinit var checkpointStorage: CheckpointStorage
|
protected lateinit var checkpointStorage: CheckpointStorage
|
||||||
protected lateinit var smm: StateMachineManager
|
protected lateinit var smm: StateMachineManager
|
||||||
@ -138,6 +130,8 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
protected val _nodeReadyFuture = openFuture<Unit>()
|
protected val _nodeReadyFuture = openFuture<Unit>()
|
||||||
protected val networkMapClient: NetworkMapClient? by lazy { configuration.compatibilityZoneURL?.let(::NetworkMapClient) }
|
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
|
/** Completes once the node has successfully registered with the network map service
|
||||||
* or has loaded network map data from local database */
|
* or has loaded network map data from local database */
|
||||||
val nodeReadyFuture: CordaFuture<Unit>
|
val nodeReadyFuture: CordaFuture<Unit>
|
||||||
@ -173,7 +167,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
check(started == null) { "Node has already been started" }
|
check(started == null) { "Node has already been started" }
|
||||||
log.info("Generating nodeInfo ...")
|
log.info("Generating nodeInfo ...")
|
||||||
initCertificate()
|
initCertificate()
|
||||||
val keyPairs = initNodeInfo()
|
val (keyPairs, info) = initNodeInfo()
|
||||||
val identityKeypair = keyPairs.first { it.public == info.legalIdentities.first().owningKey }
|
val identityKeypair = keyPairs.first { it.public == info.legalIdentities.first().owningKey }
|
||||||
val serialisedNodeInfo = info.serialize()
|
val serialisedNodeInfo = info.serialize()
|
||||||
val signature = identityKeypair.sign(serialisedNodeInfo)
|
val signature = identityKeypair.sign(serialisedNodeInfo)
|
||||||
@ -185,14 +179,13 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
check(started == null) { "Node has already been started" }
|
check(started == null) { "Node has already been started" }
|
||||||
log.info("Node starting up ...")
|
log.info("Node starting up ...")
|
||||||
initCertificate()
|
initCertificate()
|
||||||
val keyPairs = initNodeInfo()
|
val (keyPairs, info) = initNodeInfo()
|
||||||
readNetworkParameters()
|
|
||||||
val schemaService = NodeSchemaService(cordappLoader)
|
val schemaService = NodeSchemaService(cordappLoader)
|
||||||
// Do all of this in a database transaction so anything that might need a connection has one.
|
// Do all of this in a database transaction so anything that might need a connection has one.
|
||||||
val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService) { database ->
|
val (startedImpl, schedulerService) = initialiseDatabasePersistence(schemaService) { database ->
|
||||||
val transactionStorage = makeTransactionStorage(database)
|
val transactionStorage = makeTransactionStorage(database)
|
||||||
val stateLoader = StateLoaderImpl(transactionStorage)
|
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)
|
val notaryService = makeNotaryService(nodeServices, database)
|
||||||
smm = makeStateMachineManager(database)
|
smm = makeStateMachineManager(database)
|
||||||
val flowStarter = FlowStarterImpl(serverThread, smm)
|
val flowStarter = FlowStarterImpl(serverThread, smm)
|
||||||
@ -220,8 +213,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
registerCordappFlows()
|
registerCordappFlows()
|
||||||
_services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows }
|
_services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows }
|
||||||
FlowLogicRefFactoryImpl.classloader = cordappLoader.appClassLoader
|
FlowLogicRefFactoryImpl.classloader = cordappLoader.appClassLoader
|
||||||
|
startShell(rpcOps)
|
||||||
runOnStop += network::stop
|
|
||||||
Pair(StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, network, database, rpcOps, flowStarter, notaryService), schedulerService)
|
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 (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
|
||||||
val keyPairs = mutableSetOf(identityKeyPair)
|
val keyPairs = mutableSetOf(identityKeyPair)
|
||||||
|
|
||||||
myNotaryIdentity = configuration.notary?.let {
|
myNotaryIdentity = configuration.notary?.let {
|
||||||
if (it.isClusterConfig) {
|
val (notaryIdentity, notaryIdentityKeyPair) = obtainIdentity(it)
|
||||||
val (notaryIdentity, notaryIdentityKeyPair) = obtainIdentity(it)
|
keyPairs += notaryIdentityKeyPair
|
||||||
keyPairs += notaryIdentityKeyPair
|
notaryIdentity
|
||||||
notaryIdentity
|
|
||||||
} else {
|
|
||||||
// In case of a single notary service myNotaryIdentity will be the node's single identity.
|
|
||||||
identity
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
info = NodeInfo(
|
val info = NodeInfo(
|
||||||
myAddresses(),
|
myAddresses(),
|
||||||
setOf(identity, myNotaryIdentity).filterNotNull(),
|
listOf(identity, myNotaryIdentity).filterNotNull(),
|
||||||
versionInfo.platformVersion,
|
versionInfo.platformVersion,
|
||||||
platformClock.instant().toEpochMilli()
|
platformClock.instant().toEpochMilli()
|
||||||
)
|
)
|
||||||
return keyPairs
|
return Pair(keyPairs, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract fun myAddresses(): List<NetworkHostAndPort>
|
protected abstract fun myAddresses(): List<NetworkHostAndPort>
|
||||||
@ -498,12 +489,12 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
* Builds node internal, advertised, and plugin services.
|
* Builds node internal, advertised, and plugin services.
|
||||||
* Returns a list of tokenizable services to be added to the serialisation context.
|
* 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()
|
checkpointStorage = DBCheckpointStorage()
|
||||||
val metrics = MetricRegistry()
|
val metrics = MetricRegistry()
|
||||||
attachments = NodeAttachmentService(metrics)
|
attachments = NodeAttachmentService(metrics)
|
||||||
val cordappProvider = CordappProviderImpl(cordappLoader, attachments)
|
val cordappProvider = CordappProviderImpl(cordappLoader, attachments)
|
||||||
val identityService = makeIdentityService()
|
val identityService = makeIdentityService(info)
|
||||||
val keyManagementService = makeKeyManagementService(identityService, keyPairs)
|
val keyManagementService = makeKeyManagementService(identityService, keyPairs)
|
||||||
_services = ServiceHubInternalImpl(
|
_services = ServiceHubInternalImpl(
|
||||||
identityService,
|
identityService,
|
||||||
@ -513,8 +504,9 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
stateLoader,
|
stateLoader,
|
||||||
MonitoringService(metrics),
|
MonitoringService(metrics),
|
||||||
cordappProvider,
|
cordappProvider,
|
||||||
database)
|
database,
|
||||||
network = makeMessagingService(database)
|
info)
|
||||||
|
network = makeMessagingService(database, info)
|
||||||
val tokenizableServices = mutableListOf(attachments, network, services.vaultService,
|
val tokenizableServices = mutableListOf(attachments, network, services.vaultService,
|
||||||
services.keyManagementService, services.identityService, platformClock,
|
services.keyManagementService, services.identityService, platformClock,
|
||||||
services.auditService, services.monitoringService, services.networkMapCache, services.schemaService,
|
services.auditService, services.monitoringService, services.networkMapCache, services.schemaService,
|
||||||
@ -596,13 +588,6 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
return PersistentKeyManagementService(identityService, keyPairs)
|
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 {
|
private fun makeCoreNotaryService(notaryConfig: NotaryConfig, database: CordaPersistence): NotaryService {
|
||||||
val notaryKey = myNotaryIdentity?.owningKey ?: throw IllegalArgumentException("No notary identity initialized when creating a notary service")
|
val notaryKey = myNotaryIdentity?.owningKey ?: throw IllegalArgumentException("No notary identity initialized when creating a notary service")
|
||||||
return notaryConfig.run {
|
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 trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword)
|
||||||
val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
|
val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
|
||||||
val trustRoot = trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA)
|
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
|
// 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.
|
// 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
|
// Run shutdown hooks in opposite order to starting
|
||||||
for (toRun in runOnStop.reversed()) {
|
for (toRun in runOnStop.reversed()) {
|
||||||
toRun()
|
toRun()
|
||||||
@ -655,22 +637,28 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
_started = null
|
_started = null
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract fun makeMessagingService(database: CordaPersistence): MessagingService
|
protected abstract fun makeMessagingService(database: CordaPersistence, info: NodeInfo): MessagingService
|
||||||
protected abstract fun startMessagingService(rpcOps: RPCOps)
|
protected abstract fun startMessagingService(rpcOps: RPCOps)
|
||||||
|
|
||||||
private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair<PartyAndCertificate, KeyPair> {
|
private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair<PartyAndCertificate, KeyPair> {
|
||||||
val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
|
val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
|
||||||
|
|
||||||
val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) {
|
val (id, singleName) = if (notaryConfig == null) {
|
||||||
// Node's main identity or if it's a single node notary
|
// Node's main identity
|
||||||
Pair("identity", myLegalName)
|
Pair("identity", myLegalName)
|
||||||
} else {
|
} else {
|
||||||
val notaryId = notaryConfig.run {
|
val notaryId = notaryConfig.run {
|
||||||
NotaryService.constructId(validating, raft != null, bftSMaRt != null, custom)
|
NotaryService.constructId(validating, raft != null, bftSMaRt != null, custom)
|
||||||
}
|
}
|
||||||
// The node is part of a distributed notary whose identity must already be generated beforehand.
|
if (!notaryConfig.isClusterConfig) {
|
||||||
Pair(notaryId, null)
|
// 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?
|
// TODO: Integrate with Key management service?
|
||||||
val privateKeyAlias = "$id-private-key"
|
val privateKeyAlias = "$id-private-key"
|
||||||
|
|
||||||
@ -728,26 +716,19 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
private val stateLoader: StateLoader,
|
private val stateLoader: StateLoader,
|
||||||
override val monitoringService: MonitoringService,
|
override val monitoringService: MonitoringService,
|
||||||
override val cordappProvider: CordappProviderInternal,
|
override val cordappProvider: CordappProviderInternal,
|
||||||
override val database: CordaPersistence
|
override val database: CordaPersistence,
|
||||||
|
override val myInfo: NodeInfo
|
||||||
) : SingletonSerializeAsToken(), ServiceHubInternal, StateLoader by stateLoader {
|
) : SingletonSerializeAsToken(), ServiceHubInternal, StateLoader by stateLoader {
|
||||||
override val rpcFlows = ArrayList<Class<out FlowLogic<*>>>()
|
override val rpcFlows = ArrayList<Class<out FlowLogic<*>>>()
|
||||||
override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage()
|
override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage()
|
||||||
override val auditService = DummyAuditService()
|
override val auditService = DummyAuditService()
|
||||||
override val transactionVerifierService by lazy { makeTransactionVerifierService() }
|
override val transactionVerifierService by lazy { makeTransactionVerifierService() }
|
||||||
override val networkMapCache by lazy {
|
override val networkMapCache by lazy { NetworkMapCacheImpl(PersistentNetworkMapCache(database), identityService) }
|
||||||
NetworkMapCacheImpl(
|
|
||||||
PersistentNetworkMapCache(
|
|
||||||
database,
|
|
||||||
networkParameters.notaries),
|
|
||||||
identityService)
|
|
||||||
}
|
|
||||||
override val vaultService by lazy { makeVaultService(keyManagementService, stateLoader, database.hibernateConfig) }
|
override val vaultService by lazy { makeVaultService(keyManagementService, stateLoader, database.hibernateConfig) }
|
||||||
override val contractUpgradeService by lazy { ContractUpgradeServiceImpl() }
|
override val contractUpgradeService by lazy { ContractUpgradeServiceImpl() }
|
||||||
override val attachments: AttachmentStorage get() = this@AbstractNode.attachments
|
override val attachments: AttachmentStorage get() = this@AbstractNode.attachments
|
||||||
override val networkService: MessagingService get() = network
|
override val networkService: MessagingService get() = network
|
||||||
override val clock: Clock get() = platformClock
|
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 val configuration: NodeConfiguration get() = this@AbstractNode.configuration
|
||||||
override fun <T : SerializeAsToken> cordaService(type: Class<T>): T {
|
override fun <T : SerializeAsToken> cordaService(type: Class<T>): T {
|
||||||
require(type.isAnnotationPresent(CordaService::class.java)) { "${type.name} is not a Corda service" }
|
require(type.isAnnotationPresent(CordaService::class.java)) { "${type.name} is not a Corda service" }
|
||||||
|
@ -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.node.services.vault.*
|
import net.corda.core.node.services.vault.*
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.getOrThrow
|
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.FlowStarter
|
||||||
import net.corda.node.services.api.ServiceHubInternal
|
import net.corda.node.services.api.ServiceHubInternal
|
||||||
import net.corda.node.services.messaging.context
|
import net.corda.node.services.messaging.context
|
||||||
@ -117,10 +117,6 @@ internal class CordaRPCOpsImpl(
|
|||||||
return services.myInfo
|
return services.myInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun nodeStateObservable(): Observable<NodeState> {
|
|
||||||
return services.myNodeStateObservable
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun notaryIdentities(): List<Party> {
|
override fun notaryIdentities(): List<Party> {
|
||||||
return services.networkMapCache.notaryIdentities
|
return services.networkMapCache.notaryIdentities
|
||||||
}
|
}
|
||||||
@ -142,7 +138,9 @@ internal class CordaRPCOpsImpl(
|
|||||||
return FlowProgressHandleImpl(
|
return FlowProgressHandleImpl(
|
||||||
id = stateMachine.id,
|
id = stateMachine.id,
|
||||||
returnValue = stateMachine.resultFuture,
|
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 {
|
companion object {
|
||||||
private val log = loggerFor<CordaRPCOpsImpl>()
|
private val log = contextLogger()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,21 +6,22 @@ import net.corda.core.internal.concurrent.openFuture
|
|||||||
import net.corda.core.internal.concurrent.thenMatch
|
import net.corda.core.internal.concurrent.thenMatch
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.messaging.RPCOps
|
import net.corda.core.messaging.RPCOps
|
||||||
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
|
import net.corda.core.node.services.TransactionVerifierService
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
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.SerializationEnvironmentImpl
|
||||||
import net.corda.core.serialization.internal.nodeSerializationEnv
|
import net.corda.core.serialization.internal.nodeSerializationEnv
|
||||||
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.node.VersionInfo
|
import net.corda.node.VersionInfo
|
||||||
import net.corda.node.internal.cordapp.CordappLoader
|
import net.corda.node.internal.cordapp.CordappLoader
|
||||||
import net.corda.node.serialization.KryoServerSerializationScheme
|
import net.corda.node.serialization.KryoServerSerializationScheme
|
||||||
import net.corda.node.services.RPCUserService
|
|
||||||
import net.corda.node.services.RPCUserServiceImpl
|
import net.corda.node.services.RPCUserServiceImpl
|
||||||
import net.corda.node.services.api.SchemaService
|
import net.corda.node.services.api.SchemaService
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
import net.corda.node.services.messaging.ArtemisMessagingServer
|
import net.corda.node.services.config.VerifierType
|
||||||
import net.corda.node.services.messaging.MessagingService
|
import net.corda.node.services.messaging.*
|
||||||
import net.corda.node.services.messaging.NodeMessagingClient
|
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
|
||||||
import net.corda.node.utilities.AddressUtils
|
import net.corda.node.utilities.AddressUtils
|
||||||
import net.corda.node.utilities.AffinityExecutor
|
import net.corda.node.utilities.AffinityExecutor
|
||||||
import net.corda.node.utilities.CordaPersistence
|
import net.corda.node.utilities.CordaPersistence
|
||||||
@ -48,7 +49,7 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
cordappLoader: CordappLoader = makeCordappLoader(configuration)
|
cordappLoader: CordappLoader = makeCordappLoader(configuration)
|
||||||
) : AbstractNode(configuration, createClock(configuration), versionInfo, cordappLoader) {
|
) : AbstractNode(configuration, createClock(configuration), versionInfo, cordappLoader) {
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = loggerFor<Node>()
|
private val staticLog = contextLogger()
|
||||||
var renderBasicInfoToConsole = true
|
var renderBasicInfoToConsole = true
|
||||||
|
|
||||||
/** Used for useful info that we always want to show, even when not logging to the console */
|
/** 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 val log: Logger get() = staticLog
|
||||||
override fun makeTransactionVerifierService() = (network as NodeMessagingClient).verifierService
|
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"
|
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 var shutdownHook: ShutdownHook? = null
|
||||||
|
|
||||||
private lateinit var userService: RPCUserService
|
override fun makeMessagingService(database: CordaPersistence, info: NodeInfo): MessagingService {
|
||||||
override fun makeMessagingService(database: CordaPersistence): MessagingService {
|
|
||||||
userService = RPCUserServiceImpl(configuration.rpcUsers)
|
userService = RPCUserServiceImpl(configuration.rpcUsers)
|
||||||
|
|
||||||
val serverAddress = configuration.messagingServerAddress ?: makeLocalMessageBroker()
|
val serverAddress = configuration.messagingServerAddress ?: makeLocalMessageBroker()
|
||||||
val advertisedAddress = info.addresses.single()
|
val advertisedAddress = info.addresses.single()
|
||||||
|
|
||||||
printBasicNodeInfo("Incoming connection address", advertisedAddress.toString())
|
printBasicNodeInfo("Incoming connection address", advertisedAddress.toString())
|
||||||
|
rpcMessagingClient = RPCMessagingClient(configuration, serverAddress)
|
||||||
return NodeMessagingClient(
|
verifierMessagingClient = when (configuration.verifierType) {
|
||||||
|
VerifierType.OutOfProcess -> VerifierMessagingClient(configuration, serverAddress, services.monitoringService.metrics)
|
||||||
|
VerifierType.InMemory -> null
|
||||||
|
}
|
||||||
|
return P2PMessagingClient(
|
||||||
configuration,
|
configuration,
|
||||||
versionInfo,
|
versionInfo,
|
||||||
serverAddress,
|
serverAddress,
|
||||||
info.legalIdentities[0].owningKey,
|
info.legalIdentities[0].owningKey,
|
||||||
serverThread,
|
serverThread,
|
||||||
database,
|
database,
|
||||||
services.monitoringService,
|
|
||||||
advertisedAddress)
|
advertisedAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,9 +203,19 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
runOnStop += this::stop
|
runOnStop += this::stop
|
||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
|
// Start up the MQ clients.
|
||||||
// Start up the MQ client.
|
rpcMessagingClient.run {
|
||||||
(network as NodeMessagingClient).start(rpcOps, userService)
|
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)
|
_startupComplete.set(Unit)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ th -> logger.error("Unexpected exception", th) }
|
{ th -> staticLog.error("Unexpected exception", th) } // XXX: Why not use log?
|
||||||
)
|
)
|
||||||
shutdownHook = addShutdownHook {
|
shutdownHook = addShutdownHook {
|
||||||
stop()
|
stop()
|
||||||
@ -289,9 +305,13 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
checkpointContext = KRYO_CHECKPOINT_CONTEXT.withClassLoader(classloader))
|
checkpointContext = KRYO_CHECKPOINT_CONTEXT.withClassLoader(classloader))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lateinit var rpcMessagingClient: RPCMessagingClient
|
||||||
|
private var verifierMessagingClient: VerifierMessagingClient? = null
|
||||||
/** Starts a blocking event loop for message dispatch. */
|
/** Starts a blocking event loop for message dispatch. */
|
||||||
fun run() {
|
fun run() {
|
||||||
(network as NodeMessagingClient).run(messageBroker!!.serverControl)
|
rpcMessagingClient.start2(messageBroker!!.serverControl)
|
||||||
|
verifierMessagingClient?.start2()
|
||||||
|
(network as P2PMessagingClient).run()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var shutdown = false
|
private var shutdown = false
|
||||||
|
@ -23,14 +23,13 @@ import java.lang.management.ManagementFactory
|
|||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.time.LocalDate
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
/** This class is responsible for starting a Node from command line arguments. */
|
/** This class is responsible for starting a Node from command line arguments. */
|
||||||
open class NodeStartup(val args: Array<String>) {
|
open class NodeStartup(val args: Array<String>) {
|
||||||
companion object {
|
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_DIRECTORY_NAME = "logs"
|
||||||
val LOGS_CAN_BE_FOUND_IN_STRING = "Logs can be found in"
|
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")
|
Node.printBasicNodeInfo("Node for \"$name\" started up and registered in $elapsed sec")
|
||||||
|
|
||||||
// Don't start the shell if there's no console attached.
|
// Don't start the shell if there's no console attached.
|
||||||
val runShell = !cmdlineOptions.noLocalShell && System.console() != null
|
if (!cmdlineOptions.noLocalShell && System.console() != null && conf.devMode) {
|
||||||
startedNode.internals.startupComplete.then {
|
startedNode.internals.startupComplete.then {
|
||||||
try {
|
try {
|
||||||
InteractiveShell.startShell(cmdlineOptions.baseDirectory, runShell, cmdlineOptions.sshdServer, startedNode)
|
InteractiveShell.runLocalShell(startedNode)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
logger.error("Shell failed to start", e)
|
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"
|
"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)
|
if (Emoji.hasEmojiTerminal)
|
||||||
messages += "Kind of like a regular database but\nwith emojis, colours and ascii art. ${Emoji.coolGuy}"
|
messages += "Kind of like a regular database but\nwith emojis, colours and ascii art. ${Emoji.coolGuy}"
|
||||||
val (msg1, msg2) = messages.randomOrNull()!!.split('\n')
|
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)}) -----------------------------------------------").
|
a("--- ${versionInfo.vendor} ${versionInfo.releaseVersion} (${versionInfo.revision.take(7)}) -----------------------------------------------").
|
||||||
newline().
|
newline().
|
||||||
newline().
|
newline().
|
||||||
a(cordaConBanner).
|
|
||||||
newline().
|
|
||||||
reset())
|
reset())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,14 +8,12 @@ import net.corda.core.identity.CordaX500Name
|
|||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.messaging.DataFeed
|
import net.corda.core.messaging.DataFeed
|
||||||
import net.corda.core.messaging.NodeState
|
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.services.AttachmentId
|
import net.corda.core.node.services.AttachmentId
|
||||||
import net.corda.core.node.services.NetworkMapCache
|
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.node.services.vault.*
|
import net.corda.core.node.services.vault.*
|
||||||
import net.corda.node.services.messaging.RpcAuthContext
|
import net.corda.node.services.messaging.RpcAuthContext
|
||||||
import rx.Observable
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.security.PublicKey
|
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 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 notaryIdentities(): List<Party> = guard("notaryIdentities", implementation::notaryIdentities)
|
||||||
|
|
||||||
override fun addVaultTransactionNote(txnId: SecureHash, txnNote: String) = guard("addVaultTransactionNote") {
|
override fun addVaultTransactionNote(txnId: SecureHash, txnNote: String) = guard("addVaultTransactionNote") {
|
||||||
|
@ -12,10 +12,11 @@ import net.corda.core.node.services.CordaService
|
|||||||
import net.corda.core.schemas.MappedSchema
|
import net.corda.core.schemas.MappedSchema
|
||||||
import net.corda.core.serialization.SerializationWhitelist
|
import net.corda.core.serialization.SerializationWhitelist
|
||||||
import net.corda.core.serialization.SerializeAsToken
|
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.internal.classloading.requireAnnotation
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
import net.corda.nodeapi.internal.serialization.DefaultWhitelist
|
import net.corda.nodeapi.internal.serialization.DefaultWhitelist
|
||||||
|
import org.apache.commons.collections4.map.LRUMap
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
@ -52,8 +53,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = loggerFor<CordappLoader>()
|
private val logger = contextLogger()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default cordapp dir name
|
* 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)))
|
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
|
* 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.
|
* 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
|
@VisibleForTesting
|
||||||
fun createDefaultWithTestPackages(configuration: NodeConfiguration, testPackages: List<String>): CordappLoader {
|
fun createDefaultWithTestPackages(configuration: NodeConfiguration, testPackages: List<String>): CordappLoader {
|
||||||
check(configuration.devMode) { "Package scanning can only occur in dev mode" }
|
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
|
@VisibleForTesting
|
||||||
fun createWithTestPackages(testPackages: List<String>)
|
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
|
* 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()
|
return scanResult.getClassesWithSuperclass(MappedSchema::class).toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val cachedScanResult = LRUMap<RestrictedURL, RestrictedScanResult>(1000)
|
||||||
private fun scanCordapp(cordappJarPath: RestrictedURL): RestrictedScanResult {
|
private fun scanCordapp(cordappJarPath: RestrictedURL): RestrictedScanResult {
|
||||||
logger.info("Scanning CorDapp in $cordappJarPath")
|
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<*>>> {
|
private class FlowTypeHierarchyComparator(val initiatingFlow: Class<out FlowLogic<*>>) : Comparator<Class<out FlowLogic<*>>> {
|
||||||
|
@ -2,18 +2,24 @@ package net.corda.node.internal.cordapp
|
|||||||
|
|
||||||
import com.google.common.collect.HashBiMap
|
import com.google.common.collect.HashBiMap
|
||||||
import net.corda.core.contracts.ContractClassName
|
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.Cordapp
|
||||||
import net.corda.core.cordapp.CordappContext
|
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.AttachmentId
|
||||||
|
import net.corda.core.node.services.AttachmentStorage
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
|
import net.corda.core.utilities.loggerFor
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cordapp provider and store. For querying CorDapps for their attachment and vice versa.
|
* Cordapp provider and store. For querying CorDapps for their attachment and vice versa.
|
||||||
*/
|
*/
|
||||||
open class CordappProviderImpl(private val cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage) : SingletonSerializeAsToken(), CordappProviderInternal {
|
open class CordappProviderImpl(private val cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage) : SingletonSerializeAsToken(), CordappProviderInternal {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = loggerFor<CordappProviderImpl>()
|
||||||
|
}
|
||||||
|
|
||||||
override fun getAppContext(): CordappContext {
|
override fun getAppContext(): CordappContext {
|
||||||
// TODO: Use better supported APIs in Java 9
|
// TODO: Use better supported APIs in Java 9
|
||||||
Exception().stackTrace.forEach { stackFrame ->
|
Exception().stackTrace.forEach { stackFrame ->
|
||||||
@ -45,7 +51,7 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader, attachm
|
|||||||
|
|
||||||
private fun loadContractsIntoAttachmentStore(attachmentStorage: AttachmentStorage): Map<SecureHash, URL> {
|
private fun loadContractsIntoAttachmentStore(attachmentStorage: AttachmentStorage): Map<SecureHash, URL> {
|
||||||
val cordappsWithAttachments = cordapps.filter { !it.contractClassNames.isEmpty() }.map { it.jarPath }
|
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()
|
return attachmentIds.zip(cordappsWithAttachments).toMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user