mirror of
https://github.com/corda/corda.git
synced 2025-06-22 09:08:49 +00:00
Extended the data model so that every state has to define a set of 'participants' - parties that are able to consume that state in a valid transaction.
Added protocol for changing the notary for a state, which requires signatures from all participants
This commit is contained in:
@ -16,6 +16,7 @@ import com.r3corda.core.seconds
|
||||
import com.r3corda.core.serialization.deserialize
|
||||
import com.r3corda.core.serialization.serialize
|
||||
import com.r3corda.node.api.APIServer
|
||||
import com.r3corda.node.services.NotaryChangeService
|
||||
import com.r3corda.node.services.api.AcceptsFileUpload
|
||||
import com.r3corda.node.services.api.CheckpointStorage
|
||||
import com.r3corda.node.services.api.MonitoringService
|
||||
@ -101,6 +102,7 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
|
||||
lateinit var smm: StateMachineManager
|
||||
lateinit var wallet: WalletService
|
||||
lateinit var keyManagement: E2ETestKeyManagementService
|
||||
lateinit var notaryChangeService: NotaryChangeService
|
||||
var inNodeNetworkMapService: NetworkMapService? = null
|
||||
var inNodeNotaryService: NotaryService? = null
|
||||
lateinit var identity: IdentityService
|
||||
@ -128,6 +130,9 @@ abstract class AbstractNode(val dir: Path, val configuration: NodeConfiguration,
|
||||
net = makeMessagingService()
|
||||
wallet = NodeWalletService(services)
|
||||
makeInterestRatesOracleService()
|
||||
api = APIServerImpl(this)
|
||||
notaryChangeService = NotaryChangeService(net, smm)
|
||||
|
||||
identity = makeIdentityService()
|
||||
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
|
||||
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
|
||||
|
@ -0,0 +1,53 @@
|
||||
package com.r3corda.node.services
|
||||
|
||||
import com.r3corda.contracts.cash.Cash
|
||||
import com.r3corda.core.messaging.MessagingService
|
||||
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||
import com.r3corda.node.services.api.AbstractNodeService
|
||||
import com.r3corda.node.services.statemachine.StateMachineManager
|
||||
import protocols.NotaryChangeProtocol
|
||||
|
||||
/**
|
||||
* A service that monitors the network for requests for changing the notary of a state,
|
||||
* and immediately runs the [NotaryChangeProtocol] if the auto-accept criteria are met.
|
||||
*/
|
||||
class NotaryChangeService(net: MessagingService, val smm: StateMachineManager) : AbstractNodeService(net) {
|
||||
init {
|
||||
addMessageHandler(NotaryChangeProtocol.TOPIC_INITIATE,
|
||||
{ req: NotaryChangeProtocol.Handshake -> handleChangeNotaryRequest(req) }
|
||||
)
|
||||
}
|
||||
|
||||
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 state = smm.serviceHub.loadState(proposal.stateRef)
|
||||
if (state is Cash.State) return false // TODO: delete this example check
|
||||
|
||||
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 " }
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
88
node/src/test/kotlin/node/services/NotaryChangeTests.kt
Normal file
88
node/src/test/kotlin/node/services/NotaryChangeTests.kt
Normal file
@ -0,0 +1,88 @@
|
||||
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.TransactionBuilder
|
||||
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.services.transactions.NotaryService
|
||||
import com.r3corda.node.testutils.issueState
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import protocols.NotaryChangeProtocol
|
||||
import protocols.NotaryChangeProtocol.Instigator
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class NotaryChangeTests {
|
||||
lateinit var net: MockNetwork
|
||||
lateinit var oldNotaryNode: MockNetwork.MockNode
|
||||
lateinit var newNotaryNode: MockNetwork.MockNode
|
||||
lateinit var clientNodeA: MockNetwork.MockNode
|
||||
lateinit var clientNodeB: MockNetwork.MockNode
|
||||
|
||||
@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)
|
||||
|
||||
net.runNetwork() // Clear network map registration messages
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should change notary for a state with single participant`() {
|
||||
val state = issueState(clientNodeA, DUMMY_NOTARY)
|
||||
val ref = clientNodeA.services.loadState(state)
|
||||
|
||||
val newNotary = newNotaryNode.info.identity
|
||||
|
||||
val protocol = Instigator(StateAndRef(ref, state), newNotary)
|
||||
val future = clientNodeA.smm.add(NotaryChangeProtocol.TOPIC_CHANGE, protocol)
|
||||
|
||||
net.runNetwork()
|
||||
|
||||
val newState = future.get()
|
||||
assertEquals(newState.state.notary, newNotary)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should change notary for a state with multiple participants`() {
|
||||
val state = DummyContract.MultiOwnerState(0,
|
||||
listOf(clientNodeA.info.identity.owningKey, clientNodeB.info.identity.owningKey),
|
||||
DUMMY_NOTARY)
|
||||
|
||||
val tx = TransactionBuilder().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 newNotary = newNotaryNode.info.identity
|
||||
|
||||
val protocol = Instigator(stateAndRef, newNotary)
|
||||
val future = clientNodeA.smm.add(NotaryChangeProtocol.TOPIC_CHANGE, protocol)
|
||||
|
||||
net.runNetwork()
|
||||
|
||||
val newState = future.get()
|
||||
assertEquals(newState.state.notary, newNotary)
|
||||
val loadedStateA = clientNodeA.services.loadState(newState.ref)
|
||||
val loadedStateB = clientNodeB.services.loadState(newState.ref)
|
||||
assertEquals(loadedStateA, loadedStateB)
|
||||
}
|
||||
|
||||
// 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
|
||||
// - Multiple states in a single "notary change" transaction
|
||||
// - Transaction contains additional states and commands with business logic
|
||||
}
|
Reference in New Issue
Block a user