Address PR 465 comments

This commit is contained in:
Andras Slemmer 2016-11-14 16:45:16 +00:00
parent 7f0dd1ab5b
commit dcd7a8a08a
9 changed files with 41 additions and 51 deletions

View File

@ -15,10 +15,6 @@ import net.corda.core.transactions.SignedTransaction
* @param participants a list of participants involved in the transaction. * @param participants a list of participants involved in the transaction.
* @return a list of participants who were successfully notified of 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, class BroadcastTransactionProtocol(val notarisedTransaction: SignedTransaction,
val participants: Set<Party>) : ProtocolLogic<Unit>() { val participants: Set<Party>) : ProtocolLogic<Unit>() {

View File

@ -11,14 +11,9 @@ import net.corda.core.utilities.ProgressTracker
* Finalise a transaction by notarising it, then recording it locally, and then sending it to all involved parties. * Finalise a transaction by notarising it, then recording it locally, and then sending it to all involved parties.
* *
* @param transaction to commit. * @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. * @param participants a list of participants involved in the transaction.
* @return a list of participants who were successfully notified of 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, class FinalityProtocol(val transaction: SignedTransaction,
val participants: Set<Party>, val participants: Set<Party>,
override val progressTracker: ProgressTracker = tracker()): ProtocolLogic<Unit>() { override val progressTracker: ProgressTracker = tracker()): ProtocolLogic<Unit>() {

View File

@ -11,15 +11,18 @@ import net.corda.core.protocols.StateMachineRunId
import net.corda.core.serialization.OpaqueBytes import net.corda.core.serialization.OpaqueBytes
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import org.slf4j.LoggerFactory
import java.security.KeyPair import java.security.KeyPair
import java.util.* import java.util.*
class CashProtocol(val command: CashCommand): ProtocolLogic<TransactionBuildResult>() { /**
* Initiates a protocol that produces an Issue/Move or Exit Cash transaction.
*
* @param command Indicates what Cash transaction to create with what parameters.
*/
class CashProtocol(val command: CashCommand): ProtocolLogic<CashProtocolResult>() {
@Suspendable @Suspendable
override fun call(): TransactionBuildResult { override fun call(): CashProtocolResult {
LoggerFactory.getLogger("DEBUG").warn("CashProtocol call()ed with $command")
return when (command) { return when (command) {
is CashCommand.IssueCash -> issueCash(command) is CashCommand.IssueCash -> issueCash(command)
is CashCommand.PayCash -> initiatePayment(command) is CashCommand.PayCash -> initiatePayment(command)
@ -27,8 +30,9 @@ class CashProtocol(val command: CashCommand): ProtocolLogic<TransactionBuildResu
} }
} }
// TODO check with the recipient if they want to accept the cash.
@Suspendable @Suspendable
private fun initiatePayment(req: CashCommand.PayCash): TransactionBuildResult { private fun initiatePayment(req: CashCommand.PayCash): CashProtocolResult {
val builder: TransactionBuilder = TransactionType.General.Builder(null) val builder: TransactionBuilder = TransactionType.General.Builder(null)
// TODO: Have some way of restricting this to states the caller controls // TODO: Have some way of restricting this to states the caller controls
try { try {
@ -43,18 +47,18 @@ class CashProtocol(val command: CashCommand): ProtocolLogic<TransactionBuildResu
val tx = spendTX.toSignedTransaction(checkSufficientSignatures = false) val tx = spendTX.toSignedTransaction(checkSufficientSignatures = false)
val protocol = FinalityProtocol(tx, setOf(req.recipient)) val protocol = FinalityProtocol(tx, setOf(req.recipient))
subProtocol(protocol) subProtocol(protocol)
return TransactionBuildResult.ProtocolStarted( return CashProtocolResult.Success(
psm.id, psm.id,
tx, tx,
"Cash payment transaction generated" "Cash payment transaction generated"
) )
} catch(ex: InsufficientBalanceException) { } catch(ex: InsufficientBalanceException) {
return TransactionBuildResult.Failed(ex.message ?: "Insufficient balance") return CashProtocolResult.Failed(ex.message ?: "Insufficient balance")
} }
} }
@Suspendable @Suspendable
private fun exitCash(req: CashCommand.ExitCash): TransactionBuildResult { private fun exitCash(req: CashCommand.ExitCash): CashProtocolResult {
val builder: TransactionBuilder = TransactionType.General.Builder(null) val builder: TransactionBuilder = TransactionType.General.Builder(null)
try { try {
val issuer = PartyAndReference(serviceHub.myInfo.legalIdentity, req.issueRef) val issuer = PartyAndReference(serviceHub.myInfo.legalIdentity, req.issueRef)
@ -78,18 +82,18 @@ class CashProtocol(val command: CashCommand): ProtocolLogic<TransactionBuildResu
// Commit the transaction // Commit the transaction
val tx = builder.toSignedTransaction(checkSufficientSignatures = false) val tx = builder.toSignedTransaction(checkSufficientSignatures = false)
subProtocol(FinalityProtocol(tx, participants)) subProtocol(FinalityProtocol(tx, participants))
return TransactionBuildResult.ProtocolStarted( return CashProtocolResult.Success(
psm.id, psm.id,
tx, tx,
"Cash destruction transaction generated" "Cash destruction transaction generated"
) )
} catch (ex: InsufficientBalanceException) { } catch (ex: InsufficientBalanceException) {
return TransactionBuildResult.Failed(ex.message ?: "Insufficient balance") return CashProtocolResult.Failed(ex.message ?: "Insufficient balance")
} }
} }
@Suspendable @Suspendable
private fun issueCash(req: CashCommand.IssueCash): TransactionBuildResult { private fun issueCash(req: CashCommand.IssueCash): CashProtocolResult {
val builder: TransactionBuilder = TransactionType.General.Builder(notary = null) val builder: TransactionBuilder = TransactionType.General.Builder(notary = null)
val issuer = PartyAndReference(serviceHub.myInfo.legalIdentity, req.issueRef) val issuer = PartyAndReference(serviceHub.myInfo.legalIdentity, req.issueRef)
Cash().generateIssue(builder, req.amount.issuedBy(issuer), req.recipient.owningKey, req.notary) Cash().generateIssue(builder, req.amount.issuedBy(issuer), req.recipient.owningKey, req.notary)
@ -98,7 +102,7 @@ class CashProtocol(val command: CashCommand): ProtocolLogic<TransactionBuildResu
val tx = builder.toSignedTransaction(checkSufficientSignatures = true) val tx = builder.toSignedTransaction(checkSufficientSignatures = true)
// Issuance transactions do not need to be notarised, so we can skip directly to broadcasting it // Issuance transactions do not need to be notarised, so we can skip directly to broadcasting it
subProtocol(BroadcastTransactionProtocol(tx, setOf(req.recipient))) subProtocol(BroadcastTransactionProtocol(tx, setOf(req.recipient)))
return TransactionBuildResult.ProtocolStarted( return CashProtocolResult.Success(
psm.id, psm.id,
tx, tx,
"Cash issuance completed" "Cash issuance completed"
@ -120,7 +124,6 @@ sealed class CashCommand {
* to use the single byte "0x01" as a default. * to use the single byte "0x01" as a default.
* @param recipient the party to issue the cash to. * @param recipient the party to issue the cash to.
* @param notary the notary to use for this transaction. * @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>, class IssueCash(val amount: Amount<Currency>,
val issueRef: OpaqueBytes, val issueRef: OpaqueBytes,
@ -132,7 +135,6 @@ sealed class CashCommand {
* *
* @param amount the amount of currency to issue on to the ledger. * @param amount the amount of currency to issue on to the ledger.
* @param recipient the party to issue the cash to. * @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) : CashCommand() class PayCash(val amount: Amount<Issued<Currency>>, val recipient: Party) : CashCommand()
@ -141,29 +143,23 @@ sealed class CashCommand {
* *
* @param amount the amount of currency to exit from the ledger. * @param amount the amount of currency to exit from the ledger.
* @param issueRef the reference previously specified on the issuance. * @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) : CashCommand() class ExitCash(val amount: Amount<Currency>, val issueRef: OpaqueBytes) : CashCommand()
} }
sealed class TransactionBuildResult { sealed class CashProtocolResult {
/** /**
* State indicating that a protocol is managing this request, and that the client should track protocol state machine * @param transaction the transaction created as a result, in the case where the protocol completed successfully.
* updates for further information. The monitor will separately receive notification of the state machine having been
* added, as it would any other state machine. This response is used solely to enable the monitor to identify
* the state machine (and its progress) as associated with the request.
*
* @param transaction the transaction created as a result, in the case where the protocol has completed.
*/ */
class ProtocolStarted(val id: StateMachineRunId, val transaction: SignedTransaction?, val message: String?) : TransactionBuildResult() { class Success(val id: StateMachineRunId, val transaction: SignedTransaction?, val message: String?) : CashProtocolResult() {
override fun toString() = "Started($message)" override fun toString() = "Success($message)"
} }
/** /**
* State indicating the action undertaken failed, either directly (it is not something which requires a * State indicating the action undertaken failed, either directly (it is not something which requires a
* state machine), or before a state machine was started. * state machine), or before a state machine was started.
*/ */
class Failed(val message: String?) : TransactionBuildResult() { class Failed(val message: String?) : CashProtocolResult() {
override fun toString() = "Failed($message)" override fun toString() = "Failed($message)"
} }
} }

View File

@ -15,6 +15,7 @@ import net.corda.core.toObservable
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.node.services.messaging.* import net.corda.node.services.messaging.*
import net.corda.node.services.startProtocolPermission
import net.corda.node.services.statemachine.ProtocolStateMachineImpl import net.corda.node.services.statemachine.ProtocolStateMachineImpl
import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.services.statemachine.StateMachineManager
import net.corda.node.utilities.databaseTransaction import net.corda.node.utilities.databaseTransaction
@ -78,8 +79,9 @@ class CordaRPCOpsImpl(
} }
} }
override fun <T: Any> startProtocolGeneric(logicType: Class<out ProtocolLogic<T>>, vararg args: Any?): ProtocolHandle<T> { // TODO: Check that this protocol is annotated as being intended for RPC invocation
requirePermission(logicType.name) override fun <T: Any> startProtocolDynamic(logicType: Class<out ProtocolLogic<T>>, vararg args: Any?): ProtocolHandle<T> {
requirePermission(startProtocolPermission(logicType))
val stateMachine = services.invokeProtocolAsync(logicType, *args) as ProtocolStateMachineImpl<T> val stateMachine = services.invokeProtocolAsync(logicType, *args) as ProtocolStateMachineImpl<T>
return ProtocolHandle( return ProtocolHandle(
id = stateMachine.id, id = stateMachine.id,

View File

@ -41,4 +41,5 @@ data class User(val username: String, val password: String, val permissions: Set
override fun toString(): String = "${javaClass.simpleName}($username, permissions=$permissions)" override fun toString(): String = "${javaClass.simpleName}($username, permissions=$permissions)"
} }
inline fun <reified P : ProtocolLogic<*>> startProtocolPermission(): String = P::class.java.name fun <P : ProtocolLogic<*>> startProtocolPermission(clazz: Class<P>) = "StartProtocol.${clazz.name}"
inline fun <reified P : ProtocolLogic<*>> startProtocolPermission(): String = startProtocolPermission(P::class.java)

View File

@ -96,7 +96,7 @@ interface CordaRPCOps : RPCOps {
* result of running the protocol. * result of running the protocol.
*/ */
@RPCReturnsObservables @RPCReturnsObservables
fun <T: Any> startProtocolGeneric(logicType: Class<out ProtocolLogic<T>>, vararg args: Any?): ProtocolHandle<T> fun <T: Any> startProtocolDynamic(logicType: Class<out ProtocolLogic<T>>, vararg args: Any?): ProtocolHandle<T>
/** /**
* Returns Node's identity, assuming this will not change while the node is running. * Returns Node's identity, assuming this will not change while the node is running.
@ -115,7 +115,7 @@ interface CordaRPCOps : RPCOps {
} }
/** /**
* These allow type safe invocations of protocols, e.g.: * These allow type safe invocations of protocols from Kotlin, e.g.:
* *
* val rpc: CordaRPCOps = (..) * val rpc: CordaRPCOps = (..)
* rpc.startProtocol(::ResolveTransactionsProtocol, setOf<SecureHash>(), aliceIdentity) * rpc.startProtocol(::ResolveTransactionsProtocol, setOf<SecureHash>(), aliceIdentity)
@ -126,25 +126,25 @@ interface CordaRPCOps : RPCOps {
inline fun <T : Any, reified R : ProtocolLogic<T>> CordaRPCOps.startProtocol( inline fun <T : Any, reified R : ProtocolLogic<T>> CordaRPCOps.startProtocol(
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
protocolConstructor: () -> R protocolConstructor: () -> R
) = startProtocolGeneric(R::class.java) ) = startProtocolDynamic(R::class.java)
inline fun <T : Any, A, reified R : ProtocolLogic<T>> CordaRPCOps.startProtocol( inline fun <T : Any, A, reified R : ProtocolLogic<T>> CordaRPCOps.startProtocol(
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
protocolConstructor: (A) -> R, protocolConstructor: (A) -> R,
arg0: A arg0: A
) = startProtocolGeneric(R::class.java, arg0) ) = startProtocolDynamic(R::class.java, arg0)
inline fun <T : Any, A, B, reified R : ProtocolLogic<T>> CordaRPCOps.startProtocol( inline fun <T : Any, A, B, reified R : ProtocolLogic<T>> CordaRPCOps.startProtocol(
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
protocolConstructor: (A, B) -> R, protocolConstructor: (A, B) -> R,
arg0: A, arg0: A,
arg1: B arg1: B
) = startProtocolGeneric(R::class.java, arg0, arg1) ) = startProtocolDynamic(R::class.java, arg0, arg1)
inline fun <T : Any, A, B, C, reified R: ProtocolLogic<T>> CordaRPCOps.startProtocol( inline fun <T : Any, A, B, C, reified R: ProtocolLogic<T>> CordaRPCOps.startProtocol(
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
protocolConstructor: (A, B, C) -> R, protocolConstructor: (A, B, C) -> R,
arg0: A, arg0: A,
arg1: B, arg1: B,
arg2: C arg2: C
) = startProtocolGeneric(R::class.java, arg0, arg1, arg2) ) = startProtocolDynamic(R::class.java, arg0, arg1, arg2)
inline fun <T : Any, A, B, C, D, reified R : ProtocolLogic<T>> CordaRPCOps.startProtocol( inline fun <T : Any, A, B, C, D, reified R : ProtocolLogic<T>> CordaRPCOps.startProtocol(
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
protocolConstructor: (A, B, C, D) -> R, protocolConstructor: (A, B, C, D) -> R,
@ -152,7 +152,7 @@ inline fun <T : Any, A, B, C, D, reified R : ProtocolLogic<T>> CordaRPCOps.start
arg1: B, arg1: B,
arg2: C, arg2: C,
arg3: D arg3: D
) = startProtocolGeneric(R::class.java, arg0, arg1, arg2, arg3) ) = startProtocolDynamic(R::class.java, arg0, arg1, arg2, arg3)
data class ProtocolHandle<A>( data class ProtocolHandle<A>(
val id: StateMachineRunId, val id: StateMachineRunId,

View File

@ -28,7 +28,7 @@ import net.corda.core.serialization.*
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import net.corda.node.services.User import net.corda.node.services.User
import net.corda.protocols.TransactionBuildResult import net.corda.protocols.CashProtocolResult
import net.i2p.crypto.eddsa.EdDSAPrivateKey import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey import net.i2p.crypto.eddsa.EdDSAPublicKey
import org.objenesis.strategy.StdInstantiatorStrategy import org.objenesis.strategy.StdInstantiatorStrategy
@ -174,8 +174,8 @@ private class RPCKryo(observableSerializer: Serializer<Observable<Any>>? = null)
register(Cash.Clauses.ConserveAmount::class.java) register(Cash.Clauses.ConserveAmount::class.java)
register(listOf(Unit).javaClass) // SingletonList register(listOf(Unit).javaClass) // SingletonList
register(setOf(Unit).javaClass) // SingletonSet register(setOf(Unit).javaClass) // SingletonSet
register(TransactionBuildResult.ProtocolStarted::class.java) register(CashProtocolResult.Success::class.java)
register(TransactionBuildResult.Failed::class.java) register(CashProtocolResult.Failed::class.java)
register(ServiceEntry::class.java) register(ServiceEntry::class.java)
register(NodeInfo::class.java) register(NodeInfo::class.java)
register(PhysicalLocation::class.java) register(PhysicalLocation::class.java)

View File

@ -34,7 +34,7 @@ class RPCUserServiceImplTest {
@Test @Test
fun `single permission, which is in lower case`() { fun `single permission, which is in lower case`() {
val service = loadWithContents("rpcUsers : [{ user=user1, password=letmein, permissions=[cash] }]") val service = loadWithContents("rpcUsers : [{ user=user1, password=letmein, permissions=[cash] }]")
assertThat(service.getUser("user1")?.permissions).containsOnly("CASH") assertThat(service.getUser("user1")?.permissions).containsOnly("cash")
} }
@Test @Test

View File

@ -22,7 +22,7 @@ import net.corda.node.services.messaging.CordaRPCOps
import net.corda.node.services.messaging.startProtocol import net.corda.node.services.messaging.startProtocol
import net.corda.protocols.CashCommand import net.corda.protocols.CashCommand
import net.corda.protocols.CashProtocol import net.corda.protocols.CashProtocol
import net.corda.protocols.TransactionBuildResult import net.corda.protocols.CashProtocolResult
import org.controlsfx.dialog.ExceptionDialog import org.controlsfx.dialog.ExceptionDialog
import tornadofx.View import tornadofx.View
import java.math.BigDecimal import java.math.BigDecimal
@ -144,12 +144,12 @@ class NewTransaction : View() {
rpcProxy.startProtocol(::CashProtocol, command).returnValue.toBlocking().first() rpcProxy.startProtocol(::CashProtocol, command).returnValue.toBlocking().first()
}.ui { }.ui {
dialog.contentText = when (it) { dialog.contentText = when (it) {
is TransactionBuildResult.ProtocolStarted -> { is CashProtocolResult.Success -> {
dialog.alertType = Alert.AlertType.INFORMATION dialog.alertType = Alert.AlertType.INFORMATION
dialog.setOnCloseRequest { resetScreen() } dialog.setOnCloseRequest { resetScreen() }
"Transaction Started \nTransaction ID : ${it.transaction?.id} \nMessage : ${it.message}" "Transaction Started \nTransaction ID : ${it.transaction?.id} \nMessage : ${it.message}"
} }
is TransactionBuildResult.Failed -> { is CashProtocolResult.Failed -> {
dialog.alertType = Alert.AlertType.ERROR dialog.alertType = Alert.AlertType.ERROR
it.toString() it.toString()
} }