Make notary flow return a collection of signatures to support the BFT… (#264)

Make notary flow return a collection of signatures to support the BFT notary. For a single-node or RAFT notary it would just contain a single signature.
This commit is contained in:
Andrius Dagys 2017-02-22 11:11:35 +00:00 committed by GitHub
parent c7abbe8791
commit 006faa82a1
8 changed files with 36 additions and 33 deletions

View File

@ -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<DigitalSignature.WithKey> {
progressTracker.currentStep = NOTARY
try {
return subFlow(NotaryFlow.Client(stx))

View File

@ -74,8 +74,8 @@ class FinalityFlow(val transactions: Iterable<SignedTransaction>,
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<SignedTransaction>,
// 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()

View File

@ -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<DigitalSignature.WithKey>() {
override val progressTracker: ProgressTracker) : FlowLogic<List<DigitalSignature.WithKey>>() {
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<DigitalSignature.WithKey> {
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<DigitalSignature.WithKey>(notaryParty, payload)
sendAndReceive<List<DigitalSignature.WithKey>>(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?) {

View File

@ -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<out T>(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<DigitalSignature.WithKey>)
/**
* [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<DigitalSignature.WithKey> {
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<DigitalSignature.WithKey>): 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! :-)" }

View File

@ -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<DigitalSignature.WithKey> {
private fun runNotaryClient(stx: SignedTransaction): ListenableFuture<List<DigitalSignature.WithKey>> {
val flow = NotaryFlow.Client(stx)
val future = clientNode.services.startFlow(flow).resultFuture
net.runNetwork()

View File

@ -78,7 +78,7 @@ class ValidatingNotaryServiceTests {
assertEquals(setOf(expectedMissingKey), missingKeys)
}
private fun runClient(stx: SignedTransaction): ListenableFuture<DigitalSignature.WithKey> {
private fun runClient(stx: SignedTransaction): ListenableFuture<List<DigitalSignature.WithKey>> {
val flow = NotaryFlow.Client(stx)
val future = clientNode.services.startFlow(flow).resultFuture
net.runNetwork()

View File

@ -78,7 +78,7 @@ private class NotaryDemoClientApi(val rpc: CordaRPCOps) {
*/
private fun notariseTransactions(transactions: List<SignedTransaction>): List<String> {
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() }
}
}

View File

@ -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