Merge remote-tracking branch 'open/master' into andrius/os-merge-06-27

This commit is contained in:
Andrius Dagys 2018-06-27 10:41:28 +01:00
commit 4281061c47
13 changed files with 109 additions and 63 deletions
build.gradle
core/src/main/kotlin/net/corda/core/contracts
finance/src/main/kotlin/net/corda/finance/flows
gradle/wrapper
node/src
integration-test/kotlin/net/corda/node/services
main
java
kotlin/net/corda/node/utilities
tools
demobench
build.gradle
src/main/kotlin/net/corda/demobench
explorer
capsule
src/main/kotlin/net/corda/explorer/views/cordapps/cash

@ -34,20 +34,6 @@ buildscript {
ext.capsule_version = '1.0.1'
ext.asm_version = '5.0.4'
/*
* 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.5.0'
ext.jackson_version = '2.9.5'
ext.jetty_version = '9.4.7.v20170914'

@ -26,7 +26,7 @@ import net.corda.core.serialization.CordaSerializable
@CordaSerializable
interface ContractState {
/**
* A _participant_ is any party that is able to consume this state in a valid transaction.
* A _participant_ is any party that should be notified when the state is created or consumed.
*
* The list of participants is required for certain types of transactions. For example, when changing the notary
* for this state, every participant has to be involved and approve the transaction

@ -48,7 +48,7 @@ class CashIssueAndPaymentFlow(val amount: Amount<Currency>,
@Suspendable
override fun call(): Result {
subFlow(CashIssueFlow(amount, issueRef, notary))
return subFlow(CashPaymentFlow(amount, recipient, anonymous))
return subFlow(CashPaymentFlow(amount, recipient, anonymous, notary))
}
@CordaSerializable
@ -57,4 +57,4 @@ class CashIssueAndPaymentFlow(val amount: Amount<Currency>,
val recipient: Party,
val notary: Party,
val anonymous: Boolean) : AbstractRequest(amount)
}
}

@ -34,6 +34,7 @@ import java.util.*
* @param recipient the party to pay the currency to.
* @param issuerConstraint if specified, the payment will be made using only cash issued by the given parties.
* @param anonymous whether to anonymous the recipient party. Should be true for normal usage, but may be false
* @param notary if not specified, the first notary of the network map is selected
* for testing purposes.
*/
@StartableByRPC
@ -42,14 +43,17 @@ open class CashPaymentFlow(
val recipient: Party,
val anonymous: Boolean,
progressTracker: ProgressTracker,
val issuerConstraint: Set<Party> = emptySet()) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) {
val issuerConstraint: Set<Party> = emptySet(),
val notary: Party? = null) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) {
/** A straightforward constructor that constructs spends using cash states of any issuer. */
constructor(amount: Amount<Currency>, recipient: Party) : this(amount, recipient, true, tracker())
/** A straightforward constructor that constructs spends using cash states of any issuer. */
constructor(amount: Amount<Currency>, recipient: Party, anonymous: Boolean) : this(amount, recipient, anonymous, tracker())
constructor(request: PaymentRequest) : this(request.amount, request.recipient, request.anonymous, tracker(), request.issuerConstraint)
constructor(amount: Amount<Currency>, recipient: Party, anonymous: Boolean, notary: Party) : this(amount, recipient, anonymous, tracker(), notary = notary)
constructor(request: PaymentRequest) : this(request.amount, request.recipient, request.anonymous, tracker(), request.issuerConstraint, request.notary)
@Suspendable
override fun call(): AbstractCashFlow.Result {
@ -61,7 +65,7 @@ open class CashPaymentFlow(
}
val anonymousRecipient = txIdentities[recipient] ?: recipient
progressTracker.currentStep = GENERATING_TX
val builder = TransactionBuilder(notary = null)
val builder = TransactionBuilder(notary = notary ?: serviceHub.networkMapCache.notaryIdentities.first())
logger.info("Generating spend for: ${builder.lockId}")
// TODO: Have some way of restricting this to states the caller controls
val (spendTX, keysForSigning) = try {
@ -90,5 +94,6 @@ open class CashPaymentFlow(
class PaymentRequest(amount: Amount<Currency>,
val recipient: Party,
val anonymous: Boolean,
val issuerConstraint: Set<Party> = emptySet()) : AbstractRequest(amount)
val issuerConstraint: Set<Party> = emptySet(),
val notary: Party? = null) : AbstractRequest(amount)
}

Binary file not shown.

@ -21,6 +21,7 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.FlowIORequest
import net.corda.core.internal.ResolveTransactionsFlow
import net.corda.core.internal.bufferUntilSubscribed
import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.core.internal.notary.TrustedAuthorityNotaryService
import net.corda.core.internal.notary.UniquenessProvider
@ -55,8 +56,9 @@ import org.junit.rules.ExternalResource
import org.junit.rules.RuleChain
import org.slf4j.MDC
import java.security.PublicKey
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
class TimedFlowTestRule(val clusterSize: Int) : ExternalResource() {
@ -159,13 +161,11 @@ class TimedFlowTests {
val flow = NotaryFlow.Client(issueTx)
val progressTracker = flow.progressTracker
assertNotEquals(ProgressTracker.DONE, progressTracker.currentStep)
val progressTrackerDone = getDoneFuture(progressTracker)
val notarySignatures = services.startFlow(flow).resultFuture.get()
(issueTx + notarySignatures).verifyRequiredSignatures()
assertEquals(
ProgressTracker.DONE,
progressTracker.currentStep,
"Ensure the same progress tracker object is re-used after flow restart"
)
progressTrackerDone.get()
}
}
@ -178,14 +178,12 @@ class TimedFlowTests {
}
val flow = FinalityFlow(issueTx)
val progressTracker = flow.progressTracker
assertNotEquals(ProgressTracker.DONE, progressTracker.currentStep)
val progressTrackerDone = getDoneFuture(flow.progressTracker)
val stx = services.startFlow(flow).resultFuture.get()
stx.verifyRequiredSignatures()
assertEquals(
ProgressTracker.DONE,
progressTracker.currentStep,
"Ensure the same progress tracker object is re-used after flow restart"
)
progressTrackerDone.get()
}
}
@ -198,6 +196,13 @@ class TimedFlowTests {
)
}
/** Returns a future that completes when the [progressTracker] reaches the [ProgressTracker.DONE] step. */
private fun getDoneFuture(progressTracker: ProgressTracker): Future<ProgressTracker.Change> {
return progressTracker.changes.takeFirst {
it.progressTracker.currentStep == ProgressTracker.DONE
}.bufferUntilSubscribed().toBlocking().toFuture()
}
@CordaService
private class TestNotaryService(override val services: AppServiceHub, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
override val uniquenessProvider = mock<UniquenessProvider>()
@ -226,4 +231,4 @@ class TimedFlowTests {
return TransactionParts(stx.id, stx.inputs, stx.tx.timeWindow, stx.notary)
}
}
}
}

@ -35,8 +35,16 @@ public class CordaCaplet extends Capsule {
private Config parseConfigFile(List<String> args) {
String baseDirOption = getOption(args, "--base-directory");
// Ensure consistent behaviour with NodeArgsParser.kt, see CORDA-1598.
if (null == baseDirOption || baseDirOption.isEmpty()) {
baseDirOption = getOption(args, "-base-directory");
}
this.baseDir = Paths.get((baseDirOption == null) ? "." : baseDirOption).toAbsolutePath().normalize().toString();
String config = getOption(args, "--config-file");
// Same as for baseDirOption.
if (null == config || config.isEmpty()) {
config = getOption(args, "-config-file");
}
File configFile = (config == null) ? new File(baseDir, "node.conf") : new File(config);
try {
ConfigParseOptions parseOptions = ConfigParseOptions.defaults().setAllowMissing(false);

@ -3,20 +3,73 @@ package net.corda.node.utilities
import net.corda.core.flows.FlowLogic
import net.corda.core.utilities.ProgressTracker
import net.corda.node.services.statemachine.StateMachineManagerInternal
import java.lang.reflect.Field
/**
* The flow de-serialized from the checkpoint will contain a new instance of the progress tracker, which means that
* any existing flow observers would be lost. We need to replace it with the old progress tracker to ensure progress
* updates are correctly sent out after the flow is retried.
*
* If the new tracker contains any child trackers from sub-flows, we need to attach those to the old tracker as well.
*/
fun StateMachineManagerInternal.injectOldProgressTracker(oldProgressTracker: ProgressTracker?, newFlowLogic: FlowLogic<*>) {
if (oldProgressTracker != null) {
try {
val field = newFlowLogic::class.java.getDeclaredField("progressTracker")
field.isAccessible = true
field.set(newFlowLogic, oldProgressTracker)
} catch (e: NoSuchFieldException) {
// The flow does not use a progress tracker.
//TODO: instead of replacing the progress tracker after constructing the flow logic, we should inject it during fiber deserialization
fun StateMachineManagerInternal.injectOldProgressTracker(oldTracker: ProgressTracker?, newFlowLogic: FlowLogic<*>) {
if (oldTracker != null) {
val newTracker = newFlowLogic.progressTracker
if (newTracker != null) {
attachNewChildren(oldTracker, newTracker)
replaceTracker(newFlowLogic, oldTracker)
}
}
}
private fun attachNewChildren(oldTracker: ProgressTracker, newTracker: ProgressTracker) {
oldTracker.currentStep = newTracker.currentStep
oldTracker.steps.forEachIndexed { index, step ->
val newStep = newTracker.steps[index]
val newChildTracker = newTracker.getChildProgressTracker(newStep)
newChildTracker?.let { child ->
oldTracker.setChildProgressTracker(step, child)
}
}
resubscribeToChildren(oldTracker)
}
/**
* Re-subscribes to child tracker observables. When a nested progress tracker is deserialized from a checkpoint,
* it retains the child links, but does not automatically re-subscribe to the child changes.
*/
private fun resubscribeToChildren(tracker: ProgressTracker) {
tracker.steps.forEach {
val childTracker = tracker.getChildProgressTracker(it)
if (childTracker != null) {
tracker.setChildProgressTracker(it, childTracker)
resubscribeToChildren(childTracker)
}
}
}
/** Replaces the deserialized [ProgressTracker] in the [newFlowLogic] with the old one to retain old subscribers. */
private fun replaceTracker(newFlowLogic: FlowLogic<*>, oldProgressTracker: ProgressTracker?) {
val field = getProgressTrackerField(newFlowLogic)
field?.apply {
isAccessible = true
set(newFlowLogic, oldProgressTracker)
}
}
private fun getProgressTrackerField(newFlowLogic: FlowLogic<*>): Field? {
var clazz: Class<*> = newFlowLogic::class.java
var field: Field? = null
// The progress tracker field may have been overridden in an abstract superclass, so we have to traverse up
// the hierarchy.
while (clazz != FlowLogic::class.java) {
field = clazz.declaredFields.firstOrNull { it.name == "progressTracker" }
if (field == null) {
clazz = clazz.superclass
} else {
break
}
}
return field
}

@ -137,11 +137,6 @@ distributions {
into 'cordapps'
fileMode = 0444
}
from(project(':samples:bank-of-corda-demo').jar) {
rename 'bank-of-corda-demo-(.*)', 'bank-of-corda.jar'
into 'cordapps'
fileMode = 0444
}
}
}
}

@ -25,7 +25,6 @@ class CordappController : Controller() {
private val jvm by inject<JVMConfig>()
private val cordappDir: Path = jvm.applicationDir.resolve(NodeConfig.cordappDirName)
private val bankOfCorda: Path = cordappDir.resolve("bank-of-corda.jar")
private val finance: Path = cordappDir.resolve("corda-finance.jar")
/**
@ -40,11 +39,6 @@ class CordappController : Controller() {
finance.copyToDirectory(config.cordappsDir, StandardCopyOption.REPLACE_EXISTING)
log.info("Installed 'Finance' cordapp")
}
// Nodes cannot issue cash unless they contain the "Bank of Corda" cordapp.
if (config.nodeConfig.issuableCurrencies.isNotEmpty() && bankOfCorda.exists()) {
bankOfCorda.copyToDirectory(config.cordappsDir, StandardCopyOption.REPLACE_EXISTING)
log.info("Installed 'Bank of Corda' cordapp")
}
}
/**
@ -54,14 +48,12 @@ class CordappController : Controller() {
fun useCordappsFor(config: HasCordapps): List<Path> {
if (!config.cordappsDir.isDirectory()) return emptyList()
return config.cordappsDir.walk(1) { paths ->
paths
.filter(Path::isCordapp)
.filter { !bankOfCorda.endsWith(it.fileName) }
.filter { !finance.endsWith(it.fileName) }
.toList()
paths.filter(Path::isCordapp)
.filter { !finance.endsWith(it.fileName) }
.toList()
}
}
}
fun Path.isCordapp(): Boolean = this.isReadable && this.fileName.toString().endsWith(".jar")
fun Path.inCordappsDir(): Boolean = (this.parent != null) && this.parent.endsWith("cordapps/")
val Path.isCordapp: Boolean get() = this.isReadable && this.fileName.toString().endsWith(".jar")
val Path.inCordappsDir: Boolean get() = (this.parent != null) && this.parent.endsWith("cordapps/")

@ -125,7 +125,7 @@ class ProfileController : Controller() {
// Now extract all of the plugins from the ZIP file,
// and copy them to a temporary location.
StreamSupport.stream(fs.rootDirectories.spliterator(), false)
.flatMap { Files.find(it, 3, BiPredicate { p, attr -> p.inCordappsDir() && p.isCordapp() && attr.isRegularFile }) }
.flatMap { Files.find(it, 3, BiPredicate { p, attr -> p.inCordappsDir && p.isCordapp && attr.isRegularFile }) }
.forEach { cordapp ->
val config = nodeIndex[cordapp.getName(0).toString()] ?: return@forEach

@ -62,7 +62,7 @@ task buildExplorerJAR(type: FatCapsule, dependsOn: project(':tools:explorer').co
}
}
build.dependsOn buildExplorerJAR
assemble.dependsOn buildExplorerJAR
artifacts {
runtimeArtifacts buildExplorerJAR

@ -153,6 +153,8 @@ class NewTransaction : Fragment() {
}
}
private fun selectNotary(): Party = notaries.first().value!!
private fun newTransactionDialog(window: Window) = Dialog<AbstractCashFlow.AbstractRequest>().apply {
dialogPane = root
initOwner(window)
@ -162,8 +164,8 @@ class NewTransaction : Fragment() {
val issueRef = if (issueRef.value != null) OpaqueBytes.of(issueRef.value) else defaultRef
when (it) {
executeButton -> when (transactionTypeCB.value) {
CashTransaction.Issue -> IssueAndPaymentRequest(Amount.fromDecimal(amount.value, currencyChoiceBox.value), issueRef, partyBChoiceBox.value.party, notaries.first().value!!, anonymous)
CashTransaction.Pay -> PaymentRequest(Amount.fromDecimal(amount.value, currencyChoiceBox.value), partyBChoiceBox.value.party, anonymous = anonymous)
CashTransaction.Issue -> IssueAndPaymentRequest(Amount.fromDecimal(amount.value, currencyChoiceBox.value), issueRef, partyBChoiceBox.value.party, selectNotary(), anonymous)
CashTransaction.Pay -> PaymentRequest(Amount.fromDecimal(amount.value, currencyChoiceBox.value), partyBChoiceBox.value.party, anonymous = anonymous, notary = selectNotary())
CashTransaction.Exit -> ExitRequest(Amount.fromDecimal(amount.value, currencyChoiceBox.value), issueRef)
else -> null
}