Refactored NotaryChangeProtocol and tests: moved the proposal verification step into the protocol.

Added another proposal verification step in the NotaryChangeProtocol.
Added the cause exception message to the 'notary change refused' error.
This commit is contained in:
Andrius Dagys
2016-06-14 19:06:44 +01:00
parent 0a5b7ace35
commit 30ca340b6e
12 changed files with 182 additions and 107 deletions

View File

@ -3,6 +3,8 @@ package com.r3corda.node.internal.testing
import com.r3corda.core.contracts.StateAndRef
import com.r3corda.core.contracts.DummyContract
import com.r3corda.core.contracts.StateRef
import com.r3corda.core.contracts.TransactionState
import com.r3corda.core.contracts.TransactionType
import com.r3corda.core.crypto.Party
import com.r3corda.core.seconds
import com.r3corda.core.testing.DUMMY_NOTARY
@ -11,8 +13,8 @@ import com.r3corda.node.internal.AbstractNode
import java.time.Instant
import java.util.*
fun issueState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateAndRef<*> {
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), notary)
fun issueState(node: AbstractNode): StateAndRef<*> {
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), DUMMY_NOTARY)
tx.signWith(node.storage.myLegalIdentityKey)
tx.signWith(DUMMY_NOTARY_KEY)
val stx = tx.toSignedTransaction()
@ -20,6 +22,20 @@ fun issueState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateAndRef<*>
return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0))
}
fun issueMultiPartyState(nodeA: AbstractNode, nodeB: AbstractNode): StateAndRef<DummyContract.MultiOwnerState> {
val state = TransactionState(DummyContract.MultiOwnerState(0,
listOf(nodeA.info.identity.owningKey, nodeB.info.identity.owningKey)), DUMMY_NOTARY)
val tx = TransactionType.NotaryChange.Builder().withItems(state)
tx.signWith(nodeA.storage.myLegalIdentityKey)
tx.signWith(nodeB.storage.myLegalIdentityKey)
tx.signWith(DUMMY_NOTARY_KEY)
val stx = tx.toSignedTransaction()
nodeA.services.recordTransactions(listOf(stx))
nodeB.services.recordTransactions(listOf(stx))
val stateAndRef = StateAndRef(state, StateRef(stx.id, 0))
return stateAndRef
}
fun issueInvalidState(node: AbstractNode, notary: Party = DUMMY_NOTARY): StateAndRef<*> {
val tx = DummyContract().generateInitial(node.info.identity.ref(0), Random().nextInt(), notary)
tx.setTime(Instant.now(), notary, 30.seconds)

View File

@ -3,7 +3,7 @@ package com.r3corda.node.internal.testing
import com.r3corda.contracts.cash.Cash
import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.Issued
import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.contracts.TransactionType
import com.r3corda.core.crypto.Party
import com.r3corda.core.node.ServiceHub
import com.r3corda.core.serialization.OpaqueBytes
@ -34,7 +34,7 @@ object WalletFiller {
// this field as there's no other database or source of truth we need to sync with.
val depositRef = myIdentity.ref(ref)
val issuance = TransactionBuilder()
val issuance = TransactionType.General.Builder()
val freshKey = services.keyManagementService.freshKey()
cash.generateIssue(issuance, Amount(pennies, Issued(depositRef, howMuch.token)), freshKey.public, notary)
issuance.signWith(myKey)

View File

@ -17,37 +17,11 @@ class NotaryChangeService(net: MessagingService, val smm: StateMachineManager) :
)
}
private fun handleChangeNotaryRequest(req: NotaryChangeProtocol.Handshake): Boolean {
val proposal = req.payload
val autoAccept = checkProposal(proposal)
if (autoAccept) {
val protocol = NotaryChangeProtocol.Acceptor(
req.replyTo as SingleMessageRecipient,
proposal.sessionIdForReceive,
proposal.sessionIdForSend)
smm.add(NotaryChangeProtocol.TOPIC_CHANGE, protocol)
}
return autoAccept
}
/**
* Check the notary change for a state proposal and decide whether to allow the change and initiate the protocol
* or deny the change.
*
* For example, if the proposed new notary has the same behaviour (e.g. both are non-validating)
* and is also in a geographically convenient location we can just automatically approve the change.
* TODO: In more difficult cases this should call for human attention to manually verify and approve the proposal
*/
private fun checkProposal(proposal: NotaryChangeProtocol.Proposal): Boolean {
val newNotary = proposal.newNotary
val isNotary = smm.serviceHub.networkMapCache.notaryNodes.any { it.identity == newNotary }
require(isNotary) { "The proposed node $newNotary does not run a Notary service " }
// An example requirement
val blacklist = listOf("Evil Notary")
require(!blacklist.contains(newNotary.name))
return true
private fun handleChangeNotaryRequest(req: NotaryChangeProtocol.Handshake) {
val protocol = NotaryChangeProtocol.Acceptor(
req.replyTo as SingleMessageRecipient,
req.sessionID!!,
req.sessionIdForSend)
smm.add(NotaryChangeProtocol.TOPIC_CHANGE, protocol)
}
}

View File

@ -1,20 +1,24 @@
package node.services
import com.r3corda.contracts.DummyContract
import com.r3corda.core.contracts.StateAndRef
import com.r3corda.core.contracts.StateRef
import com.r3corda.core.contracts.TransactionState
import com.r3corda.core.contracts.TransactionType
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.generateKeyPair
import com.r3corda.core.testing.DUMMY_NOTARY
import com.r3corda.core.testing.DUMMY_NOTARY_KEY
import com.r3corda.node.internal.testing.MockNetwork
import com.r3corda.node.internal.testing.issueMultiPartyState
import com.r3corda.node.internal.testing.issueState
import com.r3corda.node.services.transactions.NotaryService
import com.r3corda.node.services.network.NetworkMapService
import com.r3corda.node.services.transactions.SimpleNotaryService
import org.junit.Before
import org.junit.Test
import protocols.NotaryChangeException
import protocols.NotaryChangeProtocol
import protocols.NotaryChangeProtocol.Instigator
import protocols.NotaryChangeRefused
import java.util.concurrent.ExecutionException
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class NotaryChangeTests {
lateinit var net: MockNetwork
@ -26,22 +30,22 @@ class NotaryChangeTests {
@Before
fun setup() {
net = MockNetwork()
oldNotaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
clientNodeA = net.createPartyNode(networkMapAddr = oldNotaryNode.info)
clientNodeB = net.createPartyNode(networkMapAddr = oldNotaryNode.info)
newNotaryNode = net.createNode(networkMapAddress = oldNotaryNode.info, advertisedServices = NotaryService.Type)
oldNotaryNode = net.createNode(
legalName = DUMMY_NOTARY.name,
keyPair = DUMMY_NOTARY_KEY,
advertisedServices = *arrayOf(NetworkMapService.Type, SimpleNotaryService.Type))
clientNodeA = net.createNode(networkMapAddress = oldNotaryNode.info)
clientNodeB = net.createNode(networkMapAddress = oldNotaryNode.info)
newNotaryNode = net.createNode(networkMapAddress = oldNotaryNode.info, advertisedServices = SimpleNotaryService.Type)
net.runNetwork() // Clear network map registration messages
}
@Test
fun `should change notary for a state with single participant`() {
val ref = issueState(clientNodeA, DUMMY_NOTARY).ref
val state = clientNodeA.services.loadState(ref)
val state = issueState(clientNodeA)
val newNotary = newNotaryNode.info.identity
val protocol = Instigator(StateAndRef(state, ref), newNotary)
val protocol = Instigator(state, newNotary)
val future = clientNodeA.smm.add(NotaryChangeProtocol.TOPIC_CHANGE, protocol)
net.runNetwork()
@ -52,21 +56,9 @@ class NotaryChangeTests {
@Test
fun `should change notary for a state with multiple participants`() {
val state = TransactionState(DummyContract.MultiOwnerState(0,
listOf(clientNodeA.info.identity.owningKey, clientNodeB.info.identity.owningKey)), DUMMY_NOTARY)
val tx = TransactionType.NotaryChange.Builder().withItems(state)
tx.signWith(clientNodeA.storage.myLegalIdentityKey)
tx.signWith(clientNodeB.storage.myLegalIdentityKey)
tx.signWith(DUMMY_NOTARY_KEY)
val stx = tx.toSignedTransaction()
clientNodeA.services.recordTransactions(listOf(stx))
clientNodeB.services.recordTransactions(listOf(stx))
val stateAndRef = StateAndRef(state, StateRef(stx.id, 0))
val state = issueMultiPartyState(clientNodeA, clientNodeB)
val newNotary = newNotaryNode.info.identity
val protocol = Instigator(stateAndRef, newNotary)
val protocol = Instigator(state, newNotary)
val future = clientNodeA.smm.add(NotaryChangeProtocol.TOPIC_CHANGE, protocol)
net.runNetwork()
@ -78,8 +70,21 @@ class NotaryChangeTests {
assertEquals(loadedStateA, loadedStateB)
}
@Test
fun `should throw when a participant refuses to change Notary`() {
val state = issueMultiPartyState(clientNodeA, clientNodeB)
val newEvilNotary = Party("Evil Notary", generateKeyPair().public)
val protocol = Instigator(state, newEvilNotary)
val future = clientNodeA.smm.add(NotaryChangeProtocol.TOPIC_CHANGE, protocol)
net.runNetwork()
val ex = assertFailsWith(ExecutionException::class) { future.get() }
val error = (ex.cause as NotaryChangeException).error
assertTrue(error is NotaryChangeRefused)
}
// TODO: Add more test cases once we have a general protocol/service exception handling mechanism:
// - A participant refuses to change Notary
// - A participant is offline/can't be found on the network
// - The requesting party is not a participant
// - The requesting party wants to change additional state fields