From b7fbebb490b01a1c10e8b4f8765937a95baa6624 Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Fri, 1 Jun 2018 14:26:58 +0100 Subject: [PATCH 1/8] Cleaned up blob inspector doc (#3284) --- docs/source/blob-inspector.rst | 68 ++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/docs/source/blob-inspector.rst b/docs/source/blob-inspector.rst index ba301eef0a..1742ee8771 100644 --- a/docs/source/blob-inspector.rst +++ b/docs/source/blob-inspector.rst @@ -14,8 +14,12 @@ To run simply pass in the file or URL as the first parameter: Use the ``--help`` flag for a full list of command line options. -``SerializedBytes` -~~~~~~~~~~~~~~~~~~ +When inspecting your custom data structures, there's no need to include the jars containing the class definitions for them +in the classpath. The blob inspector (or rather the serialization framework) is able to synthesis any classes found in the +blob that aren't on the classpath. + +SerializedBytes +~~~~~~~~~~~~~~~ One thing to note is that the binary blob may contain embedded ``SerializedBytes`` objects. Rather than printing these out as a Base64 string, the blob inspector will first materialise them into Java objects and then output those. You will @@ -23,41 +27,41 @@ see this when dealing with classes such as ``SignedData`` or other structures th ``nodeInfo-*`` files or the ``network-parameters`` file in the node's directory. For example, the output of a node-info file may look like: -.. container:: codeset - .. sourcecode:: yaml +**-\\-format=YAML** +:: - net.corda.nodeapi.internal.SignedNodeInfo - --- - raw: - class: "net.corda.core.node.NodeInfo" - deserialized: - addresses: - - "localhost:10011" - legalIdentitiesAndCerts: - - "O=BankOfCorda, L=New York, C=US" - platformVersion: 4 - serial: 1527074180971 - signatures: - - !!binary | - dmoAnnzcv0MzRN+3ZSCDcCJIAbXnoYy5mFWB3Nijndzu/dzIoYdIawINXbNSY/5z2XloDK01vZRV - TreFZCbZAg== + net.corda.nodeapi.internal.SignedNodeInfo + --- + raw: + class: "net.corda.core.node.NodeInfo" + deserialized: + addresses: + - "localhost:10005" + legalIdentitiesAndCerts: + - "O=BankOfCorda, L=London, C=GB" + platformVersion: 4 + serial: 1527851068715 + signatures: + - !!binary |- + VFRy4frbgRDbCpK1Vo88PyUoj01vbRnMR3ROR2abTFk7yJ14901aeScX/CiEP+CDGiMRsdw01cXt\nhKSobAY7Dw== - .. sourcecode:: json +**-\\-format=JSON** +:: - net.corda.nodeapi.internal.SignedNodeInfo - { - "raw" : { - "class" : "net.corda.core.node.NodeInfo", - "deserialized" : { - "addresses" : [ "localhost:10011" ], - "legalIdentitiesAndCerts" : [ "O=BankOfCorda, L=New York, C=US" ], - "platformVersion" : 4, - "serial" : 1527074180971 - } - }, - "signatures" : [ "dmoAnnzcv0MzRN+3ZSCDcCJIAbXnoYy5mFWB3Nijndzu/dzIoYdIawINXbNSY/5z2XloDK01vZRVTreFZCbZAg==" ] + net.corda.nodeapi.internal.SignedNodeInfo + { + "raw" : { + "class" : "net.corda.core.node.NodeInfo", + "deserialized" : { + "addresses" : [ "localhost:10005" ], + "legalIdentitiesAndCerts" : [ "O=BankOfCorda, L=London, C=GB" ], + "platformVersion" : 4, + "serial" : 1527851068715 } + }, + "signatures" : [ "VFRy4frbgRDbCpK1Vo88PyUoj01vbRnMR3ROR2abTFk7yJ14901aeScX/CiEP+CDGiMRsdw01cXthKSobAY7Dw==" ] + } Notice the file is actually a serialised ``SignedNodeInfo`` object, which has a ``raw`` property of type ``SerializedBytes``. This property is materialised into a ``NodeInfo`` and is output under the ``deserialized`` field. From 57d379597bcff397edd3b71ffb8ac9901be06b22 Mon Sep 17 00:00:00 2001 From: szymonsztuka Date: Fri, 1 Jun 2018 16:08:19 +0100 Subject: [PATCH 2/8] Fix a typo in node_attchments_contracts table name. (#3202) --- docs/source/changelog.rst | 2 ++ docs/source/upgrade-notes.rst | 6 ++++++ .../node/services/persistence/NodeAttachmentService.kt | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 5b6eec9c6c..67e44fb3aa 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -119,6 +119,8 @@ Unreleased For instance, this method will check if an ECC key lies on a valid curve or if an RSA key is >= 2048bits. This might be required for extra key validation checks, e.g., for Doorman to check that a CSR key meets the minimum security requirements. +* Table name with a typo changed from ``NODE_ATTCHMENTS_CONTRACTS`` to ``NODE_ATTACHMENTS_CONTRACTS``. + .. _changelog_v3.1: Version 3.1 diff --git a/docs/source/upgrade-notes.rst b/docs/source/upgrade-notes.rst index 223ee0fad8..252217de23 100644 --- a/docs/source/upgrade-notes.rst +++ b/docs/source/upgrade-notes.rst @@ -67,6 +67,12 @@ UNRELEASED No action is needed for default node tables as ``PersistentStateRef`` is used as Primary Key only and the backing columns are automatically not nullable or custom Cordapp entities using ``PersistentStateRef`` as Primary Key. +* H2 database upgrade - the table with a typo has been change, for each database instance and schema run the following SQL statement: + + ALTER TABLE [schema].NODE_ATTCHMENTS_CONTRACTS RENAME TO NODE_ATTACHMENTS_CONTRACTS; + + Schema is optional, run SQL when the node is not running. + v3.0 to v3.1 ------------ diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt index 81456a16c0..7ecd6b0995 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt @@ -112,7 +112,7 @@ class NodeAttachmentService( @ElementCollection @Column(name = "contract_class_name", nullable = false) - @CollectionTable(name = "node_attchments_contracts", joinColumns = [(JoinColumn(name = "att_id", referencedColumnName = "att_id"))], + @CollectionTable(name = "${NODE_DATABASE_PREFIX}attachments_contracts", joinColumns = [(JoinColumn(name = "att_id", referencedColumnName = "att_id"))], foreignKey = ForeignKey(name = "FK__ctr_class__attachments")) var contractClassNames: List? = null ) : Serializable From fc020bca4b63a6f2088ebaeda0dcb4c5b54f48d8 Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Fri, 1 Jun 2018 16:16:23 +0100 Subject: [PATCH 3/8] [CORDA-1575]: Out of process nodes started by the driver do not log to file (fix). (#3286) --- config/dev/log4j2.xml | 27 ++++++---- docs/source/changelog.rst | 2 + .../kotlin/net/corda/node/BootTests.kt | 6 +-- .../net/corda/vega/SimmValuationTest.kt | 10 +++- .../testing/node/internal/ProcessUtilities.kt | 4 +- .../src/main/resources/log4j2-test.xml | 53 +++++++++++++++++-- 6 files changed, 78 insertions(+), 24 deletions(-) diff --git a/config/dev/log4j2.xml b/config/dev/log4j2.xml index 7220687f8d..a23af7db94 100644 --- a/config/dev/log4j2.xml +++ b/config/dev/log4j2.xml @@ -2,11 +2,10 @@ - logs + ${sys:log-path:-logs} node-${hostName} - ${sys:log-path}/archive - error - info + ${log-path}/archive + ${sys:defaultLogLevel:-info} @@ -14,7 +13,7 @@ - + - + @@ -39,7 +38,7 @@ @@ -64,15 +63,23 @@ - - - + + + + + + + + + + + diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 67e44fb3aa..41e50593b6 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -7,6 +7,8 @@ release, see :doc:`upgrade-notes`. Unreleased ========== +* Fixed an issue preventing out of process nodes started by the ``Driver`` from logging to file. + * Fixed an issue with ``CashException`` not being able to deserialise after the introduction of AMQP for RPC. * Removed -xmx VM argument from Explorer's Capsule setup. This helps avoiding out of memory errors. diff --git a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt index be0825f4ff..51ccf7b974 100644 --- a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt @@ -12,12 +12,10 @@ import net.corda.core.messaging.startFlow import net.corda.core.utilities.getOrThrow import net.corda.node.internal.NodeStartup import net.corda.node.services.Permissions.Companion.startFlow -import net.corda.testing.common.internal.ProjectStructure.projectRootDir import net.corda.testing.core.ALICE_NAME import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.driver import net.corda.testing.node.User -import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test import java.io.* @@ -38,9 +36,7 @@ class BootTests { @Test fun `double node start doesn't write into log file`() { - val logConfigFile = projectRootDir / "config" / "dev" / "log4j2.xml" - assertThat(logConfigFile).isRegularFile() - driver(DriverParameters(isDebug = true, systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString()))) { + driver(DriverParameters(isDebug = true)) { val alice = startNode(providedName = ALICE_NAME).get() val logFolder = alice.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME val logFile = logFolder.list { it.filter { it.fileName.toString().endsWith(".log") }.findAny().get() } diff --git a/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt b/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt index 368821069d..da89c5a780 100644 --- a/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt +++ b/samples/simm-valuation-demo/src/integration-test/kotlin/net/corda/vega/SimmValuationTest.kt @@ -2,9 +2,11 @@ package net.corda.vega import com.opengamma.strata.product.common.BuySell import net.corda.core.identity.CordaX500Name +import net.corda.core.internal.div import net.corda.core.internal.packageName import net.corda.core.utilities.getOrThrow import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme +import net.corda.testing.common.internal.ProjectStructure.projectRootDir import net.corda.testing.core.DUMMY_BANK_A_NAME import net.corda.testing.core.DUMMY_BANK_B_NAME import net.corda.testing.driver.DriverParameters @@ -43,7 +45,13 @@ class SimmValuationTest { @Test fun `runs SIMM valuation demo`() { - driver(DriverParameters(isDebug = true, extraCordappPackagesToScan = listOf("net.corda.vega.contracts", "net.corda.vega.plugin.customserializers"))) { + val logConfigFile = projectRootDir / "samples" / "simm-valuation-demo" / "src" / "main" / "resources" / "log4j2.xml" + assertThat(logConfigFile).isRegularFile() + driver(DriverParameters( + isDebug = true, + extraCordappPackagesToScan = listOf("net.corda.vega.contracts", "net.corda.vega.plugin.customserializers"), + systemProperties = mapOf("log4j.configurationFile" to logConfigFile.toString())) + ) { val nodeAFuture = startNode(providedName = nodeALegalName) val nodeBFuture = startNode(providedName = nodeBLegalName) val (nodeA, nodeB) = listOf(nodeAFuture, nodeBFuture).map { it.getOrThrow() } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ProcessUtilities.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ProcessUtilities.kt index 20d2bd31c1..4bb77cd503 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ProcessUtilities.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/ProcessUtilities.kt @@ -22,9 +22,7 @@ object ProcessUtilities { workingDirectory: Path?, maximumHeapSize: String ): Process { - // FIXME: Instead of hacking our classpath, use the correct classpath for className. - val classpath = defaultClassPath.split(pathSeparator).filter { !(it / "log4j2-test.xml").exists() }.joinToString(pathSeparator) - return startJavaProcessImpl(className, arguments, classpath, jdwpPort, extraJvmArguments, workingDirectory, maximumHeapSize) + return startJavaProcessImpl(className, arguments, defaultClassPath, jdwpPort, extraJvmArguments, workingDirectory, maximumHeapSize) } fun startJavaProcessImpl( diff --git a/testing/test-common/src/main/resources/log4j2-test.xml b/testing/test-common/src/main/resources/log4j2-test.xml index a05f8bd585..0d4feace63 100644 --- a/testing/test-common/src/main/resources/log4j2-test.xml +++ b/testing/test-common/src/main/resources/log4j2-test.xml @@ -1,12 +1,19 @@ + - info + ${sys:log-path:-logs} + node-${hostName} + ${log-path}/archive + ${sys:defaultLogLevel:-info} + + + - + - + + - + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + From 7350cd9d1e1c8f736d8d3d2041b8f64df541ee41 Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Mon, 4 Jun 2018 09:35:38 +0100 Subject: [PATCH 4/8] [CORDA-1408]: Fixed some Demobench bugs. (#3288) --- .../node/services/vault/NodeVaultService.kt | 2 +- .../corda/demobench/model/InstallFactory.kt | 3 ++- .../net/corda/demobench/model/NodeConfig.kt | 15 +++++++------ .../corda/demobench/views/NodeTerminalView.kt | 21 +++++++++++++++---- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt index f2ada92b06..e13224e05e 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt @@ -408,7 +408,7 @@ class NodeVaultService( @Throws(VaultQueryException::class) private fun _queryBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort, contractStateType: Class, skipPagingChecks: Boolean): Vault.Page { - log.info("Vault Query for contract type: $contractStateType, criteria: $criteria, pagination: $paging, sorting: $sorting") + log.debug { "Vault Query for contract type: $contractStateType, criteria: $criteria, pagination: $paging, sorting: $sorting" } return database.transaction { // calculate total results where a page specification has been defined var totalStates = -1L diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/InstallFactory.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/InstallFactory.kt index dc253888cf..86f987c33a 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/InstallFactory.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/InstallFactory.kt @@ -4,6 +4,7 @@ import com.typesafe.config.Config import net.corda.core.internal.deleteRecursively import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort +import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy import net.corda.nodeapi.internal.config.parseAs import tornadofx.* import java.io.IOException @@ -19,7 +20,7 @@ class InstallFactory : Controller() { require(nodeController.isPortValid(port)) { "Invalid port $port" } } - val nodeConfig = config.parseAs() + val nodeConfig = config.parseAs(UnknownConfigKeysPolicy.IGNORE::handle) nodeConfig.p2pAddress.checkPort() nodeConfig.rpcAddress.checkPort() nodeConfig.webAddress.checkPort() diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt index 37cdc76263..fbe58670ad 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeConfig.kt @@ -33,7 +33,10 @@ data class NodeConfig( val issuableCurrencies: List = emptyList(), /** Pass-through for generating node.conf with external DB */ val dataSourceProperties: Properties? = null, - val database: Properties? = null + val database: Properties? = null, + private val devMode: Boolean = true, + private val detectPublicIp: Boolean = false, + private val useTestClock: Boolean = true ) { companion object { val renderOptions: ConfigRenderOptions = ConfigRenderOptions.defaults().setOriginComments(false) @@ -41,14 +44,9 @@ data class NodeConfig( const val cordappDirName = "cordapps" } - @Suppress("unused") - private val detectPublicIp = false - @Suppress("unused") - private val useTestClock = true - fun nodeConf(): Config { - val basic = NodeConfigurationData(myLegalName, p2pAddress, rpcAddress, notary, h2port, rpcUsers, useTestClock, detectPublicIp).toConfig() + val basic = NodeConfigurationData(myLegalName, p2pAddress, rpcAddress, notary, h2port, rpcUsers, useTestClock, detectPublicIp, devMode).toConfig() val rpcSettings = empty() .withValue("address", ConfigValueFactory.fromAnyRef(rpcAddress.toString())) .withValue("adminAddress", ConfigValueFactory.fromAnyRef(rpcAdminAddress.toString())) @@ -78,7 +76,8 @@ private data class NodeConfigurationData( val h2port: Int, val rpcUsers: List = listOf(NodeConfig.defaultUser), val useTestClock: Boolean, - val detectPublicIp: Boolean + val detectPublicIp: Boolean, + val devMode: Boolean ) private data class WebServerConfigurationData( diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTerminalView.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTerminalView.kt index d1e2f59492..b91677b41a 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTerminalView.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTerminalView.kt @@ -13,6 +13,7 @@ import javafx.scene.layout.HBox import javafx.scene.layout.StackPane import javafx.scene.layout.VBox import javafx.util.Duration +import net.corda.client.rpc.RPCException import net.corda.core.concurrent.match import net.corda.core.contracts.ContractState import net.corda.core.messaging.CordaRPCOps @@ -201,13 +202,25 @@ class NodeTerminalView : Fragment() { } val fxScheduler = Schedulers.from(Platform::runLater) - subscriptions.add(txNext.observeOn(fxScheduler).subscribe { + subscriptions.add(txNext.observeOn(fxScheduler).subscribe({ transactions.value = (++txCount).toString() - }) - subscriptions.add(stateNext.observeOn(fxScheduler).subscribe { + }, { error -> + if (error is RPCException && error.message?.contains("Connection failure detected") == true) { + // Ignore this ^^^, it only happens when we shutdown a node in Demobench. + } else { + throw error + } + })) + subscriptions.add(stateNext.observeOn(fxScheduler).subscribe({ stateCount += (it.produced.size - it.consumed.size) states.value = stateCount.toString() - }) + }, { error -> + if (error is RPCException && error.message?.contains("Connection failure detected") == true) { + // Ignore this ^^^, it only happens when we shutdown a node in Demobench. + } else { + throw error + } + })) } catch (e: Exception) { log.log(Level.WARNING, "RPC failed: ${e.message}", e) } From 141d45c39df69591f33fbc15aeb67fd2f77672bd Mon Sep 17 00:00:00 2001 From: Thomas Schroeter Date: Mon, 4 Jun 2018 13:33:34 +0100 Subject: [PATCH 5/8] Add simulation of the avalanche consensus protocol to experimental (#3283) --- .idea/compiler.xml | 2 + experimental/avalanche/Readme.md | 24 ++ experimental/avalanche/build.gradle | 42 ++++ .../avalanche/images/node-0-003.dot.png | Bin 0 -> 23175 bytes .../main/kotlin/net/corda/avalanche/Main.kt | 231 ++++++++++++++++++ .../kotlin/net/corda/avalanche/Parameters.kt | 38 +++ settings.gradle | 1 + 7 files changed, 338 insertions(+) create mode 100644 experimental/avalanche/Readme.md create mode 100644 experimental/avalanche/build.gradle create mode 100644 experimental/avalanche/images/node-0-003.dot.png create mode 100644 experimental/avalanche/src/main/kotlin/net/corda/avalanche/Main.kt create mode 100644 experimental/avalanche/src/main/kotlin/net/corda/avalanche/Parameters.kt diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 5cd3aa02f4..09964f0fcd 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -7,6 +7,8 @@ + + diff --git a/experimental/avalanche/Readme.md b/experimental/avalanche/Readme.md new file mode 100644 index 0000000000..e41775f225 --- /dev/null +++ b/experimental/avalanche/Readme.md @@ -0,0 +1,24 @@ +# Avalanche Simulation + +Experimental simulation of the Avalanche protocol by Team Rocket. This +implementation is incomplete. + +The paper: [Snowflake to Avalanche: A Novel Metastable Consensus Protocol Family for + Cryptocurrencies](https://ipfs.io/ipfs/QmUy4jh5mGNZvLkjies1RWM4YuvJh5o2FYopNPVYwrRVGV). + +## Running the Simulation +``` +./gradlew shadowJar +java -jar build/libs/avalanche-all.jar --dump-dags +``` + +### Visualising the DAGs +``` +for f in node-0-*.dot; do dot -Tpng -O $f; done +``` +The above command generates a number of PNG files `node-0-*.png`, showing the +evolution of the DAG. The nodes are labeled with the ID of the spent state, +the chit and confidence values. The prefered transaction of a conflict set is +labelled with a star. Accepted transactions are blue. + +![DAG](./images/node-0-003.dot.png) diff --git a/experimental/avalanche/build.gradle b/experimental/avalanche/build.gradle new file mode 100644 index 0000000000..ea6e2a4632 --- /dev/null +++ b/experimental/avalanche/build.gradle @@ -0,0 +1,42 @@ +buildscript { + ext.kotlin_version = '1.2.40' + + repositories { + mavenCentral() + jcenter() + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.3' + } +} + +plugins { + id "org.jetbrains.kotlin.jvm" + id 'com.github.johnrengelman.shadow' version '2.0.3' + id 'java' + id 'application' +} +repositories { + mavenCentral() +} +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + compile group: 'info.picocli', name: 'picocli', version: '3.0.1' + testCompile group: 'junit', name: 'junit', version: '4.12' +} +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} +compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} +mainClassName = "net.corda.avalanche.MainKt" +shadowJar { + baseName = "avalanche" +} diff --git a/experimental/avalanche/images/node-0-003.dot.png b/experimental/avalanche/images/node-0-003.dot.png new file mode 100644 index 0000000000000000000000000000000000000000..65c8bf911a5ebd094ec499ff8ccf38d941efce3b GIT binary patch literal 23175 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 { + + val parameters = Parameters() + CommandLine(parameters).parse(*args) + if (parameters.helpRequested) { + CommandLine.usage(Parameters(), System.out) + return + } + + val network = Network(parameters) + val n1 = network.nodes[0] + val c1 = mutableListOf() + val c2 = mutableListOf() + + repeat(parameters.nrTransactions) { + val n = network.nodes.shuffled(network.rng).first() + c1.add(n.onGenerateTx(it)) + if (network.rng.nextDouble() < parameters.doubleSpendRatio) { + val d = network.rng.nextInt(it) + println("double spend of $d") + val n2 = network.nodes.shuffled(network.rng).first() + c2.add(n2.onGenerateTx(d)) + } + + network.run() + + if (parameters.dumpDags) { + n1.dumpDag(File("node-0-${String.format("%03d", it)}.dot")) + } + println("$it: " + String.format("%.3f", fractionAccepted(n1))) + } + + val conflictSets = (c1 + c2).groupBy { it.data }.filterValues { it.size > 1 } + conflictSets.forEach { v, txs -> + val acceptance = txs.map { t -> network.nodes.map { it.isAccepted(t) }.any { it } } + require(acceptance.filter { it }.size < 2) { "More than one transaction of the conflict set of $v got accepted." } + } +} + +fun fractionAccepted(n: Node): Double { + val accepted = n.transactions.values.filter { n.isAccepted(it) }.size + return accepted.toDouble() / n.transactions.size +} + +data class Transaction( + val id: UUID, + val data: Int, + val parents: List, + var chit: Int = 0, + var confidence: Int = 0) { + override fun toString(): String { + return "T(id=${id.toString().take(5)}, data=$data, parents=[${parents.map {it.toString().take(5) }}, chit=$chit, confidence=$confidence)" + } +} + +data class ConflictSet( + var pref: Transaction, + var last: Transaction, + var count: Int, + var size: Int +) + +class Network(val parameters: Parameters) { + val rng = Random(parameters.seed) + val tx = Transaction(UUID.randomUUID(), -1, emptyList(), 1) + val nodes = (0..parameters.nrNodes).map { Node(it, parameters, tx.copy(),this, rng) } + fun run() { + nodes.forEach { it.avalancheLoop() } + } +} + +class Node(val id: Int, parameters: Parameters, val genesisTx: Transaction, val network: Network, val rng: Random) { + + val alpha = parameters.alpha + val k = parameters.k + val beta1 = parameters.beta1 + val beta2 = parameters.beta2 + + val transactions = LinkedHashMap(mapOf(genesisTx.id to genesisTx)) + val queried = mutableSetOf(genesisTx.id) + val conflicts = mutableMapOf(genesisTx.data to ConflictSet(genesisTx, genesisTx, 0, 1)) + + val accepted = mutableSetOf(genesisTx.id) + val parentSets = mutableMapOf>() + + fun onGenerateTx(data: Int): Transaction { + val edges = parentSelection() + val t = Transaction(UUID.randomUUID(), data, edges.map { it.id }) + onReceiveTx(this, t) + return t + } + + fun onReceiveTx(sender: Node, tx: Transaction) { + if (transactions.contains(tx.id)) return + tx.chit = 0 + tx.confidence = 0 + + tx.parents.forEach { + if (!transactions.contains(it)) { + val t = sender.onSendTx(it) + onReceiveTx(sender, t) + } + } + + if (!conflicts.contains(tx.data)) { + conflicts[tx.data] = ConflictSet(tx, tx, 0, 1) + } else { + conflicts[tx.data]!!.size++ + } + + transactions[tx.id] = tx + } + + fun onSendTx(id: UUID): Transaction { + return transactions[id]!!.copy() + } + + fun onQuery(sender: Node, tx: Transaction): Int { + onReceiveTx(sender, tx) + return if (isStronglyPreferred(tx)) 1 + else 0 + } + + fun avalancheLoop() { + val txs = transactions.values.filterNot { queried.contains(it.id) } + txs.forEach { tx -> + val sample = network.nodes.filterNot { it == this }.shuffled(rng).take(k) + val res = sample.map { + val txCopy = tx.copy() + it.onQuery(this, txCopy) + }.sum() + if (res >= alpha * k) { + tx.chit = 1 + // Update the preference for ancestors. + parentSet(tx).forEach { p -> + p.confidence += 1 + } + parentSet(tx).forEach { p-> + val cs = conflicts[p.data]!! + if (p.confidence > cs.pref.confidence) { + cs.pref = p + } + if (p != cs.last) { + cs.last = p + cs.count = 0 + } else { + cs.count++ + } + } + } + queried.add(tx.id) + } + } + + fun isPreferred(tx: Transaction): Boolean { + return conflicts[tx.data]!!.pref == tx + } + + fun isStronglyPreferred(tx: Transaction): Boolean { + return parentSet(tx).map { isPreferred(it) }.all { it } + } + + fun isAccepted(tx: Transaction): Boolean { + if (accepted.contains(tx.id)) return true + if (!queried.contains(tx.id)) return false + + val cs = conflicts[tx.data]!! + val parentsAccepted = tx.parents.map { accepted.contains(it) }.all { it } + val isAccepted = (parentsAccepted && cs.size == 1 && tx.confidence > beta1) || + (cs.pref == tx && cs.count > beta2) + if (isAccepted) accepted.add(tx.id) + return isAccepted + } + + fun parentSet(tx: Transaction): Set { + + if (parentSets.contains(tx.id)) return parentSets[tx.id]!! + + val parents = mutableSetOf() + var ps = tx.parents.toSet() + while (ps.isNotEmpty()) { + ps.forEach { + if (transactions.contains(it)) parents.add(transactions[it]!!) + } + ps = ps.flatMap { + if (transactions.contains(it)) { + transactions[it]!!.parents + } else { + emptyList() + } + }.toSet() + } + parentSets[tx.id] = parents + return parents + } + + fun parentSelection(): List { + val eps0 = transactions.values.filter { isStronglyPreferred(it) } + val eps1 = eps0.filter { conflicts[it.data]!!.size == 1 || it.confidence > 0 } + val parents = eps1.flatMap { parentSet(it) }.toSet().filterNot { eps1.contains(it) } + val fallback = if (transactions.size == 1) listOf(genesisTx) + else transactions.values.reversed().take(10).filter { !isAccepted(it) && conflicts[it.data]!!.size == 1 }.shuffled(network.rng).take(3) + require(parents.isNotEmpty() || fallback.isNotEmpty()) { "Unable to select parents." } + return if (parents.isEmpty()) return fallback else parents + } + + fun dumpDag(f: File) { + f.printWriter().use { out -> + out.println("digraph G {") + transactions.values.forEach { + val color = if (isAccepted(it)) "color=lightblue; style=filled;" else "" + val pref = if (conflicts[it.data]!!.size > 1 && isPreferred(it)) "*" else "" + val chit = if (queried.contains(it.id)) it.chit.toString() else "?" + out.println("\"${it.id}\" [$color label=\"${it.data}$pref, $chit, ${it.confidence}\"];") + } + transactions.values.forEach { + it.parents.forEach { p-> + out.println("\"${it.id}\" -> \"$p\";") + } + } + out.println("}") + } + } +} diff --git a/experimental/avalanche/src/main/kotlin/net/corda/avalanche/Parameters.kt b/experimental/avalanche/src/main/kotlin/net/corda/avalanche/Parameters.kt new file mode 100644 index 0000000000..8d13fcf799 --- /dev/null +++ b/experimental/avalanche/src/main/kotlin/net/corda/avalanche/Parameters.kt @@ -0,0 +1,38 @@ +package net.corda.avalanche + +import picocli.CommandLine + +class Parameters { + @CommandLine.Option(names = ["-n", "--num-transactions"], description = ["How many transactions to generate (default: 20)"]) + var nrTransactions: Int = 20 + + @CommandLine.Option(names = ["-d", "--double-spend-ratio"], description = ["The double spend ratio (default: 0.02)"]) + var doubleSpendRatio: Double = 0.02 + + @CommandLine.Option(names = ["-a", "--alpha"], description = ["The alpha parameter (default: 0.8)"]) + var alpha = 0.8 + + @CommandLine.Option(names = ["--num-nodes"], description = ["The number of nodes (default: 50)"]) + var nrNodes = 50 + + @CommandLine.Option(names = ["-k", "--sample-size"], description = ["The sample size (default `1 + nrNodes / 10`)"]) + var k = 1 + nrNodes / 10 + + @CommandLine.Option(names = ["--beta1"], description = ["The beta1 parameter (default: 5)"]) + var beta1 = 5 + + @CommandLine.Option(names = ["--beta2"], description = ["The beta1 parameter (default: 5)"]) + var beta2 = 5 + + @CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["Display help and exit"]) + var helpRequested = false + + @CommandLine.Option(names = ["--seed"], description = ["The RNG seed (default: 23)"]) + var seed = 23L + + @CommandLine.Option(names = ["--dump-dags"], description = ["Dump DAGs in dot format (default: false)"]) + var dumpDags = false + + @CommandLine.Option(names = ["-v", "--verbose"], description=["Verbose mode (default: false)"]) + var verbose = false +} diff --git a/settings.gradle b/settings.gradle index a597018e24..d841212cb4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,6 +16,7 @@ include 'client:rpc' include 'webserver' include 'webserver:webcapsule' include 'experimental' +include 'experimental:avalanche' include 'experimental:behave' include 'experimental:sandbox' include 'experimental:quasar-hook' From 18a6e2fa2a5adf4a373f901150c16f1181c7b7b0 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Mon, 4 Jun 2018 13:49:25 +0100 Subject: [PATCH 6/8] CORDA-1477 backport Ent test fixes (#3292) --- .../kotlin/net/corda/node/internal/AbstractNode.kt | 10 +++++++++- .../main/kotlin/net/corda/node/internal/NodeStartup.kt | 8 ++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 3cd9d28acc..1cebff87fc 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -663,7 +663,15 @@ abstract class AbstractNode(val configuration: NodeConfiguration, networkParameters: NetworkParameters): MutableList { checkpointStorage = DBCheckpointStorage() - verifyCheckpointsCompatible(checkpointStorage, cordappProvider.cordapps, versionInfo.platformVersion) + try { + verifyCheckpointsCompatible(checkpointStorage, cordappProvider.cordapps, versionInfo.platformVersion) + } catch (e: CheckpointIncompatibleException) { + if (configuration.devMode) { + Node.printWarning(e.message) + } else { + throw e + } + } val keyManagementService = makeKeyManagementService(identityService, keyPairs, database) _services = ServiceHubInternalImpl( diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index aa54420cb2..6da2426aed 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -129,12 +129,8 @@ open class NodeStartup(val args: Array) { cmdlineOptions.baseDirectory.createDirectories() startNode(conf, versionInfo, startTime, cmdlineOptions) } catch (e: CheckpointIncompatibleException) { - if (conf.devMode) { - Node.printWarning(e.message) - } else { - logger.error(e.message) - return false - } + logger.error(e.message) + return false } catch (e: Exception) { if (e is Errors.NativeIoException && e.message?.contains("Address already in use") == true) { logger.error("One of the ports required by the Corda node is already in use.") From 703f0cc86fecfd2e257ae1d0e49d4165b2c61f03 Mon Sep 17 00:00:00 2001 From: szymonsztuka Date: Mon, 4 Jun 2018 17:53:01 +0100 Subject: [PATCH 7/8] Liquibase script for a table name node_attchments_contracts change following fix for CORDA-1499 merged from OS. --- .../migration/node-core.changelog-master.xml | 2 +- .../migration/node-core.changelog-v3-GA.xml | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 node/src/main/resources/migration/node-core.changelog-v3-GA.xml diff --git a/node/src/main/resources/migration/node-core.changelog-master.xml b/node/src/main/resources/migration/node-core.changelog-master.xml index 2ef60795b7..2e039e8fae 100644 --- a/node/src/main/resources/migration/node-core.changelog-master.xml +++ b/node/src/main/resources/migration/node-core.changelog-master.xml @@ -16,5 +16,5 @@ - + diff --git a/node/src/main/resources/migration/node-core.changelog-v3-GA.xml b/node/src/main/resources/migration/node-core.changelog-v3-GA.xml new file mode 100644 index 0000000000..204fdaec6e --- /dev/null +++ b/node/src/main/resources/migration/node-core.changelog-v3-GA.xml @@ -0,0 +1,12 @@ + + + + + + + From da22c2511d0e89ff6a9b95c0c60811f1a0f0c512 Mon Sep 17 00:00:00 2001 From: szymonsztuka Date: Mon, 4 Jun 2018 19:00:38 +0100 Subject: [PATCH 8/8] Table name node_attchments_contracts change in SQL setup scritps following fix for CORDA-1499 merged from OS. --- .../resources/database-scripts/azure-sql/db-global-cleanup.sql | 2 +- .../src/main/resources/database-scripts/azure-sql/db-setup.sql | 2 +- .../src/main/resources/database-scripts/oracle/db-cleanup.sql | 2 +- .../src/main/resources/database-scripts/oracle/db-setup.sql | 2 +- .../resources/database-scripts/sql-server/db-global-cleanup.sql | 2 +- .../resources/database-scripts/sql-server/db-global-setup.sql | 2 +- .../src/main/resources/database-scripts/sql-server/db-setup.sql | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/testing/test-utils/src/main/resources/database-scripts/azure-sql/db-global-cleanup.sql b/testing/test-utils/src/main/resources/database-scripts/azure-sql/db-global-cleanup.sql index d6174089c0..7d770f34de 100644 --- a/testing/test-utils/src/main/resources/database-scripts/azure-sql/db-global-cleanup.sql +++ b/testing/test-utils/src/main/resources/database-scripts/azure-sql/db-global-cleanup.sql @@ -4,7 +4,7 @@ DROP TABLE IF EXISTS ${schema}.cp_states_v2_participants; DROP TABLE IF EXISTS ${schema}.dummy_linear_state_parts; DROP TABLE IF EXISTS ${schema}.dummy_linear_states_v2_parts; DROP TABLE IF EXISTS ${schema}.dummy_deal_states_parts; -DROP TABLE IF EXISTS ${schema}.node_attchments_contracts; +DROP TABLE IF EXISTS ${schema}.node_attachments_contracts; DROP TABLE IF EXISTS ${schema}.node_attachments; DROP TABLE IF EXISTS ${schema}.node_checkpoints; DROP TABLE IF EXISTS ${schema}.node_transactions; diff --git a/testing/test-utils/src/main/resources/database-scripts/azure-sql/db-setup.sql b/testing/test-utils/src/main/resources/database-scripts/azure-sql/db-setup.sql index 9604bc1854..dbbb9527ef 100644 --- a/testing/test-utils/src/main/resources/database-scripts/azure-sql/db-setup.sql +++ b/testing/test-utils/src/main/resources/database-scripts/azure-sql/db-setup.sql @@ -4,7 +4,7 @@ DROP TABLE IF EXISTS ${schema}.cp_states_v2_participants; DROP TABLE IF EXISTS ${schema}.dummy_linear_state_parts; DROP TABLE IF EXISTS ${schema}.dummy_linear_states_v2_parts; DROP TABLE IF EXISTS ${schema}.dummy_deal_states_parts; -DROP TABLE IF EXISTS ${schema}.node_attchments_contracts; +DROP TABLE IF EXISTS ${schema}.node_attachments_contracts; DROP TABLE IF EXISTS ${schema}.node_attachments; DROP TABLE IF EXISTS ${schema}.node_checkpoints; DROP TABLE IF EXISTS ${schema}.node_transactions; diff --git a/testing/test-utils/src/main/resources/database-scripts/oracle/db-cleanup.sql b/testing/test-utils/src/main/resources/database-scripts/oracle/db-cleanup.sql index dec3903578..97d27f2336 100644 --- a/testing/test-utils/src/main/resources/database-scripts/oracle/db-cleanup.sql +++ b/testing/test-utils/src/main/resources/database-scripts/oracle/db-cleanup.sql @@ -4,7 +4,7 @@ DROP TABLE ${schema}.cp_states_v2_participants CASCADE CONSTRAINTS DROP TABLE ${schema}.dummy_linear_state_parts CASCADE CONSTRAINTS DROP TABLE ${schema}.dummy_linear_states_v2_parts CASCADE CONSTRAINTS DROP TABLE ${schema}.dummy_deal_states_parts CASCADE CONSTRAINTS -DROP TABLE ${schema}.node_attchments_contracts CASCADE CONSTRAINTS +DROP TABLE ${schema}.node_attachments_contracts CASCADE CONSTRAINTS DROP TABLE ${schema}.node_attachments CASCADE CONSTRAINTS DROP TABLE ${schema}.node_checkpoints CASCADE CONSTRAINTS DROP TABLE ${schema}.node_transactions CASCADE CONSTRAINTS diff --git a/testing/test-utils/src/main/resources/database-scripts/oracle/db-setup.sql b/testing/test-utils/src/main/resources/database-scripts/oracle/db-setup.sql index 3ecb1c17c8..ccece802e2 100644 --- a/testing/test-utils/src/main/resources/database-scripts/oracle/db-setup.sql +++ b/testing/test-utils/src/main/resources/database-scripts/oracle/db-setup.sql @@ -6,7 +6,7 @@ DROP TABLE ${schema}.dummy_linear_states_v2_parts CASCADE CONSTRAINTS DROP TABLE ${schema}.dummy_deal_states_parts CASCADE CONSTRAINTS DROP TABLE ${schema}.dummy_test_states_parts CASCADE CONSTRAINTS DROP TABLE ${schema}.dummy_test_states CASCADE CONSTRAINTS -DROP TABLE ${schema}.node_attchments_contracts CASCADE CONSTRAINTS +DROP TABLE ${schema}.node_attachments_contracts CASCADE CONSTRAINTS DROP TABLE ${schema}.node_attachments CASCADE CONSTRAINTS DROP TABLE ${schema}.node_checkpoints CASCADE CONSTRAINTS DROP TABLE ${schema}.node_transactions CASCADE CONSTRAINTS diff --git a/testing/test-utils/src/main/resources/database-scripts/sql-server/db-global-cleanup.sql b/testing/test-utils/src/main/resources/database-scripts/sql-server/db-global-cleanup.sql index 7292a1e983..6dd0ef1510 100644 --- a/testing/test-utils/src/main/resources/database-scripts/sql-server/db-global-cleanup.sql +++ b/testing/test-utils/src/main/resources/database-scripts/sql-server/db-global-cleanup.sql @@ -4,7 +4,7 @@ DROP TABLE IF EXISTS ${schema}.cp_states_v2_participants; DROP TABLE IF EXISTS ${schema}.dummy_linear_state_parts; DROP TABLE IF EXISTS ${schema}.dummy_linear_states_v2_parts; DROP TABLE IF EXISTS ${schema}.dummy_deal_states_parts; -DROP TABLE IF EXISTS ${schema}.node_attchments_contracts; +DROP TABLE IF EXISTS ${schema}.node_attachments_contracts; DROP TABLE IF EXISTS ${schema}.node_attachments; DROP TABLE IF EXISTS ${schema}.node_checkpoints; DROP TABLE IF EXISTS ${schema}.node_transactions; diff --git a/testing/test-utils/src/main/resources/database-scripts/sql-server/db-global-setup.sql b/testing/test-utils/src/main/resources/database-scripts/sql-server/db-global-setup.sql index b6d26fd681..03fdaa0d8f 100644 --- a/testing/test-utils/src/main/resources/database-scripts/sql-server/db-global-setup.sql +++ b/testing/test-utils/src/main/resources/database-scripts/sql-server/db-global-setup.sql @@ -4,7 +4,7 @@ DROP TABLE IF EXISTS ${schema}.cp_states_v2_participants; DROP TABLE IF EXISTS ${schema}.dummy_linear_state_parts; DROP TABLE IF EXISTS ${schema}.dummy_linear_states_v2_parts; DROP TABLE IF EXISTS ${schema}.dummy_deal_states_parts; -DROP TABLE IF EXISTS ${schema}.node_attchments_contracts; +DROP TABLE IF EXISTS ${schema}.node_attachments_contracts; DROP TABLE IF EXISTS ${schema}.node_attachments; DROP TABLE IF EXISTS ${schema}.node_checkpoints; DROP TABLE IF EXISTS ${schema}.node_transactions; diff --git a/testing/test-utils/src/main/resources/database-scripts/sql-server/db-setup.sql b/testing/test-utils/src/main/resources/database-scripts/sql-server/db-setup.sql index 1ae9fb5dd8..02f97e118f 100644 --- a/testing/test-utils/src/main/resources/database-scripts/sql-server/db-setup.sql +++ b/testing/test-utils/src/main/resources/database-scripts/sql-server/db-setup.sql @@ -4,7 +4,7 @@ DROP TABLE IF EXISTS ${schema}.cp_states_v2_participants; DROP TABLE IF EXISTS ${schema}.dummy_linear_state_parts; DROP TABLE IF EXISTS ${schema}.dummy_linear_states_v2_parts; DROP TABLE IF EXISTS ${schema}.dummy_deal_states_parts; -DROP TABLE IF EXISTS ${schema}.node_attchments_contracts; +DROP TABLE IF EXISTS ${schema}.node_attachments_contracts; DROP TABLE IF EXISTS ${schema}.node_attachments; DROP TABLE IF EXISTS ${schema}.node_checkpoints; DROP TABLE IF EXISTS ${schema}.node_transactions;