Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP/NOMERGE External signing enhancements #209

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import static org.gradle.api.JavaVersion.VERSION_1_8
buildscript {
ext {
corda_release_group = 'net.corda'
corda_release_version = '4.3'
corda_release_version = '4.6-SNAPSHOT'
tokens_release_group = "com.r3.corda.lib.tokens"
tokens_release_version = "1.2-SNAPSHOT"
corda_gradle_plugins_version = '5.0.8'
@@ -13,6 +13,7 @@ buildscript {
slf4j_version = '1.7.25'
log4j_version = '2.9.1'
jackson_version = '2.9.0'
quasar_version = '0.7.11_r3'
confidential_id_release_group = "com.r3.corda.lib.ci"
confidential_id_release_version = "1.0"
aetherVersion = '1.0.0.v20140518'
@@ -24,7 +25,10 @@ buildscript {
jcenter()
mavenCentral()
mavenLocal()
maven { url "https://ci-artifactory.corda.r3cev.com/artifactory/corda-dependencies" }
maven { url "http://ci-artifactory.corda.r3cev.com/artifactory/corda-releases" }
maven { url "https://ci-artifactory.corda.r3cev.com/artifactory/corda-dependencies-dev" }
maven { url "https://ci-artifactory.corda.r3cev.com/artifactory/corda-dev" }
maven { url "https://repo.gradle.org/gradle/libs-releases-local/" }
}

@@ -71,9 +75,12 @@ subprojects {
maven { url "http://ci-artifactory.corda.r3cev.com/artifactory/corda-lib" }
maven { url "http://ci-artifactory.corda.r3cev.com/artifactory/corda-lib-dev" }
maven { url "http://ci-artifactory.corda.r3cev.com/artifactory/corda-lib" }
maven { url "https://ci-artifactory.corda.r3cev.com/artifactory/corda-dependencies" }
maven { url "https://ci-artifactory.corda.r3cev.com/artifactory/corda-dependencies-dev" }
maven { url "https://repo.gradle.org/gradle/libs-releases-local/" }
}


apply plugin: 'kotlin'

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
2 changes: 1 addition & 1 deletion contracts/build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
apply plugin: 'kotlin-jpa'
apply plugin: 'net.corda.plugins.cordapp'

if (!(corda_release_version in ['4.1'])) {
if (!(corda_release_version in ['4.1', '4.6-SNAPSHOT'])) {
apply from: "${rootProject.projectDir}/deterministic.gradle"
}

Original file line number Diff line number Diff line change
@@ -35,7 +35,8 @@ abstract class AbstractTokenContract<AT : AbstractToken> : Contract {
inputs: List<IndexedState<AT>>,
outputs: List<IndexedState<AT>>,
attachments: List<Attachment>,
references: List<StateAndRef<ContractState>>
references: List<StateAndRef<ContractState>>,
summary: List<String>
) {
// Get the JAR which implements the TokenType for this group.
val jarHash: SecureHash? = verifyAllTokensUseSameTypeJar(
@@ -49,7 +50,7 @@ abstract class AbstractTokenContract<AT : AbstractToken> : Contract {
// Issuances should only contain one issue command.
is IssueTokenCommand -> verifyIssue(commands.single(), inputs, outputs, attachments, references)
// Moves may contain more than one move command.
is MoveTokenCommand -> verifyMove(commands, inputs, outputs, attachments, references)
is MoveTokenCommand -> verifyMove(commands, inputs, outputs, attachments, references, summary)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redeem doesn't need summary? It should be signed by token holder too

// Redeems must only contain one redeem command.
is RedeemTokenCommand -> verifyRedeem(commands.single(), inputs, outputs, attachments, references)
}
@@ -77,7 +78,8 @@ abstract class AbstractTokenContract<AT : AbstractToken> : Contract {
inputs: List<IndexedState<AT>>,
outputs: List<IndexedState<AT>>,
attachments: List<Attachment>,
references: List<StateAndRef<ContractState>>
references: List<StateAndRef<ContractState>>,
summary: List<String>
)

/**
@@ -120,12 +122,11 @@ abstract class AbstractTokenContract<AT : AbstractToken> : Contract {
"TokenCommand type per group! For example: You cannot map an Issue AND a Move command " +
"to one group of tokens in a transaction."
}
dispatchOnCommand(commands, group.inputs, group.outputs, tx.attachments, tx.references)
dispatchOnCommand(commands, group.inputs, group.outputs, tx.attachments, tx.references, tx.summary)
}


val allMatchedCommands = groupsAndCommands.map { it.first.first() }.toSet()
val extraCommands = (tokenCommands - allMatchedCommands).toSet()
}

private fun groupMatchesCommand(it: CommandWithParties<TokenCommand>, group: IndexedInOutGroup<AbstractToken, TokenInfo>): Boolean {
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package com.r3.corda.lib.tokens.contracts

import com.r3.corda.lib.tokens.contracts.commands.IssueTokenCommand
import com.r3.corda.lib.tokens.contracts.commands.MoveTokenCommand
import com.r3.corda.lib.tokens.contracts.commands.RedeemTokenCommand
import com.r3.corda.lib.tokens.contracts.commands.TokenCommand
import com.r3.corda.lib.tokens.contracts.states.AbstractToken
import com.r3.corda.lib.tokens.contracts.states.FungibleToken
import com.r3.corda.lib.tokens.contracts.types.IssuedTokenType
import com.r3.corda.lib.tokens.contracts.utilities.sumTokenStatesOrZero
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.toStringShort
import net.corda.core.internal.uncheckedCast
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.contracts.DescribableContract
import java.security.PublicKey

/**
@@ -22,7 +29,7 @@ import java.security.PublicKey
* to call the super method to handle the existing commands.
* 3. Add a method to handle the new command in the new sub-class contract.
*/
open class FungibleTokenContract : AbstractTokenContract<FungibleToken>() {
open class FungibleTokenContract : AbstractTokenContract<FungibleToken>(), DescribableContract {
override val accepts: Class<FungibleToken> get() = uncheckedCast(FungibleToken::class.java)

companion object {
@@ -65,8 +72,11 @@ open class FungibleTokenContract : AbstractTokenContract<FungibleToken>() {
inputs: List<IndexedState<FungibleToken>>,
outputs: List<IndexedState<FungibleToken>>,
attachments: List<Attachment>,
references: List<StateAndRef<ContractState>>
references: List<StateAndRef<ContractState>>,
summary: List<String>
) {
val ourSummary = describeTransaction(inputs.map{ it.state}, outputs.map { it.state }, moveCommands.map { it.value }, attachments.map { it.id })
require(ourSummary == summary) { "The summary generated by the contract: '${ourSummary}' does not match the summary present in the tx: '${summary}'" }
// Commands are grouped by Token Type, so we just need a token reference.
val issuedToken: IssuedTokenType = moveCommands.first().value.token
// There must be inputs and outputs present.
@@ -138,4 +148,34 @@ open class FungibleTokenContract : AbstractTokenContract<FungibleToken>() {
}
}
}

override fun describeTransaction(
inputs: List<TransactionState<ContractState>>,
outputs: List<TransactionState<ContractState>>,
commands: List<CommandData>,
attachments: List<SecureHash>): List<String> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are references not needed?

return when(commands.first()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if this throws on empty list? add edge cases

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we check only first? what if we have more command data?

//verify the type jar presence and correctness
// Issuances should only contain one issue command.
is IssueTokenCommand -> listOf("")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be implemented too?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or at least some todo

// Moves may contain more than one move command.
is MoveTokenCommand -> constructMoveDescription(inputs, outputs)
// Redeems must only contain one redeem command.
is RedeemTokenCommand -> listOf("I AM A REDEPMPTION SONG)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😂

else -> emptyList()
}
}


private fun constructMoveDescription(inputs: List<TransactionState<ContractState>>, outputs: List<TransactionState<ContractState>>): List<String> {
val moveFrom = inputs.first().asFungibleToken().holder.owningKey
val moveTo = (outputs.map { it.asFungibleToken().holder }).firstOrNull { p -> p.owningKey != moveFrom }?.owningKey ?: throw IllegalArgumentException("")
val amountToMove = outputs.filter { p -> p.asFungibleToken().holder.owningKey != moveFrom }.first().asFungibleToken().amount
amountToMove.displayTokenSize
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what these lines do?

amountToMove.quantity
return listOf("Move ${amountToMove.toDecimal()} ${amountToMove.token.tokenType.tokenIdentifier} from key: ${moveFrom.toStringShort()} to key: ${moveTo.toStringShort()}")
}

private fun TransactionState<ContractState>.asFungibleToken() = this.data as FungibleToken

}
Original file line number Diff line number Diff line change
@@ -2,17 +2,16 @@ package com.r3.corda.lib.tokens.contracts

import com.r3.corda.lib.tokens.contracts.commands.TokenCommand
import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken
import net.corda.core.contracts.Attachment
import net.corda.core.contracts.CommandWithParties
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.internal.uncheckedCast
import net.corda.core.transactions.LedgerTransaction
import java.security.PublicKey

/**
* See kdoc for [FungibleTokenContract].
*/
class NonFungibleTokenContract : AbstractTokenContract<NonFungibleToken>() {
class NonFungibleTokenContract : AbstractTokenContract<NonFungibleToken>(), DescribableContract {

override val accepts: Class<NonFungibleToken> get() = uncheckedCast(NonFungibleToken::class.java)

@@ -47,7 +46,8 @@ class NonFungibleTokenContract : AbstractTokenContract<NonFungibleToken>() {
inputs: List<IndexedState<NonFungibleToken>>,
outputs: List<IndexedState<NonFungibleToken>>,
attachments: List<Attachment>,
references: List<StateAndRef<ContractState>>
references: List<StateAndRef<ContractState>>,
summary: List<String>
) {
// There must be inputs and outputs present.
require(inputs.isNotEmpty()) { "When moving a non fungible token, there must be one input state present." }
@@ -90,4 +90,13 @@ class NonFungibleTokenContract : AbstractTokenContract<NonFungibleToken>() {
"Holders of redeemed states must be the signing parties."
}
}

override fun describeTransaction(
inputs: List<TransactionState<ContractState>>,
outputs: List<TransactionState<ContractState>>,
commands: List<CommandData>,
attachments: List<SecureHash>
): List<String> {
TODO("Not yet implemented")
}
}
11 changes: 5 additions & 6 deletions workflows/build.gradle
Original file line number Diff line number Diff line change
@@ -3,8 +3,8 @@ apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.cordapp'

cordapp {
targetPlatformVersion 5
minimumPlatformVersion 5
targetPlatformVersion 6
minimumPlatformVersion 6
workflow {
name "Token SDK Workflows"
vendor "R3"
@@ -45,10 +45,9 @@ dependencies {
// Kotlin.
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"

// Corda dependencies.
cordaCompile("$corda_release_group:corda-core:$corda_release_version") {
changing = true
}
// Corda integration dependencies
cordaCompile("$corda_release_group:corda-core:$corda_release_version")
testCompile "$corda_release_group:corda-node-driver:$corda_release_version"

// Logging.
testCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
Original file line number Diff line number Diff line change
@@ -21,10 +21,13 @@ import net.corda.core.utilities.ProgressTracker
*
* @property participantSessions a list of flow participantSessions for the transaction participants.
* @property observerSessions a list of flow participantSessions for the transaction observers.
* @property haltForExternalSigning whether to halt the flow thread while waiting for signatures if a call to an external
* service is required to obtain them, to prevent blocking other work
*/
abstract class AbstractMoveTokensFlow : FlowLogic<SignedTransaction>() {
abstract val participantSessions: List<FlowSession>
abstract val observerSessions: List<FlowSession>
abstract val haltForExternalSigning: Boolean

companion object {
object GENERATE : ProgressTracker.Step("Generating tokens to move.")
@@ -58,7 +61,8 @@ abstract class AbstractMoveTokensFlow : FlowLogic<SignedTransaction>() {
val signedTransaction = subFlow(
ObserverAwareFinalityFlow(
transactionBuilder = transactionBuilder,
allSessions = participantSessions + observerSessions
allSessions = participantSessions + observerSessions,
haltForExternalSigning = haltForExternalSigning
)
)
progressTracker.currentStep = UPDATING
Original file line number Diff line number Diff line change
@@ -26,6 +26,8 @@ import net.corda.core.transactions.SignedTransaction
* @param changeHolder holder of the change outputs, it can be confidential identity
* @param observerSessions optional sessions with the observer nodes, to witch the transaction will be broadcasted
* @param queryCriteria additional criteria for token selection
* @param haltForExternalSigning optional - halt the flow thread while waiting for signatures if a call to an external
* service is required to obtain them, to prevent blocking other work
*/
class ConfidentialMoveFungibleTokensFlow
@JvmOverloads
@@ -34,7 +36,8 @@ constructor(
val participantSessions: List<FlowSession>,
val changeHolder: AbstractParty,
val observerSessions: List<FlowSession> = emptyList(),
val queryCriteria: QueryCriteria? = null
val queryCriteria: QueryCriteria? = null,
val haltForExternalSigning: Boolean = false
) : FlowLogic<SignedTransaction>() {

@JvmOverloads
@@ -43,9 +46,9 @@ constructor(
participantSessions: List<FlowSession>,
changeHolder: AbstractParty,
queryCriteria: QueryCriteria? = null,
observerSessions: List<FlowSession> = emptyList()

) : this(listOf(partyAndAmount), participantSessions, changeHolder, observerSessions, queryCriteria)
observerSessions: List<FlowSession> = emptyList(),
haltForExternalSigning: Boolean = false
) : this(listOf(partyAndAmount), participantSessions, changeHolder, observerSessions, queryCriteria, haltForExternalSigning)

@Suspendable
override fun call(): SignedTransaction {
@@ -61,6 +64,6 @@ constructor(
participantSessions.forEach { it.send(TransactionRole.PARTICIPANT) }
observerSessions.forEach { it.send(TransactionRole.OBSERVER) }
val confidentialOutputs = subFlow(ConfidentialTokensFlow(outputs, participantSessions))
return subFlow(MoveTokensFlow(inputs, confidentialOutputs, participantSessions, observerSessions))
return subFlow(MoveTokensFlow(inputs, confidentialOutputs, participantSessions, observerSessions, haltForExternalSigning))
}
}
Original file line number Diff line number Diff line change
@@ -22,14 +22,17 @@ import net.corda.core.transactions.SignedTransaction
* @param participantSessions sessions with the participants of move transaction
* @param observerSessions optional sessions with the observer nodes, to witch the transaction will be broadcasted
* @param queryCriteria additional criteria for token selection
* @param haltForExternalSigning optional - halt the flow thread while waiting for signatures if a call to an external
* service is required to obtain them, to prevent blocking other work
*/
class ConfidentialMoveNonFungibleTokensFlow
@JvmOverloads
constructor(
val partyAndToken: PartyAndToken,
val participantSessions: List<FlowSession>,
val observerSessions: List<FlowSession> = emptyList(),
val queryCriteria: QueryCriteria? = null
val queryCriteria: QueryCriteria? = null,
val haltForExternalSigning: Boolean = false
) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
@@ -38,6 +41,6 @@ constructor(
participantSessions.forEach { it.send(TransactionRole.PARTICIPANT) }
observerSessions.forEach { it.send(TransactionRole.OBSERVER) }
val confidentialOutput = subFlow(ConfidentialTokensFlow(listOf(output), participantSessions)).single()
return subFlow(MoveTokensFlow(input, confidentialOutput, participantSessions, observerSessions))
return subFlow(MoveTokensFlow(listOf(input), listOf(confidentialOutput), participantSessions, observerSessions, haltForExternalSigning))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need to change it to listOf(input) etc

}
}
Original file line number Diff line number Diff line change
@@ -21,6 +21,8 @@ import net.corda.core.transactions.TransactionBuilder
* @param queryCriteria additional criteria for token selection
* @param changeHolder optional holder of the change outputs, it can be confidential identity, if not specified it
* defaults to caller's legal identity
* @param haltForExternalSigning optional - halt the flow thread while waiting for signatures if a call to an external
* service is required to obtain them, to prevent blocking other work
*/
class MoveFungibleTokensFlow
@JvmOverloads
@@ -29,7 +31,8 @@ constructor(
override val participantSessions: List<FlowSession>,
override val observerSessions: List<FlowSession> = emptyList(),
val queryCriteria: QueryCriteria? = null,
val changeHolder: AbstractParty? = null
val changeHolder: AbstractParty? = null,
override val haltForExternalSigning: Boolean = false
) : AbstractMoveTokensFlow() {

@JvmOverloads
@@ -38,8 +41,9 @@ constructor(
queryCriteria: QueryCriteria? = null,
participantSessions: List<FlowSession>,
observerSessions: List<FlowSession> = emptyList(),
changeHolder: AbstractParty? = null
) : this(listOf(partyAndAmount), participantSessions, observerSessions, queryCriteria, changeHolder)
changeHolder: AbstractParty? = null,
haltForExternalSigning: Boolean = false
) : this(listOf(partyAndAmount), participantSessions, observerSessions, queryCriteria, changeHolder, haltForExternalSigning)

@Suspendable
override fun addMove(transactionBuilder: TransactionBuilder) {
Original file line number Diff line number Diff line change
@@ -18,14 +18,17 @@ import net.corda.core.transactions.TransactionBuilder
* @param participantSessions sessions with the participants of move transaction
* @param observerSessions optional sessions with the observer nodes, to witch the transaction will be broadcasted
* @param queryCriteria additional criteria for token selection
* @param haltForExternalSigning optional - halt the flow thread while waiting for signatures if a call to an external
* service is required to obtain them, to prevent blocking other work
*/
class MoveNonFungibleTokensFlow
@JvmOverloads
constructor(
val partyAndToken: PartyAndToken,
override val participantSessions: List<FlowSession>,
override val observerSessions: List<FlowSession> = emptyList(),
val queryCriteria: QueryCriteria?
val queryCriteria: QueryCriteria?,
override val haltForExternalSigning: Boolean = false
) : AbstractMoveTokensFlow() {
@Suspendable
override fun addMove(transactionBuilder: TransactionBuilder) {
Original file line number Diff line number Diff line change
@@ -18,22 +18,26 @@ import net.corda.core.transactions.TransactionBuilder
* @param outputs list of result token outputs
* @param participantSessions session with the participants of move tokens transaction
* @param observerSessions session with optional observers of the redeem transaction
* @param haltForExternalSigning optional - halt the flow thread while waiting for signatures if a call to an external
* service is required to obtain them, to prevent blocking other work
*/
class MoveTokensFlow
@JvmOverloads
constructor(
val inputs: List<StateAndRef<AbstractToken>>,
val outputs: List<AbstractToken>,
override val participantSessions: List<FlowSession>,
override val observerSessions: List<FlowSession> = emptyList()
override val observerSessions: List<FlowSession> = emptyList(),
override val haltForExternalSigning: Boolean = false
) : AbstractMoveTokensFlow() {
@JvmOverloads
constructor(
input: StateAndRef<AbstractToken>,
output: AbstractToken,
participantSessions: List<FlowSession>,
observerSessions: List<FlowSession> = emptyList()
) : this(listOf(input), listOf(output), participantSessions, observerSessions)
observerSessions: List<FlowSession> = emptyList(),
haltForExternalSigning: Boolean = false
) : this(listOf(input), listOf(output), participantSessions, observerSessions, haltForExternalSigning)

@Suspendable
override fun addMove(transactionBuilder: TransactionBuilder) {
Original file line number Diff line number Diff line change
@@ -26,6 +26,8 @@ import net.corda.core.transactions.SignedTransaction
* @param queryCriteria additional criteria for token selection
* @param changeHolder optional holder of the change outputs, it can be confidential identity, if not specified it
* defaults to caller's legal identity
* @param haltForExternalSigning optional - halt the flow thread while waiting for signatures if a call to an external
* service is required to obtain them, to prevent blocking other work
*/
@StartableByService
@StartableByRPC
@@ -36,16 +38,18 @@ constructor(
val partiesAndAmounts: List<PartyAndAmount<TokenType>>,
val observers: List<Party> = emptyList(),
val queryCriteria: QueryCriteria? = null,
val changeHolder: AbstractParty? = null
val changeHolder: AbstractParty? = null,
val haltForExternalSigning: Boolean = false
) : FlowLogic<SignedTransaction>() {

@JvmOverloads
constructor(
partyAndAmount: PartyAndAmount<TokenType>,
observers: List<Party> = emptyList(),
queryCriteria: QueryCriteria? = null,
changeHolder: AbstractParty? = null
) : this(listOf(partyAndAmount), observers, queryCriteria, changeHolder)
changeHolder: AbstractParty? = null,
haltForExternalSigning: Boolean = false
) : this(listOf(partyAndAmount), observers, queryCriteria, changeHolder, haltForExternalSigning)

constructor(amount: Amount<TokenType>, holder: AbstractParty) : this(PartyAndAmount(holder, amount), emptyList())

@@ -59,7 +63,8 @@ constructor(
participantSessions = participantSessions,
observerSessions = observerSessions,
queryCriteria = queryCriteria,
changeHolder = changeHolder
changeHolder = changeHolder,
haltForExternalSigning = haltForExternalSigning
))
}
}
@@ -83,6 +88,8 @@ class MoveFungibleTokensHandler(val otherSession: FlowSession) : FlowLogic<Unit>
* @param partyAndToken pairing party - token that is to be moved to that party
* @param observers optional observing parties to which the transaction will be broadcast
* @param queryCriteria additional criteria for token selection
* @param haltForExternalSigning optional - halt the flow thread while waiting for signatures if a call to an external
* service is required to obtain them, to prevent blocking other work
*/
@StartableByService
@StartableByRPC
@@ -92,7 +99,8 @@ class MoveNonFungibleTokens
constructor(
val partyAndToken: PartyAndToken,
val observers: List<Party> = emptyList(),
val queryCriteria: QueryCriteria? = null
val queryCriteria: QueryCriteria? = null,
val haltForExternalSigning: Boolean = false
) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
@@ -102,7 +110,8 @@ constructor(
partyAndToken = partyAndToken,
participantSessions = participantSessions,
observerSessions = observerSessions,
queryCriteria = queryCriteria
queryCriteria = queryCriteria,
haltForExternalSigning = haltForExternalSigning
))
}
}
@@ -129,6 +138,8 @@ class MoveNonFungibleTokensHandler(val otherSession: FlowSession) : FlowLogic<Un
* @param observers optional observing parties to which the transaction will be broadcast
* @param queryCriteria additional criteria for token selection
* @param changeHolder holder of the change outputs, it can be confidential identity
* @param haltForExternalSigning optional - halt the flow thread while waiting for signatures if a call to an external
* service is required to obtain them, to prevent blocking other work
*/
@StartableByService
@StartableByRPC
@@ -137,15 +148,17 @@ class ConfidentialMoveFungibleTokens(
val partiesAndAmounts: List<PartyAndAmount<TokenType>>,
val observers: List<Party>,
val queryCriteria: QueryCriteria? = null,
val changeHolder: AbstractParty? = null
val changeHolder: AbstractParty? = null,
val haltForExternalSigning: Boolean = false
) : FlowLogic<SignedTransaction>() {

constructor(
partyAndAmount: PartyAndAmount<TokenType>,
observers: List<Party>,
queryCriteria: QueryCriteria? = null,
changeHolder: AbstractParty? = null
) : this(listOf(partyAndAmount), observers, queryCriteria, changeHolder)
changeHolder: AbstractParty? = null,
haltForExternalSigning: Boolean = false
) : this(listOf(partyAndAmount), observers, queryCriteria, changeHolder, haltForExternalSigning)

@Suspendable
override fun call(): SignedTransaction {
@@ -167,7 +180,8 @@ class ConfidentialMoveFungibleTokens(
participantSessions = participantSessions,
observerSessions = observerSessions,
queryCriteria = queryCriteria,
changeHolder = confidentialHolder
changeHolder = confidentialHolder,
haltForExternalSigning = haltForExternalSigning
))
}
}
@@ -191,14 +205,17 @@ class ConfidentialMoveFungibleTokensHandler(val otherSession: FlowSession) : Flo
* @param partyAndToken list of pairing party - token that is to be moved to that party
* @param observers optional observing parties to which the transaction will be broadcast
* @param queryCriteria additional criteria for token selection
* @param haltForExternalSigning optional - halt the flow thread while waiting for signatures if a call to an external
* service is required to obtain them, to prevent blocking other work
*/
@StartableByService
@StartableByRPC
@InitiatingFlow
class ConfidentialMoveNonFungibleTokens(
val partyAndToken: PartyAndToken,
val observers: List<Party>,
val queryCriteria: QueryCriteria? = null
val queryCriteria: QueryCriteria? = null,
val haltForExternalSigning: Boolean = false
) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
@@ -208,7 +225,8 @@ class ConfidentialMoveNonFungibleTokens(
partyAndToken = partyAndToken,
participantSessions = participantSessions,
observerSessions = observerSessions,
queryCriteria = queryCriteria
queryCriteria = queryCriteria,
haltForExternalSigning = haltForExternalSigning
))
}
}
Original file line number Diff line number Diff line change
@@ -8,13 +8,22 @@ import com.r3.corda.lib.tokens.workflows.utilities.requireSessionsForParticipant
import com.r3.corda.lib.tokens.workflows.utilities.toWellKnownParties
import net.corda.core.contracts.CommandWithParties
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowExternalAsyncOperation
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import java.security.PublicKey
import java.util.concurrent.Callable
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import java.util.function.Supplier

/**
* This flow is a wrapper around [FinalityFlow] and properly handles broadcasting transactions to observers (those which
@@ -31,16 +40,22 @@ import net.corda.core.transactions.TransactionBuilder
* transaction with observers, notice that this flow can be called either with [transactionBuilder] or
* [signedTransaction]
* @property allSessions a set of sessions for, at least, all the transaction participants and maybe observers
* @property haltForExternalSigning optional - halt the flow thread while waiting for signatures if a call to an external
* service is required to obtain them, to prevent blocking other work
*/
class ObserverAwareFinalityFlow private constructor(
val allSessions: List<FlowSession>,
val signedTransaction: SignedTransaction? = null,
val transactionBuilder: TransactionBuilder? = null
val transactionBuilder: TransactionBuilder? = null,
val haltForExternalSigning: Boolean = false
) : FlowLogic<SignedTransaction>() {

constructor(transactionBuilder: TransactionBuilder, allSessions: List<FlowSession>)
: this(allSessions, null, transactionBuilder)

constructor(transactionBuilder: TransactionBuilder, allSessions: List<FlowSession>, haltForExternalSigning: Boolean)
: this(allSessions, null, transactionBuilder, haltForExternalSigning)

constructor(signedTransaction: SignedTransaction, allSessions: List<FlowSession>)
: this(allSessions, signedTransaction)

@@ -68,10 +83,42 @@ class ObserverAwareFinalityFlow private constructor(
}
// Sign and finalise the transaction, obtaining the signing keys required from the LedgerTransaction.
val ourSigningKeys = ledgerTransaction.ourSigningKeys(serviceHub)
val stx = transactionBuilder?.let {
serviceHub.signInitialTransaction(it, signingPubKeys = ourSigningKeys)
} ?: signedTransaction
?: throw IllegalArgumentException("Didn't provide transactionBuilder nor signedTransaction to the flow.")

val stx = if (haltForExternalSigning) {
await(SignTransactionOperation(transactionBuilder!!, ourSigningKeys, serviceHub))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove !! because it throws null pointer exception without explaining what happened. Have meaningful error handling if the transaction builder is null

} else {
transactionBuilder?.let {
serviceHub.signInitialTransaction(it, signingPubKeys = ourSigningKeys)
} ?: signedTransaction
?: throw IllegalArgumentException("Didn't provide transactionBuilder nor signedTransaction to the flow.")
}

return subFlow(FinalityFlow(transaction = stx, sessions = finalSessions))
}

}

class SignTransactionOperation(private val transactionBuilder: TransactionBuilder, private val signingKeys: List<PublicKey>,
private val serviceHub: ServiceHub) : FlowExternalAsyncOperation<SignedTransaction> {

override fun execute(deduplicationId: String) : CompletableFuture<SignedTransaction> {
val executor = Executors.newFixedThreadPool(2)
return CompletableFuture.supplyAsync(
Supplier {
transactionBuilder.let {
val future = executor.submit(Callable {
serviceHub.signInitialTransaction(it, signingPubKeys = signingKeys)
})
try {
future.get(30, TimeUnit.MINUTES)
} finally {
future.cancel(true)
executor.shutdown()
}
}
},
executor
)
}

}