State replacement protocols: preserve the UntrustworthyData<T> marker for longer to try and get subclass authors in the right frame of mind.

This commit is contained in:
Mike Hearn 2016-09-06 15:52:04 +02:00
parent b89f86d013
commit 793e912324
2 changed files with 36 additions and 23 deletions

View File

@ -1,7 +1,9 @@
package com.r3corda.protocols package com.r3corda.protocols
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import com.r3corda.core.contracts.* import com.r3corda.core.contracts.ContractState
import com.r3corda.core.contracts.StateAndRef
import com.r3corda.core.contracts.StateRef
import com.r3corda.core.crypto.DigitalSignature import com.r3corda.core.crypto.DigitalSignature
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.signWithECDSA import com.r3corda.core.crypto.signWithECDSA
@ -12,6 +14,7 @@ import com.r3corda.core.random63BitValue
import com.r3corda.core.transactions.SignedTransaction import com.r3corda.core.transactions.SignedTransaction
import com.r3corda.core.transactions.WireTransaction import com.r3corda.core.transactions.WireTransaction
import com.r3corda.core.utilities.ProgressTracker import com.r3corda.core.utilities.ProgressTracker
import com.r3corda.core.utilities.UntrustworthyData
import com.r3corda.protocols.AbstractStateReplacementProtocol.Acceptor import com.r3corda.protocols.AbstractStateReplacementProtocol.Acceptor
import com.r3corda.protocols.AbstractStateReplacementProtocol.Instigator import com.r3corda.protocols.AbstractStateReplacementProtocol.Instigator
import java.security.PublicKey import java.security.PublicKey
@ -119,7 +122,7 @@ abstract class AbstractStateReplacementProtocol<T> {
} }
} }
abstract class Acceptor<in T>(val otherSide: Party, abstract class Acceptor<T>(val otherSide: Party,
val sessionIdForSend: Long, val sessionIdForSend: Long,
val sessionIdForReceive: Long, val sessionIdForReceive: Long,
override val progressTracker: ProgressTracker = tracker()) : ProtocolLogic<Unit>() { override val progressTracker: ProgressTracker = tracker()) : ProtocolLogic<Unit>() {
@ -137,24 +140,21 @@ abstract class AbstractStateReplacementProtocol<T> {
@Suspendable @Suspendable
override fun call() { override fun call() {
progressTracker.currentStep = VERIFYING progressTracker.currentStep = VERIFYING
val proposal = receive<Proposal<T>>(sessionIdForReceive).validate { it } val maybeProposal: UntrustworthyData<Proposal<T>> = receive(sessionIdForReceive)
try { try {
verifyProposal(proposal) val stx: SignedTransaction = maybeProposal.validate { verifyProposal(maybeProposal).stx }
verifyTx(proposal.stx) verifyTx(stx)
approve(stx)
} catch(e: Exception) { } catch(e: Exception) {
// TODO: catch only specific exceptions. However, there are numerous validation exceptions // TODO: catch only specific exceptions. However, there are numerous validation exceptions
// that might occur (tx validation/resolution, invalid proposal). Need to rethink how // that might occur (tx validation/resolution, invalid proposal). Need to rethink how
// we manage exceptions and maybe introduce some platform exception hierarchy // we manage exceptions and maybe introduce some platform exception hierarchy
val myIdentity = serviceHub.storageService.myLegalIdentity val myIdentity = serviceHub.storageService.myLegalIdentity
val state = proposal.stateRef val state = maybeProposal.validate { it.stateRef }
val reason = StateReplacementRefused(myIdentity, state, e.message) val reason = StateReplacementRefused(myIdentity, state, e.message)
reject(reason) reject(reason)
return
} }
approve(proposal.stx)
} }
@Suspendable @Suspendable
@ -185,9 +185,10 @@ abstract class AbstractStateReplacementProtocol<T> {
/** /**
* Check the state change proposal to confirm that it's acceptable to this node. Rules for verification depend * Check the state change proposal to confirm that it's acceptable to this node. Rules for verification depend
* on the change proposed, and may further depend on the node itself (for example configuration). * on the change proposed, and may further depend on the node itself (for example configuration). The
* proposal is returned if acceptable, otherwise an exception is thrown.
*/ */
abstract internal fun verifyProposal(proposal: Proposal<T>) abstract fun verifyProposal(maybeProposal: UntrustworthyData<Proposal<T>>): Proposal<T>
@Suspendable @Suspendable
private fun verifyTx(stx: SignedTransaction) { private fun verifyTx(stx: SignedTransaction) {

View File

@ -1,10 +1,16 @@
package com.r3corda.protocols package com.r3corda.protocols
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import com.r3corda.core.contracts.* import com.r3corda.core.contracts.ContractState
import com.r3corda.core.contracts.StateAndRef
import com.r3corda.core.contracts.StateRef
import com.r3corda.core.contracts.TransactionType
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import com.r3corda.core.transactions.SignedTransaction import com.r3corda.core.transactions.SignedTransaction
import com.r3corda.core.utilities.ProgressTracker import com.r3corda.core.utilities.ProgressTracker
import com.r3corda.core.utilities.UntrustworthyData
import com.r3corda.protocols.NotaryChangeProtocol.Acceptor
import com.r3corda.protocols.NotaryChangeProtocol.Instigator
import java.security.PublicKey import java.security.PublicKey
/** /**
@ -62,18 +68,24 @@ object NotaryChangeProtocol: AbstractStateReplacementProtocol<Party>() {
* TODO: In more difficult cases this should call for human attention to manually verify and approve the proposal * TODO: In more difficult cases this should call for human attention to manually verify and approve the proposal
*/ */
@Suspendable @Suspendable
override fun verifyProposal(proposal: AbstractStateReplacementProtocol.Proposal<Party>) { override fun verifyProposal(maybeProposal: UntrustworthyData<AbstractStateReplacementProtocol.Proposal<Party>>): AbstractStateReplacementProtocol.Proposal<Party> {
val newNotary = proposal.modification return maybeProposal.validate { proposal ->
val isNotary = serviceHub.networkMapCache.notaryNodes.any { it.identity == newNotary } val newNotary = proposal.modification
require(isNotary) { "The proposed node $newNotary does not run a Notary service " } val isNotary = serviceHub.networkMapCache.notaryNodes.any { it.identity == newNotary }
require(isNotary) { "The proposed node $newNotary does not run a Notary service " }
val state = proposal.stateRef val state = proposal.stateRef
val proposedTx = proposal.stx.tx val proposedTx = proposal.stx.tx
require(proposedTx.inputs.contains(state)) { "The proposed state $state is not in the proposed transaction inputs" } require(proposedTx.inputs.contains(state)) { "The proposed state $state is not in the proposed transaction inputs" }
// An example requirement // An example requirement
val blacklist = listOf("Evil Notary") val blacklist = listOf("Evil Notary")
require(!blacklist.contains(newNotary.name)) { "The proposed new notary $newNotary is not trusted by the party" } require(!blacklist.contains(newNotary.name)) { "The proposed new notary $newNotary is not trusted by the party" }
// TODO: Verify the type!
proposal
}
} }
} }
} }