Refactoring related to BFT notary demo (#680)

* Fix: Add missing @StartableByRPC to fix the Raft notary demo
* Make loadConfig take a Config object, for cordformation Node
* Unduplicate User.toMap
* Unduplicate WHITESPACE regex, choose possessive form
* Use slash to make a Path
* Remove Companion where redundant
* Remove unused code
This commit is contained in:
Andrzej Cichocki 2017-05-16 11:30:50 +01:00 committed by GitHub
parent 42d0a3c638
commit d3bb040355
23 changed files with 86 additions and 101 deletions

View File

@ -46,9 +46,3 @@ class NodeConfig(
return if (obj == null) config else body(config, obj).atPath(path)
}
}
private fun User.toMap(): Map<String, Any> = mapOf(
"username" to username,
"password" to password,
"permissions" to permissions
)

View File

@ -136,7 +136,8 @@ fun <A> ListenableFuture<out A>.toObservable(): Observable<A> {
}
/** Allows you to write code like: Paths.get("someDir") / "subdir" / "filename" but using the Paths API to avoid platform separator problems. */
operator fun Path.div(other: String): Path = resolve(other)
operator fun Path.div(other: String) = resolve(other)
operator fun String.div(other: String) = Paths.get(this) / other
fun Path.createDirectory(vararg attrs: FileAttribute<*>): Path = Files.createDirectory(this, *attrs)
fun Path.createDirectories(vararg attrs: FileAttribute<*>): Path = Files.createDirectories(this, *attrs)

View File

@ -24,12 +24,14 @@ fun validateLegalName(normalizedLegalName: String) {
rules.forEach { it.validate(normalizedLegalName) }
}
val WHITESPACE = "\\s++".toRegex()
/**
* The normalize function will trim the input string, replace any multiple spaces with a single space,
* and normalize the string according to NFKC normalization form.
*/
fun normaliseLegalName(legalName: String): String {
val trimmedLegalName = legalName.trim().replace(Regex("\\s+"), " ")
val trimmedLegalName = legalName.trim().replace(WHITESPACE, " ")
return Normalizer.normalize(trimmedLegalName, Normalizer.Form.NFKC)
}

View File

@ -61,7 +61,7 @@ class PartialMerkleTreeTest {
@Test
fun `building Merkle tree - no hashes`() {
assertFailsWith<MerkleTreeException> { MerkleTree.Companion.getMerkleTree(emptyList()) }
assertFailsWith<MerkleTreeException> { MerkleTree.getMerkleTree(emptyList()) }
}
@Test

View File

@ -71,7 +71,7 @@ class SecureHashGenerator : Generator<SecureHash>(SecureHash::class.java) {
class StateRefGenerator : Generator<StateRef>(StateRef::class.java) {
override fun generate(random: SourceOfRandomness, status: GenerationStatus): StateRef {
return StateRef(SecureHash.Companion.sha256(random.nextBytes(16)), random.nextInt(0, 10))
return StateRef(SecureHash.sha256(random.nextBytes(16)), random.nextInt(0, 10))
}
}

View File

@ -21,7 +21,7 @@ class ObligationTests {
val defaultRef = OpaqueBytes(ByteArray(1, { 1 }))
val defaultIssuer = MEGA_CORP.ref(defaultRef)
val oneMillionDollars = 1000000.DOLLARS `issued by` defaultIssuer
val trustedCashContract = nonEmptySetOf(SecureHash.Companion.randomSHA256() as SecureHash)
val trustedCashContract = nonEmptySetOf(SecureHash.randomSHA256() as SecureHash)
val megaIssuedDollars = nonEmptySetOf(Issued(defaultIssuer, USD))
val megaIssuedPounds = nonEmptySetOf(Issued(defaultIssuer, GBP))
val fivePm = TEST_TX_TIME.truncatedTo(ChronoUnit.DAYS).plus(17, ChronoUnit.HOURS)
@ -759,7 +759,7 @@ class ObligationTests {
// States must not be nettable if the cash contract differs
assertNotEquals(fiveKDollarsFromMegaToMega.bilateralNetState,
fiveKDollarsFromMegaToMega.copy(template = megaCorpDollarSettlement.copy(acceptableContracts = nonEmptySetOf(SecureHash.Companion.randomSHA256()))).bilateralNetState)
fiveKDollarsFromMegaToMega.copy(template = megaCorpDollarSettlement.copy(acceptableContracts = nonEmptySetOf(SecureHash.randomSHA256()))).bilateralNetState)
// States must not be nettable if the trusted issuers differ
val miniCorpIssuer = nonEmptySetOf(Issued(MINI_CORP.ref(1), USD))

View File

@ -40,7 +40,7 @@ class IssuerFlowTest {
bankClientNode = net.createPartyNode(notaryNode.info.address, MEGA_CORP.name)
// using default IssueTo Party Reference
val issueToPartyAndRef = bankClientNode.info.legalIdentity.ref(OpaqueBytes.Companion.of(123))
val issueToPartyAndRef = bankClientNode.info.legalIdentity.ref(OpaqueBytes.of(123))
val (issuer, issuerResult) = runIssuerAndIssueRequester(bankOfCordaNode, bankClientNode, 1000000.DOLLARS, issueToPartyAndRef)
assertEquals(issuerResult.get(), issuer.get().resultFuture.get())
@ -62,7 +62,7 @@ class IssuerFlowTest {
bankOfCordaNode = net.createPartyNode(notaryNode.info.address, BOC.name)
// using default IssueTo Party Reference
val issueToPartyAndRef = bankOfCordaNode.info.legalIdentity.ref(OpaqueBytes.Companion.of(123))
val issueToPartyAndRef = bankOfCordaNode.info.legalIdentity.ref(OpaqueBytes.of(123))
val (issuer, issuerResult) = runIssuerAndIssueRequester(bankOfCordaNode, bankOfCordaNode, 1000000.DOLLARS, issueToPartyAndRef)
assertEquals(issuerResult.get(), issuer.get().resultFuture.get())
@ -80,7 +80,7 @@ class IssuerFlowTest {
bankClientNode = net.createPartyNode(notaryNode.info.address, MEGA_CORP.name)
// using default IssueTo Party Reference
val issueToPartyAndRef = bankClientNode.info.legalIdentity.ref(OpaqueBytes.Companion.of(123))
val issueToPartyAndRef = bankClientNode.info.legalIdentity.ref(OpaqueBytes.of(123))
// this test exercises the Cashflow issue and move subflows to ensure consistent spending of issued states
val amount = 10000.DOLLARS

View File

@ -18,6 +18,11 @@ data class User(
val password: String,
val permissions: Set<String>) {
override fun toString(): String = "${javaClass.simpleName}($username, permissions=$permissions)"
fun toMap() = mapOf(
"username" to username,
"password" to password,
"permissions" to permissions
)
}
/** Records the protocol version in which this RPC was added. */

View File

@ -12,12 +12,10 @@ import org.bouncycastle.asn1.x500.X500Name
import org.junit.Test
import java.net.URL
import java.nio.file.Path
import java.nio.file.Paths
import java.time.Instant
import java.time.LocalDate
import java.util.*
import kotlin.reflect.full.primaryConstructor
import kotlin.test.assertEquals
class ConfigParsingTest {
@Test
@ -70,7 +68,7 @@ class ConfigParsingTest {
@Test
fun `Path`() {
val path = Paths.get("tmp") / "test"
val path = "tmp" / "test"
testPropertyType<PathData, PathListData, Path>(path, path / "file", valuesToString = true)
}

View File

@ -65,9 +65,7 @@ data class CmdLineOptions(val baseDirectory: Path,
val isVersion: Boolean,
val noLocalShell: Boolean,
val sshdServer: Boolean) {
fun loadConfig(allowMissingConfig: Boolean = false, configOverrides: Map<String, Any?> = emptyMap()): FullNodeConfiguration {
return ConfigHelper
.loadConfig(baseDirectory, configFile, allowMissingConfig, configOverrides)
fun loadConfig() = ConfigHelper
.loadConfig(baseDirectory, configFile)
.parseAs<FullNodeConfiguration>()
}
}

View File

@ -18,9 +18,7 @@ import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType
import net.corda.core.utilities.*
import net.corda.node.LOGS_DIRECTORY_NAME
import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.FullNodeConfiguration
import net.corda.node.services.config.VerifierType
import net.corda.node.services.config.*
import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.node.utilities.ServiceIdentityGenerator
@ -491,9 +489,9 @@ class DriverDSL(
val webAddress = portAllocation.nextHostAndPort()
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
// TODO: Derive name from the full picked name, don't just wrap the common name
val name = providedName ?: X509Utilities.getDevX509Name("${pickA(name).commonName}-${p2pAddress.port}")
val name = providedName ?: X509Utilities.getDevX509Name("${oneOf(names).commonName}-${p2pAddress.port}")
val baseDirectory = driverDirectory / name.commonName
val configOverrides = mapOf(
val configOverrides = configOf(
"myLegalName" to name.toString(),
"p2pAddress" to p2pAddress.toString(),
"rpcAddress" to rpcAddress.toString(),
@ -511,13 +509,7 @@ class DriverDSL(
}
},
"useTestClock" to useTestClock,
"rpcUsers" to rpcUsers.map {
mapOf(
"username" to it.username,
"password" to it.password,
"permissions" to it.permissions
)
},
"rpcUsers" to rpcUsers.map { it.toMap() },
"verifierType" to verifierType.name
) + customOverrides
@ -612,7 +604,7 @@ class DriverDSL(
val config = ConfigHelper.loadConfig(
baseDirectory = baseDirectory,
allowMissingConfig = true,
configOverrides = mapOf(
configOverrides = configOf(
"myLegalName" to dedicatedNetworkMapLegalName.toString(),
// TODO: remove the webAddress as NMS doesn't need to run a web server. This will cause all
// node port numbers to be shifted, so all demos and docs need to be updated accordingly.
@ -635,13 +627,13 @@ class DriverDSL(
}
companion object {
val name = arrayOf(
private val names = arrayOf(
ALICE.name,
BOB.name,
DUMMY_BANK_A.name
)
fun <A> pickA(array: Array<A>): A = array[Math.abs(Random().nextInt()) % array.size]
private fun <A> oneOf(array: Array<A>) = array[Random().nextInt(array.size)]
private fun startNode(
executorService: ListeningScheduledExecutorService,

View File

@ -18,21 +18,23 @@ import net.corda.nodeapi.config.SSLConfiguration
import org.bouncycastle.asn1.x500.X500Name
import java.nio.file.Path
fun configOf(vararg pairs: Pair<String, Any?>) = ConfigFactory.parseMap(mapOf(*pairs))
operator fun Config.plus(overrides: Map<String, Any?>) = ConfigFactory.parseMap(overrides).withFallback(this)
object ConfigHelper {
private val log = loggerFor<ConfigHelper>()
fun loadConfig(baseDirectory: Path,
configFile: Path = baseDirectory / "node.conf",
allowMissingConfig: Boolean = false,
configOverrides: Map<String, Any?> = emptyMap()): Config {
configOverrides: Config = ConfigFactory.empty()): Config {
val parseOptions = ConfigParseOptions.defaults()
val defaultConfig = ConfigFactory.parseResources("reference.conf", parseOptions.setAllowMissing(false))
val appConfig = ConfigFactory.parseFile(configFile.toFile(), parseOptions.setAllowMissing(allowMissingConfig))
val overrideConfig = ConfigFactory.parseMap(configOverrides + mapOf(
val finalConfig = configOf(
// Add substitution values here
"basedir" to baseDirectory.toString())
)
val finalConfig = overrideConfig
.withFallback(configOverrides)
.withFallback(appConfig)
.withFallback(defaultConfig)
.resolve()

View File

@ -0,0 +1,24 @@
package net.corda.node.services.config
import com.typesafe.config.ConfigFactory
import net.corda.nodeapi.config.toProperties
import org.junit.Test
import kotlin.test.assertEquals
class ConfigOperatorTests {
@Test
fun `config plus behaves the same as map plus`() {
val config = arrayOf("x" to "y1", "a" to "b", "z" to "Z")
val overrides = arrayOf("x" to "y2", "c" to "d", "z" to null)
val old = ConfigFactory.parseMap(mapOf(*config) + mapOf(*overrides))
val new = configOf(*config) + mapOf(*overrides)
listOf(old, new).map { it.toProperties() }.forEach { c ->
assertEquals("y2", c["x"])
assertEquals("b", c["a"])
assertEquals("d", c["c"])
assertEquals(null, c["z"])
}
}
}

View File

@ -8,7 +8,6 @@ import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.node.driver.driver
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.nodeapi.User
import java.nio.file.Paths
/**
* This file is exclusively for being able to run your nodes through an IDE (as opposed to running deployNodes)
@ -16,8 +15,8 @@ import java.nio.file.Paths
*/
fun main(args: Array<String>) {
val demoUser = listOf(User("demo", "demo", setOf("StartFlow.net.corda.flows.FinalityFlow")))
driver(isDebug = true, driverDirectory = Paths.get("build") / "attachment-demo-nodes") {
startNode(DUMMY_NOTARY.name, setOf(ServiceInfo(SimpleNotaryService.Companion.type)))
driver(isDebug = true, driverDirectory = "build" / "attachment-demo-nodes") {
startNode(DUMMY_NOTARY.name, setOf(ServiceInfo(SimpleNotaryService.type)))
startNode(DUMMY_BANK_A.name, rpcUsers = demoUser)
startNode(DUMMY_BANK_B.name, rpcUsers = demoUser)
waitForAllNodesToFinish()

View File

@ -1,3 +1,5 @@
import net.corda.plugins.Cordform
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'idea'
@ -68,7 +70,7 @@ task generateNotaryIdentity(type: JavaExec, dependsOn: 'cleanNodes') {
args = [nodeDirs, notaryType, notaryName]
}
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', 'generateNotaryIdentity']) {
task deployNodes(type: Cordform, dependsOn: ['jar', generateNotaryIdentity]) {
directory deployTo
networkMap "CN=Notary 1,O=R3,OU=corda,L=London,C=UK"
node {
@ -80,7 +82,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', 'generateN
cordapps = []
rpcUsers = [['username': "demo", 'password': "demo", 'permissions': [
'StartFlow.net.corda.notarydemo.flows.DummyIssueAndMove',
'StartFlow.net.corda.flows.NotaryFlow$Client'
'StartFlow.net.corda.notarydemo.flows.RPCStartableNotaryFlowClient'
]]]
}
node {
@ -126,4 +128,3 @@ task notarise(type: JavaExec) {
classpath = sourceSets.main.runtimeClasspath
main = 'net.corda.notarydemo.NotaryDemoKt'
}

View File

@ -6,7 +6,6 @@ import net.corda.core.div
import net.corda.core.utilities.ALICE
import net.corda.core.utilities.BOB
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.flows.NotaryFlow
import net.corda.node.driver.NetworkMapStartStrategy
import net.corda.node.driver.PortAllocation
import net.corda.node.driver.driver
@ -14,14 +13,14 @@ import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.nodeapi.User
import net.corda.notarydemo.flows.DummyIssueAndMove
import net.corda.notarydemo.flows.RPCStartableNotaryFlowClient
import org.bouncycastle.asn1.x500.X500Name
import java.nio.file.Paths
/** Creates and starts all nodes required for the demo. */
fun main(args: Array<String>) {
val demoUser = listOf(User("demo", "demo", setOf(startFlowPermission<DummyIssueAndMove>(), startFlowPermission<NotaryFlow.Client>())))
val demoUser = listOf(User("demo", "demo", setOf(startFlowPermission<DummyIssueAndMove>(), startFlowPermission<RPCStartableNotaryFlowClient>())))
val networkMap = NetworkMapStartStrategy.Nominated(DUMMY_NOTARY.name.appendToCommonName("1"), HostAndPort.fromParts("localhost", 10009))
driver(isDebug = true, driverDirectory = Paths.get("build") / "notary-demo-nodes", networkMapStartStrategy = networkMap, portAllocation = PortAllocation.Incremental(10001)) {
driver(isDebug = true, driverDirectory = "build" / "notary-demo-nodes", networkMapStartStrategy = networkMap, portAllocation = PortAllocation.Incremental(10001)) {
startNode(ALICE.name, rpcUsers = demoUser)
startNode(BOB.name)
startNotaryCluster(X500Name("CN=Raft,O=R3,OU=corda,L=Zurich,C=CH"), clusterSize = 3, type = RaftValidatingNotaryService.type)

View File

@ -2,23 +2,16 @@ package net.corda.notarydemo
import com.google.common.net.HostAndPort
import com.google.common.util.concurrent.Futures
import joptsimple.OptionParser
import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.notUsed
import net.corda.core.crypto.toStringShort
import net.corda.core.div
import net.corda.core.getOrThrow
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.BOB
import net.corda.flows.NotaryFlow
import net.corda.nodeapi.config.SSLConfiguration
import net.corda.notarydemo.flows.DummyIssueAndMove
import org.bouncycastle.asn1.x500.X500Name
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.system.exitProcess
import net.corda.notarydemo.flows.RPCStartableNotaryFlowClient
fun main(args: Array<String>) {
val host = HostAndPort.fromString("localhost:10003")
@ -85,28 +78,7 @@ private class NotaryDemoClientApi(val rpc: CordaRPCOps) {
private fun notariseTransactions(transactions: List<SignedTransaction>): List<String> {
// TODO: Remove this suppress when we upgrade to kotlin 1.1 or when JetBrain fixes the bug.
@Suppress("UNSUPPORTED_FEATURE")
val signatureFutures = transactions.map { rpc.startFlow(NotaryFlow::Client, it).returnValue }
val signatureFutures = transactions.map { rpc.startFlow(::RPCStartableNotaryFlowClient, it).returnValue }
return Futures.allAsList(signatureFutures).getOrThrow().map { it.map { it.by.toStringShort() }.joinToString() }
}
}
private fun getCertPath(args: Array<String>): String? {
val parser = OptionParser()
val certsPath = parser.accepts("certificates").withRequiredArg()
val options = try {
parser.parse(*args)
} catch (e: Exception) {
println(e.message)
exitProcess(1)
}
return options.valueOf(certsPath)
}
// TODO: Take this out once we have a dedicated RPC port and allow SSL on it to be optional.
private fun sslConfigFor(nodename: String, certsPath: String?): SSLConfiguration {
return object : SSLConfiguration {
override val keyStorePassword: String = "cordacadevpass"
override val trustStorePassword: String = "trustpass"
override val certificatesDirectory: Path = if (certsPath != null) Paths.get(certsPath) else Paths.get("build") / "nodes" / nodename / "certificates"
}
}

View File

@ -4,9 +4,11 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.DummyContract
import net.corda.core.identity.Party
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import net.corda.core.transactions.SignedTransaction
import java.util.*
@StartableByRPC
class DummyIssueAndMove(private val notary: Party, private val counterpartyNode: Party) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {

View File

@ -0,0 +1,8 @@
package net.corda.notarydemo.flows
import net.corda.core.flows.StartableByRPC
import net.corda.core.transactions.SignedTransaction
import net.corda.flows.NotaryFlow
@StartableByRPC
class RPCStartableNotaryFlowClient(stx: SignedTransaction) : NotaryFlow.Client(stx)

View File

@ -11,7 +11,6 @@ import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.nodeapi.User
import net.corda.testing.BOC
import java.nio.file.Paths
/**
* This file is exclusively for being able to run your nodes through an IDE (as opposed to running deployNodes)
@ -22,7 +21,7 @@ fun main(args: Array<String>) {
startFlowPermission<IssuerFlow.IssuanceRequester>(),
startFlowPermission<net.corda.traderdemo.flow.SellerFlow>())
val demoUser = listOf(User("demo", "demo", permissions))
driver(driverDirectory = Paths.get("build") / "trader-demo-nodes", isDebug = true) {
driver(driverDirectory = "build" / "trader-demo-nodes", isDebug = true) {
val user = User("user1", "test", permissions = setOf(startFlowPermission<IssuerFlow.IssuanceRequester>()))
startNode(DUMMY_NOTARY.name, setOf(ServiceInfo(SimpleNotaryService.type)))
startNode(DUMMY_BANK_A.name, rpcUsers = demoUser)

View File

@ -12,6 +12,8 @@ import net.corda.node.driver.addressMustNotBeBound
import net.corda.node.internal.Node
import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.FullNodeConfiguration
import net.corda.node.services.config.configOf
import net.corda.node.services.config.plus
import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.node.utilities.ServiceIdentityGenerator
import net.corda.nodeapi.User
@ -141,18 +143,12 @@ abstract class NodeBasedTest {
val config = ConfigHelper.loadConfig(
baseDirectory = baseDirectory,
allowMissingConfig = true,
configOverrides = mapOf(
configOverrides = configOf(
"myLegalName" to legalName.toString(),
"p2pAddress" to localPort[0].toString(),
"rpcAddress" to localPort[1].toString(),
"extraAdvertisedServiceIds" to advertisedServices.map { it.toString() },
"rpcUsers" to rpcUsers.map {
mapOf(
"username" to it.username,
"password" to it.password,
"permissions" to it.permissions
)
}
"rpcUsers" to rpcUsers.map { it.toMap() }
) + configOverrides
)

View File

@ -1,6 +1,7 @@
package net.corda.demobench.model
import net.corda.core.crypto.commonName
import net.corda.core.utilities.WHITESPACE
import org.bouncycastle.asn1.x500.X500Name
open class NetworkMapConfig(val legalName: X500Name, val p2pPort: Int) {
@ -9,7 +10,5 @@ open class NetworkMapConfig(val legalName: X500Name, val p2pPort: Int) {
}
private val WHITESPACE = "\\s++".toRegex()
fun String.stripWhitespace() = this.replace(WHITESPACE, "")
fun String.toKey() = this.stripWhitespace().toLowerCase()
fun String.stripWhitespace() = replace(WHITESPACE, "")
fun String.toKey() = stripWhitespace().toLowerCase()

View File

@ -5,12 +5,6 @@ package net.corda.demobench.model
import net.corda.nodeapi.User
import java.util.*
fun User.toMap(): Map<String, Any> = mapOf(
"username" to username,
"password" to password,
"permissions" to permissions
)
@Suppress("UNCHECKED_CAST")
fun toUser(map: Map<String, Any>) = User(
map.getOrElse("username", { "none" }) as String,