mirror of
https://github.com/corda/corda.git
synced 2025-06-18 07:08:15 +00:00
Add CommitTransactionProtocol
Add new protocol which manages the entire process of taking a signed transaction ready for notarisation, through notarisation and onto recording it both locally and informing remote nodes. This protocol also optionally can include the ClientToServiceCommand which triggered a transaction being created, to give the remote nodes context on why a change occurred (i.e. "You are being sent £100")
This commit is contained in:
@ -0,0 +1,49 @@
|
||||
package com.r3corda.core.contracts
|
||||
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.serialization.OpaqueBytes
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* A command from the monitoring client, to the node.
|
||||
*
|
||||
* @param id ID used to tag event(s) resulting from a command.
|
||||
*/
|
||||
sealed class ClientToServiceCommand(val id: UUID) {
|
||||
/**
|
||||
* Issue cash state objects.
|
||||
*
|
||||
* @param amount the amount of currency to issue on to the ledger.
|
||||
* @param issueRef the reference to specify on the issuance, used to differentiate pools of cash. Convention is
|
||||
* to use the single byte "0x01" as a default.
|
||||
* @param recipient the party to issue the cash to.
|
||||
* @param notary the notary to use for this transaction.
|
||||
* @param id the ID to be provided in events resulting from this request.
|
||||
*/
|
||||
class IssueCash(val amount: Amount<Currency>,
|
||||
val issueRef: OpaqueBytes,
|
||||
val recipient: Party,
|
||||
val notary: Party,
|
||||
id: UUID = UUID.randomUUID()) : ClientToServiceCommand(id)
|
||||
|
||||
/**
|
||||
* Pay cash to someone else.
|
||||
*
|
||||
* @param amount the amount of currency to issue on to the ledger.
|
||||
* @param recipient the party to issue the cash to.
|
||||
* @param id the ID to be provided in events resulting from this request.
|
||||
*/
|
||||
class PayCash(val amount: Amount<Issued<Currency>>, val recipient: Party,
|
||||
id: UUID = UUID.randomUUID()) : ClientToServiceCommand(id)
|
||||
|
||||
/**
|
||||
* Exit cash from the ledger.
|
||||
*
|
||||
* @param amount the amount of currency to exit from the ledger.
|
||||
* @param issueRef the reference previously specified on the issuance.
|
||||
* @param id the ID to be provided in events resulting from this request.
|
||||
*/
|
||||
class ExitCash(val amount: Amount<Currency>, val issueRef: OpaqueBytes,
|
||||
id: UUID = UUID.randomUUID()) : ClientToServiceCommand(id)
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package com.r3corda.core.protocols
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.node.ServiceHub
|
||||
import com.r3corda.core.utilities.UntrustworthyData
|
||||
@ -23,4 +24,9 @@ interface ProtocolStateMachine<R> {
|
||||
|
||||
val serviceHub: ServiceHub
|
||||
val logger: Logger
|
||||
|
||||
/** Unique ID for this machine, valid only while it is in memory. */
|
||||
val machineId: Long
|
||||
/** This future will complete when the call method returns. */
|
||||
val resultFuture: ListenableFuture<R>
|
||||
}
|
||||
|
@ -0,0 +1,56 @@
|
||||
package com.r3corda.protocols
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.r3corda.core.contracts.ClientToServiceCommand
|
||||
import com.r3corda.core.contracts.SignedTransaction
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.protocols.ProtocolLogic
|
||||
import com.r3corda.core.random63BitValue
|
||||
import com.r3corda.core.serialization.serialize
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* Notify all involved parties about a transaction, including storing a copy. Normally this would be called via
|
||||
* [FinalityProtocol].
|
||||
*
|
||||
* @param notarisedTransaction transaction which has been notarised (if needed) and is ready to notify nodes about.
|
||||
* @param events information on the event(s) which triggered the transaction.
|
||||
* @param participants a list of participants involved in the transaction.
|
||||
* @return a list of participants who were successfully notified of the transaction.
|
||||
*/
|
||||
// TODO: Event needs to be replaced with something that's meaningful, but won't ever contain sensitive
|
||||
// information (such as internal details of an account to take payment from). Suggest
|
||||
// splitting ClientToServiceCommand into public and private parts, with only the public parts
|
||||
// relayed here.
|
||||
class BroadcastTransactionProtocol(val notarisedTransaction: SignedTransaction,
|
||||
val events: Set<ClientToServiceCommand>,
|
||||
val participants: Set<Party>) : ProtocolLogic<Unit>() {
|
||||
companion object {
|
||||
/** Topic for messages notifying a node of a new transaction */
|
||||
val TOPIC = "platform.wallet.notify_tx"
|
||||
}
|
||||
|
||||
override val topic: String = TOPIC
|
||||
|
||||
data class NotifyTxRequestMessage(
|
||||
val tx: SignedTransaction,
|
||||
val events: Set<ClientToServiceCommand>,
|
||||
override val replyToParty: Party,
|
||||
override val sessionID: Long
|
||||
) : PartyRequestMessage
|
||||
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
// Record it locally
|
||||
serviceHub.recordTransactions(notarisedTransaction)
|
||||
|
||||
// TODO: Messaging layer should handle this broadcast for us (although we need to not be sending
|
||||
// session ID, for that to work, as well).
|
||||
participants.filter { it != serviceHub.storageService.myLegalIdentity }.forEach { participant ->
|
||||
val sessionID = random63BitValue()
|
||||
val msg = NotifyTxRequestMessage(notarisedTransaction, events, serviceHub.storageService.myLegalIdentity, sessionID)
|
||||
send(participant, 0, msg)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package com.r3corda.protocols
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.r3corda.core.contracts.ClientToServiceCommand
|
||||
import com.r3corda.core.contracts.SignedTransaction
|
||||
import com.r3corda.core.contracts.TransactionBuilder
|
||||
import com.r3corda.core.contracts.WireTransaction
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.node.ServiceHub
|
||||
import com.r3corda.core.protocols.ProtocolLogic
|
||||
import com.r3corda.core.random63BitValue
|
||||
import com.r3corda.core.serialization.serialize
|
||||
import com.r3corda.core.utilities.ProgressTracker
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* Finalise a transaction by notarising it, then recording it locally, and then sending it to all involved parties.
|
||||
*
|
||||
* @param transaction to commit.
|
||||
* @param events information on the event(s) which triggered the transaction.
|
||||
* @param participants a list of participants involved in the transaction.
|
||||
* @return a list of participants who were successfully notified of the transaction.
|
||||
*/
|
||||
// TODO: Event needs to be replaced with something that's meaningful, but won't ever contain sensitive
|
||||
// information (such as internal details of an account to take payment from). Suggest
|
||||
// splitting ClientToServiceCommand into public and private parts, with only the public parts
|
||||
// relayed here.
|
||||
class FinalityProtocol(val transaction: SignedTransaction,
|
||||
val events: Set<ClientToServiceCommand>,
|
||||
val participants: Set<Party>,
|
||||
override val progressTracker: ProgressTracker = tracker()): ProtocolLogic<Unit>() {
|
||||
companion object {
|
||||
object NOTARISING : ProgressTracker.Step("Requesting signature by notary service")
|
||||
object BROADCASTING : ProgressTracker.Step("Broadcasting transaction to participants")
|
||||
|
||||
fun tracker() = ProgressTracker(NOTARISING, BROADCASTING)
|
||||
}
|
||||
|
||||
override val topic: String
|
||||
get() = throw UnsupportedOperationException()
|
||||
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
progressTracker.currentStep = NOTARISING
|
||||
// Notarise the transaction if needed
|
||||
val notarisedTransaction = if (needsNotarySignature(transaction)) {
|
||||
val notarySig = subProtocol(NotaryProtocol.Client(transaction))
|
||||
transaction.withAdditionalSignature(notarySig)
|
||||
} else {
|
||||
transaction
|
||||
}
|
||||
|
||||
// Let everyone else know about the transaction
|
||||
progressTracker.currentStep = BROADCASTING
|
||||
subProtocol(BroadcastTransactionProtocol(notarisedTransaction, events, participants))
|
||||
}
|
||||
|
||||
private fun needsNotarySignature(transaction: SignedTransaction) = expectsNotarySignature(transaction.tx) && hasNoNotarySignature(transaction)
|
||||
private fun expectsNotarySignature(transaction: WireTransaction) = transaction.notary != null && transaction.notary.owningKey in transaction.signers
|
||||
private fun hasNoNotarySignature(transaction: SignedTransaction) = transaction.tx.notary?.owningKey !in transaction.sigs.map { it.by }
|
||||
}
|
Reference in New Issue
Block a user