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

View File

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

View File

@ -26,7 +26,7 @@ import net.corda.core.serialization.CordaSerializable
@CordaSerializable @CordaSerializable
interface ContractState { 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 * 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 * for this state, every participant has to be involved and approve the transaction

View File

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

View File

@ -34,6 +34,7 @@ import java.util.*
* @param recipient the party to pay the currency to. * @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 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 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. * for testing purposes.
*/ */
@StartableByRPC @StartableByRPC
@ -42,14 +43,17 @@ open class CashPaymentFlow(
val recipient: Party, val recipient: Party,
val anonymous: Boolean, val anonymous: Boolean,
progressTracker: ProgressTracker, 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. */ /** A straightforward constructor that constructs spends using cash states of any issuer. */
constructor(amount: Amount<Currency>, recipient: Party) : this(amount, recipient, true, tracker()) constructor(amount: Amount<Currency>, recipient: Party) : this(amount, recipient, true, tracker())
/** A straightforward constructor that constructs spends using cash states of any issuer. */ /** 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(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 @Suspendable
override fun call(): AbstractCashFlow.Result { override fun call(): AbstractCashFlow.Result {
@ -61,7 +65,7 @@ open class CashPaymentFlow(
} }
val anonymousRecipient = txIdentities[recipient] ?: recipient val anonymousRecipient = txIdentities[recipient] ?: recipient
progressTracker.currentStep = GENERATING_TX 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}") logger.info("Generating spend for: ${builder.lockId}")
// TODO: Have some way of restricting this to states the caller controls // TODO: Have some way of restricting this to states the caller controls
val (spendTX, keysForSigning) = try { val (spendTX, keysForSigning) = try {
@ -90,5 +94,6 @@ open class CashPaymentFlow(
class PaymentRequest(amount: Amount<Currency>, class PaymentRequest(amount: Amount<Currency>,
val recipient: Party, val recipient: Party,
val anonymous: Boolean, 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.

View File

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

View File

@ -35,8 +35,16 @@ public class CordaCaplet extends Capsule {
private Config parseConfigFile(List<String> args) { private Config parseConfigFile(List<String> args) {
String baseDirOption = getOption(args, "--base-directory"); 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(); this.baseDir = Paths.get((baseDirOption == null) ? "." : baseDirOption).toAbsolutePath().normalize().toString();
String config = getOption(args, "--config-file"); 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); File configFile = (config == null) ? new File(baseDir, "node.conf") : new File(config);
try { try {
ConfigParseOptions parseOptions = ConfigParseOptions.defaults().setAllowMissing(false); ConfigParseOptions parseOptions = ConfigParseOptions.defaults().setAllowMissing(false);

View File

@ -3,20 +3,73 @@ package net.corda.node.utilities
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.node.services.statemachine.StateMachineManagerInternal 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 * 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 * 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. * 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<*>) { //TODO: instead of replacing the progress tracker after constructing the flow logic, we should inject it during fiber deserialization
if (oldProgressTracker != null) { fun StateMachineManagerInternal.injectOldProgressTracker(oldTracker: ProgressTracker?, newFlowLogic: FlowLogic<*>) {
try { if (oldTracker != null) {
val field = newFlowLogic::class.java.getDeclaredField("progressTracker") val newTracker = newFlowLogic.progressTracker
field.isAccessible = true if (newTracker != null) {
field.set(newFlowLogic, oldProgressTracker) attachNewChildren(oldTracker, newTracker)
} catch (e: NoSuchFieldException) { replaceTracker(newFlowLogic, oldTracker)
// The flow does not use a progress tracker.
} }
} }
}
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
} }

View File

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

View File

@ -25,7 +25,6 @@ class CordappController : Controller() {
private val jvm by inject<JVMConfig>() private val jvm by inject<JVMConfig>()
private val cordappDir: Path = jvm.applicationDir.resolve(NodeConfig.cordappDirName) 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") private val finance: Path = cordappDir.resolve("corda-finance.jar")
/** /**
@ -40,11 +39,6 @@ class CordappController : Controller() {
finance.copyToDirectory(config.cordappsDir, StandardCopyOption.REPLACE_EXISTING) finance.copyToDirectory(config.cordappsDir, StandardCopyOption.REPLACE_EXISTING)
log.info("Installed 'Finance' cordapp") 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> { fun useCordappsFor(config: HasCordapps): List<Path> {
if (!config.cordappsDir.isDirectory()) return emptyList() if (!config.cordappsDir.isDirectory()) return emptyList()
return config.cordappsDir.walk(1) { paths -> return config.cordappsDir.walk(1) { paths ->
paths paths.filter(Path::isCordapp)
.filter(Path::isCordapp) .filter { !finance.endsWith(it.fileName) }
.filter { !bankOfCorda.endsWith(it.fileName) } .toList()
.filter { !finance.endsWith(it.fileName) }
.toList()
} }
} }
} }
fun Path.isCordapp(): Boolean = this.isReadable && this.fileName.toString().endsWith(".jar") val Path.isCordapp: Boolean get() = this.isReadable && this.fileName.toString().endsWith(".jar")
fun Path.inCordappsDir(): Boolean = (this.parent != null) && this.parent.endsWith("cordapps/") val Path.inCordappsDir: Boolean get() = (this.parent != null) && this.parent.endsWith("cordapps/")

View File

@ -125,7 +125,7 @@ class ProfileController : Controller() {
// Now extract all of the plugins from the ZIP file, // Now extract all of the plugins from the ZIP file,
// and copy them to a temporary location. // and copy them to a temporary location.
StreamSupport.stream(fs.rootDirectories.spliterator(), false) 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 -> .forEach { cordapp ->
val config = nodeIndex[cordapp.getName(0).toString()] ?: return@forEach val config = nodeIndex[cordapp.getName(0).toString()] ?: return@forEach

View File

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

View File

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