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
Show file tree
Hide file tree
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
Expand Up @@ -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'
Expand All @@ -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'
Expand All @@ -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/" }
}

Expand Down Expand Up @@ -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) {
Expand Down
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"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)
}
Expand Down Expand Up @@ -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>
)

/**
Expand Down Expand Up @@ -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 {
Expand Down
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

/**
Expand All @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Up @@ -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)

Expand Down Expand Up @@ -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." }
Expand Down Expand Up @@ -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
Expand Up @@ -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"
Expand Down Expand Up @@ -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}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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
Expand Up @@ -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 {
Expand All @@ -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
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading