diff --git a/core/src/main/kotlin/net/corda/flows/AbstractStateReplacementFlow.kt b/core/src/main/kotlin/net/corda/flows/AbstractStateReplacementFlow.kt index a3bd012535..607c16bede 100644 --- a/core/src/main/kotlin/net/corda/flows/AbstractStateReplacementFlow.kt +++ b/core/src/main/kotlin/net/corda/flows/AbstractStateReplacementFlow.kt @@ -63,7 +63,7 @@ abstract class AbstractStateReplacementFlow { val me = listOf(myKey) val signatures = if (participants == me) { - listOf(getNotarySignature(stx)) + getNotarySignatures(stx) } else { collectSignatures(participants - me, stx) } @@ -87,7 +87,7 @@ abstract class AbstractStateReplacementFlow { val allPartySignedTx = stx + participantSignatures - val allSignatures = participantSignatures + getNotarySignature(allPartySignedTx) + val allSignatures = participantSignatures + getNotarySignatures(allPartySignedTx) parties.forEach { send(it, allSignatures) } return allSignatures @@ -105,7 +105,7 @@ abstract class AbstractStateReplacementFlow { } @Suspendable - private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.WithKey { + private fun getNotarySignatures(stx: SignedTransaction): List { progressTracker.currentStep = NOTARY try { return subFlow(NotaryFlow.Client(stx)) diff --git a/core/src/main/kotlin/net/corda/flows/FinalityFlow.kt b/core/src/main/kotlin/net/corda/flows/FinalityFlow.kt index 91e1ff0caf..a017a137cc 100644 --- a/core/src/main/kotlin/net/corda/flows/FinalityFlow.kt +++ b/core/src/main/kotlin/net/corda/flows/FinalityFlow.kt @@ -74,8 +74,8 @@ class FinalityFlow(val transactions: Iterable, return stxnsAndParties.map { pair -> val stx = pair.first val notarised = if (needsNotarySignature(stx)) { - val notarySig = subFlow(NotaryFlow.Client(stx)) - stx + notarySig + val notarySignatures = subFlow(NotaryFlow.Client(stx)) + stx + notarySignatures } else { stx } @@ -122,7 +122,7 @@ class FinalityFlow(val transactions: Iterable, // Load and verify each transaction. return sorted.map { stx -> val notary = stx.tx.notary - // The notary signature is allowed to be missing but no others. + // The notary signature(s) are allowed to be missing but no others. val wtx = if (notary != null) stx.verifySignatures(notary.owningKey) else stx.verifySignatures() val ltx = wtx.toLedgerTransaction(augmentedLookup) ltx.verify() diff --git a/core/src/main/kotlin/net/corda/flows/NotaryFlow.kt b/core/src/main/kotlin/net/corda/flows/NotaryFlow.kt index 0cea77446d..2db11e00a3 100644 --- a/core/src/main/kotlin/net/corda/flows/NotaryFlow.kt +++ b/core/src/main/kotlin/net/corda/flows/NotaryFlow.kt @@ -16,14 +16,17 @@ import net.corda.core.utilities.unwrap object NotaryFlow { /** - * A flow to be used by a party for obtaining a signature from a [NotaryService] ascertaining the transaction + * A flow to be used by a party for obtaining signature(s) from a [NotaryService] ascertaining the transaction * timestamp is correct and none of its inputs have been used in another completed transaction. * + * In case of a single-node or Raft notary, the flow will return a single signature. For the BFT notary multiple + * signatures will be returned – one from each replica that accepted the input state commit. + * * @throws NotaryException in case the any of the inputs to the transaction have been consumed * by another transaction or the timestamp is invalid. */ open class Client(private val stx: SignedTransaction, - override val progressTracker: ProgressTracker) : FlowLogic() { + override val progressTracker: ProgressTracker) : FlowLogic>() { constructor(stx: SignedTransaction) : this(stx, Client.tracker()) companion object { @@ -37,7 +40,7 @@ object NotaryFlow { @Suspendable @Throws(NotaryException::class) - override fun call(): DigitalSignature.WithKey { + override fun call(): List { progressTracker.currentStep = REQUESTING val wtx = stx.tx notaryParty = wtx.notary ?: throw IllegalStateException("Transaction does not specify a Notary") @@ -57,7 +60,7 @@ object NotaryFlow { } val response = try { - sendAndReceive(notaryParty, payload) + sendAndReceive>(notaryParty, payload) } catch (e: NotaryException) { if (e.error is NotaryError.Conflict) { e.error.conflict.verified() @@ -65,9 +68,9 @@ object NotaryFlow { throw e } - return response.unwrap { sig -> - validateSignature(sig, stx.id.bytes) - sig + return response.unwrap { signatures -> + signatures.forEach { validateSignature(it, stx.id.bytes) } + signatures } } @@ -113,8 +116,8 @@ object NotaryFlow { @Suspendable private fun signAndSendResponse(txId: SecureHash) { - val sig = sign(txId.bytes) - send(otherSide, sig) + val signature = sign(txId.bytes) + send(otherSide, listOf(signature)) } private fun validateTimestamp(t: Timestamp?) { diff --git a/core/src/main/kotlin/net/corda/flows/TwoPartyDealFlow.kt b/core/src/main/kotlin/net/corda/flows/TwoPartyDealFlow.kt index 51462cd3aa..6bf7d0cd99 100644 --- a/core/src/main/kotlin/net/corda/flows/TwoPartyDealFlow.kt +++ b/core/src/main/kotlin/net/corda/flows/TwoPartyDealFlow.kt @@ -42,7 +42,7 @@ object TwoPartyDealFlow { // This object is serialised to the network and is the first flow message the seller sends to the buyer. data class Handshake(val payload: T, val publicKey: CompositeKey) - class SignaturesFromPrimary(val sellerSig: DigitalSignature.WithKey, val notarySig: DigitalSignature.WithKey) + class SignaturesFromPrimary(val sellerSig: DigitalSignature.WithKey, val notarySigs: List) /** * [Primary] at the end sends the signed tx to all the regulator parties. This a seperate workflow which needs a @@ -139,9 +139,9 @@ object TwoPartyDealFlow { // These two steps could be done in parallel, in theory. Our framework doesn't support that yet though. val ourSignature = computeOurSignature(stx) val allPartySignedTx = stx + ourSignature - val notarySignature = getNotarySignature(allPartySignedTx) + val notarySignatures = getNotarySignatures(allPartySignedTx) - val fullySigned = sendSignatures(allPartySignedTx, ourSignature, notarySignature) + val fullySigned = sendSignatures(allPartySignedTx, ourSignature, notarySignatures) progressTracker.currentStep = RECORDING @@ -161,7 +161,7 @@ object TwoPartyDealFlow { } @Suspendable - private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.WithKey { + private fun getNotarySignatures(stx: SignedTransaction): List { progressTracker.currentStep = NOTARY return subFlow(NotaryFlow.Client(stx)) } @@ -173,13 +173,13 @@ object TwoPartyDealFlow { @Suspendable private fun sendSignatures(allPartySignedTx: SignedTransaction, ourSignature: DigitalSignature.WithKey, - notarySignature: DigitalSignature.WithKey): SignedTransaction { + notarySignatures: List): SignedTransaction { progressTracker.currentStep = SENDING_SIGS - val fullySigned = allPartySignedTx + notarySignature + val fullySigned = allPartySignedTx + notarySignatures logger.trace { "Built finished transaction, sending back to other party!" } - send(otherParty, SignaturesFromPrimary(ourSignature, notarySignature)) + send(otherParty, SignaturesFromPrimary(ourSignature, notarySignatures)) return fullySigned } } @@ -217,7 +217,7 @@ object TwoPartyDealFlow { logger.trace { "Got signatures from other party, verifying ... " } - val fullySigned = stx + signatures.sellerSig + signatures.notarySig + val fullySigned = stx + signatures.sellerSig + signatures.notarySigs fullySigned.verifySignatures() logger.trace { "Signatures received are valid. Deal transaction complete! :-)" } diff --git a/node/src/test/kotlin/net/corda/node/services/NotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/NotaryServiceTests.kt index 0e79733d7c..c6c9110860 100644 --- a/node/src/test/kotlin/net/corda/node/services/NotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/NotaryServiceTests.kt @@ -55,8 +55,8 @@ class NotaryServiceTests { } val future = runNotaryClient(stx) - val signature = future.getOrThrow() - signature.verifyWithECDSA(stx.id) + val signatures = future.getOrThrow() + signatures.forEach { it.verifyWithECDSA(stx.id) } } @Test fun `should sign a unique transaction without a timestamp`() { @@ -68,8 +68,8 @@ class NotaryServiceTests { } val future = runNotaryClient(stx) - val signature = future.getOrThrow() - signature.verifyWithECDSA(stx.id) + val signatures = future.getOrThrow() + signatures.forEach { it.verifyWithECDSA(stx.id) } } @Test fun `should report error for transaction with an invalid timestamp`() { @@ -132,7 +132,7 @@ class NotaryServiceTests { notaryError.conflict.verified() } - private fun runNotaryClient(stx: SignedTransaction): ListenableFuture { + private fun runNotaryClient(stx: SignedTransaction): ListenableFuture> { val flow = NotaryFlow.Client(stx) val future = clientNode.services.startFlow(flow).resultFuture net.runNetwork() diff --git a/node/src/test/kotlin/net/corda/node/services/ValidatingNotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/ValidatingNotaryServiceTests.kt index 17f8b9996f..3e7a311dda 100644 --- a/node/src/test/kotlin/net/corda/node/services/ValidatingNotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/ValidatingNotaryServiceTests.kt @@ -78,7 +78,7 @@ class ValidatingNotaryServiceTests { assertEquals(setOf(expectedMissingKey), missingKeys) } - private fun runClient(stx: SignedTransaction): ListenableFuture { + private fun runClient(stx: SignedTransaction): ListenableFuture> { val flow = NotaryFlow.Client(stx) val future = clientNode.services.startFlow(flow).resultFuture net.runNetwork() diff --git a/samples/raft-notary-demo/src/main/kotlin/net/corda/notarydemo/NotaryDemo.kt b/samples/raft-notary-demo/src/main/kotlin/net/corda/notarydemo/NotaryDemo.kt index df8b546fa7..6562cb59b3 100644 --- a/samples/raft-notary-demo/src/main/kotlin/net/corda/notarydemo/NotaryDemo.kt +++ b/samples/raft-notary-demo/src/main/kotlin/net/corda/notarydemo/NotaryDemo.kt @@ -78,7 +78,7 @@ private class NotaryDemoClientApi(val rpc: CordaRPCOps) { */ private fun notariseTransactions(transactions: List): List { val signatureFutures = transactions.map { rpc.startFlow(NotaryFlow::Client, it).returnValue } - return Futures.allAsList(signatureFutures).getOrThrow().map { it.by.toStringShort() } + return Futures.allAsList(signatureFutures).getOrThrow().map { it.map { it.by.toStringShort() }.joinToString() } } } diff --git a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt index 49c3b240ec..8e913d886d 100644 --- a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt +++ b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt @@ -80,8 +80,8 @@ class SellerFlow(val otherParty: Party, tx.signWith(keyPair) // Get the notary to sign the timestamp - val notarySig = subFlow(NotaryFlow.Client(tx.toSignedTransaction(false))) - tx.addSignatureUnchecked(notarySig) + val notarySigs = subFlow(NotaryFlow.Client(tx.toSignedTransaction(false))) + notarySigs.forEach { tx.addSignatureUnchecked(it) } // Commit it to local storage. val stx = tx.toSignedTransaction(true) @@ -96,7 +96,7 @@ class SellerFlow(val otherParty: Party, CommercialPaper().generateMove(builder, issuance.tx.outRef(0), ownedBy) builder.signWith(keyPair) val notarySignature = subFlow(NotaryFlow.Client(builder.toSignedTransaction(false))) - builder.addSignatureUnchecked(notarySignature) + notarySignature.forEach { builder.addSignatureUnchecked(it) } val tx = builder.toSignedTransaction(true) serviceHub.recordTransactions(listOf(tx)) tx