Merged in notary-check-signatures (pull request #173)

Validating notary: check for missing signatures
This commit is contained in:
Andrius Dagys 2016-06-23 16:16:04 +01:00
commit 497fcabd4d
8 changed files with 122 additions and 55 deletions

View File

@ -97,7 +97,7 @@ object TwoPartyTradeProtocol {
@Suspendable @Suspendable
private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.LegallyIdentifiable { private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.LegallyIdentifiable {
progressTracker.currentStep = NOTARY progressTracker.currentStep = NOTARY
return subProtocol(NotaryProtocol.Client(stx.tx)) return subProtocol(NotaryProtocol.Client(stx))
} }
@Suspendable @Suspendable

View File

@ -1,6 +1,7 @@
package com.r3corda.protocols package com.r3corda.protocols
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import com.r3corda.core.contracts.SignedTransaction
import com.r3corda.core.contracts.TimestampCommand import com.r3corda.core.contracts.TimestampCommand
import com.r3corda.core.contracts.WireTransaction import com.r3corda.core.contracts.WireTransaction
import com.r3corda.core.crypto.DigitalSignature import com.r3corda.core.crypto.DigitalSignature
@ -17,7 +18,6 @@ import com.r3corda.core.noneOrSingle
import com.r3corda.core.protocols.ProtocolLogic import com.r3corda.core.protocols.ProtocolLogic
import com.r3corda.core.random63BitValue import com.r3corda.core.random63BitValue
import com.r3corda.core.serialization.SerializedBytes import com.r3corda.core.serialization.SerializedBytes
import com.r3corda.core.serialization.deserialize
import com.r3corda.core.serialization.serialize import com.r3corda.core.serialization.serialize
import com.r3corda.core.utilities.ProgressTracker import com.r3corda.core.utilities.ProgressTracker
import com.r3corda.core.utilities.UntrustworthyData import com.r3corda.core.utilities.UntrustworthyData
@ -34,7 +34,7 @@ object NotaryProtocol {
* @throws NotaryException in case the any of the inputs to the transaction have been consumed * @throws NotaryException in case the any of the inputs to the transaction have been consumed
* by another transaction or the timestamp is invalid * by another transaction or the timestamp is invalid
*/ */
class Client(private val wtx: WireTransaction, class Client(private val stx: SignedTransaction,
override val progressTracker: ProgressTracker = Client.tracker()) : ProtocolLogic<DigitalSignature.LegallyIdentifiable>() { override val progressTracker: ProgressTracker = Client.tracker()) : ProtocolLogic<DigitalSignature.LegallyIdentifiable>() {
companion object { companion object {
@ -58,7 +58,7 @@ object NotaryProtocol {
val handshake = Handshake(serviceHub.networkService.myAddress, sendSessionID, receiveSessionID) val handshake = Handshake(serviceHub.networkService.myAddress, sendSessionID, receiveSessionID)
sendAndReceive<Ack>(TOPIC_INITIATE, notaryNode.address, 0, receiveSessionID, handshake) sendAndReceive<Ack>(TOPIC_INITIATE, notaryNode.address, 0, receiveSessionID, handshake)
val request = SignRequest(wtx.serialized, serviceHub.storageService.myLegalIdentity) val request = SignRequest(stx, serviceHub.storageService.myLegalIdentity)
val response = sendAndReceive<Result>(TOPIC, notaryNode.address, sendSessionID, receiveSessionID, request) val response = sendAndReceive<Result>(TOPIC, notaryNode.address, sendSessionID, receiveSessionID, request)
val notaryResult = validateResponse(response) val notaryResult = validateResponse(response)
@ -69,7 +69,7 @@ object NotaryProtocol {
progressTracker.currentStep = VALIDATING progressTracker.currentStep = VALIDATING
response.validate { response.validate {
if (it.sig != null) validateSignature(it.sig, wtx.serialized) if (it.sig != null) validateSignature(it.sig, stx.txBits)
else if (it.error is NotaryError.Conflict) it.error.conflict.verified() else if (it.error is NotaryError.Conflict) it.error.conflict.verified()
else if (it.error == null || it.error !is NotaryError) else if (it.error == null || it.error !is NotaryError)
throw IllegalStateException("Received invalid result from Notary service '${notaryNode.identity}'") throw IllegalStateException("Received invalid result from Notary service '${notaryNode.identity}'")
@ -84,6 +84,7 @@ object NotaryProtocol {
private fun findNotaryNode(): NodeInfo { private fun findNotaryNode(): NodeInfo {
var maybeNotaryKey: PublicKey? = null var maybeNotaryKey: PublicKey? = null
val wtx = stx.tx
val timestampCommand = wtx.commands.singleOrNull { it.value is TimestampCommand } val timestampCommand = wtx.commands.singleOrNull { it.value is TimestampCommand }
if (timestampCommand != null) maybeNotaryKey = timestampCommand.signers.first() if (timestampCommand != null) maybeNotaryKey = timestampCommand.signers.first()
@ -117,17 +118,17 @@ object NotaryProtocol {
@Suspendable @Suspendable
override fun call() { override fun call() {
val request = receive<SignRequest>(TOPIC, receiveSessionID).validate { it } val request = receive<SignRequest>(TOPIC, receiveSessionID).validate { it }
val txBits = request.txBits val stx = request.tx
val wtx = stx.tx
val reqIdentity = request.callerIdentity val reqIdentity = request.callerIdentity
val wtx = txBits.deserialize()
val result: Result val result: Result
try { try {
validateTimestamp(wtx) validateTimestamp(wtx)
beforeCommit(wtx, reqIdentity) beforeCommit(stx, reqIdentity)
commitInputStates(wtx, reqIdentity) commitInputStates(wtx, reqIdentity)
val sig = sign(txBits) val sig = sign(stx.txBits)
result = Result.noError(sig) result = Result.noError(sig)
} catch(e: NotaryException) { } catch(e: NotaryException) {
@ -159,7 +160,7 @@ object NotaryProtocol {
* undo the commit of the input states (the exact mechanism still needs to be worked out) * undo the commit of the input states (the exact mechanism still needs to be worked out)
*/ */
@Suspendable @Suspendable
open fun beforeCommit(wtx: WireTransaction, reqIdentity: Party) { open fun beforeCommit(stx: SignedTransaction, reqIdentity: Party) {
} }
private fun commitInputStates(tx: WireTransaction, reqIdentity: Party) { private fun commitInputStates(tx: WireTransaction, reqIdentity: Party) {
@ -185,7 +186,7 @@ object NotaryProtocol {
sessionID: Long) : AbstractRequestMessage(replyTo, sessionID) sessionID: Long) : AbstractRequestMessage(replyTo, sessionID)
/** TODO: The caller must authenticate instead of just specifying its identity */ /** TODO: The caller must authenticate instead of just specifying its identity */
class SignRequest(val txBits: SerializedBytes<WireTransaction>, class SignRequest(val tx: SignedTransaction,
val callerIdentity: Party) val callerIdentity: Party)
data class Result private constructor(val sig: DigitalSignature.LegallyIdentifiable?, val error: NotaryError?) { data class Result private constructor(val sig: DigitalSignature.LegallyIdentifiable?, val error: NotaryError?) {
@ -232,4 +233,6 @@ sealed class NotaryError {
class TimestampInvalid : NotaryError() class TimestampInvalid : NotaryError()
class TransactionInvalid : NotaryError() class TransactionInvalid : NotaryError()
class SignaturesMissing(val missingSigners: List<PublicKey>) : NotaryError()
} }

View File

@ -158,7 +158,7 @@ object TwoPartyDealProtocol {
@Suspendable @Suspendable
private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.LegallyIdentifiable { private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.LegallyIdentifiable {
progressTracker.currentStep = NOTARY progressTracker.currentStep = NOTARY
return subProtocol(NotaryProtocol.Client(stx.tx)) return subProtocol(NotaryProtocol.Client(stx))
} }
open fun signWithOurKey(partialTX: SignedTransaction): DigitalSignature.WithKey { open fun signWithOurKey(partialTX: SignedTransaction): DigitalSignature.WithKey {

View File

@ -1,6 +1,7 @@
package com.r3corda.protocols package com.r3corda.protocols
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import com.r3corda.core.contracts.SignedTransaction
import com.r3corda.core.contracts.TransactionVerificationException import com.r3corda.core.contracts.TransactionVerificationException
import com.r3corda.core.contracts.WireTransaction import com.r3corda.core.contracts.WireTransaction
import com.r3corda.core.contracts.toLedgerTransaction import com.r3corda.core.contracts.toLedgerTransaction
@ -22,8 +23,10 @@ class ValidatingNotaryProtocol(otherSide: SingleMessageRecipient,
timestampChecker: TimestampChecker, timestampChecker: TimestampChecker,
uniquenessProvider: UniquenessProvider) : NotaryProtocol.Service(otherSide, sessionIdForSend, sessionIdForReceive, timestampChecker, uniquenessProvider) { uniquenessProvider: UniquenessProvider) : NotaryProtocol.Service(otherSide, sessionIdForSend, sessionIdForReceive, timestampChecker, uniquenessProvider) {
@Suspendable @Suspendable
override fun beforeCommit(wtx: WireTransaction, reqIdentity: Party) { override fun beforeCommit(stx: SignedTransaction, reqIdentity: Party) {
val wtx = stx.tx
try { try {
checkSignatures(stx)
validateDependencies(reqIdentity, wtx) validateDependencies(reqIdentity, wtx)
checkContractValid(wtx) checkContractValid(wtx)
} catch (e: Exception) { } catch (e: Exception) {
@ -35,6 +38,13 @@ class ValidatingNotaryProtocol(otherSide: SingleMessageRecipient,
} }
} }
private fun checkSignatures(stx: SignedTransaction) {
val myKey = serviceHub.storageService.myLegalIdentity.owningKey
val missing = stx.verify(false) - myKey
if (missing.isNotEmpty()) throw NotaryException(NotaryError.SignaturesMissing(missing.toList()))
}
private fun checkContractValid(wtx: WireTransaction) { private fun checkContractValid(wtx: WireTransaction) {
val ltx = wtx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments) val ltx = wtx.toLedgerTransaction(serviceHub.identityService, serviceHub.storageService.attachments)
serviceHub.verifyTransaction(ltx) serviceHub.verifyTransaction(ltx)

View File

@ -59,7 +59,7 @@ object NotaryChangeProtocol {
val me = listOf(myKey) val me = listOf(myKey)
val signatures = if (participants == me) { val signatures = if (participants == me) {
listOf(getNotarySignature(stx.tx)) listOf(getNotarySignature(stx))
} else { } else {
collectSignatures(participants - me, stx) collectSignatures(participants - me, stx)
} }
@ -93,7 +93,7 @@ object NotaryChangeProtocol {
getParticipantSignature(participantNode, stx, sessionIdForSend) getParticipantSignature(participantNode, stx, sessionIdForSend)
} }
val allSignatures = participantSignatures + getNotarySignature(stx.tx) val allSignatures = participantSignatures + getNotarySignature(stx)
sessions.forEach { send(TOPIC_CHANGE, it.key.address, it.value, allSignatures) } sessions.forEach { send(TOPIC_CHANGE, it.key.address, it.value, allSignatures) }
return allSignatures return allSignatures
@ -121,9 +121,9 @@ object NotaryChangeProtocol {
} }
@Suspendable @Suspendable
private fun getNotarySignature(wtx: WireTransaction): DigitalSignature.LegallyIdentifiable { private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.LegallyIdentifiable {
progressTracker.currentStep = NOTARY progressTracker.currentStep = NOTARY
return subProtocol(NotaryProtocol.Client(wtx)) return subProtocol(NotaryProtocol.Client(stx))
} }
} }

View File

@ -5,6 +5,7 @@ import com.r3corda.core.contracts.TransactionType
import com.r3corda.core.seconds import com.r3corda.core.seconds
import com.r3corda.core.testing.DUMMY_NOTARY import com.r3corda.core.testing.DUMMY_NOTARY
import com.r3corda.core.testing.DUMMY_NOTARY_KEY import com.r3corda.core.testing.DUMMY_NOTARY_KEY
import com.r3corda.core.testing.MINI_CORP_KEY
import com.r3corda.node.internal.testing.MockNetwork import com.r3corda.node.internal.testing.MockNetwork
import com.r3corda.node.internal.testing.issueState import com.r3corda.node.internal.testing.issueState
import com.r3corda.node.services.network.NetworkMapService import com.r3corda.node.services.network.NetworkMapService
@ -32,43 +33,53 @@ class NotaryServiceTests {
keyPair = DUMMY_NOTARY_KEY, keyPair = DUMMY_NOTARY_KEY,
advertisedServices = *arrayOf(NetworkMapService.Type, SimpleNotaryService.Type) advertisedServices = *arrayOf(NetworkMapService.Type, SimpleNotaryService.Type)
) )
clientNode = net.createNode(networkMapAddress = notaryNode.info) clientNode = net.createNode(networkMapAddress = notaryNode.info, keyPair = MINI_CORP_KEY)
net.runNetwork() // Clear network map registration messages net.runNetwork() // Clear network map registration messages
} }
@Test fun `should sign a unique transaction with a valid timestamp`() { @Test fun `should sign a unique transaction with a valid timestamp`() {
val stx = run {
val inputState = issueState(clientNode) val inputState = issueState(clientNode)
val tx = TransactionType.General.Builder().withItems(inputState) val tx = TransactionType.General.Builder().withItems(inputState)
tx.setTime(Instant.now(), DUMMY_NOTARY, 30.seconds) tx.setTime(Instant.now(), DUMMY_NOTARY, 30.seconds)
val wtx = tx.toWireTransaction() tx.signWith(clientNode.keyPair!!)
tx.toSignedTransaction(false)
}
val protocol = NotaryProtocol.Client(wtx) val protocol = NotaryProtocol.Client(stx)
val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol) val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol)
net.runNetwork() net.runNetwork()
val signature = future.get() val signature = future.get()
signature.verifyWithECDSA(wtx.serialized) signature.verifyWithECDSA(stx.txBits)
} }
@Test fun `should sign a unique transaction without a timestamp`() { @Test fun `should sign a unique transaction without a timestamp`() {
val stx = run {
val inputState = issueState(clientNode) val inputState = issueState(clientNode)
val wtx = TransactionType.General.Builder().withItems(inputState).toWireTransaction() val tx = TransactionType.General.Builder().withItems(inputState)
tx.signWith(clientNode.keyPair!!)
tx.toSignedTransaction(false)
}
val protocol = NotaryProtocol.Client(wtx) val protocol = NotaryProtocol.Client(stx)
val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol) val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol)
net.runNetwork() net.runNetwork()
val signature = future.get() val signature = future.get()
signature.verifyWithECDSA(wtx.serialized) signature.verifyWithECDSA(stx.txBits)
} }
@Test fun `should report error for transaction with an invalid timestamp`() { @Test fun `should report error for transaction with an invalid timestamp`() {
val stx = run {
val inputState = issueState(clientNode) val inputState = issueState(clientNode)
val tx = TransactionType.General.Builder().withItems(inputState) val tx = TransactionType.General.Builder().withItems(inputState)
tx.setTime(Instant.now().plusSeconds(3600), DUMMY_NOTARY, 30.seconds) tx.setTime(Instant.now().plusSeconds(3600), DUMMY_NOTARY, 30.seconds)
val wtx = tx.toWireTransaction() tx.signWith(clientNode.keyPair!!)
tx.toSignedTransaction(false)
}
val protocol = NotaryProtocol.Client(wtx) val protocol = NotaryProtocol.Client(stx)
val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol) val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol)
net.runNetwork() net.runNetwork()
@ -77,15 +88,19 @@ class NotaryServiceTests {
assertTrue(error is NotaryError.TimestampInvalid) assertTrue(error is NotaryError.TimestampInvalid)
} }
@Test fun `should report error for transaction with more than one timestamp`() { @Test fun `should report error for transaction with more than one timestamp`() {
val stx = run {
val inputState = issueState(clientNode) val inputState = issueState(clientNode)
val tx = TransactionType.General.Builder().withItems(inputState) val tx = TransactionType.General.Builder().withItems(inputState)
val timestamp = TimestampCommand(Instant.now(), 30.seconds) val timestamp = TimestampCommand(Instant.now(), 30.seconds)
tx.addCommand(timestamp, DUMMY_NOTARY.owningKey) tx.addCommand(timestamp, DUMMY_NOTARY.owningKey)
tx.addCommand(timestamp, DUMMY_NOTARY.owningKey) tx.addCommand(timestamp, DUMMY_NOTARY.owningKey)
val wtx = tx.toWireTransaction() tx.signWith(clientNode.keyPair!!)
tx.toSignedTransaction(false)
}
val protocol = NotaryProtocol.Client(wtx) val protocol = NotaryProtocol.Client(stx)
val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol) val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol)
net.runNetwork() net.runNetwork()
@ -94,12 +109,17 @@ class NotaryServiceTests {
assertTrue(error is NotaryError.MoreThanOneTimestamp) assertTrue(error is NotaryError.MoreThanOneTimestamp)
} }
@Test fun `should report conflict for a duplicate transaction`() {
val inputState = issueState(clientNode)
val wtx = TransactionType.General.Builder().withItems(inputState).toWireTransaction()
val firstSpend = NotaryProtocol.Client(wtx) @Test fun `should report conflict for a duplicate transaction`() {
val secondSpend = NotaryProtocol.Client(wtx) val stx = run {
val inputState = issueState(clientNode)
val tx = TransactionType.General.Builder().withItems(inputState)
tx.signWith(clientNode.keyPair!!)
tx.toSignedTransaction(false)
}
val firstSpend = NotaryProtocol.Client(stx)
val secondSpend = NotaryProtocol.Client(stx)
clientNode.smm.add("${NotaryProtocol.TOPIC}.first", firstSpend) clientNode.smm.add("${NotaryProtocol.TOPIC}.first", firstSpend)
val future = clientNode.smm.add("${NotaryProtocol.TOPIC}.second", secondSpend) val future = clientNode.smm.add("${NotaryProtocol.TOPIC}.second", secondSpend)
@ -107,7 +127,7 @@ class NotaryServiceTests {
val ex = assertFailsWith(ExecutionException::class) { future.get() } val ex = assertFailsWith(ExecutionException::class) { future.get() }
val notaryError = (ex.cause as NotaryException).error as NotaryError.Conflict val notaryError = (ex.cause as NotaryException).error as NotaryError.Conflict
assertEquals(notaryError.tx, wtx) assertEquals(notaryError.tx, stx.tx)
notaryError.conflict.verified() notaryError.conflict.verified()
} }
} }

View File

@ -1,20 +1,26 @@
package com.r3corda.node.services package com.r3corda.node.services
import com.r3corda.core.contracts.Command
import com.r3corda.core.contracts.DummyContract
import com.r3corda.core.contracts.TransactionType import com.r3corda.core.contracts.TransactionType
import com.r3corda.core.testing.DUMMY_NOTARY import com.r3corda.core.testing.DUMMY_NOTARY
import com.r3corda.core.testing.DUMMY_NOTARY_KEY import com.r3corda.core.testing.DUMMY_NOTARY_KEY
import com.r3corda.core.testing.MEGA_CORP_KEY
import com.r3corda.core.testing.MINI_CORP_KEY
import com.r3corda.node.internal.testing.MockNetwork import com.r3corda.node.internal.testing.MockNetwork
import com.r3corda.node.internal.testing.issueInvalidState import com.r3corda.node.internal.testing.issueInvalidState
import com.r3corda.node.internal.testing.issueState
import com.r3corda.node.services.network.NetworkMapService import com.r3corda.node.services.network.NetworkMapService
import com.r3corda.node.services.transactions.ValidatingNotaryService import com.r3corda.node.services.transactions.ValidatingNotaryService
import com.r3corda.protocols.NotaryError import com.r3corda.protocols.NotaryError
import com.r3corda.protocols.NotaryException import com.r3corda.protocols.NotaryException
import com.r3corda.protocols.NotaryProtocol import com.r3corda.protocols.NotaryProtocol
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class ValidatingNotaryServiceTests { class ValidatingNotaryServiceTests {
lateinit var net: MockNetwork lateinit var net: MockNetwork
@ -28,20 +34,47 @@ class ValidatingNotaryServiceTests {
keyPair = DUMMY_NOTARY_KEY, keyPair = DUMMY_NOTARY_KEY,
advertisedServices = *arrayOf(NetworkMapService.Type, ValidatingNotaryService.Type) advertisedServices = *arrayOf(NetworkMapService.Type, ValidatingNotaryService.Type)
) )
clientNode = net.createNode(networkMapAddress = notaryNode.info) clientNode = net.createNode(networkMapAddress = notaryNode.info, keyPair = MINI_CORP_KEY)
net.runNetwork() // Clear network map registration messages net.runNetwork() // Clear network map registration messages
} }
@Test fun `should report error for invalid transaction dependency`() { @Test fun `should report error for invalid transaction dependency`() {
val stx = run {
val inputState = issueInvalidState(clientNode) val inputState = issueInvalidState(clientNode)
val wtx = TransactionType.General.Builder().withItems(inputState).toWireTransaction() val tx = TransactionType.General.Builder().withItems(inputState)
tx.signWith(clientNode.keyPair!!)
tx.toSignedTransaction(false)
}
val protocol = NotaryProtocol.Client(wtx) val protocol = NotaryProtocol.Client(stx)
val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol) val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol)
net.runNetwork() net.runNetwork()
val ex = assertFailsWith(ExecutionException::class) { future.get() } val ex = assertFailsWith(ExecutionException::class) { future.get() }
val notaryError = (ex.cause as NotaryException).error val notaryError = (ex.cause as NotaryException).error
assertTrue(notaryError is NotaryError.TransactionInvalid, "Received wrong Notary error") assertThat(notaryError).isInstanceOf(NotaryError.TransactionInvalid::class.java)
}
@Test fun `should report error for missing signatures`() {
val expectedMissingKey = MEGA_CORP_KEY.public
val stx = run {
val inputState = issueState(clientNode)
val command = Command(DummyContract.Commands.Move(), expectedMissingKey)
val tx = TransactionType.General.Builder().withItems(inputState, command)
tx.signWith(clientNode.keyPair!!)
tx.toSignedTransaction(false)
}
val protocol = NotaryProtocol.Client(stx)
val future = clientNode.smm.add(NotaryProtocol.TOPIC, protocol)
net.runNetwork()
val ex = assertFailsWith(ExecutionException::class) { future.get() }
val notaryError = (ex.cause as NotaryException).error
assertThat(notaryError).isInstanceOf(NotaryError.SignaturesMissing::class.java)
val missingKeys = (notaryError as NotaryError.SignaturesMissing).missingSigners
assertEquals(missingKeys, listOf(expectedMissingKey))
} }
} }

View File

@ -353,7 +353,7 @@ class TraderDemoProtocolSeller(val myAddress: HostAndPort,
tx.signWith(keyPair) tx.signWith(keyPair)
// Get the notary to sign the timestamp // Get the notary to sign the timestamp
val notarySig = subProtocol(NotaryProtocol.Client(tx.toWireTransaction())) val notarySig = subProtocol(NotaryProtocol.Client(tx.toSignedTransaction(false)))
tx.addSignatureUnchecked(notarySig) tx.addSignatureUnchecked(notarySig)
// Commit it to local storage. // Commit it to local storage.
@ -368,7 +368,8 @@ class TraderDemoProtocolSeller(val myAddress: HostAndPort,
val builder = TransactionType.General.Builder() val builder = TransactionType.General.Builder()
CommercialPaper().generateMove(builder, issuance.tx.outRef(0), ownedBy) CommercialPaper().generateMove(builder, issuance.tx.outRef(0), ownedBy)
builder.signWith(keyPair) builder.signWith(keyPair)
builder.addSignatureUnchecked(subProtocol(NotaryProtocol.Client(builder.toWireTransaction()))) val notarySignature = subProtocol(NotaryProtocol.Client(builder.toSignedTransaction(false)))
builder.addSignatureUnchecked(notarySignature)
val tx = builder.toSignedTransaction(true) val tx = builder.toSignedTransaction(true)
serviceHub.recordTransactions(listOf(tx)) serviceHub.recordTransactions(listOf(tx))
tx tx