mirror of
https://github.com/corda/corda.git
synced 2024-12-26 16:11:12 +00:00
Merge remote-tracking branch 'open/master' into colljos-os-merge-rc01
This commit is contained in:
commit
e9fc6f7c8d
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@ -10,6 +10,8 @@
|
||||
<module name="bank-of-corda-demo_integrationTest" target="1.8" />
|
||||
<module name="bank-of-corda-demo_main" target="1.8" />
|
||||
<module name="bank-of-corda-demo_test" target="1.8" />
|
||||
<module name="bootstrapper_main" target="1.8" />
|
||||
<module name="bootstrapper_test" target="1.8" />
|
||||
<module name="buildSrc_main" target="1.8" />
|
||||
<module name="buildSrc_test" target="1.8" />
|
||||
<module name="business-network-demo_integrationTest" target="1.8" />
|
||||
|
18
build.gradle
18
build.gradle
@ -4,7 +4,7 @@ buildscript {
|
||||
file("$projectDir/constants.properties").withInputStream { constants.load(it) }
|
||||
|
||||
// Our version: bump this on release.
|
||||
ext.corda_release_version = "0.16-SNAPSHOT"
|
||||
ext.corda_release_version = "3.0-ENT-RC01"
|
||||
// Increment this on any release that changes public APIs anywhere in the Corda platform
|
||||
ext.corda_platform_version = constants.getProperty("platformVersion")
|
||||
ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion")
|
||||
@ -20,7 +20,21 @@ buildscript {
|
||||
ext.capsule_version = '1.0.1'
|
||||
|
||||
ext.asm_version = '0.5.3'
|
||||
ext.artemis_version = '2.4.0'
|
||||
|
||||
/*
|
||||
* TODO Upgrade to version 2.4 for large message streaming support
|
||||
*
|
||||
* Due to a memory leak in the connection handling code in Artemis, we are
|
||||
* temporarily downgrading to version 2.2.0.
|
||||
*
|
||||
* The memory leak essentially triggers an out-of-memory exception within
|
||||
* less than 10 seconds and can take down a node if a non-TLS connection is
|
||||
* attempted against the P2P port.
|
||||
*
|
||||
* The issue has been reported to upstream:
|
||||
* https://issues.apache.org/jira/browse/ARTEMIS-1559
|
||||
*/
|
||||
ext.artemis_version = '2.2.0'
|
||||
ext.jackson_version = '2.9.2'
|
||||
ext.jetty_version = '9.4.7.v20170914'
|
||||
ext.jersey_version = '2.25'
|
||||
|
@ -12,6 +12,7 @@ import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.finance.USD
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@ -24,7 +25,7 @@ class JacksonSupportTest {
|
||||
private companion object {
|
||||
val SEED = BigInteger.valueOf(20170922L)!!
|
||||
val mapper = JacksonSupport.createNonRpcMapper()
|
||||
val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).pubkey
|
||||
val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).publicKey
|
||||
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
|
||||
val MINI_CORP = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).party
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ import static net.corda.finance.Currencies.DOLLARS;
|
||||
import static net.corda.finance.contracts.GetBalances.getCashBalance;
|
||||
import static net.corda.node.services.Permissions.invokeRpc;
|
||||
import static net.corda.node.services.Permissions.startFlow;
|
||||
import static net.corda.testing.TestConstants.getALICE_NAME;
|
||||
import static net.corda.testing.TestConstants.getDUMMY_NOTARY_NAME;
|
||||
import static net.corda.testing.TestConstants.ALICE_NAME;
|
||||
import static net.corda.testing.TestConstants.DUMMY_NOTARY_NAME;
|
||||
|
||||
public class CordaRPCJavaClientTest extends NodeBasedTest {
|
||||
public CordaRPCJavaClientTest() {
|
||||
@ -41,7 +41,7 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
|
||||
|
||||
@ClassRule
|
||||
public static IntegrationTestSchemas databaseSchemas = new IntegrationTestSchemas(IntegrationTestKt.toDatabaseSchemaName(getALICE_NAME()),
|
||||
IntegrationTestKt.toDatabaseSchemaName(getDUMMY_NOTARY_NAME()));
|
||||
IntegrationTestKt.toDatabaseSchemaName(DUMMY_NOTARY_NAME));
|
||||
|
||||
private List<String> perms = Arrays.asList(
|
||||
startFlow(CashPaymentFlow.class),
|
||||
@ -65,7 +65,7 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
node = startNode(getALICE_NAME(), 1, singletonList(rpcUser));
|
||||
node = startNode(ALICE_NAME, 1, singletonList(rpcUser));
|
||||
client = new CordaRPCClient(requireNonNull(node.getInternals().getConfiguration().getRpcAddress()));
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package net.corda.testing
|
||||
package net.corda.client.rpc
|
||||
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import kotlin.reflect.KCallable
|
@ -9,9 +9,9 @@ import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.millis
|
||||
import net.corda.node.services.messaging.RPCServerConfiguration
|
||||
import net.corda.testing.internal.testThreadFactory
|
||||
import net.corda.testing.node.internal.RPCDriverDSL
|
||||
import net.corda.testing.node.internal.rpcDriver
|
||||
import net.corda.testing.internal.testThreadFactory
|
||||
import org.apache.activemq.artemis.utils.collections.ConcurrentHashSet
|
||||
import org.junit.After
|
||||
import org.junit.Test
|
||||
|
@ -12,7 +12,6 @@ import net.corda.testing.node.internal.performance.startPublishingFixedRateInjec
|
||||
import net.corda.testing.node.internal.performance.startReporter
|
||||
import net.corda.testing.node.internal.performance.startTightLoopInjector
|
||||
import net.corda.testing.node.internal.rpcDriver
|
||||
import net.corda.testing.measure
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
@ -1,6 +1,9 @@
|
||||
package net.corda.client.rpc
|
||||
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.RPCOps
|
||||
import net.corda.node.services.Permissions
|
||||
import net.corda.node.services.messaging.rpcContext
|
||||
import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.testing.node.internal.RPCDriverDSL
|
||||
|
@ -15,7 +15,7 @@ class SwapIdentitiesFlowTests {
|
||||
@Before
|
||||
fun setup() {
|
||||
// We run this in parallel threads to help catch any race conditions that may exist.
|
||||
mockNet = MockNetwork(networkSendManuallyPumped = false, threadPerNode = true)
|
||||
mockNet = MockNetwork(emptyList(), networkSendManuallyPumped = false, threadPerNode = true)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1,4 +1,4 @@
|
||||
gradlePluginsVersion=3.0.1
|
||||
gradlePluginsVersion=3.0.2
|
||||
kotlinVersion=1.1.60
|
||||
platformVersion=1
|
||||
guavaVersion=21.0
|
||||
|
@ -63,7 +63,7 @@ import javax.crypto.spec.SecretKeySpec
|
||||
*/
|
||||
object Crypto {
|
||||
/**
|
||||
* RSA_SHA256 signature scheme using SHA256 as hash algorithm.
|
||||
* RSA signature scheme using SHA256 for message hashing.
|
||||
* Note: Recommended key size >= 3072 bits.
|
||||
*/
|
||||
@JvmField
|
||||
@ -80,7 +80,7 @@ object Crypto {
|
||||
"RSA_SHA256 signature scheme using SHA256 as hash algorithm."
|
||||
)
|
||||
|
||||
/** ECDSA signature scheme using the secp256k1 Koblitz curve. */
|
||||
/** ECDSA signature scheme using the secp256k1 Koblitz curve and SHA256 for message hashing. */
|
||||
@JvmField
|
||||
val ECDSA_SECP256K1_SHA256 = SignatureScheme(
|
||||
2,
|
||||
@ -95,7 +95,7 @@ object Crypto {
|
||||
"ECDSA signature scheme using the secp256k1 Koblitz curve."
|
||||
)
|
||||
|
||||
/** ECDSA signature scheme using the secp256r1 (NIST P-256) curve. */
|
||||
/** ECDSA signature scheme using the secp256r1 (NIST P-256) curve and SHA256 for message hashing. */
|
||||
@JvmField
|
||||
val ECDSA_SECP256R1_SHA256 = SignatureScheme(
|
||||
3,
|
||||
@ -110,7 +110,7 @@ object Crypto {
|
||||
"ECDSA signature scheme using the secp256r1 (NIST P-256) curve."
|
||||
)
|
||||
|
||||
/** EdDSA signature scheme using the ed255519 twisted Edwards curve. */
|
||||
/** EdDSA signature scheme using the ed25519 twisted Edwards curve and SHA512 for message hashing. */
|
||||
@JvmField
|
||||
val EDDSA_ED25519_SHA512 = SignatureScheme(
|
||||
4,
|
||||
@ -127,13 +127,15 @@ object Crypto {
|
||||
"EdDSA signature scheme using the ed25519 twisted Edwards curve."
|
||||
)
|
||||
|
||||
/**
|
||||
* SPHINCS-256 hash-based signature scheme. It provides 128bit security against post-quantum attackers
|
||||
* at the cost of larger key sizes and loss of compatibility.
|
||||
*/
|
||||
/** DLSequence (ASN1Sequence) for SHA512 truncated to 256 bits, used in SPHINCS-256 signature scheme. */
|
||||
@JvmField
|
||||
val SHA512_256 = DLSequence(arrayOf(NISTObjectIdentifiers.id_sha512_256))
|
||||
|
||||
/**
|
||||
* SPHINCS-256 hash-based signature scheme using SHA512 for message hashing. It provides 128bit security against
|
||||
* post-quantum attackers at the cost of larger key nd signature sizes and loss of compatibility.
|
||||
*/
|
||||
// TODO: change val name to SPHINCS256_SHA512. This will break backwards compatibility.
|
||||
@JvmField
|
||||
val SPHINCS256_SHA256 = SignatureScheme(
|
||||
5,
|
||||
@ -149,7 +151,8 @@ object Crypto {
|
||||
"at the cost of larger key sizes and loss of compatibility."
|
||||
)
|
||||
|
||||
/** Corda composite key type. */
|
||||
/** Corda [CompositeKey] signature type. */
|
||||
// TODO: change the val name to a more descriptive one as it's now confusing and looks like a Key type.
|
||||
@JvmField
|
||||
val COMPOSITE_KEY = SignatureScheme(
|
||||
6,
|
||||
|
@ -22,7 +22,7 @@ open class DigitalSignature(bytes: ByteArray) : OpaqueBytes(bytes) {
|
||||
* @throws SignatureException if the signature is invalid (i.e. damaged), or does not match the key (incorrect).
|
||||
*/
|
||||
@Throws(InvalidKeyException::class, SignatureException::class)
|
||||
fun verify(content: ByteArray) = by.verify(content, this)
|
||||
fun verify(content: ByteArray): Boolean = by.verify(content, this)
|
||||
|
||||
/**
|
||||
* Utility to simplify the act of verifying a signature.
|
||||
@ -32,7 +32,7 @@ open class DigitalSignature(bytes: ByteArray) : OpaqueBytes(bytes) {
|
||||
* @throws SignatureException if the signature is invalid (i.e. damaged), or does not match the key (incorrect).
|
||||
*/
|
||||
@Throws(InvalidKeyException::class, SignatureException::class)
|
||||
fun verify(content: OpaqueBytes) = by.verify(content.bytes, this)
|
||||
fun verify(content: OpaqueBytes): Boolean = by.verify(content.bytes, this)
|
||||
|
||||
/**
|
||||
* Utility to simplify the act of verifying a signature. In comparison to [verify] doesn't throw an
|
||||
@ -45,7 +45,8 @@ open class DigitalSignature(bytes: ByteArray) : OpaqueBytes(bytes) {
|
||||
* @return whether the signature is correct for this key.
|
||||
*/
|
||||
@Throws(InvalidKeyException::class, SignatureException::class)
|
||||
fun isValid(content: ByteArray) = by.isValid(content, this)
|
||||
fun withoutKey() : DigitalSignature = DigitalSignature(this.bytes)
|
||||
fun isValid(content: ByteArray): Boolean = by.isValid(content, this)
|
||||
|
||||
fun withoutKey(): DigitalSignature = DigitalSignature(this.bytes)
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package net.corda.core.identity
|
||||
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import net.corda.core.internal.LegalNameValidator
|
||||
import net.corda.core.internal.VisibleForTesting
|
||||
import net.corda.core.internal.x500Name
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import org.bouncycastle.asn1.ASN1Encodable
|
||||
@ -78,8 +79,9 @@ data class CordaX500Name(val commonName: String?,
|
||||
const val MAX_LENGTH_ORGANISATION_UNIT = 64
|
||||
const val MAX_LENGTH_COMMON_NAME = 64
|
||||
private val supportedAttributes = setOf(BCStyle.O, BCStyle.C, BCStyle.L, BCStyle.CN, BCStyle.ST, BCStyle.OU)
|
||||
private val countryCodes: Set<String> = ImmutableSet.copyOf(Locale.getISOCountries())
|
||||
|
||||
@VisibleForTesting
|
||||
val unspecifiedCountry = "ZZ"
|
||||
private val countryCodes: Set<String> = ImmutableSet.copyOf(Locale.getISOCountries() + unspecifiedCountry)
|
||||
@JvmStatic
|
||||
fun build(principal: X500Principal): CordaX500Name {
|
||||
val x500Name = X500Name.getInstance(principal.encoded)
|
||||
|
@ -12,4 +12,5 @@ import net.corda.core.serialization.CordaSerializable
|
||||
*/
|
||||
@CordaSerializable
|
||||
class MissingContractAttachments(val states: List<TransactionState<ContractState>>)
|
||||
: FlowException("Cannot find contract attachments for ${states.map { it.contract }.distinct()}")
|
||||
: FlowException("Cannot find contract attachments for ${states.map { it.contract }.distinct()}. " +
|
||||
"See https://docs.corda.net/api-contract-constraints.html#debugging")
|
@ -13,21 +13,22 @@ import org.junit.Test;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static net.corda.testing.CoreTestUtils.singleIdentity;
|
||||
import static net.corda.testing.node.NodeTestUtils.startFlow;
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class FlowsInJavaTest {
|
||||
private final MockNetwork mockNet = new MockNetwork();
|
||||
private final MockNetwork mockNet = new MockNetwork(emptyList());
|
||||
private StartedNode<MockNetwork.MockNode> aliceNode;
|
||||
private StartedNode<MockNetwork.MockNode> bobNode;
|
||||
private Party bob;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
aliceNode = mockNet.createPartyNode(TestConstants.getALICE_NAME());
|
||||
bobNode = mockNet.createPartyNode(TestConstants.getBOB_NAME());
|
||||
aliceNode = mockNet.createPartyNode(TestConstants.ALICE_NAME);
|
||||
bobNode = mockNet.createPartyNode(TestConstants.BOB_NAME);
|
||||
bob = singleIdentity(bobNode.getInfo());
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ package net.corda.core.concurrent
|
||||
import com.nhaarman.mockito_kotlin.*
|
||||
import net.corda.core.internal.concurrent.openFuture
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.testing.rigorousMock
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.Test
|
||||
import org.slf4j.Logger
|
||||
|
@ -9,6 +9,7 @@ import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.contracts.DummyContractV2
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
@ -9,7 +9,7 @@ import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.toBase58String
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
import net.corda.testing.kryoSpecific
|
||||
import net.corda.testing.internal.kryoSpecific
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -14,6 +14,10 @@ import net.corda.finance.`issued by`
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.dsl.LedgerDSL
|
||||
import net.corda.testing.dsl.TestLedgerDSLInterpreter
|
||||
import net.corda.testing.dsl.TestTransactionDSLInterpreter
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.ledger
|
||||
import org.junit.Before
|
||||
@ -31,9 +35,9 @@ class PartialMerkleTreeTest {
|
||||
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
|
||||
val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
|
||||
val MEGA_CORP get() = megaCorp.party
|
||||
val MEGA_CORP_PUBKEY get() = megaCorp.pubkey
|
||||
val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
|
||||
val MINI_CORP get() = miniCorp.party
|
||||
val MINI_CORP_PUBKEY get() = miniCorp.pubkey
|
||||
val MINI_CORP_PUBKEY get() = miniCorp.publicKey
|
||||
}
|
||||
|
||||
@Rule
|
||||
@ -51,7 +55,7 @@ class PartialMerkleTreeTest {
|
||||
hashed = nodes.map { it.serialize().sha256() }
|
||||
expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash)).hash
|
||||
merkleTree = MerkleTree.getMerkleTree(hashed)
|
||||
testLedger = MockServices(rigorousMock<IdentityServiceInternal>().also {
|
||||
testLedger = MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||
}, MEGA_CORP.name).ledger(DUMMY_NOTARY) {
|
||||
unverifiedTransaction {
|
||||
|
@ -29,7 +29,7 @@ class AttachmentTests {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mockNet = MockNetwork()
|
||||
mockNet = MockNetwork(emptyList())
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -14,6 +14,7 @@ import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.internal.StartedNode
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.startFlow
|
||||
|
@ -13,7 +13,7 @@ import org.junit.After
|
||||
import org.junit.Test
|
||||
|
||||
class ReceiveMultipleFlowTests {
|
||||
private val mockNet = MockNetwork()
|
||||
private val mockNet = MockNetwork(emptyList())
|
||||
private val nodes = (0..2).map { mockNet.createPartyNode() }
|
||||
@After
|
||||
fun stopNodes() {
|
||||
|
@ -4,7 +4,7 @@ import com.nhaarman.mockito_kotlin.*
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.internal.join
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.testing.rigorousMock
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.Test
|
||||
import org.slf4j.Logger
|
||||
|
@ -70,7 +70,7 @@ class AttachmentSerializationTest {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mockNet = MockNetwork()
|
||||
mockNet = MockNetwork(emptyList())
|
||||
server = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME))
|
||||
client = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME))
|
||||
client.internals.disableDBCloseOnStop() // Otherwise the in-memory database may disappear (taking the checkpoint with it) while we reboot the client.
|
||||
|
@ -9,6 +9,7 @@ import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.finance.POUNDS
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.node.MockServices
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
@ -25,9 +26,9 @@ class TransactionSerializationTests {
|
||||
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
|
||||
val MINI_CORP = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).party
|
||||
val DUMMY_NOTARY get() = dummyNotary.party
|
||||
val DUMMY_NOTARY_KEY get() = dummyNotary.key
|
||||
val DUMMY_NOTARY_KEY get() = dummyNotary.keyPair
|
||||
val MEGA_CORP get() = megaCorp.party
|
||||
val MEGA_CORP_KEY get() = megaCorp.key
|
||||
val MEGA_CORP_KEY get() = megaCorp.keyPair
|
||||
}
|
||||
|
||||
@Rule
|
||||
@ -106,7 +107,7 @@ class TransactionSerializationTests {
|
||||
Command(TestCash.Commands.Move(), DUMMY_KEY_2.public))
|
||||
|
||||
val ptx2 = notaryServices.signInitialTransaction(tx2)
|
||||
val dummyServices = MockServices(rigorousMock(), MEGA_CORP.name, DUMMY_KEY_2)
|
||||
val dummyServices = MockServices(emptyList(), rigorousMock(), MEGA_CORP.name, DUMMY_KEY_2)
|
||||
val stx2 = dummyServices.addSignature(ptx2)
|
||||
|
||||
stx.copy(sigs = stx2.sigs).verifyRequiredSignatures()
|
||||
@ -124,8 +125,7 @@ class TransactionSerializationTests {
|
||||
@Test
|
||||
fun storeAndLoadWhenSigning() {
|
||||
val ptx = megaCorpServices.signInitialTransaction(tx)
|
||||
ptx.verifySignaturesExcept(notaryServices.key.public)
|
||||
|
||||
ptx.verifySignaturesExcept(DUMMY_NOTARY_KEY.public)
|
||||
val stored = ptx.serialize()
|
||||
val loaded = stored.deserialize()
|
||||
|
||||
|
@ -10,6 +10,7 @@ import net.corda.core.identity.Party
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.node.MockServices
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
@ -28,7 +29,7 @@ class LedgerTransactionQueryTests {
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
private val keyPair = generateKeyPair()
|
||||
private val services = MockServices(rigorousMock<IdentityServiceInternal>().also {
|
||||
private val services = MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(null).whenever(it).partyFromKey(keyPair.public)
|
||||
}, CordaX500Name("MegaCorp", "London", "GB"), keyPair)
|
||||
private val identity: Party = services.myInfo.singleIdentity()
|
||||
|
@ -12,6 +12,7 @@ import net.corda.finance.`issued by`
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.ledger
|
||||
import org.junit.Rule
|
||||
@ -27,7 +28,7 @@ class TransactionEncumbranceTests {
|
||||
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
|
||||
val MINI_CORP = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")).party
|
||||
val MEGA_CORP get() = megaCorp.party
|
||||
val MEGA_CORP_PUBKEY get() = megaCorp.pubkey
|
||||
val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
|
||||
}
|
||||
|
||||
@Rule
|
||||
@ -61,7 +62,7 @@ class TransactionEncumbranceTests {
|
||||
}
|
||||
}
|
||||
|
||||
private val ledgerServices = MockServices(rigorousMock<IdentityServiceInternal>().also {
|
||||
private val ledgerServices = MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||
}, MEGA_CORP.name)
|
||||
|
||||
|
@ -6,6 +6,7 @@ import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.math.BigInteger
|
||||
@ -23,7 +24,7 @@ class TransactionTests {
|
||||
val BOB = TestIdentity(BOB_NAME, 80).party
|
||||
val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
|
||||
val DUMMY_NOTARY get() = dummyNotary.party
|
||||
val DUMMY_NOTARY_KEY get() = dummyNotary.key
|
||||
val DUMMY_NOTARY_KEY get() = dummyNotary.keyPair
|
||||
}
|
||||
|
||||
@Rule
|
||||
|
@ -7,9 +7,10 @@ import com.google.common.collect.testing.features.CollectionSize
|
||||
import junit.framework.TestSuite
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.testing.withTestSerialization
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Suite
|
||||
@ -36,6 +37,10 @@ class NonEmptySetTest {
|
||||
}
|
||||
|
||||
class General {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
@Test
|
||||
fun `copyOf - empty source`() {
|
||||
assertThatThrownBy { NonEmptySet.copyOf(HashSet<Int>()) }.isInstanceOf(IllegalArgumentException::class.java)
|
||||
@ -48,11 +53,9 @@ class NonEmptySetTest {
|
||||
|
||||
@Test
|
||||
fun `serialize deserialize`() {
|
||||
withTestSerialization {
|
||||
val original = NonEmptySet.of(-17, 22, 17)
|
||||
val copy = original.serialize().deserialize()
|
||||
assertThat(copy).isEqualTo(original).isNotSameAs(original)
|
||||
}
|
||||
val original = NonEmptySet.of(-17, 22, 17)
|
||||
val copy = original.serialize().deserialize()
|
||||
assertThat(copy).isEqualTo(original).isNotSameAs(original)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,18 @@
|
||||
API: Contract Constraints
|
||||
=========================
|
||||
|
||||
A basic understanding of contract key concepts, which can be found :doc:`here </key-concepts-contracts>`,
|
||||
is required reading for this page.
|
||||
.. note:: Before reading this page, you should be familiar with the key concepts of :doc:`key-concepts-contracts`.
|
||||
|
||||
.. contents::
|
||||
|
||||
Contract constraints
|
||||
--------------------
|
||||
Transaction states specify a constraint over the contract that will be used to verify it. For a transaction to be
|
||||
valid, the ``verify`` function associated with each state must run successfully. However, for this to be secure, it is
|
||||
not sufficient to specify the ``verify`` function by name as there may exist multiple different implementations with
|
||||
the same method signature and enclosing class. Contract constraints solve this problem by allowing a contract developer
|
||||
to constrain which ``verify`` functions out of the universe of implementations can be used (i.e. the universe is
|
||||
everything that matches the signature and contract constraints restricts this universe to a subset).
|
||||
everything that matches the signature and contract constraints restrict this universe to a subset).
|
||||
|
||||
A typical constraint is the hash of the CorDapp JAR that contains the contract and states but will in future releases
|
||||
include constraints that require specific signers of the JAR, or both the signer and the hash. Constraints can be
|
||||
@ -60,10 +63,10 @@ that they were loaded from. This makes it possible to find the attachment for an
|
||||
automatic resolution of attachments is done by the ``TransactionBuilder`` and how, when verifying the constraints and
|
||||
contracts, attachments are associated with their respective contracts.
|
||||
|
||||
Implementations
|
||||
---------------
|
||||
Implementations of AttachmentConstraint
|
||||
---------------------------------------
|
||||
|
||||
There are three implementations of ``AttachmentConstraints`` with more planned in the future.
|
||||
There are three implementations of ``AttachmentConstraint`` with more planned in the future.
|
||||
|
||||
``AlwaysAcceptAttachmentConstraint``: Any attachment (except a missing one) will satisfy this constraint.
|
||||
|
||||
@ -137,3 +140,18 @@ Full Nodes
|
||||
**********
|
||||
|
||||
When testing against full nodes simply place your CorDapp into the cordapps directory of the node.
|
||||
|
||||
Debugging
|
||||
---------
|
||||
If an attachment constraint cannot be resolved, a ``MissingContractAttachments`` exception is thrown. There are two
|
||||
common sources of ``MissingContractAttachments`` exceptions:
|
||||
|
||||
Not setting CorDapp packages in tests
|
||||
*************************************
|
||||
You are running a test and have not specified the CorDapp packages to scan. See the instructions above.
|
||||
|
||||
Wrong fully-qualified contract name
|
||||
***********************************
|
||||
You are specifying the fully-qualified name of the contract incorrectly. For example, you've defined ``MyContract`` in
|
||||
the package ``com.mycompany.myapp.contracts``, but the fully-qualified contract name you pass to the
|
||||
``TransactionBuilder`` is ``com.mycompany.myapp.MyContract`` (instead of ``com.mycompany.myapp.contracts.MyContract``).
|
@ -55,8 +55,8 @@ Helper methods are also provided with default values for arguments:
|
||||
The API provides both static (snapshot) and dynamic (snapshot with streaming updates) methods for a defined set of
|
||||
filter criteria:
|
||||
|
||||
- Use ``queryBy`` to obtain a only current snapshot of data (for a given ``QueryCriteria``)
|
||||
- Use ``trackBy`` to obtain a both a current snapshot and a future stream of updates (for a given ``QueryCriteria``)
|
||||
- Use ``queryBy`` to obtain a current snapshot of data (for a given ``QueryCriteria``)
|
||||
- Use ``trackBy`` to obtain both a current snapshot and a future stream of updates (for a given ``QueryCriteria``)
|
||||
|
||||
.. note:: Streaming updates are only filtered based on contract type and state status (UNCONSUMED, CONSUMED, ALL)
|
||||
|
||||
|
@ -6,6 +6,7 @@ CorDapps
|
||||
|
||||
cordapp-overview
|
||||
writing-a-cordapp
|
||||
upgrade-notes
|
||||
cordapp-build-systems
|
||||
building-against-master
|
||||
corda-api
|
||||
|
@ -6,6 +6,50 @@ from the previous milestone release.
|
||||
|
||||
UNRELEASED
|
||||
----------
|
||||
|
||||
* The network map service concept has been re-designed. More information can be found in :doc:`network-map`.
|
||||
|
||||
* The previous design was never intended to be final but was rather a quick implementation in the earliest days of the
|
||||
Corda project to unblock higher priority items. It suffers from numerous disadvantages including lack of scalability,
|
||||
as one node is expected to hold open and manage connections to every node on the network; not reliable; hard to defend
|
||||
against DoS attacks; etc.
|
||||
|
||||
* There is no longer a special network map node for distributing the network map to the other nodes. Instead the network
|
||||
map is now a collection of signed ``NodeInfo`` files distributed via HTTP.
|
||||
|
||||
* The ``certificateSigningService`` config has been replaced by ``compatibilityZoneURL`` which is the base URL for the
|
||||
doorman registration and for downloading the network map. There is also an end-point for the node to publish its node-info
|
||||
object, which the node does each time it changes. ``networkMapService`` config has been removed.
|
||||
|
||||
* To support local and test deployments, the node polls the ``additional-node-infos`` directory for these signed ``NodeInfo``
|
||||
objects which are stored in its local cache. On startup the node generates its own signed file with the filename format
|
||||
"nodeInfo-*". This can be copied to every node's ``additional-node-infos`` directory that is part of the network.
|
||||
|
||||
* Cordform (which is the ``deployNodes`` gradle task) does this copying automatically for the demos. The ``NetworkMap``
|
||||
parameter is no longer needed.
|
||||
|
||||
* For test deployments we've introduced a bootstrapping tool (see :doc:`setting-up-a-corda-network`).
|
||||
|
||||
* ``extraAdvertisedServiceIds``, ``notaryNodeAddress``, ``notaryClusterAddresses`` and ``bftSMaRt`` configs have been
|
||||
removed. The configuration of notaries has been simplified into a single ``notary`` config object. See
|
||||
:doc:`corda-configuration-file` for more details.
|
||||
|
||||
* Introducing the concept of network parameters which are a set of constants which all nodes on a network must agree on
|
||||
to correctly interop.
|
||||
|
||||
* The set of valid notaries has been moved to the network parameters. Notaries are no longer identified by the CN in
|
||||
their X500 name.
|
||||
|
||||
* Single node notaries no longer have a second separate notary identity. Their main identity *is* their notary identity.
|
||||
Use ``NetworkMapCache.notaryIdentities`` to get the list of available notaries.
|
||||
|
||||
* The common name in the node's X500 legal name is no longer reserved and can be used as part of the node's name.
|
||||
|
||||
* Moved ``NodeInfoSchema`` to internal package as the node info's database schema is not part of the public API. This
|
||||
was needed to allow changes to the schema.
|
||||
|
||||
* Support for external user credentials data source and password encryption [CORDA-827].
|
||||
|
||||
* Integrate database migration tool: http://www.liquibase.org/ :
|
||||
* The migration files are split per ``MappedSchemas``. (added new property: migrationResource used to point to the resource file containing the db changes corresponding to the JPA entities)
|
||||
* config flag ``database.initialiseSchema`` was renamed to: ``database.runMigration`` (if true then the migration is run during startup just before hibernate is initialised.)
|
||||
@ -45,24 +89,12 @@ UNRELEASED
|
||||
deploys nodes for development and testing, the latter turns a project into a cordapp project that generates JARs in
|
||||
the standard CorDapp format.
|
||||
|
||||
* ``Cordform`` and node identity generation:
|
||||
* Removed the parameter ``NetworkMap`` from Cordform. Now at the end of the deployment the following happens:
|
||||
1. Each node is started and its signed serialized NodeInfo is written to disk in the node base directory.
|
||||
2. Every serialized ``NodeInfo`` above is copied in every other node "additional-node-info" folder under the NodeInfo folder.
|
||||
|
||||
* Nodes read and poll the filesystem for serialized ``NodeInfo`` in the ``additional-node-info`` directory.
|
||||
|
||||
* ``Cordapp`` now has a name field for identifying CorDapps and all CorDapp names are printed to console at startup.
|
||||
|
||||
* Enums now respect the whitelist applied to the Serializer factory serializing / deserializing them. If the enum isn't
|
||||
either annotated with the @CordaSerializable annotation or explicitly whitelisted then a NotSerializableException is
|
||||
thrown.
|
||||
|
||||
* ``extraAdvertisedServiceIds`` config has been removed as part of the previous work to retire the concept of advertised
|
||||
services. The remaining use of this config was for notaries, the configuring of which has been cleaned up and simplified.
|
||||
``notaryNodeAddress``, ``notaryClusterAddresses`` and ``bftSMaRt`` have also been removed and replaced by a single
|
||||
``notary`` config object. See :doc:`corda-configuration-file` for more details.
|
||||
|
||||
* Gradle task ``deployNodes`` can have an additional parameter ``configFile`` with the path to a properties file
|
||||
to be appended to node.conf.
|
||||
|
||||
@ -79,8 +111,8 @@ UNRELEASED
|
||||
allows services to start flows marked with the ``StartableByService`` annotation. For backwards compatability
|
||||
service classes with only ``ServiceHub`` constructors will still work.
|
||||
|
||||
* ``TimeWindow`` now has a ``length`` property that returns the length of the time-window, or ``null`` if the
|
||||
time-window is open-ended.
|
||||
* ``TimeWindow`` now has a ``length`` property that returns the length of the time-window as a ``java.time.Duration`` object,
|
||||
or ``null`` if the time-window isn't closed.
|
||||
|
||||
* A new ``SIGNERS_GROUP`` with ordinal 6 has been added to ``ComponentGroupEnum`` that corresponds to the ``Command``
|
||||
signers.
|
||||
@ -96,18 +128,16 @@ UNRELEASED
|
||||
* The ``ReceiveTransactionFlow`` can now be told to record the transaction at the same time as receiving it. Using this
|
||||
feature, better support for observer/regulator nodes has been added. See :doc:`tutorial-observer-nodes`.
|
||||
|
||||
* Moved ``NodeInfoSchema`` to internal package as the node info's database schema is not part of the public API. This is
|
||||
needed to allow new ``node_info_hash`` column to be added for the network map redesign work.
|
||||
|
||||
* Added an overload of ``TransactionWithSignatures.verifySignaturesExcept`` which takes in a collection of ``PublicKey``s.
|
||||
|
||||
* Replaced node configuration parameter ``certificateSigningService`` with ``compatibilityZoneURL``, which is Corda
|
||||
compatibility zone network management service's address.
|
||||
* ``DriverDSLExposedInterface`` has been renamed to ``DriverDSL`` and the ``waitForAllNodesToFinish()`` method has instead
|
||||
become a parameter on driver creation.
|
||||
|
||||
* ``waitForAllNodesToFinish()`` method in ``DriverDSLExposedInterface`` has instead become a parameter on driver creation.
|
||||
* Values for the ``database.transactionIsolationLevel`` config now follow the ``java.sql.Connection`` int constants but
|
||||
without the "TRANSACTION_" prefix, i.e. "NONE", "READ_UNCOMMITTED", etc.
|
||||
|
||||
* ``database.transactionIsolationLevel`` values now follow the ``java.sql.Connection`` int constants but without the
|
||||
"TRANSACTION_" prefix, i.e. "NONE", "READ_UNCOMMITTED", etc.
|
||||
* Peer-to-peer communications is now via AMQP 1.0 as default.
|
||||
Although the legacy Artemis CORE bridging can still be used by setting the ``useAMQPBridges`` configuration property to false.
|
||||
|
||||
* Enterprise Corda only: Compatibility with SQL Server 2017 and SQL Azure databases.
|
||||
|
||||
|
@ -26,7 +26,7 @@ permissions that RPC can use for fine-grain access control.
|
||||
|
||||
These users are added to the node's ``node.conf`` file.
|
||||
|
||||
The syntax for adding an RPC user is:
|
||||
The simplest way of adding an RPC user is to include it in the ``rpcUsers`` list:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -59,9 +59,6 @@ Users need permissions to invoke any RPC call. By default, nothing is allowed. T
|
||||
...
|
||||
]
|
||||
|
||||
.. note:: Currently, the node's web server has super-user access, meaning that it can run any RPC operation without
|
||||
logging in. This will be changed in a future release.
|
||||
|
||||
Permissions Syntax
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -71,6 +68,134 @@ Fine grained permissions allow a user to invoke a specific RPC operation, or to
|
||||
- to invoke a RPC operation: ``InvokeRpc.<rpc method name>`` e.g., ``InvokeRpc.nodeInfo``.
|
||||
.. note:: Permission ``InvokeRpc.startFlow`` allows a user to initiate all flows.
|
||||
|
||||
RPC security management
|
||||
-----------------------
|
||||
|
||||
Setting ``rpcUsers`` provides a simple way of granting RPC permissions to a fixed set of users, but has some
|
||||
obvious shortcomings. To support use cases aiming for higher security and flexibility, Corda offers additional security
|
||||
features such as:
|
||||
|
||||
* Fetching users credentials and permissions from an external data source (e.g.: a remote RDBMS), with optional in-memory
|
||||
caching. In particular, this allows credentials and permissions to be updated externally without requiring nodes to be
|
||||
restarted.
|
||||
* Password stored in hash-encrypted form. This is regarded as must-have when security is a concern. Corda currently supports
|
||||
a flexible password hash format conforming to the Modular Crypt Format provided by the `Apache Shiro framework <https://shiro.apache.org/static/1.2.5/apidocs/org/apache/shiro/crypto/hash/format/Shiro1CryptFormat.html>`_
|
||||
|
||||
These features are controlled by a set of options nested in the ``security`` field of ``node.conf``.
|
||||
The following example shows how to configure retrieval of users credentials and permissions from a remote database with
|
||||
passwords in hash-encrypted format and enable in-memory caching of users data:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
security = {
|
||||
authService = {
|
||||
dataSource = {
|
||||
type = "DB",
|
||||
passwordEncryption = "SHIRO_1_CRYPT",
|
||||
connection = {
|
||||
jdbcUrl = "<jdbc connection string>"
|
||||
username = "<db username>"
|
||||
password = "<db user password>"
|
||||
driverClassName = "<JDBC driver>"
|
||||
}
|
||||
}
|
||||
options = {
|
||||
cache = {
|
||||
expireAfterSecs = 120
|
||||
maxEntries = 10000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
It is also possible to have a static list of users embedded in the ``security`` structure by specifying a ``dataSource``
|
||||
of ``INMEMORY`` type:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
security = {
|
||||
authService = {
|
||||
dataSource = {
|
||||
type = "INMEMORY",
|
||||
users = [
|
||||
{
|
||||
username = "<username>",
|
||||
password = "<password>",
|
||||
permissions = ["<permission 1>", "<permission 2>", ...]
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.. warning:: A valid configuration cannot specify both the ``rpcUsers`` and ``security`` fields. Doing so will trigger
|
||||
an exception at node startup.
|
||||
|
||||
Authentication/authorisation data
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The ``dataSource`` structure defines the data provider supplying credentials and permissions for users. There exist two
|
||||
supported types of such data source, identified by the ``dataSource.type`` field:
|
||||
|
||||
:INMEMORY: A static list of user credentials and permissions specified by the ``users`` field.
|
||||
|
||||
:DB: An external RDBMS accessed via the JDBC connection described by ``connection``. Note that, unlike the ``INMEMORY``
|
||||
case, in a user database permissions are assigned to *roles* rather than individual users. The current implementation
|
||||
expects the database to store data according to the following schema:
|
||||
|
||||
- Table ``users`` containing columns ``username`` and ``password``. The ``username`` column *must have unique values*.
|
||||
- Table ``user_roles`` containing columns ``username`` and ``role_name`` associating a user to a set of *roles*.
|
||||
- Table ``roles_permissions`` containing columns ``role_name`` and ``permission`` associating a role to a set of
|
||||
permission strings.
|
||||
|
||||
.. note:: There is no prescription on the SQL type of each column (although our tests were conducted on ``username`` and
|
||||
``role_name`` declared of SQL type ``VARCHAR`` and ``password`` of ``TEXT`` type). It is also possible to have extra columns
|
||||
in each table alongside the expected ones.
|
||||
|
||||
Password encryption
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Storing passwords in plain text is discouraged in applications where security is critical. Passwords are assumed
|
||||
to be in plain format by default, unless a different format is specified by the ``passwordEncryption`` field, like:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
passwordEncryption = SHIRO_1_CRYPT
|
||||
|
||||
``SHIRO_1_CRYPT`` identifies the `Apache Shiro fully reversible
|
||||
Modular Crypt Format <https://shiro.apache.org/static/1.2.5/apidocs/org/apache/shiro/crypto/hash/format/Shiro1CryptFormat.html>`_,
|
||||
it is currently the only non-plain password hash-encryption format supported. Hash-encrypted passwords in this
|
||||
format can be produced by using the `Apache Shiro Hasher command line tool <https://shiro.apache.org/command-line-hasher.html>`_.
|
||||
|
||||
Caching user accounts data
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
A cache layer on top of the external data source of users credentials and permissions can significantly improve
|
||||
performances in some cases, with the disadvantage of causing a (controllable) delay in picking up updates to the underlying data.
|
||||
Caching is disabled by default, it can be enabled by defining the ``options.cache`` field in ``security.authService``,
|
||||
for example:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
options = {
|
||||
cache = {
|
||||
expireAfterSecs = 120
|
||||
maxEntries = 10000
|
||||
}
|
||||
}
|
||||
|
||||
This will enable a non-persistent cache contained in the node's memory with maximum number of entries set to ``maxEntries``
|
||||
where entries are expired and refreshed after ``expireAfterSecs`` seconds.
|
||||
|
||||
Observables
|
||||
-----------
|
||||
The RPC system handles observables in a special way. When a method returns an observable, whether directly or
|
||||
|
@ -101,6 +101,9 @@ path to the node's base directory.
|
||||
|
||||
:rpcAddress: The address of the RPC system on which RPC requests can be made to the node. If not provided then the node will run without RPC.
|
||||
|
||||
:security: Contains various nested fields controlling user authentication/authorization, in particular for RPC accesses. See
|
||||
:doc:`clientrpc` for details.
|
||||
|
||||
:webAddress: The host and port on which the webserver will listen if it is started. This is not used by the node itself.
|
||||
|
||||
.. note:: If HTTPS is enabled then the browser security checks will require that the accessing url host name is one
|
||||
@ -191,4 +194,7 @@ path to the node's base directory.
|
||||
:sshPort: Port to be used for SSH connection, default ``22``.
|
||||
|
||||
:exportJMXTo: If set to ``http``, will enable JMX metrics reporting via the Jolokia HTTP/JSON agent.
|
||||
Default Jolokia access url is http://127.0.0.1:7005/jolokia/
|
||||
Default Jolokia access url is http://127.0.0.1:7005/jolokia/
|
||||
|
||||
:useAMQPBridges: Optionally can be set to ``false`` to use Artemis CORE Bridges for peer-to-peer communications.
|
||||
Otherwise, defaults to ``true`` and the AMQP 1.0 protocol will be used for message transfer between nodes.
|
@ -10,7 +10,6 @@ Corda nodes
|
||||
corda-configuration-file
|
||||
clientrpc
|
||||
shell
|
||||
node-auth-config
|
||||
node-database
|
||||
node-administration
|
||||
out-of-process-verification
|
@ -36,6 +36,7 @@ dependencies {
|
||||
compile project(':core')
|
||||
compile project(':client:jfx')
|
||||
compile project(':node-driver')
|
||||
compile project(':webserver')
|
||||
testCompile project(':verifier')
|
||||
testCompile project(':test-utils')
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package net.corda.docs.java.tutorial.helloworld;
|
||||
package com.template;
|
||||
|
||||
import net.corda.core.contracts.CommandData;
|
||||
import net.corda.core.contracts.Contract;
|
@ -1,17 +1,21 @@
|
||||
package net.corda.docs.java.tutorial.helloworld;
|
||||
|
||||
// DOCSTART 01
|
||||
import co.paralleluniverse.fibers.Suspendable;
|
||||
import com.template.TemplateContract;
|
||||
import net.corda.core.flows.*;
|
||||
|
||||
// DOCSTART 01
|
||||
// Add these imports:
|
||||
import net.corda.core.contracts.Command;
|
||||
import net.corda.core.contracts.CommandData;
|
||||
import net.corda.core.flows.*;
|
||||
import net.corda.core.identity.Party;
|
||||
import net.corda.core.transactions.SignedTransaction;
|
||||
import net.corda.core.transactions.TransactionBuilder;
|
||||
import net.corda.core.utilities.ProgressTracker;
|
||||
|
||||
import static net.corda.docs.java.tutorial.helloworld.TemplateContract.TEMPLATE_CONTRACT_ID;
|
||||
import static com.template.TemplateContract.TEMPLATE_CONTRACT_ID;
|
||||
|
||||
// Replace TemplateFlow's definition with:
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
public class IOUFlow extends FlowLogic<Void> {
|
||||
|
@ -1,13 +1,15 @@
|
||||
package net.corda.docs.java.tutorial.helloworld;
|
||||
|
||||
// DOCSTART 01
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.corda.core.contracts.ContractState;
|
||||
import net.corda.core.identity.AbstractParty;
|
||||
import net.corda.core.identity.Party;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
// DOCSTART 01
|
||||
// Add these imports:
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.corda.core.identity.Party;
|
||||
|
||||
// Replace TemplateState's definition with:
|
||||
public class IOUState implements ContractState {
|
||||
private final int value;
|
||||
private final Party lender;
|
||||
|
@ -3,48 +3,33 @@ package net.corda.docs.java.tutorial.testdsl;
|
||||
import kotlin.Unit;
|
||||
import net.corda.core.contracts.PartyAndReference;
|
||||
import net.corda.core.identity.CordaX500Name;
|
||||
import net.corda.core.identity.Party;
|
||||
import net.corda.finance.contracts.ICommercialPaperState;
|
||||
import net.corda.finance.contracts.JavaCommercialPaper;
|
||||
import net.corda.finance.contracts.asset.Cash;
|
||||
import net.corda.node.services.api.IdentityServiceInternal;
|
||||
import net.corda.testing.SerializationEnvironmentRule;
|
||||
import net.corda.testing.node.MockServices;
|
||||
import net.corda.testing.TestIdentity;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static net.corda.core.crypto.Crypto.generateKeyPair;
|
||||
import static net.corda.finance.Currencies.DOLLARS;
|
||||
import static net.corda.finance.Currencies.issuedBy;
|
||||
import static net.corda.finance.contracts.JavaCommercialPaper.JCP_PROGRAM_ID;
|
||||
import static net.corda.testing.node.MockServicesKt.makeTestIdentityService;
|
||||
import static net.corda.testing.node.NodeTestUtils.ledger;
|
||||
import static net.corda.testing.node.NodeTestUtils.transaction;
|
||||
import static net.corda.testing.CoreTestUtils.rigorousMock;
|
||||
import static net.corda.testing.TestConstants.*;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
|
||||
public class CommercialPaperTest {
|
||||
private static final TestIdentity ALICE = new TestIdentity(getALICE_NAME(), 70L);
|
||||
private static final TestIdentity ALICE = new TestIdentity(ALICE_NAME, 70L);
|
||||
private static final PublicKey BIG_CORP_PUBKEY = generateKeyPair().getPublic();
|
||||
private static final TestIdentity BOB = new TestIdentity(getBOB_NAME(), 80L);
|
||||
private static final TestIdentity BOB = new TestIdentity(BOB_NAME, 80L);
|
||||
private static final TestIdentity MEGA_CORP = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB"));
|
||||
private static final Party DUMMY_NOTARY = new TestIdentity(getDUMMY_NOTARY_NAME(), 20L).getParty();
|
||||
@Rule
|
||||
public final SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule();
|
||||
private final byte[] defaultRef = {123};
|
||||
private final MockServices ledgerServices;
|
||||
|
||||
{
|
||||
IdentityServiceInternal identityService = rigorousMock(IdentityServiceInternal.class);
|
||||
doReturn(MEGA_CORP.getParty()).when(identityService).partyFromKey(MEGA_CORP.getPubkey());
|
||||
doReturn(null).when(identityService).partyFromKey(BIG_CORP_PUBKEY);
|
||||
doReturn(null).when(identityService).partyFromKey(ALICE.getPubkey());
|
||||
ledgerServices = new MockServices(identityService, MEGA_CORP.getName());
|
||||
}
|
||||
private final MockServices ledgerServices = new MockServices(emptyList(), makeTestIdentityService(MEGA_CORP.getIdentity()));
|
||||
|
||||
// DOCSTART 1
|
||||
private ICommercialPaperState getPaper() {
|
||||
@ -52,7 +37,7 @@ public class CommercialPaperTest {
|
||||
MEGA_CORP.ref(defaultRef),
|
||||
MEGA_CORP.getParty(),
|
||||
issuedBy(DOLLARS(1000), MEGA_CORP.ref(defaultRef)),
|
||||
getTEST_TX_TIME().plus(7, ChronoUnit.DAYS)
|
||||
TEST_TX_TIME.plus(7, ChronoUnit.DAYS)
|
||||
);
|
||||
}
|
||||
// DOCEND 1
|
||||
@ -61,7 +46,7 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void simpleCP() {
|
||||
ICommercialPaperState inState = getPaper();
|
||||
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
||||
ledger(ledgerServices, l -> {
|
||||
l.transaction(tx -> {
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.input(JCP_PROGRAM_ID, inState);
|
||||
@ -76,10 +61,10 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void simpleCPMove() {
|
||||
ICommercialPaperState inState = getPaper();
|
||||
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
||||
ledger(ledgerServices, l -> {
|
||||
l.transaction(tx -> {
|
||||
tx.input(JCP_PROGRAM_ID, inState);
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
return tx.verifies();
|
||||
});
|
||||
@ -92,10 +77,10 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void simpleCPMoveFails() {
|
||||
ICommercialPaperState inState = getPaper();
|
||||
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
||||
ledger(ledgerServices, l -> {
|
||||
l.transaction(tx -> {
|
||||
tx.input(JCP_PROGRAM_ID, inState);
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
return tx.failsWith("the state is propagated");
|
||||
});
|
||||
@ -108,10 +93,10 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void simpleCPMoveSuccess() {
|
||||
ICommercialPaperState inState = getPaper();
|
||||
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
||||
ledger(ledgerServices, l -> {
|
||||
l.transaction(tx -> {
|
||||
tx.input(JCP_PROGRAM_ID, inState);
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.failsWith("the state is propagated");
|
||||
tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(ALICE.getParty()));
|
||||
@ -125,17 +110,17 @@ public class CommercialPaperTest {
|
||||
// DOCSTART 6
|
||||
@Test
|
||||
public void simpleIssuanceWithTweak() {
|
||||
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
||||
ledger(ledgerServices, l -> {
|
||||
l.transaction(tx -> {
|
||||
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.tweak(tw -> {
|
||||
tw.command(BIG_CORP_PUBKEY, new JavaCommercialPaper.Commands.Issue());
|
||||
tw.timeWindow(getTEST_TX_TIME());
|
||||
tw.timeWindow(TEST_TX_TIME);
|
||||
return tw.failsWith("output states are issued by a command signer");
|
||||
});
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.timeWindow(getTEST_TX_TIME());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.timeWindow(TEST_TX_TIME);
|
||||
return tx.verifies();
|
||||
});
|
||||
return Unit.INSTANCE;
|
||||
@ -146,16 +131,16 @@ public class CommercialPaperTest {
|
||||
// DOCSTART 7
|
||||
@Test
|
||||
public void simpleIssuanceWithTweakTopLevelTx() {
|
||||
transaction(ledgerServices, DUMMY_NOTARY, tx -> {
|
||||
transaction(ledgerServices, tx -> {
|
||||
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.tweak(tw -> {
|
||||
tw.command(BIG_CORP_PUBKEY, new JavaCommercialPaper.Commands.Issue());
|
||||
tw.timeWindow(getTEST_TX_TIME());
|
||||
tw.timeWindow(TEST_TX_TIME);
|
||||
return tw.failsWith("output states are issued by a command signer");
|
||||
});
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.timeWindow(getTEST_TX_TIME());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.timeWindow(TEST_TX_TIME);
|
||||
return tx.verifies();
|
||||
});
|
||||
}
|
||||
@ -165,7 +150,7 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void chainCommercialPaper() {
|
||||
PartyAndReference issuer = MEGA_CORP.ref(defaultRef);
|
||||
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
||||
ledger(ledgerServices, l -> {
|
||||
l.unverifiedTransaction(tx -> {
|
||||
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
||||
new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty()));
|
||||
@ -176,9 +161,9 @@ public class CommercialPaperTest {
|
||||
// Some CP is issued onto the ledger by MegaCorp.
|
||||
l.transaction("Issuance", tx -> {
|
||||
tx.output(JCP_PROGRAM_ID, "paper", getPaper());
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.timeWindow(getTEST_TX_TIME());
|
||||
tx.timeWindow(TEST_TX_TIME);
|
||||
return tx.verifies();
|
||||
});
|
||||
|
||||
@ -188,8 +173,8 @@ public class CommercialPaperTest {
|
||||
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty()));
|
||||
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
||||
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty()));
|
||||
tx.command(ALICE.getPubkey(), new Cash.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.command(ALICE.getPublicKey(), new Cash.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||
return tx.verifies();
|
||||
});
|
||||
return Unit.INSTANCE;
|
||||
@ -201,7 +186,7 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void chainCommercialPaperDoubleSpend() {
|
||||
PartyAndReference issuer = MEGA_CORP.ref(defaultRef);
|
||||
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
||||
ledger(ledgerServices, l -> {
|
||||
l.unverifiedTransaction(tx -> {
|
||||
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
||||
new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty()));
|
||||
@ -212,9 +197,9 @@ public class CommercialPaperTest {
|
||||
// Some CP is issued onto the ledger by MegaCorp.
|
||||
l.transaction("Issuance", tx -> {
|
||||
tx.output(Cash.PROGRAM_ID, "paper", getPaper());
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.timeWindow(getTEST_TX_TIME());
|
||||
tx.timeWindow(TEST_TX_TIME);
|
||||
return tx.verifies();
|
||||
});
|
||||
|
||||
@ -224,8 +209,8 @@ public class CommercialPaperTest {
|
||||
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty()));
|
||||
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
||||
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty()));
|
||||
tx.command(ALICE.getPubkey(), new Cash.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.command(ALICE.getPublicKey(), new Cash.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||
return tx.verifies();
|
||||
});
|
||||
|
||||
@ -234,7 +219,7 @@ public class CommercialPaperTest {
|
||||
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
||||
// We moved a paper to other pubkey.
|
||||
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(BOB.getParty()));
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||
return tx.verifies();
|
||||
});
|
||||
l.fails();
|
||||
@ -247,7 +232,7 @@ public class CommercialPaperTest {
|
||||
@Test
|
||||
public void chainCommercialPaperTweak() {
|
||||
PartyAndReference issuer = MEGA_CORP.ref(defaultRef);
|
||||
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
||||
ledger(ledgerServices, l -> {
|
||||
l.unverifiedTransaction(tx -> {
|
||||
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
||||
new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty()));
|
||||
@ -258,9 +243,9 @@ public class CommercialPaperTest {
|
||||
// Some CP is issued onto the ledger by MegaCorp.
|
||||
l.transaction("Issuance", tx -> {
|
||||
tx.output(Cash.PROGRAM_ID, "paper", getPaper());
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.timeWindow(getTEST_TX_TIME());
|
||||
tx.timeWindow(TEST_TX_TIME);
|
||||
return tx.verifies();
|
||||
});
|
||||
|
||||
@ -270,8 +255,8 @@ public class CommercialPaperTest {
|
||||
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty()));
|
||||
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
||||
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty()));
|
||||
tx.command(ALICE.getPubkey(), new Cash.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.command(ALICE.getPublicKey(), new Cash.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||
return tx.verifies();
|
||||
});
|
||||
|
||||
@ -281,7 +266,7 @@ public class CommercialPaperTest {
|
||||
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
||||
// We moved a paper to another pubkey.
|
||||
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(BOB.getParty()));
|
||||
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.command(MEGA_CORP.getPublicKey(), new JavaCommercialPaper.Commands.Move());
|
||||
return tx.verifies();
|
||||
});
|
||||
lw.fails();
|
||||
|
@ -1,20 +1,25 @@
|
||||
package net.corda.docs.java.tutorial.twoparty;
|
||||
|
||||
// DOCSTART 01
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.corda.core.contracts.CommandData;
|
||||
import net.corda.core.contracts.CommandWithParties;
|
||||
import net.corda.core.contracts.Contract;
|
||||
import net.corda.core.identity.Party;
|
||||
import net.corda.core.transactions.LedgerTransaction;
|
||||
|
||||
// DOCSTART 01
|
||||
// Add these imports:
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.corda.core.contracts.CommandWithParties;
|
||||
import net.corda.core.identity.Party;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.util.List;
|
||||
|
||||
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
|
||||
import static net.corda.core.contracts.ContractsDSL.requireThat;
|
||||
|
||||
// Replace TemplateContract's definition with:
|
||||
public class IOUContract implements Contract {
|
||||
public static final String IOU_CONTRACT_ID = "com.template.IOUContract";
|
||||
|
||||
// Our Create command.
|
||||
public static class Create implements CommandData {
|
||||
}
|
||||
|
@ -45,15 +45,14 @@ public class IOUFlow extends FlowLogic<Void> {
|
||||
// We retrieve the notary identity from the network map.
|
||||
final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
|
||||
|
||||
// DOCSTART 02
|
||||
// We create a transaction builder.
|
||||
final TransactionBuilder txBuilder = new TransactionBuilder();
|
||||
txBuilder.setNotary(notary);
|
||||
|
||||
// DOCSTART 02
|
||||
// We create the transaction components.
|
||||
IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty);
|
||||
String outputContract = IOUContract.class.getName();
|
||||
StateAndContract outputContractAndState = new StateAndContract(outputState, outputContract);
|
||||
StateAndContract outputContractAndState = new StateAndContract(outputState, IOUContract.IOU_CONTRACT_ID);
|
||||
List<PublicKey> requiredSigners = ImmutableList.of(getOurIdentity().getOwningKey(), otherParty.getOwningKey());
|
||||
Command cmd = new Command<>(new IOUContract.Create(), requiredSigners);
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
package net.corda.docs.java.tutorial.twoparty;
|
||||
|
||||
// DOCSTART 01
|
||||
// Add these imports:
|
||||
import co.paralleluniverse.fibers.Suspendable;
|
||||
import net.corda.core.contracts.ContractState;
|
||||
import net.corda.core.flows.*;
|
||||
import net.corda.core.transactions.SignedTransaction;
|
||||
import net.corda.core.utilities.ProgressTracker;
|
||||
import net.corda.docs.java.tutorial.helloworld.IOUFlow;
|
||||
import net.corda.docs.java.tutorial.helloworld.IOUState;
|
||||
|
||||
import static net.corda.core.contracts.ContractsDSL.requireThat;
|
||||
|
||||
// Define IOUFlowResponder:
|
||||
@InitiatedBy(IOUFlow.class)
|
||||
public class IOUFlowResponder extends FlowLogic<Void> {
|
||||
private final FlowSession otherPartySession;
|
||||
|
@ -1,16 +1,19 @@
|
||||
package net.corda.docs.tutorial.helloworld
|
||||
|
||||
// DOCSTART 01
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.flows.FinalityFlow
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
|
||||
// DOCSTART 01
|
||||
// Add these imports:
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
|
||||
// Replace TemplateFlow's definition with:
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
class IOUFlow(val iouValue: Int,
|
||||
|
@ -1,9 +1,12 @@
|
||||
package net.corda.docs.tutorial.helloworld
|
||||
|
||||
// DOCSTART 01
|
||||
import net.corda.core.contracts.ContractState
|
||||
|
||||
// DOCSTART 01
|
||||
// Add these imports:
|
||||
import net.corda.core.identity.Party
|
||||
|
||||
// Replace TemplateState's definition with:
|
||||
class IOUState(val value: Int,
|
||||
val lender: Party,
|
||||
val borrower: Party) : ContractState {
|
||||
|
@ -67,7 +67,7 @@ class TutorialMockNetwork {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mockNet = MockNetwork()
|
||||
mockNet = MockNetwork(emptyList())
|
||||
nodeA = mockNet.createPartyNode()
|
||||
nodeB = mockNet.createPartyNode()
|
||||
nodeB.registerInitiatedFlow(FlowB::class.java)
|
||||
|
@ -14,6 +14,7 @@ import net.corda.finance.contracts.asset.CASH
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.ledger
|
||||
import net.corda.testing.node.transaction
|
||||
@ -28,15 +29,15 @@ class CommercialPaperTest {
|
||||
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
|
||||
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
|
||||
val ALICE get() = alice.party
|
||||
val ALICE_PUBKEY get() = alice.pubkey
|
||||
val ALICE_PUBKEY get() = alice.publicKey
|
||||
val MEGA_CORP get() = megaCorp.party
|
||||
val MEGA_CORP_PUBKEY get() = megaCorp.pubkey
|
||||
val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
private val ledgerServices = MockServices(rigorousMock<IdentityServiceInternal>().also {
|
||||
private val ledgerServices = MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||
doReturn(null).whenever(it).partyFromKey(BIG_CORP_PUBKEY)
|
||||
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
|
||||
|
@ -1,12 +1,16 @@
|
||||
package net.corda.docs.tutorial.twoparty
|
||||
|
||||
// DOCSTART 01
|
||||
import net.corda.core.contracts.CommandData
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.requireSingleCommand
|
||||
import net.corda.core.contracts.requireThat
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
|
||||
// DOCSTART 01
|
||||
// Add these imports:
|
||||
import net.corda.core.contracts.*
|
||||
|
||||
// Replace IOUContract's contract ID and definition with:
|
||||
val IOU_CONTRACT_ID = "com.template.IOUContract"
|
||||
|
||||
class IOUContract : Contract {
|
||||
// Our Create command.
|
||||
class Create : CommandData
|
||||
@ -20,7 +24,7 @@ class IOUContract : Contract {
|
||||
"There should be one output state of type IOUState." using (tx.outputs.size == 1)
|
||||
|
||||
// IOU-specific constraints.
|
||||
val out = tx.outputsOfType<net.corda.docs.tutorial.helloworld.IOUState>().single()
|
||||
val out = tx.outputsOfType<IOUState>().single()
|
||||
"The IOU's value must be non-negative." using (out.value > 0)
|
||||
"The lender and the borrower cannot be the same entity." using (out.lender != out.borrower)
|
||||
|
||||
|
@ -6,9 +6,17 @@ import net.corda.core.contracts.Command
|
||||
import net.corda.core.contracts.StateAndContract
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import kotlin.reflect.jvm.jvmName
|
||||
import net.corda.webserver.services.WebServerPluginRegistry
|
||||
import java.util.function.Function
|
||||
import javax.ws.rs.GET
|
||||
import javax.ws.rs.Path
|
||||
import javax.ws.rs.Produces
|
||||
import javax.ws.rs.core.MediaType
|
||||
import javax.ws.rs.core.Response
|
||||
// DOCEND 01
|
||||
|
||||
@InitiatingFlow
|
||||
@ -25,14 +33,13 @@ class IOUFlow(val iouValue: Int,
|
||||
// We retrieve the notary identity from the network map.
|
||||
val notary = serviceHub.networkMapCache.notaryIdentities[0]
|
||||
|
||||
// We create a transaction builder
|
||||
// DOCSTART 02
|
||||
// We create a transaction builder.
|
||||
val txBuilder = TransactionBuilder(notary = notary)
|
||||
|
||||
// DOCSTART 02
|
||||
// We create the transaction components.
|
||||
val outputState = IOUState(iouValue, ourIdentity, otherParty)
|
||||
val outputContract = IOUContract::class.jvmName
|
||||
val outputContractAndState = StateAndContract(outputState, outputContract)
|
||||
val outputContractAndState = StateAndContract(outputState, IOU_CONTRACT_ID)
|
||||
val cmd = Command(IOUContract.Create(), listOf(ourIdentity.owningKey, otherParty.owningKey))
|
||||
|
||||
// We add the items to the builder.
|
||||
|
@ -1,16 +1,19 @@
|
||||
package net.corda.docs.tutorial.twoparty
|
||||
|
||||
// DOCSTART 01
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.requireThat
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowSession
|
||||
import net.corda.core.flows.InitiatedBy
|
||||
import net.corda.core.flows.SignTransactionFlow
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.docs.tutorial.helloworld.IOUFlow
|
||||
import net.corda.docs.tutorial.helloworld.IOUState
|
||||
|
||||
// DOCSTART 01
|
||||
// Add these imports:
|
||||
import net.corda.core.contracts.requireThat
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
|
||||
// Define IOUFlowResponder:
|
||||
@InitiatedBy(IOUFlow::class)
|
||||
class IOUFlowResponder(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
|
@ -6,8 +6,8 @@
|
||||
|
||||
Writing the flow
|
||||
================
|
||||
A flow encodes a sequence of steps that a node can run to achieve a specific ledger update. By installing new flows on
|
||||
a node, we allow the node to handle new business processes. The flow we define will allow a node to issue an
|
||||
A flow encodes a sequence of steps that a node can perform to achieve a specific ledger update. By installing new flows
|
||||
on a node, we allow the node to handle new business processes. The flow we define will allow a node to issue an
|
||||
``IOUState`` onto the ledger.
|
||||
|
||||
Flow outline
|
||||
@ -40,8 +40,8 @@ FlowLogic
|
||||
---------
|
||||
All flows must subclass ``FlowLogic``. You then define the steps taken by the flow by overriding ``FlowLogic.call``.
|
||||
|
||||
Let's define our ``IOUFlow`` in either ``TemplateFlow.java`` or ``App.kt``. Delete both the existing flows in the
|
||||
template, and replace them with the following:
|
||||
Let's define our ``IOUFlow`` in either ``TemplateFlow.java`` or ``App.kt``. Delete the two existing flows in the
|
||||
template (``Initiator`` and ``Responder``), and replace them with the following:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
|
@ -107,9 +107,15 @@ commands.
|
||||
|
||||
We want to create an IOU of 100 with PartyB. We start the ``IOUFlow`` by typing:
|
||||
|
||||
.. code:: bash
|
||||
.. container:: codeset
|
||||
|
||||
start IOUFlow iouValue: 99, otherParty: "O=PartyB,L=New York,C=US"
|
||||
.. code-block:: java
|
||||
|
||||
start IOUFlow arg0: 99, arg1: "O=PartyB,L=New York,C=US"
|
||||
|
||||
.. code-block:: kotlin
|
||||
|
||||
start IOUFlow iouValue: 99, otherParty: "O=PartyB,L=New York,C=US"
|
||||
|
||||
This single command will cause PartyA and PartyB to automatically agree an IOU. This is one of the great advantages of
|
||||
the flow framework - it allows you to reduce complex negotiation and update processes into a single function call.
|
||||
@ -118,13 +124,7 @@ If the flow worked, it should have recorded a new IOU in the vaults of both Part
|
||||
|
||||
We can check the contents of each node's vault by running:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
run vaultQuery contractStateType: com.template.state.IOUState
|
||||
|
||||
.. code-block:: kotlin
|
||||
.. code-block:: base
|
||||
|
||||
run vaultQuery contractStateType: com.template.IOUState
|
||||
|
||||
@ -174,6 +174,11 @@ parts:
|
||||
* The ``IOUState``, representing IOUs on the ledger
|
||||
* The ``IOUFlow``, orchestrating the process of agreeing the creation of an IOU on-ledger
|
||||
|
||||
After completing this tutorial, your CorDapp should look like this:
|
||||
|
||||
* Java: https://github.com/corda/corda-tut1-solution-java
|
||||
* Kotlin: https://github.com/corda/corda-tut1-solution-kotlin
|
||||
|
||||
Next steps
|
||||
----------
|
||||
There are a number of improvements we could make to this CorDapp:
|
||||
@ -183,4 +188,4 @@ There are a number of improvements we could make to this CorDapp:
|
||||
* We could add an API, to make it easier to interact with the CorDapp
|
||||
|
||||
But for now, the biggest priority is to add an ``IOUContract`` imposing constraints on the evolution of each
|
||||
``IOUState`` over time. This will be the focus of our next tutorial.
|
||||
``IOUState`` over time. This will be the focus of our next tutorial.
|
@ -24,7 +24,6 @@ interface is defined as follows:
|
||||
val participants: List<AbstractParty>
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
The first thing you'll probably notice about this interface declaration is that its not written in Java or another
|
||||
common language. The core Corda platform, including the interface declaration above, is entirely written in Kotlin.
|
||||
|
||||
@ -70,7 +69,7 @@ later is often as simple as adding an additional property to your class definiti
|
||||
|
||||
Defining IOUState
|
||||
-----------------
|
||||
Let's get started by opening ``TemplateState.java`` (for Java) or ``App.kt`` (for Kotlin) and updating
|
||||
Let's get started by opening ``TemplateState.java`` (for Java) or ``StatesAndContracts.kt`` (for Kotlin) and updating
|
||||
``TemplateState`` to define an ``IOUState``:
|
||||
|
||||
.. container:: codeset
|
||||
|
@ -41,34 +41,33 @@ https://docs.corda.net/tutorial-cordapp.html#opening-the-example-cordapp-in-inte
|
||||
|
||||
Template structure
|
||||
------------------
|
||||
The template has a number of files, but we can ignore most of them. To implement our IOU CorDapp in Java, we'll only
|
||||
need to modify two files. For Kotlin, we'll simply be modifying the ``App.kt`` file:
|
||||
The template has a number of files, but we can ignore most of them. We will only be modifying the following files:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
// 1. The state
|
||||
src/main/java/com/template/TemplateState.java
|
||||
cordapp-contracts-states/src/main/java/com/template/TemplateState.java
|
||||
|
||||
// 2. The flow
|
||||
src/main/java/com/template/TemplateFlow.java
|
||||
cordapp/src/main/java/com/template/TemplateFlow.java
|
||||
|
||||
.. code-block:: kotlin
|
||||
|
||||
src/main/kotlin/com/template/App.kt
|
||||
// 1. The state
|
||||
cordapp-contracts-states/src/main/kotlin/com/template/StatesAndContracts.kt
|
||||
|
||||
// 2. The flow
|
||||
cordapp/src/main/kotlin/com/template/App.kt
|
||||
|
||||
Clean up
|
||||
--------
|
||||
To prevent build errors later on, we should delete the following files before we begin:
|
||||
|
||||
* Java:
|
||||
* ``src/main/java/com/template/TemplateClient.java``
|
||||
* ``src/test/java/com/template/FlowTests.java``
|
||||
* Java: ``cordapp/src/main/java/com/template/TemplateClient.java``
|
||||
|
||||
* Kotlin:
|
||||
* ``src/main/kotlin/com/template/TemplateClient.kt``
|
||||
* ``src/test/kotlin/com/template/FlowTests.kt``
|
||||
* Kotlin: ``cordapp/src/main/kotlin/com/template/TemplateClient.kt``
|
||||
|
||||
Progress so far
|
||||
---------------
|
||||
|
@ -2,7 +2,7 @@ Network Map
|
||||
===========
|
||||
|
||||
The network map stores a collection of ``NodeInfo`` objects, each representing another node with which the node can interact.
|
||||
There two sources from which a Corda node can retrieve ``NodeInfo`` objects:
|
||||
There are two sources from which a Corda node can retrieve ``NodeInfo`` objects:
|
||||
|
||||
1. the REST protocol with the network map service, which also provides a publishing API,
|
||||
|
||||
@ -13,7 +13,7 @@ Protocol Design
|
||||
---------------
|
||||
The node info publishing protocol:
|
||||
|
||||
* Create a ``NodeInfo`` object, and sign it to create a ``SignedData<NodeInfo>`` object. TODO: We will need list of signatures in ``SignedData`` to support multiple node identities in the future.
|
||||
* Create a ``NodeInfo`` object, and sign it to create a ``SignedNodeInfo`` object.
|
||||
|
||||
* Serialise the signed data and POST the data to the network map server.
|
||||
|
||||
@ -25,7 +25,18 @@ Node side network map update protocol:
|
||||
|
||||
* The Corda node will query the network map service periodically according to the ``Expires`` attribute in the HTTP header.
|
||||
|
||||
* The network map service returns a signed ``NetworkMap`` object, containing list of node info hashes and the network parameters hashes.
|
||||
* The network map service returns a signed ``NetworkMap`` object which looks as follows:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
data class NetworkMap {
|
||||
val nodeInfoHashes: List<SecureHash>,
|
||||
val networkParametersHash: SecureHash
|
||||
}
|
||||
|
||||
The object contains list of node info hashes and hash of the network parameters data structure (without the signatures).
|
||||
|
||||
* The node updates its local copy of ``NodeInfos`` if it is different from the newly downloaded ``NetworkMap``.
|
||||
|
||||
@ -34,13 +45,13 @@ Network Map service REST API:
|
||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| Request method | Path | Description |
|
||||
+================+===================================+========================================================================================================================================================+
|
||||
| POST | /api/network-map/publish | Publish new ``NodeInfo`` to the network map service, the legal identity in ``NodeInfo`` must match with the identity registered with the doorman. |
|
||||
| POST | /network-map/publish | Publish new ``NodeInfo`` to the network map service, the legal identity in ``NodeInfo`` must match with the identity registered with the doorman. |
|
||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| GET | /api/network-map | Retrieve ``NetworkMap`` from the server, the ``NetworkMap`` object contains list of node info hashes and NetworkParameters hash. |
|
||||
| GET | /network-map | Retrieve ``NetworkMap`` from the server, the ``NetworkMap`` object contains list of node info hashes and ``NetworkParameters`` hash. |
|
||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| GET | /api/network-map/node-info/{hash} | Retrieve ``NodeInfo`` object with the same hash. |
|
||||
| GET | /network-map/node-info/{hash} | Retrieve ``NodeInfo`` object with the same hash. |
|
||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| GET | /api/network-map/parameters/{hash}| Retrieve ``NetworkParameters`` object with the same hash. |
|
||||
| GET | /network-map/parameters/{hash} | Retrieve ``NetworkParameters`` object with the same hash. |
|
||||
+----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
TODO: Access control of the network map will be added in the future.
|
||||
@ -50,8 +61,24 @@ The ``additional-node-infos`` directory
|
||||
---------------------------------------
|
||||
Each Corda node reads, and continuously polls, the files contained in a directory named ``additional-node-infos`` inside the node base directory.
|
||||
|
||||
Nodes expect to find a serialized ``SignedData<NodeInfo>`` object, the same object which is sent to network map server.
|
||||
Nodes expect to find a serialized ``SignedNodeInfo`` object, the same object which is sent to network map server.
|
||||
|
||||
Whenever a node starts it writes on disk a file containing its own ``NodeInfo``, this file is called ``nodeInfo-XXX`` where ``XXX`` is a long string.
|
||||
|
||||
Hence if an operator wants node A to see node B they can pick B's ``NodeInfo`` file from B base directory and drop it into A's ``additional-node-infos`` directory.
|
||||
|
||||
|
||||
Network parameters
|
||||
------------------
|
||||
Network parameters are constants that every node participating in the network needs to agree on and use for interop purposes.
|
||||
The structure is distributed as a file containing serialized ``SignedData<NetworkParameters>`` with a signature from
|
||||
a sub-key of the compatibility zone root cert. Network map advertises the hash of currently used network parameters.
|
||||
The ``NetworkParameters`` structure contains:
|
||||
* ``minimumPlatformVersion`` - minimum version of Corda platform that is required for nodes in the network.
|
||||
* ``notaries`` - list of well known and trusted notary identities with information on validation type.
|
||||
* ``maxMessageSize`` - maximum P2P message size sent over the wire in bytes.
|
||||
* ``maxTransactionSize`` - maximum permitted transaction size in bytes.
|
||||
* ``modifiedTime`` - the time the network parameters were created by the CZ operator.
|
||||
* ``epoch`` - version number of the network parameters. Starting from 1, this will always increment on each new set of parameters.
|
||||
|
||||
The set of parameters is still under development and we may find the need to add additional fields.
|
||||
|
@ -1,136 +0,0 @@
|
||||
Access security settings
|
||||
========================
|
||||
|
||||
Access to node functionalities via SSH or RPC is protected by an authentication and authorisation policy.
|
||||
|
||||
The field ``security`` in ``node.conf`` exposes various sub-fields related to authentication/authorisation specifying:
|
||||
|
||||
* The data source providing credentials and permissions for users (e.g.: a remote RDBMS)
|
||||
* An optional password encryption method.
|
||||
* An optional caching of users data from Node side.
|
||||
|
||||
.. warning:: Specifying both ``rpcUsers`` and ``security`` fields in ``node.conf`` is considered an illegal setting and
|
||||
rejected by the node at startup since ``rpcUsers`` is effectively deprecated in favour of ``security.authService``.
|
||||
|
||||
**Example 1:** connect to remote RDBMS for credentials/permissions, with encrypted user passwords and
|
||||
caching on node-side:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
security = {
|
||||
authService = {
|
||||
dataSource = {
|
||||
type = "DB",
|
||||
passwordEncryption = "SHIRO_1_CRYPT",
|
||||
connection = {
|
||||
jdbcUrl = "<jdbc connection string>"
|
||||
username = "<db username>"
|
||||
password = "<db user password>"
|
||||
driverClassName = "<JDBC driver>"
|
||||
}
|
||||
}
|
||||
options = {
|
||||
cache = {
|
||||
expiryTimeSecs = 120
|
||||
capacity = 10000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
**Example 2:** list of user credentials and permissions hard-coded in ``node.conf``
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
security = {
|
||||
authService = {
|
||||
dataSource = {
|
||||
type = "INMEMORY",
|
||||
users =[
|
||||
{
|
||||
username = "user1"
|
||||
password = "password"
|
||||
permissions = [
|
||||
"StartFlow.net.corda.flows.ExampleFlow1",
|
||||
"StartFlow.net.corda.flows.ExampleFlow2",
|
||||
...
|
||||
]
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Let us look in more details at the structure of ``security.authService``:
|
||||
|
||||
Authentication/authorisation data
|
||||
---------------------------------
|
||||
|
||||
The ``dataSource`` field defines the data provider supplying credentials and permissions for users. The ``type``
|
||||
subfield identify the type of data provider, currently supported one are:
|
||||
|
||||
* **INMEMORY:** a list of user credentials and permissions hard-coded in configuration in the ``users`` field
|
||||
(see example 2 above)
|
||||
|
||||
* **DB:** An external RDBMS accessed via the JDBC connection described by ``connection``. The current implementation
|
||||
expect the database to store data according to the following schema:
|
||||
|
||||
- Table ``users`` containing columns ``username`` and ``password``.
|
||||
The ``username`` column *must have unique values*.
|
||||
- Table ``user_roles`` containing columns ``username`` and ``role_name`` associating a user to a set of *roles*
|
||||
- Table ``roles_permissions`` containing columns ``role_name`` and ``permission`` associating a role to a set of
|
||||
permission strings
|
||||
|
||||
Note in particular how in the DB case permissions are assigned to _roles_ rather than individual users.
|
||||
Also, there is no prescription on the SQL type of the columns (although in our tests we defined ``username`` and
|
||||
``role_name`` of SQL type ``VARCHAR`` and ``password`` of ``TEXT`` type) and it is allowed to put additional columns
|
||||
besides the one expected by the implementation.
|
||||
|
||||
Password encryption
|
||||
-------------------
|
||||
|
||||
Storing passwords in plain text is discouraged in production systems aiming for high security requirements. We support
|
||||
reading passwords stored using the Apache Shiro fully reversible Modular Crypt Format, specified in the documentation
|
||||
of ``org.apache.shiro.crypto.hash.format.Shiro1CryptFormat``.
|
||||
|
||||
Password are assumed in plain format by default. To specify an encryption it is necessary to use the field:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
passwordEncryption = SHIRO_1_CRYPT
|
||||
|
||||
Hash encrypted password based on the Shiro1CryptFormat can be produced with the `Apache Shiro Hasher tool <https://shiro.apache.org/command-line-hasher.html>`_
|
||||
|
||||
Cache
|
||||
-----
|
||||
|
||||
Adding a cache layer on top of an external provider of users credentials and permissions can significantly benefit
|
||||
performances in some cases, with the disadvantage of introducing a latency in the propagation of changes to the data.
|
||||
|
||||
Caching of users data is disabled by default, it can be enabled by defining the ``options.cache`` field, like seen in
|
||||
the examples above:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: groovy
|
||||
|
||||
options = {
|
||||
cache = {
|
||||
expiryTimeSecs = 120
|
||||
capacity = 10000
|
||||
}
|
||||
}
|
||||
|
||||
This will enable an in-memory cache with maximum capacity (number of entries) and maximum life time of entries given by
|
||||
respectively the values set by the ``capacity`` and ``expiryTimeSecs`` fields.
|
||||
|
||||
|
||||
|
||||
|
@ -32,7 +32,13 @@ A Corda network has three types of certificate authorities (CAs):
|
||||
* The **node CAs**
|
||||
|
||||
* Each node serves as its own CA in issuing the child certificates that it uses to sign its identity
|
||||
keys, anonymous keys and TLS certificates
|
||||
keys and TLS certificates
|
||||
|
||||
We can visualise the permissioning structure as follows:
|
||||
|
||||
.. image:: resources/certificate_structure.png
|
||||
:scale: 55%
|
||||
:align: center
|
||||
|
||||
Keypair and certificate formats
|
||||
-------------------------------
|
||||
@ -45,6 +51,13 @@ public/private keypairs and certificates. The keypairs and certificates should o
|
||||
|
||||
* The TLS certificates must follow the `TLS v1.2 standard <https://tools.ietf.org/html/rfc5246>`_
|
||||
|
||||
* The root network CA, intermediate network CA and node CA keys, as well as the node TLS
|
||||
keys, must follow one of the following schemes:
|
||||
|
||||
* ECDSA using the NIST P-256 curve (secp256r1)
|
||||
|
||||
* RSA with 3072-bit key size
|
||||
|
||||
Creating the root and intermediate network CAs
|
||||
----------------------------------------------
|
||||
|
||||
|
@ -6,6 +6,5 @@ Release process
|
||||
|
||||
release-notes
|
||||
changelog
|
||||
upgrade-notes
|
||||
codestyle
|
||||
testing
|
BIN
docs/source/resources/certificate_structure.png
Normal file
BIN
docs/source/resources/certificate_structure.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 108 KiB |
@ -6,11 +6,9 @@ Creating a Corda network
|
||||
A Corda network consists of a number of machines running nodes. These nodes communicate using persistent protocols in
|
||||
order to create and validate transactions.
|
||||
|
||||
There are four broader categories of functionality one such node may have. These pieces of functionality are provided
|
||||
There are three broader categories of functionality one such node may have. These pieces of functionality are provided
|
||||
as services, and one node may run several of them.
|
||||
|
||||
* Network map: The node running the network map provides a way to resolve identities to physical node addresses and
|
||||
associated public keys
|
||||
* Notary: Nodes running a notary service witness state spends and have the final say in whether a transaction is a
|
||||
double-spend or not
|
||||
* Oracle: Network services that link the ledger to the outside world by providing facts that affect the validity of
|
||||
@ -46,12 +44,38 @@ The most important fields regarding network configuration are:
|
||||
* ``rpcAddress``: The address to which Artemis will bind for RPC calls.
|
||||
* ``webAddress``: The address the webserver should bind. Note that the port must be distinct from that of ``p2pAddress`` and ``rpcAddress`` if they are on the same machine.
|
||||
|
||||
Bootstrapping the network
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The nodes see each other using the network map. This is a collection of statically signed node-info files, one for each
|
||||
node in the network. Most production deployments will use a highly available, secure distribution of the network map via HTTP.
|
||||
|
||||
For test deployments where the nodes (at least initially) reside on the same filesystem, these node-info files can be
|
||||
placed directly in the node's ``additional-node-infos`` directory from where the node will pick them up and store them
|
||||
in its local network map cache. The node generates its own node-info file on startup.
|
||||
|
||||
In addition to the network map, all the nodes on a network must use the same set of network parameters. These are a set
|
||||
of constants which guarantee interoperability between nodes. The HTTP network map distributes the network parameters
|
||||
which the node downloads automatically. In the absence of this the network parameters must be generated locally. This can
|
||||
be done with the network bootstrapper. This a tool that scans all the node configurations from a common directory to
|
||||
generate the network parameters file which is copied to the nodes' directories. It also copies each node's node-info file
|
||||
to every other node.
|
||||
|
||||
The bootstrapper tool can be built with the command:
|
||||
|
||||
``gradlew buildBootstrapperJar``
|
||||
|
||||
The resulting jar can be found in ``tools/bootstrapper/build/libs/``.
|
||||
|
||||
To use it, run the following command, specifying the root directory which hosts all the node directories as the argument:
|
||||
|
||||
``java -jar network-bootstrapper.jar <nodes-root-dir>``
|
||||
|
||||
Starting the nodes
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You may now start the nodes in any order. Note that the node is not fully started until it has successfully registered with the network map!
|
||||
|
||||
You should see a banner, some log lines and eventually ``Node started up and registered``, indicating that the node is fully started.
|
||||
You may now start the nodes in any order. You should see a banner, some log lines and eventually ``Node started up and registered``,
|
||||
indicating that the node is fully started.
|
||||
|
||||
.. TODO: Add a better way of polling for startup. A programmatic way of determining whether a node is up is to check whether it's ``webAddress`` is bound.
|
||||
|
||||
@ -66,7 +90,6 @@ details/diagnosing problems check the logs.
|
||||
Logging is standard log4j2_ and may be configured accordingly. Logs
|
||||
are by default redirected to files in ``NODE_DIRECTORY/logs/``.
|
||||
|
||||
|
||||
Connecting to the nodes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -77,7 +77,7 @@ We can picture this transaction as follows:
|
||||
Defining IOUContract
|
||||
--------------------
|
||||
Let's write a contract that enforces these constraints. We'll do this by modifying either ``TemplateContract.java`` or
|
||||
``App.kt`` and updating ``TemplateContract`` to define an ``IOUContract``:
|
||||
``StatesAndContracts.kt`` and updating ``TemplateContract`` to define an ``IOUContract``:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
|
@ -17,7 +17,7 @@ We'll do this by modifying the flow we wrote in the previous tutorial.
|
||||
|
||||
Verifying the transaction
|
||||
-------------------------
|
||||
In ``IOUFlow.java``/``IOUFlow.kt``, change the imports block to the following:
|
||||
In ``IOUFlow.java``/``App.kt``, change the imports block to the following:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -31,7 +31,8 @@ In ``IOUFlow.java``/``IOUFlow.kt``, change the imports block to the following:
|
||||
:start-after: DOCSTART 01
|
||||
:end-before: DOCEND 01
|
||||
|
||||
And update ``IOUFlow.call`` by changing the code following the creation of the ``TransactionBuilder`` as follows:
|
||||
And update ``IOUFlow.call`` by changing the code following the retrieval of the notary's identity from the network as
|
||||
follows:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
@ -138,6 +139,11 @@ Our CorDapp now imposes restrictions on the issuance of IOUs. Most importantly,
|
||||
from both the lender and the borrower before an IOU can be created on the ledger. This prevents either the lender or
|
||||
the borrower from unilaterally updating the ledger in a way that only benefits themselves.
|
||||
|
||||
After completing this tutorial, your CorDapp should look like this:
|
||||
|
||||
* Java: https://github.com/corda/corda-tut2-solution-java
|
||||
* Kotlin: https://github.com/corda/corda-tut2-solution-kotlin
|
||||
|
||||
You should now be ready to develop your own CorDapps. You can also find a list of sample CorDapps
|
||||
`here <https://www.corda.net/samples/>`_. As you write CorDapps, you'll also want to learn more about the
|
||||
:doc:`Corda API <corda-api>`.
|
||||
|
@ -1,11 +1,23 @@
|
||||
Tutorials
|
||||
=========
|
||||
|
||||
This section is split into two parts.
|
||||
|
||||
The Hello, World tutorials should be followed in sequence and show how to extend the Java or Kotlin CorDapp Template
|
||||
into a full CorDapp.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
hello-world-introduction
|
||||
tut-two-party-introduction
|
||||
|
||||
The remaining tutorials cover individual platform features in isolation. They don't depend on the code from the Hello,
|
||||
World tutorials, and can be read in any order.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
tutorial-contract
|
||||
tutorial-test-dsl
|
||||
contract-upgrade
|
||||
|
@ -1,5 +1,5 @@
|
||||
Upgrade notes
|
||||
=============
|
||||
Upgrading a CorDapp to a new version
|
||||
====================================
|
||||
|
||||
These notes provide instructions for upgrading your CorDapps from previous versions, starting with the upgrade from our
|
||||
first public Beta (:ref:`Milestone 12 <changelog_m12>`), to :ref:`V1.0 <changelog_v1>`.
|
||||
|
@ -32,8 +32,8 @@ Flow versioning
|
||||
---------------
|
||||
|
||||
In addition to the evolution of the platform, flows that run on top of the platform can also evolve. It may be that the
|
||||
flow protocol between an initiating flow and it's intiated flow changes from one CorDapp release to the next in such as
|
||||
way to be backwards incompatible with existing flows. For example, if a sequence of sends and receives needs to change
|
||||
flow protocol between an initiating flow and its initiated flow changes from one CorDapp release to the next in such a
|
||||
way to be backward incompatible with existing flows. For example, if a sequence of sends and receives needs to change
|
||||
or if the semantics of a particular receive changes.
|
||||
|
||||
The ``InitiatingFlow`` annotation (see :doc:`flow-state-machine` for more information on the flow annotations) has a ``version``
|
||||
|
@ -9,6 +9,10 @@ import net.corda.finance.contracts.Frequency
|
||||
import net.corda.finance.contracts.Tenor
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.dsl.EnforceVerifyOrFail
|
||||
import net.corda.testing.dsl.TransactionDSL
|
||||
import net.corda.testing.dsl.TransactionDSLInterpreter
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.transaction
|
||||
import org.junit.Ignore
|
||||
|
@ -12,51 +12,52 @@ import net.corda.testing.node.MockServices;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static net.corda.finance.Currencies.DOLLARS;
|
||||
import static net.corda.finance.Currencies.issuedBy;
|
||||
import static net.corda.testing.node.NodeTestUtils.transaction;
|
||||
import static net.corda.testing.CoreTestUtils.rigorousMock;
|
||||
import static net.corda.testing.TestConstants.getDUMMY_NOTARY_NAME;
|
||||
import static net.corda.testing.internal.InternalTestUtilsKt.rigorousMock;
|
||||
import static net.corda.testing.TestConstants.DUMMY_NOTARY_NAME;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
|
||||
/**
|
||||
* This is an incomplete Java replica of CashTests.kt to show how to use the Java test DSL
|
||||
*/
|
||||
public class CashTestsJava {
|
||||
private static final Party DUMMY_NOTARY = new TestIdentity(getDUMMY_NOTARY_NAME(), 20L).getParty();
|
||||
private static final Party DUMMY_NOTARY = new TestIdentity(DUMMY_NOTARY_NAME, 20L).getParty();
|
||||
private static final TestIdentity MEGA_CORP = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB"));
|
||||
private static final TestIdentity MINI_CORP = new TestIdentity(new CordaX500Name("MiniCorp", "London", "GB"));
|
||||
private final PartyAndReference defaultIssuer = MEGA_CORP.ref((byte) 1);
|
||||
private final Cash.State inState = new Cash.State(issuedBy(DOLLARS(1000), defaultIssuer), new AnonymousParty(MEGA_CORP.getPubkey()));
|
||||
private final Cash.State outState = new Cash.State(inState.getAmount(), new AnonymousParty(MINI_CORP.getPubkey()));
|
||||
private final Cash.State inState = new Cash.State(issuedBy(DOLLARS(1000), defaultIssuer), new AnonymousParty(MEGA_CORP.getPublicKey()));
|
||||
private final Cash.State outState = new Cash.State(inState.getAmount(), new AnonymousParty(MINI_CORP.getPublicKey()));
|
||||
@Rule
|
||||
public final SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule();
|
||||
|
||||
@Test
|
||||
public void trivial() {
|
||||
IdentityServiceInternal identityService = rigorousMock(IdentityServiceInternal.class);
|
||||
doReturn(MEGA_CORP.getParty()).when(identityService).partyFromKey(MEGA_CORP.getPubkey());
|
||||
doReturn(MINI_CORP.getParty()).when(identityService).partyFromKey(MINI_CORP.getPubkey());
|
||||
transaction(new MockServices(identityService, MEGA_CORP.getName()), DUMMY_NOTARY, tx -> {
|
||||
doReturn(MEGA_CORP.getParty()).when(identityService).partyFromKey(MEGA_CORP.getPublicKey());
|
||||
doReturn(MINI_CORP.getParty()).when(identityService).partyFromKey(MINI_CORP.getPublicKey());
|
||||
transaction(new MockServices(emptyList(), identityService, MEGA_CORP.getName()), DUMMY_NOTARY, tx -> {
|
||||
tx.attachment(Cash.PROGRAM_ID);
|
||||
|
||||
tx.input(Cash.PROGRAM_ID, inState);
|
||||
|
||||
tx.tweak(tw -> {
|
||||
tw.output(Cash.PROGRAM_ID, new Cash.State(issuedBy(DOLLARS(2000), defaultIssuer), new AnonymousParty(MINI_CORP.getPubkey())));
|
||||
tw.command(MEGA_CORP.getPubkey(), new Cash.Commands.Move());
|
||||
tw.output(Cash.PROGRAM_ID, new Cash.State(issuedBy(DOLLARS(2000), defaultIssuer), new AnonymousParty(MINI_CORP.getPublicKey())));
|
||||
tw.command(MEGA_CORP.getPublicKey(), new Cash.Commands.Move());
|
||||
return tw.failsWith("the amounts balance");
|
||||
});
|
||||
|
||||
tx.tweak(tw -> {
|
||||
tw.output(Cash.PROGRAM_ID, outState);
|
||||
tw.command(MEGA_CORP.getPubkey(), DummyCommandData.INSTANCE);
|
||||
tw.command(MEGA_CORP.getPublicKey(), DummyCommandData.INSTANCE);
|
||||
// Invalid command
|
||||
return tw.failsWith("required net.corda.finance.contracts.asset.Cash.Commands.Move command");
|
||||
});
|
||||
tx.tweak(tw -> {
|
||||
tw.output(Cash.PROGRAM_ID, outState);
|
||||
tw.command(MINI_CORP.getPubkey(), new Cash.Commands.Move());
|
||||
tw.command(MINI_CORP.getPublicKey(), new Cash.Commands.Move());
|
||||
return tw.failsWith("the owning keys are a subset of the signing keys");
|
||||
});
|
||||
tx.tweak(tw -> {
|
||||
@ -64,14 +65,14 @@ public class CashTestsJava {
|
||||
// issuedBy() can't be directly imported because it conflicts with other identically named functions
|
||||
// with different overloads (for some reason).
|
||||
tw.output(Cash.PROGRAM_ID, outState.issuedBy(MINI_CORP.getParty()));
|
||||
tw.command(MEGA_CORP.getPubkey(), new Cash.Commands.Move());
|
||||
tw.command(MEGA_CORP.getPublicKey(), new Cash.Commands.Move());
|
||||
return tw.failsWith("at least one cash input");
|
||||
});
|
||||
|
||||
// Simple reallocation works.
|
||||
return tx.tweak(tw -> {
|
||||
tw.output(Cash.PROGRAM_ID, outState);
|
||||
tw.command(MEGA_CORP.getPubkey(), new Cash.Commands.Move());
|
||||
tw.command(MEGA_CORP.getPublicKey(), new Cash.Commands.Move());
|
||||
return tw.verifies();
|
||||
});
|
||||
});
|
||||
|
@ -18,7 +18,11 @@ import net.corda.finance.`issued by`
|
||||
import net.corda.finance.contracts.asset.*
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.VaultFiller
|
||||
import net.corda.testing.dsl.EnforceVerifyOrFail
|
||||
import net.corda.testing.dsl.TransactionDSL
|
||||
import net.corda.testing.dsl.TransactionDSLInterpreter
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.internal.vault.VaultFiller
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
|
||||
import net.corda.testing.node.ledger
|
||||
@ -46,7 +50,7 @@ interface ICommercialPaperTestTemplate {
|
||||
private val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
|
||||
private val MEGA_CORP get() = megaCorp.party
|
||||
private val MEGA_CORP_IDENTITY get() = megaCorp.identity
|
||||
private val MEGA_CORP_PUBKEY get() = megaCorp.pubkey
|
||||
private val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
|
||||
|
||||
class JavaCommercialPaperTest : ICommercialPaperTestTemplate {
|
||||
override fun getPaper(): ICommercialPaperState = JavaCommercialPaper.State(
|
||||
@ -105,13 +109,13 @@ class CommercialPaperTestsGeneric {
|
||||
private val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
|
||||
private val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
|
||||
private val ALICE get() = alice.party
|
||||
private val ALICE_KEY get() = alice.key
|
||||
private val ALICE_PUBKEY get() = alice.pubkey
|
||||
private val ALICE_KEY get() = alice.keyPair
|
||||
private val ALICE_PUBKEY get() = alice.publicKey
|
||||
private val DUMMY_NOTARY get() = dummyNotary.party
|
||||
private val DUMMY_NOTARY_IDENTITY get() = dummyNotary.identity
|
||||
private val MINI_CORP get() = miniCorp.party
|
||||
private val MINI_CORP_IDENTITY get() = miniCorp.identity
|
||||
private val MINI_CORP_PUBKEY get() = miniCorp.pubkey
|
||||
private val MINI_CORP_PUBKEY get() = miniCorp.publicKey
|
||||
}
|
||||
|
||||
@Parameterized.Parameter
|
||||
@ -120,7 +124,7 @@ class CommercialPaperTestsGeneric {
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
val issuer = MEGA_CORP.ref(123)
|
||||
private val ledgerServices = MockServices(rigorousMock<IdentityServiceInternal>().also {
|
||||
private val ledgerServices = MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||
doReturn(MINI_CORP).whenever(it).partyFromKey(MINI_CORP_PUBKEY)
|
||||
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
|
||||
@ -260,16 +264,15 @@ class CommercialPaperTestsGeneric {
|
||||
private lateinit var aliceServices: MockServices
|
||||
private lateinit var aliceVaultService: VaultService
|
||||
private lateinit var alicesVault: Vault<ContractState>
|
||||
private val notaryServices = MockServices(rigorousMock(), MEGA_CORP.name, dummyNotary.key)
|
||||
private val issuerServices = MockServices(listOf("net.corda.finance.contracts", "net.corda.finance.schemas"), rigorousMock(), MEGA_CORP.name, dummyCashIssuer.key)
|
||||
private val notaryServices = MockServices(emptyList(), rigorousMock(), MEGA_CORP.name, dummyNotary.keyPair)
|
||||
private val issuerServices = MockServices(listOf("net.corda.finance.contracts", "net.corda.finance.schemas"), rigorousMock(), MEGA_CORP.name, dummyCashIssuer.keyPair)
|
||||
private lateinit var moveTX: SignedTransaction
|
||||
@Test
|
||||
fun `issue move and then redeem`() {
|
||||
val aliceDatabaseAndServices = makeTestDatabaseAndMockServices(
|
||||
listOf(ALICE_KEY),
|
||||
makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)),
|
||||
listOf("net.corda.finance.contracts", "net.corda.finance.schemas"),
|
||||
MEGA_CORP.name)
|
||||
listOf("net.corda.finance.contracts"),
|
||||
makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY),
|
||||
TestIdentity(MEGA_CORP.name, ALICE_KEY))
|
||||
val databaseAlice = aliceDatabaseAndServices.first
|
||||
aliceServices = aliceDatabaseAndServices.second
|
||||
aliceVaultService = aliceServices.vaultService
|
||||
@ -279,10 +282,9 @@ class CommercialPaperTestsGeneric {
|
||||
aliceVaultService = aliceServices.vaultService
|
||||
}
|
||||
val bigCorpDatabaseAndServices = makeTestDatabaseAndMockServices(
|
||||
listOf(BIG_CORP_KEY),
|
||||
makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)),
|
||||
listOf("net.corda.finance.contracts", "net.corda.finance.schemas"),
|
||||
MEGA_CORP.name)
|
||||
listOf("net.corda.finance.contracts"),
|
||||
makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY),
|
||||
TestIdentity(MEGA_CORP.name, BIG_CORP_KEY))
|
||||
val databaseBigCorp = bigCorpDatabaseAndServices.first
|
||||
bigCorpServices = bigCorpDatabaseAndServices.second
|
||||
bigCorpVaultService = bigCorpServices.vaultService
|
||||
@ -308,8 +310,8 @@ class CommercialPaperTestsGeneric {
|
||||
// Alice pays $9000 to BigCorp to own some of their debt.
|
||||
moveTX = run {
|
||||
val builder = TransactionBuilder(DUMMY_NOTARY)
|
||||
Cash.generateSpend(aliceServices, builder, 9000.DOLLARS, AnonymousParty(bigCorpServices.key.public))
|
||||
CommercialPaper().generateMove(builder, issueTx.tx.outRef(0), AnonymousParty(aliceServices.key.public))
|
||||
Cash.generateSpend(aliceServices, builder, 9000.DOLLARS, AnonymousParty(BIG_CORP_KEY.public))
|
||||
CommercialPaper().generateMove(builder, issueTx.tx.outRef(0), AnonymousParty(ALICE_KEY.public))
|
||||
val ptx = aliceServices.signInitialTransaction(builder)
|
||||
val ptx2 = bigCorpServices.addSignature(ptx)
|
||||
val stx = notaryServices.addSignature(ptx2)
|
||||
|
@ -3,7 +3,6 @@ package net.corda.finance.contracts.asset
|
||||
import com.nhaarman.mockito_kotlin.*
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.identity.*
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.services.VaultService
|
||||
@ -21,7 +20,12 @@ import net.corda.node.services.vault.NodeVaultService
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.DummyState
|
||||
import net.corda.testing.contracts.VaultFiller
|
||||
import net.corda.testing.internal.LogHelper
|
||||
import net.corda.testing.dsl.EnforceVerifyOrFail
|
||||
import net.corda.testing.dsl.TransactionDSL
|
||||
import net.corda.testing.dsl.TransactionDSLInterpreter
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.internal.vault.VaultFiller
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
|
||||
import net.corda.testing.node.ledger
|
||||
@ -37,27 +41,27 @@ import kotlin.test.*
|
||||
class CashTests {
|
||||
private companion object {
|
||||
val alice = TestIdentity(ALICE_NAME, 70)
|
||||
val BOB_PUBKEY = TestIdentity(BOB_NAME, 80).pubkey
|
||||
val BOB_PUBKEY = TestIdentity(BOB_NAME, 80).publicKey
|
||||
val charlie = TestIdentity(CHARLIE_NAME, 90)
|
||||
val DUMMY_CASH_ISSUER_IDENTITY = TestIdentity(CordaX500Name("Snake Oil Issuer", "London", "GB"), 10).identity
|
||||
val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
|
||||
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
|
||||
val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
|
||||
val ALICE get() = alice.party
|
||||
val ALICE_PUBKEY get() = alice.pubkey
|
||||
val ALICE_PUBKEY get() = alice.publicKey
|
||||
val CHARLIE get() = charlie.party
|
||||
val CHARLIE_IDENTITY get() = charlie.identity
|
||||
val DUMMY_NOTARY get() = dummyNotary.party
|
||||
val DUMMY_NOTARY_IDENTITY get() = dummyNotary.identity
|
||||
val DUMMY_NOTARY_KEY get() = dummyNotary.key
|
||||
val DUMMY_NOTARY_KEY get() = dummyNotary.keyPair
|
||||
val MEGA_CORP get() = megaCorp.party
|
||||
val MEGA_CORP_IDENTITY get() = megaCorp.identity
|
||||
val MEGA_CORP_KEY get() = megaCorp.key
|
||||
val MEGA_CORP_PUBKEY get() = megaCorp.pubkey
|
||||
val MEGA_CORP_KEY get() = megaCorp.keyPair
|
||||
val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
|
||||
val MINI_CORP get() = miniCorp.party
|
||||
val MINI_CORP_IDENTITY get() = miniCorp.identity
|
||||
val MINI_CORP_KEY get() = miniCorp.key
|
||||
val MINI_CORP_PUBKEY get() = miniCorp.pubkey
|
||||
val MINI_CORP_KEY get() = miniCorp.keyPair
|
||||
val MINI_CORP_PUBKEY get() = miniCorp.publicKey
|
||||
}
|
||||
|
||||
@Rule
|
||||
@ -99,10 +103,9 @@ class CashTests {
|
||||
}, MINI_CORP.name, MINI_CORP_KEY)
|
||||
val notaryServices = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
|
||||
val databaseAndServices = makeTestDatabaseAndMockServices(
|
||||
listOf(generateKeyPair()),
|
||||
makeTestIdentityService(listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY)),
|
||||
listOf("net.corda.finance.contracts.asset", "net.corda.finance.schemas"),
|
||||
CordaX500Name("Me", "London", "GB"))
|
||||
listOf("net.corda.finance.contracts.asset"),
|
||||
makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY),
|
||||
TestIdentity(CordaX500Name("Me", "London", "GB")))
|
||||
database = databaseAndServices.first
|
||||
ourServices = databaseAndServices.second
|
||||
|
||||
@ -138,7 +141,7 @@ class CashTests {
|
||||
}
|
||||
|
||||
private fun transaction(script: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) = run {
|
||||
MockServices(rigorousMock<IdentityServiceInternal>().also {
|
||||
MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||
doReturn(MINI_CORP).whenever(it).partyFromKey(MINI_CORP_PUBKEY)
|
||||
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
|
||||
|
@ -22,6 +22,8 @@ import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.contracts.DummyState
|
||||
import net.corda.testing.dsl.*
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.ledger
|
||||
import net.corda.testing.node.transaction
|
||||
@ -45,14 +47,14 @@ class ObligationTests {
|
||||
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
|
||||
val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
|
||||
val ALICE get() = alice.party
|
||||
val ALICE_PUBKEY get() = alice.pubkey
|
||||
val ALICE_PUBKEY get() = alice.publicKey
|
||||
val BOB get() = bob.party
|
||||
val BOB_PUBKEY get() = bob.pubkey
|
||||
val BOB_PUBKEY get() = bob.publicKey
|
||||
val DUMMY_NOTARY get() = dummyNotary.party
|
||||
val MEGA_CORP get() = megaCorp.party
|
||||
val MEGA_CORP_PUBKEY get() = megaCorp.pubkey
|
||||
val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
|
||||
val MINI_CORP get() = miniCorp.party
|
||||
val MINI_CORP_PUBKEY get() = miniCorp.pubkey
|
||||
val MINI_CORP_PUBKEY get() = miniCorp.publicKey
|
||||
}
|
||||
|
||||
@Rule
|
||||
@ -77,7 +79,7 @@ class ObligationTests {
|
||||
)
|
||||
private val outState = inState.copy(beneficiary = AnonymousParty(BOB_PUBKEY))
|
||||
private val miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock(), miniCorp)
|
||||
private val notaryServices = MockServices(rigorousMock(), MEGA_CORP.name, dummyNotary.key)
|
||||
private val notaryServices = MockServices(emptyList(), rigorousMock(), MEGA_CORP.name, dummyNotary.keyPair)
|
||||
private val identityService = rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
|
||||
doReturn(null).whenever(it).partyFromKey(BOB_PUBKEY)
|
||||
@ -86,7 +88,7 @@ class ObligationTests {
|
||||
doReturn(MINI_CORP).whenever(it).partyFromKey(MINI_CORP_PUBKEY)
|
||||
}
|
||||
private val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), identityService, MEGA_CORP.name)
|
||||
private val ledgerServices get() = MockServices(identityService, MEGA_CORP.name)
|
||||
private val ledgerServices get() = MockServices(emptyList(), identityService, MEGA_CORP.name)
|
||||
private fun cashObligationTestRoots(
|
||||
group: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
|
||||
) = group.apply {
|
||||
|
@ -2,18 +2,16 @@ package net.corda.plugins
|
||||
|
||||
import groovy.lang.Closure
|
||||
import net.corda.cordform.CordformDefinition
|
||||
import net.corda.cordform.CordformNode
|
||||
import org.apache.tools.ant.filters.FixCrLfFilter
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.plugins.JavaPluginConvention
|
||||
import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import java.io.File
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.net.URLClassLoader
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.jar.JarInputStream
|
||||
|
||||
/**
|
||||
@ -118,13 +116,13 @@ open class Cordform : DefaultTask() {
|
||||
}
|
||||
|
||||
/**
|
||||
* The parametersGenerator needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
|
||||
* The NetworkBootstrapper needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
|
||||
*/
|
||||
private fun loadNetworkParamsGenClass(): Class<*> {
|
||||
private fun loadNetworkBootstrapperClass(): Class<*> {
|
||||
val plugin = project.convention.getPlugin(JavaPluginConvention::class.java)
|
||||
val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath
|
||||
val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray()
|
||||
return URLClassLoader(urls, javaClass.classLoader).loadClass("net.corda.nodeapi.internal.NetworkParametersGenerator")
|
||||
return URLClassLoader(urls, javaClass.classLoader).loadClass("net.corda.nodeapi.internal.network.NetworkBootstrapper")
|
||||
}
|
||||
|
||||
/**
|
||||
@ -136,8 +134,7 @@ open class Cordform : DefaultTask() {
|
||||
initializeConfiguration()
|
||||
installRunScript()
|
||||
nodes.forEach(Node::build)
|
||||
generateAndInstallNodeInfos()
|
||||
generateAndInstallNetworkParameters()
|
||||
bootstrapNetwork()
|
||||
}
|
||||
|
||||
private fun initializeConfiguration() {
|
||||
@ -164,14 +161,17 @@ open class Cordform : DefaultTask() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateAndInstallNetworkParameters() {
|
||||
project.logger.info("Generating and installing network parameters")
|
||||
val networkParamsGenClass = loadNetworkParamsGenClass()
|
||||
val nodeDirs = nodes.map(Node::fullPath)
|
||||
val networkParamsGenObject = networkParamsGenClass.newInstance()
|
||||
val runMethod = networkParamsGenClass.getMethod("run", List::class.java).apply { isAccessible = true }
|
||||
// Call NetworkParametersGenerator.run
|
||||
runMethod.invoke(networkParamsGenObject, nodeDirs)
|
||||
private fun bootstrapNetwork() {
|
||||
val networkBootstrapperClass = loadNetworkBootstrapperClass()
|
||||
val networkBootstrapper = networkBootstrapperClass.newInstance()
|
||||
val bootstrapMethod = networkBootstrapperClass.getMethod("bootstrap", Path::class.java).apply { isAccessible = true }
|
||||
// Call NetworkBootstrapper.bootstrap
|
||||
try {
|
||||
val rootDir = project.projectDir.toPath().resolve(directory).toAbsolutePath().normalize()
|
||||
bootstrapMethod.invoke(networkBootstrapper, rootDir)
|
||||
} catch (e: InvocationTargetException) {
|
||||
throw e.cause!!
|
||||
}
|
||||
}
|
||||
|
||||
private fun CordformDefinition.getMatchingCordapps(): List<File> {
|
||||
@ -197,90 +197,4 @@ open class Cordform : DefaultTask() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateAndInstallNodeInfos() {
|
||||
generateNodeInfos()
|
||||
installNodeInfos()
|
||||
}
|
||||
|
||||
private fun generateNodeInfos() {
|
||||
project.logger.info("Generating node infos")
|
||||
val nodeProcesses = buildNodeProcesses()
|
||||
try {
|
||||
validateNodeProcessess(nodeProcesses)
|
||||
} finally {
|
||||
destroyNodeProcesses(nodeProcesses)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildNodeProcesses(): Map<Node, Process> {
|
||||
val command = generateNodeInfoCommand()
|
||||
return nodes.map {
|
||||
it.makeLogDirectory()
|
||||
buildProcess(it, command, "generate-info.log") }.toMap()
|
||||
}
|
||||
|
||||
private fun validateNodeProcessess(nodeProcesses: Map<Node, Process>) {
|
||||
nodeProcesses.forEach { (node, process) ->
|
||||
validateNodeProcess(node, process)
|
||||
}
|
||||
}
|
||||
|
||||
private fun destroyNodeProcesses(nodeProcesses: Map<Node, Process>) {
|
||||
nodeProcesses.forEach { (_, process) ->
|
||||
process.destroyForcibly()
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildProcess(node: Node, command: List<String>, logFile: String): Pair<Node, Process> {
|
||||
val process = ProcessBuilder(command)
|
||||
.directory(node.fullPath().toFile())
|
||||
.redirectErrorStream(true)
|
||||
// InheritIO causes hangs on windows due the gradle buffer also not being flushed.
|
||||
// Must redirect to output or logger (node log is still written, this is just startup banner)
|
||||
.redirectOutput(node.logFile(logFile).toFile())
|
||||
.addEnvironment("CAPSULE_CACHE_DIR", Node.capsuleCacheDir)
|
||||
.start()
|
||||
return Pair(node, process)
|
||||
}
|
||||
|
||||
private fun generateNodeInfoCommand(): List<String> = listOf(
|
||||
"java",
|
||||
"-Dcapsule.log=verbose",
|
||||
"-Dcapsule.dir=${Node.capsuleCacheDir}",
|
||||
"-jar",
|
||||
Node.nodeJarName,
|
||||
"--just-generate-node-info"
|
||||
)
|
||||
|
||||
private fun validateNodeProcess(node: Node, process: Process) {
|
||||
val generateTimeoutSeconds = 60L
|
||||
if (!process.waitFor(generateTimeoutSeconds, TimeUnit.SECONDS)) {
|
||||
throw GradleException("Node took longer $generateTimeoutSeconds seconds than too to generate node info - see node log at ${node.fullPath()}/logs")
|
||||
}
|
||||
if (process.exitValue() != 0) {
|
||||
throw GradleException("Node exited with ${process.exitValue()} when generating node infos - see node log at ${node.fullPath()}/logs")
|
||||
}
|
||||
project.logger.info("Generated node info for ${node.fullPath()}")
|
||||
}
|
||||
|
||||
private fun installNodeInfos() {
|
||||
project.logger.info("Installing node infos")
|
||||
for (source in nodes) {
|
||||
for (destination in nodes) {
|
||||
if (source.nodeDir != destination.nodeDir) {
|
||||
project.copy {
|
||||
it.apply {
|
||||
from(source.fullPath().toString())
|
||||
include("nodeInfo-*")
|
||||
into(destination.fullPath().resolve(CordformNode.NODE_INFO_DIRECTORY).toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Node.logFile(name: String): Path = this.logDirectory().resolve(name)
|
||||
private fun ProcessBuilder.addEnvironment(key: String, value: String) = this.apply { environment().put(key, value) }
|
||||
}
|
||||
|
@ -20,13 +20,8 @@ class Node(private val project: Project) : CordformNode() {
|
||||
@JvmStatic
|
||||
val webJarName = "corda-webserver.jar"
|
||||
private val configFileProperty = "configFile"
|
||||
val capsuleCacheDir: String = "./cache"
|
||||
}
|
||||
|
||||
fun fullPath(): Path = project.projectDir.toPath().resolve(nodeDir.toPath())
|
||||
fun logDirectory(): Path = fullPath().resolve("logs")
|
||||
fun makeLogDirectory() = Files.createDirectories(logDirectory())
|
||||
|
||||
/**
|
||||
* Set the list of CorDapps to install to the plugins directory. Each cordapp is a fully qualified Maven
|
||||
* dependency name, eg: com.example:product-name:0.1
|
||||
|
@ -122,7 +122,7 @@ private class TerminalWindowJavaCommand(jarName: String, dir: File, debugPort: I
|
||||
end tell""")
|
||||
}
|
||||
OS.WINDOWS -> {
|
||||
listOf("cmd", "/C", "start ${command.joinToString(" ")}")
|
||||
listOf("cmd", "/C", "start ${command.joinToString(" ") { windowsSpaceEscape(it) }}")
|
||||
}
|
||||
OS.LINUX -> {
|
||||
// Start shell to keep window open unless java terminated normally or due to SIGTERM:
|
||||
@ -136,12 +136,11 @@ end tell""")
|
||||
})
|
||||
|
||||
private fun unixCommand() = command.map(::quotedFormOf).joinToString(" ")
|
||||
override fun getJavaPath(): String {
|
||||
val path = File(File(System.getProperty("java.home"), "bin"), "java").path
|
||||
// Replace below is to fix an issue with spaces in paths on Windows.
|
||||
// Quoting the entire path does not work, only the space or directory within the path.
|
||||
return if (os == OS.WINDOWS) path.replace(" ", "\" \"") else path
|
||||
}
|
||||
override fun getJavaPath(): String = File(File(System.getProperty("java.home"), "bin"), "java").path
|
||||
|
||||
// Replace below is to fix an issue with spaces in paths on Windows.
|
||||
// Quoting the entire path does not work, only the space or directory within the path.
|
||||
private fun windowsSpaceEscape(s:String) = s.replace(" ", "\" \"")
|
||||
}
|
||||
|
||||
private fun quotedFormOf(text: String) = "'${text.replace("'", "'\\''")}'" // Suitable for UNIX shells.
|
||||
|
@ -29,12 +29,14 @@ class ArtemisTcpTransport {
|
||||
// but we allow classical RSA certificates to work in case:
|
||||
// a) we need to use keytool certificates in some demos,
|
||||
// b) we use cloud providers or HSMs that do not support ECC.
|
||||
private val CIPHER_SUITES = listOf(
|
||||
val CIPHER_SUITES = listOf(
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256"
|
||||
)
|
||||
|
||||
val TLS_VERSIONS = listOf("TLSv1.2")
|
||||
|
||||
fun tcpTransport(
|
||||
direction: ConnectionDirection,
|
||||
hostAndPort: NetworkHostAndPort,
|
||||
@ -68,7 +70,7 @@ class ArtemisTcpTransport {
|
||||
TransportConstants.TRUSTSTORE_PATH_PROP_NAME to config.trustStoreFile,
|
||||
TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME to config.trustStorePassword,
|
||||
TransportConstants.ENABLED_CIPHER_SUITES_PROP_NAME to CIPHER_SUITES.joinToString(","),
|
||||
TransportConstants.ENABLED_PROTOCOLS_PROP_NAME to "TLSv1.2",
|
||||
TransportConstants.ENABLED_PROTOCOLS_PROP_NAME to TLS_VERSIONS.joinToString(","),
|
||||
TransportConstants.NEED_CLIENT_AUTH_PROP_NAME to true,
|
||||
VERIFY_PEER_LEGAL_NAME to (direction as? ConnectionDirection.Outbound)?.expectedCommonNames
|
||||
)
|
||||
|
@ -1,32 +0,0 @@
|
||||
package net.corda.nodeapi.internal
|
||||
|
||||
import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.crypto.entropyToKeyPair
|
||||
import net.corda.core.crypto.sign
|
||||
import net.corda.core.internal.copyTo
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.nodeapi.internal.NetworkParameters
|
||||
import java.math.BigInteger
|
||||
import java.nio.file.FileAlreadyExistsException
|
||||
import java.nio.file.Path
|
||||
|
||||
class NetworkParametersCopier(networkParameters: NetworkParameters) {
|
||||
private companion object {
|
||||
val DUMMY_MAP_KEY = entropyToKeyPair(BigInteger.valueOf(123))
|
||||
}
|
||||
|
||||
private val serializedNetworkParameters = networkParameters.let {
|
||||
val serialize = it.serialize()
|
||||
val signature = DUMMY_MAP_KEY.sign(serialize)
|
||||
SignedData(serialize, signature).serialize()
|
||||
}
|
||||
|
||||
fun install(dir: Path) {
|
||||
try {
|
||||
serializedNetworkParameters.open().copyTo(dir / "network-parameters")
|
||||
} catch (e: FileAlreadyExistsException) {
|
||||
// Leave the file untouched if it already exists
|
||||
}
|
||||
}
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
package net.corda.nodeapi.internal
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.list
|
||||
import net.corda.core.internal.readAll
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
||||
import net.corda.core.serialization.internal._contextSerializationEnv
|
||||
import net.corda.core.utilities.ByteSequence
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
|
||||
import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT
|
||||
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
|
||||
import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
|
||||
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
|
||||
import java.nio.file.Path
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* This class is loaded by Cordform using reflection to generate the network parameters. It is assumed that Cordform has
|
||||
* already asked each node to generate its node info file.
|
||||
*/
|
||||
@Suppress("UNUSED")
|
||||
class NetworkParametersGenerator {
|
||||
companion object {
|
||||
private val logger = contextLogger()
|
||||
}
|
||||
|
||||
fun run(nodesDirs: List<Path>) {
|
||||
logger.info("NetworkParameters generation using node directories: $nodesDirs")
|
||||
try {
|
||||
initialiseSerialization()
|
||||
val notaryInfos = gatherNotaryIdentities(nodesDirs)
|
||||
val copier = NetworkParametersCopier(NetworkParameters(
|
||||
minimumPlatformVersion = 1,
|
||||
notaries = notaryInfos,
|
||||
modifiedTime = Instant.now(),
|
||||
eventHorizon = 10000.days,
|
||||
maxMessageSize = 40000,
|
||||
maxTransactionSize = 40000,
|
||||
epoch = 1
|
||||
))
|
||||
nodesDirs.forEach(copier::install)
|
||||
} finally {
|
||||
_contextSerializationEnv.set(null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun gatherNotaryIdentities(nodesDirs: List<Path>): List<NotaryInfo> {
|
||||
return nodesDirs.mapNotNull { nodeDir ->
|
||||
val nodeConfig = ConfigFactory.parseFile((nodeDir / "node.conf").toFile())
|
||||
if (nodeConfig.hasPath("notary")) {
|
||||
val validating = nodeConfig.getConfig("notary").getBoolean("validating")
|
||||
val nodeInfoFile = nodeDir.list { paths -> paths.filter { it.fileName.toString().startsWith("nodeInfo-") }.findFirst().get() }
|
||||
processFile(nodeInfoFile)?.let { NotaryInfo(it.notaryIdentity(), validating) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.distinct() // We need distinct as nodes part of a distributed notary share the same notary identity
|
||||
}
|
||||
|
||||
private fun NodeInfo.notaryIdentity(): Party {
|
||||
return when (legalIdentities.size) {
|
||||
// Single node notaries have just one identity like all other nodes. This identity is the notary identity
|
||||
1 -> legalIdentities[0]
|
||||
// Nodes which are part of a distributed notary have a second identity which is the composite identity of the
|
||||
// cluster and is shared by all the other members. This is the notary identity.
|
||||
2 -> legalIdentities[1]
|
||||
else -> throw IllegalArgumentException("Not sure how to get the notary identity in this scenerio: $this")
|
||||
}
|
||||
}
|
||||
|
||||
private fun processFile(file: Path): NodeInfo? {
|
||||
return try {
|
||||
logger.info("Reading NodeInfo from file: $file")
|
||||
val signedData = file.readAll().deserialize<SignedData<NodeInfo>>()
|
||||
signedData.verified()
|
||||
} catch (e: Exception) {
|
||||
logger.warn("Exception parsing NodeInfo from file. $file", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
// We need to to set serialization env, because generation of parameters is run from Cordform.
|
||||
// KryoServerSerializationScheme is not accessible from nodeapi.
|
||||
private fun initialiseSerialization() {
|
||||
val context = if (java.lang.Boolean.getBoolean("net.corda.testing.amqp.enable")) AMQP_P2P_CONTEXT else KRYO_P2P_CONTEXT
|
||||
_contextSerializationEnv.set(SerializationEnvironmentImpl(
|
||||
SerializationFactoryImpl().apply {
|
||||
registerScheme(KryoParametersSerializationScheme)
|
||||
registerScheme(AMQPServerSerializationScheme())
|
||||
},
|
||||
context)
|
||||
)
|
||||
}
|
||||
|
||||
private object KryoParametersSerializationScheme : AbstractKryoSerializationScheme() {
|
||||
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean {
|
||||
return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P
|
||||
}
|
||||
override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
|
||||
override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package net.corda.nodeapi.internal
|
||||
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.verify
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.deserialize
|
||||
import java.security.SignatureException
|
||||
|
||||
/**
|
||||
* A signed [NodeInfo] object containing a signature for each identity. The list of signatures is expected
|
||||
* to be in the same order as the identities.
|
||||
*/
|
||||
// TODO Add signatures for composite keys. The current thinking is to make sure there is a signature for each leaf key
|
||||
// that the node owns. This check can only be done by the network map server as it can check with the doorman if a node
|
||||
// is part of a composite identity. This of course further requires the doorman being able to issue CSRs for composite
|
||||
// public keys.
|
||||
@CordaSerializable
|
||||
class SignedNodeInfo(val raw: SerializedBytes<NodeInfo>, val signatures: List<DigitalSignature>) {
|
||||
fun verified(): NodeInfo {
|
||||
val nodeInfo = raw.deserialize()
|
||||
val identities = nodeInfo.legalIdentities.filterNot { it.owningKey is CompositeKey }
|
||||
|
||||
if (identities.size < signatures.size) {
|
||||
throw SignatureException("Extra signatures. Found ${signatures.size} expected ${identities.size}")
|
||||
}
|
||||
if (identities.size > signatures.size) {
|
||||
throw SignatureException("Missing signatures. Found ${signatures.size} expected ${identities.size}")
|
||||
}
|
||||
|
||||
val rawBytes = raw.bytes // To avoid cloning the byte array multiple times
|
||||
identities.zip(signatures).forEach { (identity, signature) ->
|
||||
try {
|
||||
identity.owningKey.verify(rawBytes, signature)
|
||||
} catch (e: SignatureException) {
|
||||
throw SignatureException("$identity: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
return nodeInfo
|
||||
}
|
||||
}
|
@ -0,0 +1,167 @@
|
||||
package net.corda.nodeapi.internal.network
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
||||
import net.corda.core.serialization.internal._contextSerializationEnv
|
||||
import net.corda.core.utilities.ByteSequence
|
||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
|
||||
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
|
||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
|
||||
import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
|
||||
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.TimeUnit.SECONDS
|
||||
import kotlin.streams.toList
|
||||
|
||||
/**
|
||||
* Class to bootstrap a local network of Corda nodes on the same filesystem.
|
||||
*/
|
||||
class NetworkBootstrapper {
|
||||
companion object {
|
||||
// TODO This will probably need to change once we start using a bundled JVM
|
||||
private val nodeInfoGenCmd = listOf(
|
||||
"java",
|
||||
"-jar",
|
||||
"corda.jar",
|
||||
"--just-generate-node-info"
|
||||
)
|
||||
|
||||
private const val LOGS_DIR_NAME = "logs"
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
val arg = args.singleOrNull() ?: throw IllegalArgumentException("Expecting single argument which is the nodes' parent directory")
|
||||
NetworkBootstrapper().bootstrap(Paths.get(arg).toAbsolutePath().normalize())
|
||||
}
|
||||
}
|
||||
|
||||
fun bootstrap(directory: Path) {
|
||||
directory.createDirectories()
|
||||
println("Bootstrapping local network in $directory")
|
||||
val nodeDirs = directory.list { paths -> paths.filter { (it / "corda.jar").exists() }.toList() }
|
||||
require(nodeDirs.isNotEmpty()) { "No nodes found" }
|
||||
println("Nodes found in the following sub-directories: ${nodeDirs.map { it.fileName }}")
|
||||
val processes = startNodeInfoGeneration(nodeDirs)
|
||||
initialiseSerialization()
|
||||
try {
|
||||
println("Waiting for all nodes to generate their node-info files")
|
||||
val nodeInfoFiles = gatherNodeInfoFiles(processes, nodeDirs)
|
||||
println("Distributing all node info-files to all nodes")
|
||||
distributeNodeInfos(nodeDirs, nodeInfoFiles)
|
||||
println("Gathering notary identities")
|
||||
val notaryInfos = gatherNotaryInfos(nodeInfoFiles)
|
||||
println("Notary identities to be used in network-parameters file: ${notaryInfos.joinToString("; ") { it.prettyPrint() }}")
|
||||
installNetworkParameters(notaryInfos, nodeDirs)
|
||||
println("Bootstrapping complete!")
|
||||
} finally {
|
||||
_contextSerializationEnv.set(null)
|
||||
processes.forEach { if (it.isAlive) it.destroyForcibly() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun startNodeInfoGeneration(nodeDirs: List<Path>): List<Process> {
|
||||
return nodeDirs.map { nodeDir ->
|
||||
val logsDir = (nodeDir / LOGS_DIR_NAME).createDirectories()
|
||||
ProcessBuilder(nodeInfoGenCmd)
|
||||
.directory(nodeDir.toFile())
|
||||
.redirectErrorStream(true)
|
||||
.redirectOutput((logsDir / "node-info-gen.log").toFile())
|
||||
.apply { environment()["CAPSULE_CACHE_DIR"] = "../.cache" }
|
||||
.start()
|
||||
}
|
||||
}
|
||||
|
||||
private fun gatherNodeInfoFiles(processes: List<Process>, nodeDirs: List<Path>): List<Path> {
|
||||
val timeOutInSeconds = 60L
|
||||
return processes.zip(nodeDirs).map { (process, nodeDir) ->
|
||||
check(process.waitFor(timeOutInSeconds, SECONDS)) {
|
||||
"Node in ${nodeDir.fileName} took longer than ${timeOutInSeconds}s to generate its node-info - see logs in ${nodeDir / LOGS_DIR_NAME}"
|
||||
}
|
||||
check(process.exitValue() == 0) {
|
||||
"Node in ${nodeDir.fileName} exited with ${process.exitValue()} when generating its node-info - see logs in ${nodeDir / LOGS_DIR_NAME}"
|
||||
}
|
||||
nodeDir.list { paths -> paths.filter { it.fileName.toString().startsWith("nodeInfo-") }.findFirst().get() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun distributeNodeInfos(nodeDirs: List<Path>, nodeInfoFiles: List<Path>) {
|
||||
for (nodeDir in nodeDirs) {
|
||||
val additionalNodeInfosDir = (nodeDir / CordformNode.NODE_INFO_DIRECTORY).createDirectories()
|
||||
for (nodeInfoFile in nodeInfoFiles) {
|
||||
nodeInfoFile.copyToDirectory(additionalNodeInfosDir, StandardCopyOption.REPLACE_EXISTING)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun gatherNotaryInfos(nodeInfoFiles: List<Path>): List<NotaryInfo> {
|
||||
return nodeInfoFiles.mapNotNull { nodeInfoFile ->
|
||||
// The config contains the notary type
|
||||
val nodeConfig = ConfigFactory.parseFile((nodeInfoFile.parent / "node.conf").toFile())
|
||||
if (nodeConfig.hasPath("notary")) {
|
||||
val validating = nodeConfig.getConfig("notary").getBoolean("validating")
|
||||
// And the node-info file contains the notary's identity
|
||||
val nodeInfo = nodeInfoFile.readAll().deserialize<SignedNodeInfo>().verified()
|
||||
NotaryInfo(nodeInfo.notaryIdentity(), validating)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.distinct() // We need distinct as nodes part of a distributed notary share the same notary identity
|
||||
}
|
||||
|
||||
private fun installNetworkParameters(notaryInfos: List<NotaryInfo>, nodeDirs: List<Path>) {
|
||||
// TODO Add config for minimumPlatformVersion, maxMessageSize and maxTransactionSize
|
||||
val copier = NetworkParametersCopier(NetworkParameters(
|
||||
minimumPlatformVersion = 1,
|
||||
notaries = notaryInfos,
|
||||
modifiedTime = Instant.now(),
|
||||
maxMessageSize = 10485760,
|
||||
maxTransactionSize = 40000,
|
||||
epoch = 1
|
||||
), overwriteFile = true)
|
||||
|
||||
nodeDirs.forEach(copier::install)
|
||||
}
|
||||
|
||||
private fun NotaryInfo.prettyPrint(): String = "${identity.name} (${if (validating) "" else "non-"}validating)"
|
||||
|
||||
private fun NodeInfo.notaryIdentity(): Party {
|
||||
return when (legalIdentities.size) {
|
||||
// Single node notaries have just one identity like all other nodes. This identity is the notary identity
|
||||
1 -> legalIdentities[0]
|
||||
// Nodes which are part of a distributed notary have a second identity which is the composite identity of the
|
||||
// cluster and is shared by all the other members. This is the notary identity.
|
||||
2 -> legalIdentities[1]
|
||||
else -> throw IllegalArgumentException("Not sure how to get the notary identity in this scenerio: $this")
|
||||
}
|
||||
}
|
||||
|
||||
// We need to to set serialization env, because generation of parameters is run from Cordform.
|
||||
// KryoServerSerializationScheme is not accessible from nodeapi.
|
||||
private fun initialiseSerialization() {
|
||||
_contextSerializationEnv.set(SerializationEnvironmentImpl(
|
||||
SerializationFactoryImpl().apply {
|
||||
registerScheme(KryoParametersSerializationScheme)
|
||||
registerScheme(AMQPServerSerializationScheme())
|
||||
},
|
||||
AMQP_P2P_CONTEXT)
|
||||
)
|
||||
}
|
||||
|
||||
private object KryoParametersSerializationScheme : AbstractKryoSerializationScheme() {
|
||||
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean {
|
||||
return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P
|
||||
}
|
||||
override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
|
||||
override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
@ -1,19 +1,21 @@
|
||||
package net.corda.nodeapi.internal
|
||||
package net.corda.nodeapi.internal.network
|
||||
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.verify
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import java.security.SignatureException
|
||||
import java.security.cert.CertPathValidatorException
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
// TODO: Need more discussion on rather we should move this class out of internal.
|
||||
const val NETWORK_PARAMS_FILE_NAME = "network-parameters"
|
||||
|
||||
/**
|
||||
* Data class containing hash of [NetworkParameters] and network participant's [NodeInfo] hashes.
|
||||
*/
|
||||
@ -21,21 +23,21 @@ import java.time.Instant
|
||||
data class NetworkMap(val nodeInfoHashes: List<SecureHash>, val networkParameterHash: SecureHash)
|
||||
|
||||
/**
|
||||
* @property minimumPlatformVersion
|
||||
* @property notaries
|
||||
* @property eventHorizon
|
||||
* @property minimumPlatformVersion Minimum version of Corda platform that is required for nodes in the network.
|
||||
* @property notaries List of well known and trusted notary identities with information on validation type.
|
||||
* @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
|
||||
// TODO Add eventHorizon - how many days a node can be offline before being automatically ejected from the network.
|
||||
// It needs separate design.
|
||||
// TODO Currently maxTransactionSize is not wired.
|
||||
@CordaSerializable
|
||||
data class NetworkParameters(
|
||||
val minimumPlatformVersion: Int,
|
||||
val notaries: List<NotaryInfo>,
|
||||
val eventHorizon: Duration,
|
||||
val maxMessageSize: Int,
|
||||
val maxTransactionSize: Int,
|
||||
val modifiedTime: Instant,
|
||||
@ -45,6 +47,8 @@ data class NetworkParameters(
|
||||
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" }
|
||||
require(maxMessageSize > 0) { "maxMessageSize must be at least 1" }
|
||||
require(maxTransactionSize > 0) { "maxTransactionSize must be at least 1" }
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,20 +60,23 @@ data class NotaryInfo(val identity: Party, val validating: Boolean)
|
||||
* contained within.
|
||||
*/
|
||||
@CordaSerializable
|
||||
class SignedNetworkMap(val raw: SerializedBytes<NetworkMap>, val sig: DigitalSignatureWithCert) {
|
||||
class SignedNetworkMap(val raw: SerializedBytes<NetworkMap>, val signature: DigitalSignatureWithCert) {
|
||||
/**
|
||||
* Return the deserialized NetworkMap if the signature and certificate can be verified.
|
||||
*
|
||||
* @throws CertPathValidatorException if the certificate path is invalid.
|
||||
* @throws SignatureException if the signature is invalid.
|
||||
*/
|
||||
@Throws(SignatureException::class)
|
||||
fun verified(): NetworkMap {
|
||||
sig.by.publicKey.verify(raw.bytes, sig)
|
||||
@Throws(SignatureException::class, CertPathValidatorException::class)
|
||||
fun verified(trustedRoot: X509Certificate): NetworkMap {
|
||||
signature.by.publicKey.verify(raw.bytes, signature)
|
||||
// Assume network map cert is under the default trust root.
|
||||
X509Utilities.validateCertificateChain(trustedRoot, signature.by, trustedRoot)
|
||||
return raw.deserialize()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This class should reside in the [DigitalSignature] class.
|
||||
// TODO: Removing the val from signatureBytes causes serialisation issues
|
||||
/** A digital signature that identifies who the public key is owned by, and the certificate which provides prove of the identity */
|
||||
class DigitalSignatureWithCert(val by: X509Certificate, val signatureBytes: ByteArray) : DigitalSignature(signatureBytes)
|
||||
class DigitalSignatureWithCert(val by: X509Certificate, val signatureBytes: ByteArray) : DigitalSignature(signatureBytes)
|
@ -0,0 +1,35 @@
|
||||
package net.corda.nodeapi.internal.network
|
||||
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.crypto.sign
|
||||
import net.corda.core.internal.copyTo
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import java.nio.file.FileAlreadyExistsException
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.security.KeyPair
|
||||
|
||||
class NetworkParametersCopier(
|
||||
networkParameters: NetworkParameters,
|
||||
signingKeyPair: KeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME),
|
||||
overwriteFile: Boolean = false
|
||||
) {
|
||||
private val copyOptions = if (overwriteFile) arrayOf(StandardCopyOption.REPLACE_EXISTING) else emptyArray()
|
||||
private val serializedNetworkParameters = networkParameters.let {
|
||||
val serialize = it.serialize()
|
||||
val signature = signingKeyPair.sign(serialize)
|
||||
SignedData(serialize, signature).serialize()
|
||||
}
|
||||
|
||||
fun install(nodeDir: Path) {
|
||||
try {
|
||||
serializedNetworkParameters.open().copyTo(nodeDir / NETWORK_PARAMS_FILE_NAME, *copyOptions)
|
||||
} catch (e: FileAlreadyExistsException) {
|
||||
// This is only thrown if the file already exists and we didn't specify to overwrite it. In that case we
|
||||
// ignore this exception as we're happy with the existing file.
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package net.corda.nodeapi.internal
|
||||
package net.corda.nodeapi.internal.network
|
||||
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.core.internal.ThreadBox
|
||||
@ -65,7 +65,6 @@ class NodeInfoFilesCopier(scheduler: Scheduler = Schedulers.io()) : AutoCloseabl
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nodeConfig the configuration to be removed.
|
||||
* Remove the configuration of a node which is about to be stopped or already stopped.
|
||||
* No files written by that node will be copied to other nodes, nor files from other nodes will be copied to this
|
||||
* one.
|
@ -1,4 +1,4 @@
|
||||
package net.corda.testing
|
||||
package net.corda.nodeapi
|
||||
|
||||
import java.time.Duration
|
||||
|
@ -14,6 +14,7 @@ import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.node.internal.cordapp.CordappLoader
|
||||
import net.corda.node.internal.cordapp.CordappProviderImpl
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.services.MockAttachmentStorage
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Rule
|
||||
|
@ -19,6 +19,8 @@ import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl
|
||||
import net.corda.nodeapi.internal.serialization.attachmentsClassLoaderEnabledPropertyName
|
||||
import net.corda.nodeapi.internal.serialization.withTokenContext
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.internal.kryoSpecific
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.services.MockAttachmentStorage
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.junit.Assert.*
|
||||
|
@ -0,0 +1,80 @@
|
||||
package net.corda.nodeapi.internal
|
||||
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.testing.ALICE_NAME
|
||||
import net.corda.testing.BOB_NAME
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import net.corda.testing.internal.TestNodeInfoBuilder
|
||||
import net.corda.testing.internal.signWith
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.security.SignatureException
|
||||
|
||||
class SignedNodeInfoTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
private val nodeInfoBuilder = TestNodeInfoBuilder()
|
||||
|
||||
@Test
|
||||
fun `verifying single identity`() {
|
||||
nodeInfoBuilder.addIdentity(ALICE_NAME)
|
||||
val (nodeInfo, signedNodeInfo) = nodeInfoBuilder.buildWithSigned()
|
||||
assertThat(signedNodeInfo.verified()).isEqualTo(nodeInfo)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verifying multiple identities`() {
|
||||
nodeInfoBuilder.addIdentity(ALICE_NAME)
|
||||
nodeInfoBuilder.addIdentity(BOB_NAME)
|
||||
val (nodeInfo, signedNodeInfo) = nodeInfoBuilder.buildWithSigned()
|
||||
assertThat(signedNodeInfo.verified()).isEqualTo(nodeInfo)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verifying missing signature`() {
|
||||
val (_, aliceKey) = nodeInfoBuilder.addIdentity(ALICE_NAME)
|
||||
nodeInfoBuilder.addIdentity(BOB_NAME)
|
||||
val nodeInfo = nodeInfoBuilder.build()
|
||||
val signedNodeInfo = nodeInfo.signWith(listOf(aliceKey))
|
||||
assertThatThrownBy { signedNodeInfo.verified() }
|
||||
.isInstanceOf(SignatureException::class.java)
|
||||
.hasMessageContaining("Missing signatures")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verifying extra signature`() {
|
||||
val (_, aliceKey) = nodeInfoBuilder.addIdentity(ALICE_NAME)
|
||||
val nodeInfo = nodeInfoBuilder.build()
|
||||
val signedNodeInfo = nodeInfo.signWith(listOf(aliceKey, generateKeyPair().private))
|
||||
assertThatThrownBy { signedNodeInfo.verified() }
|
||||
.isInstanceOf(SignatureException::class.java)
|
||||
.hasMessageContaining("Extra signatures")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verifying incorrect signature`() {
|
||||
nodeInfoBuilder.addIdentity(ALICE_NAME)
|
||||
val nodeInfo = nodeInfoBuilder.build()
|
||||
val signedNodeInfo = nodeInfo.signWith(listOf(generateKeyPair().private))
|
||||
assertThatThrownBy { signedNodeInfo.verified() }
|
||||
.isInstanceOf(SignatureException::class.java)
|
||||
.hasMessageContaining(ALICE_NAME.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verifying with signatures in wrong order`() {
|
||||
val (_, aliceKey) = nodeInfoBuilder.addIdentity(ALICE_NAME)
|
||||
val (_, bobKey) = nodeInfoBuilder.addIdentity(BOB_NAME)
|
||||
val nodeInfo = nodeInfoBuilder.build()
|
||||
val signedNodeInfo = nodeInfo.signWith(listOf(bobKey, aliceKey))
|
||||
assertThatThrownBy { signedNodeInfo.verified() }
|
||||
.isInstanceOf(SignatureException::class.java)
|
||||
.hasMessageContaining(ALICE_NAME.toString())
|
||||
}
|
||||
|
||||
private fun generateKeyPair() = Crypto.generateKeyPair()
|
||||
}
|
@ -51,7 +51,7 @@ class X509UtilitiesTest {
|
||||
val bob = TestIdentity(BOB_NAME, 80)
|
||||
val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).party
|
||||
val BOB get() = bob.party
|
||||
val BOB_PUBKEY get() = bob.pubkey
|
||||
val BOB_PUBKEY get() = bob.publicKey
|
||||
}
|
||||
|
||||
@Rule
|
||||
|
@ -1,8 +1,7 @@
|
||||
package net.corda.nodeapi
|
||||
package net.corda.nodeapi.internal.network
|
||||
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.nodeapi.internal.NodeInfoFilesCopier
|
||||
import net.corda.testing.eventually
|
||||
import net.corda.nodeapi.eventually
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@ -15,14 +14,7 @@ import java.util.concurrent.TimeUnit
|
||||
import kotlin.streams.toList
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/**
|
||||
* tests for [NodeInfoFilesCopier]
|
||||
*/
|
||||
class NodeInfoFilesCopierTest {
|
||||
|
||||
@Rule @JvmField var folder = TemporaryFolder()
|
||||
private val rootPath get() = folder.root.toPath()
|
||||
private val scheduler = TestScheduler()
|
||||
companion object {
|
||||
private const val ORGANIZATION = "Organization"
|
||||
private const val NODE_1_PATH = "node1"
|
||||
@ -34,6 +26,13 @@ class NodeInfoFilesCopierTest {
|
||||
private val BAD_NODE_INFO_NAME = "something"
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val folder = TemporaryFolder()
|
||||
|
||||
private val rootPath get() = folder.root.toPath()
|
||||
private val scheduler = TestScheduler()
|
||||
|
||||
private fun nodeDir(nodeBaseDir : String) = rootPath.resolve(nodeBaseDir).resolve(ORGANIZATION.toLowerCase())
|
||||
|
||||
private val node1RootPath by lazy { nodeDir(NODE_1_PATH) }
|
||||
@ -41,7 +40,7 @@ class NodeInfoFilesCopierTest {
|
||||
private val node1AdditionalNodeInfoPath by lazy { node1RootPath.resolve(CordformNode.NODE_INFO_DIRECTORY) }
|
||||
private val node2AdditionalNodeInfoPath by lazy { node2RootPath.resolve(CordformNode.NODE_INFO_DIRECTORY) }
|
||||
|
||||
lateinit var nodeInfoFilesCopier: NodeInfoFilesCopier
|
||||
private lateinit var nodeInfoFilesCopier: NodeInfoFilesCopier
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
@ -5,6 +5,7 @@ import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.node.MockServices
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
@ -23,7 +24,7 @@ class ContractAttachmentSerializerTest {
|
||||
private lateinit var factory: SerializationFactory
|
||||
private lateinit var context: SerializationContext
|
||||
private lateinit var contextWithToken: SerializationContext
|
||||
private val mockServices = MockServices(rigorousMock(), CordaX500Name("MegaCorp", "London", "GB"))
|
||||
private val mockServices = MockServices(emptyList(), rigorousMock(), CordaX500Name("MegaCorp", "London", "GB"))
|
||||
@Before
|
||||
fun setup() {
|
||||
factory = testSerialization.env.serializationFactory
|
||||
|
@ -13,7 +13,7 @@ import net.corda.nodeapi.internal.AttachmentsClassLoaderTests
|
||||
import net.corda.nodeapi.internal.serialization.kryo.CordaKryo
|
||||
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
|
||||
import net.corda.testing.services.MockAttachmentStorage
|
||||
import net.corda.testing.rigorousMock
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.ExpectedException
|
||||
|
@ -33,7 +33,7 @@ import kotlin.test.assertTrue
|
||||
|
||||
class KryoTests {
|
||||
companion object {
|
||||
private val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).pubkey
|
||||
private val ALICE_PUBKEY = TestIdentity(ALICE_NAME, 70).publicKey
|
||||
}
|
||||
|
||||
@Rule
|
||||
|
@ -8,8 +8,8 @@ import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput
|
||||
import net.corda.nodeapi.internal.serialization.amqp.Envelope
|
||||
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
|
||||
import net.corda.testing.amqpSpecific
|
||||
import net.corda.testing.kryoSpecific
|
||||
import net.corda.testing.internal.amqpSpecific
|
||||
import net.corda.testing.internal.kryoSpecific
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
|
@ -9,8 +9,8 @@ import net.corda.core.serialization.serialize
|
||||
import net.corda.node.services.statemachine.DataSessionMessage
|
||||
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import net.corda.testing.amqpSpecific
|
||||
import net.corda.testing.kryoSpecific
|
||||
import net.corda.testing.internal.amqpSpecific
|
||||
import net.corda.testing.internal.kryoSpecific
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Rule
|
||||
|
@ -8,7 +8,7 @@ import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.nodeapi.internal.serialization.kryo.CordaKryo
|
||||
import net.corda.nodeapi.internal.serialization.kryo.DefaultKryoCustomizer
|
||||
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
|
||||
import net.corda.testing.rigorousMock
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Before
|
||||
|
@ -6,8 +6,8 @@ import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.node.services.statemachine.DataSessionMessage
|
||||
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
|
||||
import net.corda.testing.internal.kryoSpecific
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import net.corda.testing.kryoSpecific
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
|
@ -29,6 +29,7 @@ import org.apache.qpid.proton.codec.EncoderImpl
|
||||
import org.assertj.core.api.Assertions.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Ignore
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
@ -49,11 +50,15 @@ class SerializationOutputTests {
|
||||
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
|
||||
val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
|
||||
val MEGA_CORP get() = megaCorp.party
|
||||
val MEGA_CORP_PUBKEY get() = megaCorp.pubkey
|
||||
val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
|
||||
val MINI_CORP get() = miniCorp.party
|
||||
val MINI_CORP_PUBKEY get() = miniCorp.pubkey
|
||||
val MINI_CORP_PUBKEY get() = miniCorp.publicKey
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
data class Foo(val bar: String, val pub: Int)
|
||||
|
||||
data class testFloat(val f: Float)
|
||||
@ -473,9 +478,9 @@ class SerializationOutputTests {
|
||||
assertSerializedThrowableEquivalent(t, desThrowable)
|
||||
}
|
||||
|
||||
private fun serdesThrowableWithInternalInfo(t: Throwable, factory: SerializerFactory, factory2: SerializerFactory, expectedEqual: Boolean = true): Throwable = withTestSerialization {
|
||||
private fun serdesThrowableWithInternalInfo(t: Throwable, factory: SerializerFactory, factory2: SerializerFactory, expectedEqual: Boolean = true): Throwable {
|
||||
val newContext = SerializationFactory.defaultFactory.defaultContext.withProperty(CommonPropertyNames.IncludeInternalInfo, true)
|
||||
SerializationFactory.defaultFactory.asCurrent { withCurrentContext(newContext) { serdes(t, factory, factory2, expectedEqual) } }
|
||||
return SerializationFactory.defaultFactory.asCurrent { withCurrentContext(newContext) { serdes(t, factory, factory2, expectedEqual) } }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -0,0 +1,242 @@
|
||||
package net.corda.node.amqp
|
||||
|
||||
import com.nhaarman.mockito_kotlin.any
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.NetworkMapCache
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.toBase58String
|
||||
import net.corda.node.internal.protonwrapper.netty.AMQPServer
|
||||
import net.corda.node.internal.security.RPCSecurityManager
|
||||
import net.corda.node.services.config.*
|
||||
import net.corda.node.services.messaging.ArtemisMessagingClient
|
||||
import net.corda.node.services.messaging.ArtemisMessagingServer
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_QUEUE
|
||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID
|
||||
import org.apache.activemq.artemis.api.core.RoutingType
|
||||
import org.apache.activemq.artemis.api.core.SimpleString
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import rx.Observable
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotEquals
|
||||
|
||||
class AMQPBridgeTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val temporaryFolder = TemporaryFolder()
|
||||
|
||||
private val ALICE = TestIdentity(ALICE_NAME)
|
||||
private val BOB = TestIdentity(BOB_NAME)
|
||||
|
||||
private val artemisPort = freePort()
|
||||
private val artemisPort2 = freePort()
|
||||
private val amqpPort = freePort()
|
||||
private val artemisAddress = NetworkHostAndPort("localhost", artemisPort)
|
||||
private val artemisAddress2 = NetworkHostAndPort("localhost", artemisPort2)
|
||||
private val amqpAddress = NetworkHostAndPort("localhost", amqpPort)
|
||||
|
||||
private abstract class AbstractNodeConfiguration : NodeConfiguration
|
||||
|
||||
@Test
|
||||
fun `test acked and nacked messages`() {
|
||||
// Create local queue
|
||||
val sourceQueueName = "internal.peers." + BOB.publicKey.toBase58String()
|
||||
val (artemisServer, artemisClient) = createArtemis(sourceQueueName)
|
||||
|
||||
// Pre-populate local queue with 3 messages
|
||||
val artemis = artemisClient.started!!
|
||||
for (i in 0 until 3) {
|
||||
val artemisMessage = artemis.session.createMessage(true).apply {
|
||||
putIntProperty("CountProp", i)
|
||||
writeBodyBufferBytes("Test$i".toByteArray())
|
||||
// Use the magic deduplication property built into Artemis as our message identity too
|
||||
putStringProperty(HDR_DUPLICATE_DETECTION_ID, SimpleString(UUID.randomUUID().toString()))
|
||||
}
|
||||
artemis.producer.send(sourceQueueName, artemisMessage)
|
||||
}
|
||||
|
||||
//Create target server
|
||||
val amqpServer = createAMQPServer()
|
||||
|
||||
val receive = amqpServer.onReceive.toBlocking().iterator
|
||||
amqpServer.start()
|
||||
|
||||
val received1 = receive.next()
|
||||
val messageID1 = received1.applicationProperties["CountProp"] as Int
|
||||
assertArrayEquals("Test$messageID1".toByteArray(), received1.payload)
|
||||
assertEquals(0, messageID1)
|
||||
received1.complete(true) // Accept first message
|
||||
|
||||
val received2 = receive.next()
|
||||
val messageID2 = received2.applicationProperties["CountProp"] as Int
|
||||
assertArrayEquals("Test$messageID2".toByteArray(), received2.payload)
|
||||
assertEquals(1, messageID2)
|
||||
received2.complete(false) // Reject message
|
||||
|
||||
while (true) {
|
||||
val received3 = receive.next()
|
||||
val messageID3 = received3.applicationProperties["CountProp"] as Int
|
||||
assertArrayEquals("Test$messageID3".toByteArray(), received3.payload)
|
||||
assertNotEquals(0, messageID3)
|
||||
if (messageID3 != 1) { // keep rejecting any batched items following rejection
|
||||
received3.complete(false)
|
||||
} else { // beginnings of replay so accept again
|
||||
received3.complete(true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
val received4 = receive.next()
|
||||
val messageID4 = received4.applicationProperties["CountProp"] as Int
|
||||
assertArrayEquals("Test$messageID4".toByteArray(), received4.payload)
|
||||
if (messageID4 != 1) { // we may get a duplicate of the rejected message, in which case skip
|
||||
assertEquals(2, messageID4) // next message should be in order though
|
||||
break
|
||||
}
|
||||
received4.complete(true)
|
||||
}
|
||||
|
||||
// Send a fresh item and check receive
|
||||
val artemisMessage = artemis.session.createMessage(true).apply {
|
||||
putIntProperty("CountProp", -1)
|
||||
writeBodyBufferBytes("Test_end".toByteArray())
|
||||
// Use the magic deduplication property built into Artemis as our message identity too
|
||||
putStringProperty(HDR_DUPLICATE_DETECTION_ID, SimpleString(UUID.randomUUID().toString()))
|
||||
}
|
||||
artemis.producer.send(sourceQueueName, artemisMessage)
|
||||
|
||||
val received5 = receive.next()
|
||||
val messageID5 = received5.applicationProperties["CountProp"] as Int
|
||||
assertArrayEquals("Test_end".toByteArray(), received5.payload)
|
||||
assertEquals(-1, messageID5) // next message should be in order
|
||||
received5.complete(true)
|
||||
|
||||
amqpServer.stop()
|
||||
artemisClient.stop()
|
||||
artemisServer.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Test legacy bridge still works`() {
|
||||
// Create local queue
|
||||
val sourceQueueName = "internal.peers." + ALICE.publicKey.toBase58String()
|
||||
val (artemisLegacyServer, artemisLegacyClient) = createLegacyArtemis(sourceQueueName)
|
||||
|
||||
|
||||
val (artemisServer, artemisClient) = createArtemis(null)
|
||||
|
||||
val artemis = artemisLegacyClient.started!!
|
||||
for (i in 0 until 3) {
|
||||
val artemisMessage = artemis.session.createMessage(true).apply {
|
||||
putIntProperty("CountProp", i)
|
||||
writeBodyBufferBytes("Test$i".toByteArray())
|
||||
// Use the magic deduplication property built into Artemis as our message identity too
|
||||
putStringProperty(HDR_DUPLICATE_DETECTION_ID, SimpleString(UUID.randomUUID().toString()))
|
||||
}
|
||||
artemis.producer.send(sourceQueueName, artemisMessage)
|
||||
}
|
||||
|
||||
|
||||
val subs = artemisClient.started!!.session.createConsumer(P2P_QUEUE)
|
||||
for (i in 0 until 3) {
|
||||
val msg = subs.receive()
|
||||
val messageBody = ByteArray(msg.bodySize).apply { msg.bodyBuffer.readBytes(this) }
|
||||
assertArrayEquals("Test$i".toByteArray(), messageBody)
|
||||
assertEquals(i, msg.getIntProperty("CountProp"))
|
||||
}
|
||||
|
||||
artemisClient.stop()
|
||||
artemisServer.stop()
|
||||
artemisLegacyClient.stop()
|
||||
artemisLegacyServer.stop()
|
||||
|
||||
}
|
||||
|
||||
private fun createArtemis(sourceQueueName: String?): Pair<ArtemisMessagingServer, ArtemisMessagingClient> {
|
||||
val artemisConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
||||
doReturn(temporaryFolder.root.toPath() / "artemis").whenever(it).baseDirectory
|
||||
doReturn(ALICE_NAME).whenever(it).myLegalName
|
||||
doReturn("trustpass").whenever(it).trustStorePassword
|
||||
doReturn("cordacadevpass").whenever(it).keyStorePassword
|
||||
doReturn("").whenever(it).exportJMXto
|
||||
doReturn(emptyList<CertChainPolicyConfig>()).whenever(it).certificateChainCheckPolicies
|
||||
doReturn(true).whenever(it).useAMQPBridges
|
||||
}
|
||||
artemisConfig.configureWithDevSSLCertificate()
|
||||
val networkMap = rigorousMock<NetworkMapCache>().also {
|
||||
doReturn(Observable.never<NetworkMapCache.MapChange>()).whenever(it).changed
|
||||
doReturn(listOf(NodeInfo(listOf(amqpAddress), listOf(BOB.identity), 1, 1L))).whenever(it).getNodesByLegalIdentityKey(any())
|
||||
}
|
||||
val userService = rigorousMock<RPCSecurityManager>()
|
||||
val artemisServer = ArtemisMessagingServer(artemisConfig, artemisPort, null, networkMap, userService, MAX_MESSAGE_SIZE)
|
||||
val artemisClient = ArtemisMessagingClient(artemisConfig, artemisAddress, MAX_MESSAGE_SIZE)
|
||||
artemisServer.start()
|
||||
artemisClient.start()
|
||||
val artemis = artemisClient.started!!
|
||||
if (sourceQueueName != null) {
|
||||
// Local queue for outgoing messages
|
||||
artemis.session.createQueue(sourceQueueName, RoutingType.MULTICAST, sourceQueueName, true)
|
||||
}
|
||||
return Pair(artemisServer, artemisClient)
|
||||
}
|
||||
|
||||
private fun createLegacyArtemis(sourceQueueName: String): Pair<ArtemisMessagingServer, ArtemisMessagingClient> {
|
||||
val artemisConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
||||
doReturn(temporaryFolder.root.toPath() / "artemis2").whenever(it).baseDirectory
|
||||
doReturn(BOB_NAME).whenever(it).myLegalName
|
||||
doReturn("trustpass").whenever(it).trustStorePassword
|
||||
doReturn("cordacadevpass").whenever(it).keyStorePassword
|
||||
doReturn("").whenever(it).exportJMXto
|
||||
doReturn(emptyList<CertChainPolicyConfig>()).whenever(it).certificateChainCheckPolicies
|
||||
doReturn(false).whenever(it).useAMQPBridges
|
||||
doReturn(ActiveMqServerConfiguration(BridgeConfiguration(0, 0, 0.0))).whenever(it).activeMQServer
|
||||
}
|
||||
artemisConfig.configureWithDevSSLCertificate()
|
||||
val networkMap = rigorousMock<NetworkMapCache>().also {
|
||||
doReturn(Observable.never<NetworkMapCache.MapChange>()).whenever(it).changed
|
||||
doReturn(listOf(NodeInfo(listOf(artemisAddress), listOf(ALICE.identity), 1, 1L))).whenever(it).getNodesByLegalIdentityKey(any())
|
||||
}
|
||||
val userService = rigorousMock<RPCSecurityManager>()
|
||||
val artemisServer = ArtemisMessagingServer(artemisConfig, artemisPort2, null, networkMap, userService, MAX_MESSAGE_SIZE)
|
||||
val artemisClient = ArtemisMessagingClient(artemisConfig, artemisAddress2, MAX_MESSAGE_SIZE)
|
||||
artemisServer.start()
|
||||
artemisClient.start()
|
||||
val artemis = artemisClient.started!!
|
||||
// Local queue for outgoing messages
|
||||
artemis.session.createQueue(sourceQueueName, RoutingType.MULTICAST, sourceQueueName, true)
|
||||
return Pair(artemisServer, artemisClient)
|
||||
}
|
||||
|
||||
private fun createAMQPServer(): AMQPServer {
|
||||
val serverConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
||||
doReturn(temporaryFolder.root.toPath() / "server").whenever(it).baseDirectory
|
||||
doReturn(BOB_NAME).whenever(it).myLegalName
|
||||
doReturn("trustpass").whenever(it).trustStorePassword
|
||||
doReturn("cordacadevpass").whenever(it).keyStorePassword
|
||||
}
|
||||
serverConfig.configureWithDevSSLCertificate()
|
||||
|
||||
val serverTruststore = loadKeyStore(serverConfig.trustStoreFile, serverConfig.trustStorePassword)
|
||||
val serverKeystore = loadKeyStore(serverConfig.sslKeystore, serverConfig.keyStorePassword)
|
||||
val amqpServer = AMQPServer("0.0.0.0",
|
||||
amqpPort,
|
||||
ArtemisMessagingComponent.PEER_USER,
|
||||
ArtemisMessagingComponent.PEER_USER,
|
||||
serverKeystore,
|
||||
serverConfig.keyStorePassword,
|
||||
serverTruststore,
|
||||
trace = true)
|
||||
return amqpServer
|
||||
}
|
||||
}
|
@ -0,0 +1,309 @@
|
||||
package net.corda.node.amqp
|
||||
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import io.netty.channel.EventLoopGroup
|
||||
import io.netty.channel.nio.NioEventLoopGroup
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.node.services.NetworkMapCache
|
||||
import net.corda.core.toFuture
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.node.internal.protonwrapper.messages.MessageStatus
|
||||
import net.corda.node.internal.protonwrapper.netty.AMQPClient
|
||||
import net.corda.node.internal.protonwrapper.netty.AMQPServer
|
||||
import net.corda.node.internal.security.RPCSecurityManager
|
||||
import net.corda.node.services.config.CertChainPolicyConfig
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.config.configureWithDevSSLCertificate
|
||||
import net.corda.node.services.messaging.ArtemisMessagingClient
|
||||
import net.corda.node.services.messaging.ArtemisMessagingServer
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import org.apache.activemq.artemis.api.core.RoutingType
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import rx.Observable.never
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class ProtonWrapperTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val temporaryFolder = TemporaryFolder()
|
||||
|
||||
private val serverPort = freePort()
|
||||
private val serverPort2 = freePort()
|
||||
private val artemisPort = freePort()
|
||||
|
||||
private abstract class AbstractNodeConfiguration : NodeConfiguration
|
||||
|
||||
@Test
|
||||
fun `Simple AMPQ Client to Server`() {
|
||||
val amqpServer = createServer(serverPort)
|
||||
amqpServer.use {
|
||||
amqpServer.start()
|
||||
val receiveSubs = amqpServer.onReceive.subscribe {
|
||||
assertEquals(BOB_NAME.toString(), it.sourceLegalName)
|
||||
assertEquals("p2p.inbound", it.topic)
|
||||
assertEquals("Test", String(it.payload))
|
||||
it.complete(true)
|
||||
}
|
||||
val amqpClient = createClient()
|
||||
amqpClient.use {
|
||||
val serverConnected = amqpServer.onConnection.toFuture()
|
||||
val clientConnected = amqpClient.onConnection.toFuture()
|
||||
amqpClient.start()
|
||||
val serverConnect = serverConnected.get()
|
||||
assertEquals(true, serverConnect.connected)
|
||||
assertEquals(BOB_NAME, CordaX500Name.parse(serverConnect.remoteCert!!.subject.toString()))
|
||||
val clientConnect = clientConnected.get()
|
||||
assertEquals(true, clientConnect.connected)
|
||||
assertEquals(ALICE_NAME, CordaX500Name.parse(clientConnect.remoteCert!!.subject.toString()))
|
||||
val msg = amqpClient.createMessage("Test".toByteArray(),
|
||||
"p2p.inbound",
|
||||
ALICE_NAME.toString(),
|
||||
emptyMap())
|
||||
amqpClient.write(msg)
|
||||
assertEquals(MessageStatus.Acknowledged, msg.onComplete.get())
|
||||
receiveSubs.unsubscribe()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AMPQ Client refuses to connect to unexpected server`() {
|
||||
val amqpServer = createServer(serverPort, CordaX500Name("Rogue 1", "London", "GB"))
|
||||
amqpServer.use {
|
||||
amqpServer.start()
|
||||
val amqpClient = createClient()
|
||||
amqpClient.use {
|
||||
val clientConnected = amqpClient.onConnection.toFuture()
|
||||
amqpClient.start()
|
||||
val clientConnect = clientConnected.get()
|
||||
assertEquals(false, clientConnect.connected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Client Failover for multiple IP`() {
|
||||
val amqpServer = createServer(serverPort)
|
||||
val amqpServer2 = createServer(serverPort2)
|
||||
val amqpClient = createClient()
|
||||
try {
|
||||
val serverConnected = amqpServer.onConnection.toFuture()
|
||||
val serverConnected2 = amqpServer2.onConnection.toFuture()
|
||||
val clientConnected = amqpClient.onConnection.toBlocking().iterator
|
||||
amqpServer.start()
|
||||
amqpClient.start()
|
||||
val serverConn1 = serverConnected.get()
|
||||
assertEquals(true, serverConn1.connected)
|
||||
assertEquals(BOB_NAME, CordaX500Name.parse(serverConn1.remoteCert!!.subject.toString()))
|
||||
val connState1 = clientConnected.next()
|
||||
assertEquals(true, connState1.connected)
|
||||
assertEquals(ALICE_NAME, CordaX500Name.parse(connState1.remoteCert!!.subject.toString()))
|
||||
assertEquals(serverPort, connState1.remoteAddress.port)
|
||||
|
||||
// Fail over
|
||||
amqpServer2.start()
|
||||
amqpServer.stop()
|
||||
val connState2 = clientConnected.next()
|
||||
assertEquals(false, connState2.connected)
|
||||
assertEquals(serverPort, connState2.remoteAddress.port)
|
||||
val serverConn2 = serverConnected2.get()
|
||||
assertEquals(true, serverConn2.connected)
|
||||
assertEquals(BOB_NAME, CordaX500Name.parse(serverConn2.remoteCert!!.subject.toString()))
|
||||
val connState3 = clientConnected.next()
|
||||
assertEquals(true, connState3.connected)
|
||||
assertEquals(ALICE_NAME, CordaX500Name.parse(connState3.remoteCert!!.subject.toString()))
|
||||
assertEquals(serverPort2, connState3.remoteAddress.port)
|
||||
|
||||
// Fail back
|
||||
amqpServer.start()
|
||||
amqpServer2.stop()
|
||||
val connState4 = clientConnected.next()
|
||||
assertEquals(false, connState4.connected)
|
||||
assertEquals(serverPort2, connState4.remoteAddress.port)
|
||||
val serverConn3 = serverConnected.get()
|
||||
assertEquals(true, serverConn3.connected)
|
||||
assertEquals(BOB_NAME, CordaX500Name.parse(serverConn3.remoteCert!!.subject.toString()))
|
||||
val connState5 = clientConnected.next()
|
||||
assertEquals(true, connState5.connected)
|
||||
assertEquals(ALICE_NAME, CordaX500Name.parse(connState5.remoteCert!!.subject.toString()))
|
||||
assertEquals(serverPort, connState5.remoteAddress.port)
|
||||
} finally {
|
||||
amqpClient.close()
|
||||
amqpServer.close()
|
||||
amqpServer2.close()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Send a message from AMQP to Artemis inbox`() {
|
||||
val (server, artemisClient) = createArtemisServerAndClient()
|
||||
val amqpClient = createClient()
|
||||
val clientConnected = amqpClient.onConnection.toFuture()
|
||||
amqpClient.start()
|
||||
assertEquals(true, clientConnected.get().connected)
|
||||
assertEquals(CHARLIE_NAME, CordaX500Name.parse(clientConnected.get().remoteCert!!.subject.toString()))
|
||||
val artemis = artemisClient.started!!
|
||||
val sendAddress = "p2p.inbound"
|
||||
artemis.session.createQueue(sendAddress, RoutingType.MULTICAST, "queue", true)
|
||||
val consumer = artemis.session.createConsumer("queue")
|
||||
val testData = "Test".toByteArray()
|
||||
val testProperty = mutableMapOf<Any?, Any?>()
|
||||
testProperty["TestProp"] = "1"
|
||||
val message = amqpClient.createMessage(testData, sendAddress, CHARLIE_NAME.toString(), testProperty)
|
||||
amqpClient.write(message)
|
||||
assertEquals(MessageStatus.Acknowledged, message.onComplete.get())
|
||||
val received = consumer.receive()
|
||||
assertEquals("1", received.getStringProperty("TestProp"))
|
||||
assertArrayEquals(testData, ByteArray(received.bodySize).apply { received.bodyBuffer.readBytes(this) })
|
||||
amqpClient.stop()
|
||||
artemisClient.stop()
|
||||
server.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `shared AMQPClient threadpool tests`() {
|
||||
val amqpServer = createServer(serverPort)
|
||||
amqpServer.use {
|
||||
val connectionEvents = amqpServer.onConnection.toBlocking().iterator
|
||||
amqpServer.start()
|
||||
val sharedThreads = NioEventLoopGroup()
|
||||
val amqpClient1 = createSharedThreadsClient(sharedThreads, 0)
|
||||
val amqpClient2 = createSharedThreadsClient(sharedThreads, 1)
|
||||
amqpClient1.start()
|
||||
val connection1 = connectionEvents.next()
|
||||
assertEquals(true, connection1.connected)
|
||||
val connection1ID = CordaX500Name.parse(connection1.remoteCert!!.subject.toString())
|
||||
assertEquals("client 0", connection1ID.organisationUnit)
|
||||
val source1 = connection1.remoteAddress
|
||||
amqpClient2.start()
|
||||
val connection2 = connectionEvents.next()
|
||||
assertEquals(true, connection2.connected)
|
||||
val connection2ID = CordaX500Name.parse(connection2.remoteCert!!.subject.toString())
|
||||
assertEquals("client 1", connection2ID.organisationUnit)
|
||||
val source2 = connection2.remoteAddress
|
||||
// Stopping one shouldn't disconnect the other
|
||||
amqpClient1.stop()
|
||||
val connection3 = connectionEvents.next()
|
||||
assertEquals(false, connection3.connected)
|
||||
assertEquals(source1, connection3.remoteAddress)
|
||||
assertEquals(false, amqpClient1.connected)
|
||||
assertEquals(true, amqpClient2.connected)
|
||||
// Now shutdown both
|
||||
amqpClient2.stop()
|
||||
val connection4 = connectionEvents.next()
|
||||
assertEquals(false, connection4.connected)
|
||||
assertEquals(source2, connection4.remoteAddress)
|
||||
assertEquals(false, amqpClient1.connected)
|
||||
assertEquals(false, amqpClient2.connected)
|
||||
// Now restarting one should work
|
||||
amqpClient1.start()
|
||||
val connection5 = connectionEvents.next()
|
||||
assertEquals(true, connection5.connected)
|
||||
val connection5ID = CordaX500Name.parse(connection5.remoteCert!!.subject.toString())
|
||||
assertEquals("client 0", connection5ID.organisationUnit)
|
||||
assertEquals(true, amqpClient1.connected)
|
||||
assertEquals(false, amqpClient2.connected)
|
||||
// Cleanup
|
||||
amqpClient1.stop()
|
||||
sharedThreads.shutdownGracefully()
|
||||
sharedThreads.terminationFuture().sync()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createArtemisServerAndClient(): Pair<ArtemisMessagingServer, ArtemisMessagingClient> {
|
||||
val artemisConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
||||
doReturn(temporaryFolder.root.toPath() / "artemis").whenever(it).baseDirectory
|
||||
doReturn(CHARLIE_NAME).whenever(it).myLegalName
|
||||
doReturn("trustpass").whenever(it).trustStorePassword
|
||||
doReturn("cordacadevpass").whenever(it).keyStorePassword
|
||||
doReturn("").whenever(it).exportJMXto
|
||||
doReturn(emptyList<CertChainPolicyConfig>()).whenever(it).certificateChainCheckPolicies
|
||||
doReturn(true).whenever(it).useAMQPBridges
|
||||
}
|
||||
artemisConfig.configureWithDevSSLCertificate()
|
||||
|
||||
val networkMap = rigorousMock<NetworkMapCache>().also {
|
||||
doReturn(never<NetworkMapCache.MapChange>()).whenever(it).changed
|
||||
}
|
||||
val userService = rigorousMock<RPCSecurityManager>()
|
||||
val server = ArtemisMessagingServer(artemisConfig, artemisPort, null, networkMap, userService, MAX_MESSAGE_SIZE)
|
||||
val client = ArtemisMessagingClient(artemisConfig, NetworkHostAndPort("localhost", artemisPort), MAX_MESSAGE_SIZE)
|
||||
server.start()
|
||||
client.start()
|
||||
return Pair(server, client)
|
||||
}
|
||||
|
||||
private fun createClient(): AMQPClient {
|
||||
val clientConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
||||
doReturn(temporaryFolder.root.toPath() / "client").whenever(it).baseDirectory
|
||||
doReturn(BOB_NAME).whenever(it).myLegalName
|
||||
doReturn("trustpass").whenever(it).trustStorePassword
|
||||
doReturn("cordacadevpass").whenever(it).keyStorePassword
|
||||
}
|
||||
clientConfig.configureWithDevSSLCertificate()
|
||||
|
||||
val clientTruststore = loadKeyStore(clientConfig.trustStoreFile, clientConfig.trustStorePassword)
|
||||
val clientKeystore = loadKeyStore(clientConfig.sslKeystore, clientConfig.keyStorePassword)
|
||||
val amqpClient = AMQPClient(listOf(NetworkHostAndPort("localhost", serverPort),
|
||||
NetworkHostAndPort("localhost", serverPort2),
|
||||
NetworkHostAndPort("localhost", artemisPort)),
|
||||
setOf(ALICE_NAME, CHARLIE_NAME),
|
||||
PEER_USER,
|
||||
PEER_USER,
|
||||
clientKeystore,
|
||||
clientConfig.keyStorePassword,
|
||||
clientTruststore, true)
|
||||
return amqpClient
|
||||
}
|
||||
|
||||
private fun createSharedThreadsClient(sharedEventGroup: EventLoopGroup, id: Int): AMQPClient {
|
||||
val clientConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
||||
doReturn(temporaryFolder.root.toPath() / "client_%$id").whenever(it).baseDirectory
|
||||
doReturn(CordaX500Name(null, "client $id", "Corda", "London", null, "GB")).whenever(it).myLegalName
|
||||
doReturn("trustpass").whenever(it).trustStorePassword
|
||||
doReturn("cordacadevpass").whenever(it).keyStorePassword
|
||||
}
|
||||
clientConfig.configureWithDevSSLCertificate()
|
||||
|
||||
val clientTruststore = loadKeyStore(clientConfig.trustStoreFile, clientConfig.trustStorePassword)
|
||||
val clientKeystore = loadKeyStore(clientConfig.sslKeystore, clientConfig.keyStorePassword)
|
||||
val amqpClient = AMQPClient(listOf(NetworkHostAndPort("localhost", serverPort)),
|
||||
setOf(ALICE_NAME),
|
||||
PEER_USER,
|
||||
PEER_USER,
|
||||
clientKeystore,
|
||||
clientConfig.keyStorePassword,
|
||||
clientTruststore, true, sharedEventGroup)
|
||||
return amqpClient
|
||||
}
|
||||
|
||||
private fun createServer(port: Int, name: CordaX500Name = ALICE_NAME): AMQPServer {
|
||||
val serverConfig = rigorousMock<AbstractNodeConfiguration>().also {
|
||||
doReturn(temporaryFolder.root.toPath() / "server").whenever(it).baseDirectory
|
||||
doReturn(name).whenever(it).myLegalName
|
||||
doReturn("trustpass").whenever(it).trustStorePassword
|
||||
doReturn("cordacadevpass").whenever(it).keyStorePassword
|
||||
}
|
||||
serverConfig.configureWithDevSSLCertificate()
|
||||
|
||||
val serverTruststore = loadKeyStore(serverConfig.trustStoreFile, serverConfig.trustStorePassword)
|
||||
val serverKeystore = loadKeyStore(serverConfig.sslKeystore, serverConfig.keyStorePassword)
|
||||
val amqpServer = AMQPServer("0.0.0.0",
|
||||
port,
|
||||
PEER_USER,
|
||||
PEER_USER,
|
||||
serverKeystore,
|
||||
serverConfig.keyStorePassword,
|
||||
serverTruststore)
|
||||
return amqpServer
|
||||
}
|
||||
|
||||
}
|
@ -24,15 +24,21 @@ import net.corda.testing.*
|
||||
import net.corda.testing.driver.DriverDSL
|
||||
import net.corda.testing.driver.NodeHandle
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.internal.withoutTestSerialization
|
||||
import net.corda.testing.services.MockAttachmentStorage
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.net.URLClassLoader
|
||||
import java.nio.file.Files
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class AttachmentLoadingTests : IntegrationTest() {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
private val attachments = MockAttachmentStorage()
|
||||
private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR)), attachments)
|
||||
private val cordapp get() = provider.cordapps.first()
|
||||
@ -83,7 +89,7 @@ class AttachmentLoadingTests : IntegrationTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test a wire transaction has loaded the correct attachment`() = withTestSerialization {
|
||||
fun `test a wire transaction has loaded the correct attachment`() {
|
||||
val appClassLoader = appContext.classLoader
|
||||
val contractClass = appClassLoader.loadClass(ISOLATED_CONTRACT_ID).asSubclass(Contract::class.java)
|
||||
val generateInitialMethod = contractClass.getDeclaredMethod("generateInitial", PartyAndReference::class.java, Integer.TYPE, Party::class.java)
|
||||
@ -99,7 +105,7 @@ class AttachmentLoadingTests : IntegrationTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test that attachments retrieved over the network are not used for code`() {
|
||||
fun `test that attachments retrieved over the network are not used for code`() = withoutTestSerialization {
|
||||
driver {
|
||||
installIsolatedCordappTo(bankAName)
|
||||
val (bankA, bankB) = createTwoNodes()
|
||||
@ -107,15 +113,17 @@ class AttachmentLoadingTests : IntegrationTest() {
|
||||
bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow()
|
||||
}
|
||||
}
|
||||
Unit
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `tests that if the attachment is loaded on both sides already that a flow can run`() {
|
||||
fun `tests that if the attachment is loaded on both sides already that a flow can run`() = withoutTestSerialization {
|
||||
driver {
|
||||
installIsolatedCordappTo(bankAName)
|
||||
installIsolatedCordappTo(bankBName)
|
||||
val (bankA, bankB) = createTwoNodes()
|
||||
bankA.rpc.startFlowDynamic(flowInitiatorClass, bankB.nodeInfo.legalIdentities.first()).returnValue.getOrThrow()
|
||||
}
|
||||
Unit
|
||||
}
|
||||
}
|
||||
|
@ -24,10 +24,12 @@ import net.corda.node.services.config.BFTSMaRtConfiguration
|
||||
import net.corda.node.services.config.NotaryConfig
|
||||
import net.corda.node.services.transactions.minClusterSize
|
||||
import net.corda.node.services.transactions.minCorrectReplicas
|
||||
import net.corda.nodeapi.internal.NetworkParametersCopier
|
||||
import net.corda.nodeapi.internal.NotaryInfo
|
||||
import net.corda.nodeapi.internal.ServiceIdentityGenerator
|
||||
import net.corda.testing.*
|
||||
import net.corda.nodeapi.internal.network.NotaryInfo
|
||||
import net.corda.testing.chooseIdentity
|
||||
import net.corda.nodeapi.internal.network.NetworkParametersCopier
|
||||
import net.corda.testing.IntegrationTest
|
||||
import net.corda.testing.IntegrationTestSchemas
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.node.MockNetwork
|
||||
@ -55,7 +57,7 @@ class BFTNotaryServiceTests : IntegrationTest() {
|
||||
|
||||
@Before
|
||||
fun before() {
|
||||
mockNet = MockNetwork()
|
||||
mockNet = MockNetwork(emptyList())
|
||||
node = mockNet.createNode()
|
||||
}
|
||||
@After
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user