Moved the initiated-side of core flows in net.corda.flows to net.corda.node.services. They are not meant to be visible to end-users.

This commit is contained in:
Shams Asari 2017-05-03 13:46:04 +01:00
parent e22ad19fcd
commit eba753ddfe
17 changed files with 251 additions and 248 deletions

View File

@ -1,84 +1,62 @@
package net.corda.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.*
import net.corda.core.crypto.Party
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.flows.AbstractStateReplacementFlow.Proposal
import net.corda.flows.ContractUpgradeFlow.Acceptor
import net.corda.flows.ContractUpgradeFlow.Instigator
import java.security.PublicKey
/**
* A flow to be used for upgrading state objects of an old contract to a new contract.
*
* The [Instigator] assembles the transaction for contract upgrade and sends out change proposals to all participants
* ([Acceptor]) of that state. If participants agree to the proposed change, they each sign the transaction.
* Finally, [Instigator] sends the transaction containing all signatures back to each participant so they can record it and
* This assembles the transaction for contract upgrade and sends out change proposals to all participants
* of that state. If participants agree to the proposed change, they each sign the transaction.
* Finally, the transaction containing all signatures is sent back to each participant so they can record it and
* use the new updated state for future transactions.
*/
object ContractUpgradeFlow {
@JvmStatic
fun verify(tx: TransactionForContract) {
// Contract Upgrade transaction should have 1 input, 1 output and 1 command.
verify(tx.inputs.single(), tx.outputs.single(), tx.commands.map { Command(it.value, it.signers) }.single())
}
class ContractUpgradeFlow<OldState : ContractState, out NewState : ContractState>(
originalState: StateAndRef<OldState>,
newContractClass: Class<out UpgradedContract<OldState, NewState>>
) : AbstractStateReplacementFlow.Instigator<OldState, NewState, Class<out UpgradedContract<OldState, NewState>>>(originalState, newContractClass) {
@JvmStatic
fun verify(input: ContractState, output: ContractState, commandData: Command) {
val command = commandData.value as UpgradeCommand
val participants: Set<PublicKey> = input.participants.toSet()
val keysThatSigned: Set<PublicKey> = commandData.signers.toSet()
@Suppress("UNCHECKED_CAST")
val upgradedContract = command.upgradedContractClass.newInstance() as UpgradedContract<ContractState, *>
requireThat {
"The signing keys include all participant keys" using keysThatSigned.containsAll(participants)
"Inputs state reference the legacy contract" using (input.contract.javaClass == upgradedContract.legacyContract)
"Outputs state reference the upgraded contract" using (output.contract.javaClass == command.upgradedContractClass)
"Output state must be an upgraded version of the input state" using (output == upgradedContract.upgrade(input))
companion object {
@JvmStatic
fun verify(tx: TransactionForContract) {
// Contract Upgrade transaction should have 1 input, 1 output and 1 command.
verify(tx.inputs.single(), tx.outputs.single(), tx.commands.map { Command(it.value, it.signers) }.single())
}
}
private fun <OldState : ContractState, NewState : ContractState> assembleBareTx(
stateRef: StateAndRef<OldState>,
upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>
): TransactionBuilder {
val contractUpgrade = upgradedContractClass.newInstance()
return TransactionType.General.Builder(stateRef.state.notary)
.withItems(stateRef, contractUpgrade.upgrade(stateRef.state.data), Command(UpgradeCommand(upgradedContractClass), stateRef.state.data.participants))
}
class Instigator<OldState : ContractState, out NewState : ContractState>(
originalState: StateAndRef<OldState>,
newContractClass: Class<out UpgradedContract<OldState, NewState>>
) : AbstractStateReplacementFlow.Instigator<OldState, NewState, Class<out UpgradedContract<OldState, NewState>>>(originalState, newContractClass) {
override fun assembleTx(): Pair<SignedTransaction, Iterable<PublicKey>> {
val stx = assembleBareTx(originalState, modification)
.signWith(serviceHub.legalIdentityKey)
.toSignedTransaction(false)
return Pair(stx, originalState.state.data.participants)
}
}
class Acceptor(otherSide: Party) : AbstractStateReplacementFlow.Acceptor<Class<out UpgradedContract<ContractState, *>>>(otherSide) {
@Suspendable
@Throws(StateReplacementException::class)
override fun verifyProposal(proposal: Proposal<Class<out UpgradedContract<ContractState, *>>>) {
// Retrieve signed transaction from our side, we will apply the upgrade logic to the transaction on our side, and verify outputs matches the proposed upgrade.
val stx = subFlow(FetchTransactionsFlow(setOf(proposal.stateRef.txhash), otherSide)).fromDisk.singleOrNull()
requireNotNull(stx) { "We don't have a copy of the referenced state" }
val oldStateAndRef = stx!!.tx.outRef<ContractState>(proposal.stateRef.index)
val authorisedUpgrade = serviceHub.vaultService.getAuthorisedContractUpgrade(oldStateAndRef.ref) ?: throw IllegalStateException("Contract state upgrade is unauthorised. State hash : ${oldStateAndRef.ref}")
val proposedTx = proposal.stx.tx
val expectedTx = assembleBareTx(oldStateAndRef, proposal.modification).toWireTransaction()
@JvmStatic
fun verify(input: ContractState, output: ContractState, commandData: Command) {
val command = commandData.value as UpgradeCommand
val participants: Set<PublicKey> = input.participants.toSet()
val keysThatSigned: Set<PublicKey> = commandData.signers.toSet()
@Suppress("UNCHECKED_CAST")
val upgradedContract = command.upgradedContractClass.newInstance() as UpgradedContract<ContractState, *>
requireThat {
"The instigator is one of the participants" using oldStateAndRef.state.data.participants.contains(otherSide.owningKey)
"The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification == authorisedUpgrade)
"The proposed tx matches the expected tx for this upgrade" using (proposedTx == expectedTx)
"The signing keys include all participant keys" using keysThatSigned.containsAll(participants)
"Inputs state reference the legacy contract" using (input.contract.javaClass == upgradedContract.legacyContract)
"Outputs state reference the upgraded contract" using (output.contract.javaClass == command.upgradedContractClass)
"Output state must be an upgraded version of the input state" using (output == upgradedContract.upgrade(input))
}
ContractUpgradeFlow.verify(oldStateAndRef.state.data, expectedTx.outRef<ContractState>(0).state.data, expectedTx.commands.single())
}
fun <OldState : ContractState, NewState : ContractState> assembleBareTx(
stateRef: StateAndRef<OldState>,
upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>
): TransactionBuilder {
val contractUpgrade = upgradedContractClass.newInstance()
return TransactionType.General.Builder(stateRef.state.notary)
.withItems(
stateRef,
contractUpgrade.upgrade(stateRef.state.data),
Command(UpgradeCommand(upgradedContractClass), stateRef.state.data.participants))
}
}
override fun assembleTx(): Pair<SignedTransaction, Iterable<PublicKey>> {
val stx = assembleBareTx(originalState, modification)
.signWith(serviceHub.legalIdentityKey)
.toSignedTransaction(false)
return stx to originalState.state.data.participants
}
}

View File

@ -5,119 +5,81 @@ import net.corda.core.crypto.Party
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
import net.corda.flows.NotaryChangeFlow.Acceptor
import net.corda.flows.NotaryChangeFlow.Instigator
import java.security.PublicKey
/**
* A flow to be used for changing a state's Notary. This is required since all input states to a transaction
* must point to the same notary.
*
* The [Instigator] assembles the transaction for notary replacement and sends out change proposals to all participants
* ([Acceptor]) of that state. If participants agree to the proposed change, they each sign the transaction.
* Finally, [Instigator] sends the transaction containing all signatures back to each participant so they can record it and
* This assembles the transaction for notary replacement and sends out change proposals to all participants
* of that state. If participants agree to the proposed change, they each sign the transaction.
* Finally, the transaction containing all signatures is sent back to each participant so they can record it and
* use the new updated state for future transactions.
*/
object NotaryChangeFlow : AbstractStateReplacementFlow() {
class NotaryChangeFlow<out T : ContractState>(
originalState: StateAndRef<T>,
newNotary: Party,
progressTracker: ProgressTracker = tracker())
: AbstractStateReplacementFlow.Instigator<T, T, Party>(originalState, newNotary, progressTracker) {
class Instigator<out T : ContractState>(
originalState: StateAndRef<T>,
newNotary: Party,
progressTracker: ProgressTracker = tracker()) : AbstractStateReplacementFlow.Instigator<T, T, Party>(originalState, newNotary, progressTracker) {
override fun assembleTx(): Pair<SignedTransaction, Iterable<PublicKey>> {
val state = originalState.state
val tx = TransactionType.NotaryChange.Builder(originalState.state.notary)
override fun assembleTx(): Pair<SignedTransaction, Iterable<PublicKey>> {
val state = originalState.state
val tx = TransactionType.NotaryChange.Builder(originalState.state.notary)
val participants: Iterable<PublicKey>
val participants: Iterable<PublicKey>
if (state.encumbrance == null) {
val modifiedState = TransactionState(state.data, modification)
tx.addInputState(originalState)
tx.addOutputState(modifiedState)
participants = state.data.participants
} else {
participants = resolveEncumbrances(tx)
}
val myKey = serviceHub.legalIdentityKey
tx.signWith(myKey)
val stx = tx.toSignedTransaction(false)
return Pair(stx, participants)
if (state.encumbrance == null) {
val modifiedState = TransactionState(state.data, modification)
tx.addInputState(originalState)
tx.addOutputState(modifiedState)
participants = state.data.participants
} else {
participants = resolveEncumbrances(tx)
}
/**
* Adds the notary change state transitions to the [tx] builder for the [originalState] and its encumbrance
* state chain (encumbrance states might be themselves encumbered by other states).
*
* @return union of all added states' participants
*/
private fun resolveEncumbrances(tx: TransactionBuilder): Iterable<PublicKey> {
val stateRef = originalState.ref
val txId = stateRef.txhash
val issuingTx = serviceHub.storageService.validatedTransactions.getTransaction(txId)
?: throw StateReplacementException("Transaction $txId not found")
val outputs = issuingTx.tx.outputs
val myKey = serviceHub.legalIdentityKey
tx.signWith(myKey)
val participants = mutableSetOf<PublicKey>()
var nextStateIndex = stateRef.index
var newOutputPosition = tx.outputStates().size
while (true) {
val nextState = outputs[nextStateIndex]
tx.addInputState(StateAndRef(nextState, StateRef(txId, nextStateIndex)))
participants.addAll(nextState.data.participants)
if (nextState.encumbrance == null) {
val modifiedState = TransactionState(nextState.data, modification)
tx.addOutputState(modifiedState)
break
} else {
val modifiedState = TransactionState(nextState.data, modification, newOutputPosition + 1)
tx.addOutputState(modifiedState)
nextStateIndex = nextState.encumbrance
}
newOutputPosition++
}
return participants
}
val stx = tx.toSignedTransaction(false)
return Pair(stx, participants)
}
class Acceptor(otherSide: Party) : AbstractStateReplacementFlow.Acceptor<Party>(otherSide) {
/**
* Check the notary change proposal.
*
* 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
*/
override fun verifyProposal(proposal: AbstractStateReplacementFlow.Proposal<Party>): Unit {
val state = proposal.stateRef
val proposedTx = proposal.stx.tx
/**
* Adds the notary change state transitions to the [tx] builder for the [originalState] and its encumbrance
* state chain (encumbrance states might be themselves encumbered by other states).
*
* @return union of all added states' participants
*/
private fun resolveEncumbrances(tx: TransactionBuilder): Iterable<PublicKey> {
val stateRef = originalState.ref
val txId = stateRef.txhash
val issuingTx = serviceHub.storageService.validatedTransactions.getTransaction(txId)
?: throw StateReplacementException("Transaction $txId not found")
val outputs = issuingTx.tx.outputs
if (proposedTx.type !is TransactionType.NotaryChange) {
throw StateReplacementException("The proposed transaction is not a notary change transaction.")
val participants = mutableSetOf<PublicKey>()
var nextStateIndex = stateRef.index
var newOutputPosition = tx.outputStates().size
while (true) {
val nextState = outputs[nextStateIndex]
tx.addInputState(StateAndRef(nextState, StateRef(txId, nextStateIndex)))
participants.addAll(nextState.data.participants)
if (nextState.encumbrance == null) {
val modifiedState = TransactionState(nextState.data, modification)
tx.addOutputState(modifiedState)
break
} else {
val modifiedState = TransactionState(nextState.data, modification, newOutputPosition + 1)
tx.addOutputState(modifiedState)
nextStateIndex = nextState.encumbrance
}
val newNotary = proposal.modification
val isNotary = serviceHub.networkMapCache.notaryNodes.any { it.notaryIdentity == newNotary }
if (!isNotary) {
throw StateReplacementException("The proposed node $newNotary does not run a Notary service")
}
if (state !in proposedTx.inputs) {
throw StateReplacementException("The proposed state $state is not in the proposed transaction inputs")
}
// // An example requirement
// val blacklist = listOf("Evil Notary")
// checkProposal(newNotary.name !in blacklist) {
// "The proposed new notary $newNotary is not trusted by the party"
// }
newOutputPosition++
}
return participants
}
}

View File

@ -21,12 +21,12 @@ import net.corda.testing.node.MockNetwork
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.security.PublicKey
import java.util.*
import java.util.concurrent.ExecutionException
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
import java.security.*
class ContractUpgradeFlowTest {
lateinit var mockNet: MockNetwork
@ -66,7 +66,7 @@ class ContractUpgradeFlowTest {
requireNotNull(btx)
// The request is expected to be rejected because party B haven't authorise the upgrade yet.
val rejectedFuture = a.services.startFlow(ContractUpgradeFlow.Instigator(atx!!.tx.outRef(0), DummyContractV2::class.java)).resultFuture
val rejectedFuture = a.services.startFlow(ContractUpgradeFlow(atx!!.tx.outRef(0), DummyContractV2::class.java)).resultFuture
mockNet.runNetwork()
assertFailsWith(ExecutionException::class) { rejectedFuture.get() }
@ -74,7 +74,7 @@ class ContractUpgradeFlowTest {
b.services.vaultService.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
// Party A initiates contract upgrade flow, expected to succeed this time.
val resultFuture = a.services.startFlow(ContractUpgradeFlow.Instigator(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture
val resultFuture = a.services.startFlow(ContractUpgradeFlow(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture
mockNet.runNetwork()
val result = resultFuture.get()
@ -121,10 +121,10 @@ class ContractUpgradeFlowTest {
val rpcB = CordaRPCOpsImpl(b.services, b.smm, b.database)
CURRENT_RPC_USER.set(User("user", "pwd", permissions = setOf(
startFlowPermission<ContractUpgradeFlow.Instigator<*, *>>()
startFlowPermission<ContractUpgradeFlow<*, *>>()
)))
val rejectedFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Instigator(stateAndRef, upgrade) },
val rejectedFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow(stateAndRef, upgrade) },
atx!!.tx.outRef<DummyContract.State>(0),
DummyContractV2::class.java).returnValue
@ -135,7 +135,7 @@ class ContractUpgradeFlowTest {
rpcB.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
// Party A initiates contract upgrade flow, expected to succeed this time.
val resultFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Instigator(stateAndRef, upgrade) },
val resultFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow(stateAndRef, upgrade) },
atx.tx.outRef<DummyContract.State>(0),
DummyContractV2::class.java).returnValue
@ -165,7 +165,7 @@ class ContractUpgradeFlowTest {
assertTrue(baseState.state.data is Cash.State, "Contract state is old version.")
val stateAndRef = result.getOrThrow().tx.outRef<Cash.State>(0)
// Starts contract upgrade flow.
a.services.startFlow(ContractUpgradeFlow.Instigator(stateAndRef, CashV2::class.java))
a.services.startFlow(ContractUpgradeFlow(stateAndRef, CashV2::class.java))
mockNet.runNetwork()
// Get contract state from the vault.
val firstState = a.database.transaction { a.vault.unconsumedStates<ContractState>().single() }

View File

@ -13,6 +13,10 @@ UNRELEASED
* ``FlowLogic.getCounterpartyMarker`` is no longer used and been deprecated for removal. If you were using this to
manage multiple independent message streams with the same party in the same flow then use sub-flows instead.
* ``ContractUpgradeFlow.Instigator`` has been renamed to just ``ContractUpgradeFlow``.
* ``NotaryChangeFlow.Instigator`` has been renamed to just ``NotaryChangeFlow``.
Milestone 11.0
--------------

View File

@ -56,9 +56,9 @@ Currently the vault service is used to manage the authorisation records. The adm
/**
* Authorise a contract state upgrade.
* This will store the upgrade authorisation in the vault, and will be queried by [ContractUpgradeFlow.Acceptor] during contract upgrade process.
* This will store the upgrade authorisation in the vault, and will be queried by [ContractUpgradeFlow] during contract upgrade process.
* Invoking this method indicate the node is willing to upgrade the [state] using the [upgradedContractClass].
* This method will NOT initiate the upgrade process. To start the upgrade process, see [ContractUpgradeFlow.Instigator].
* This method will NOT initiate the upgrade process.
*/
fun authoriseContractUpgrade(state: StateAndRef<*>, upgradedContractClass: Class<UpgradedContract<*, *>>)
/**
@ -111,7 +111,7 @@ The upgraded transaction state will be recorded in every participant's node at t
val rpcClient : CordaRPCClient = << Bank B's Corda RPC Client >>
val rpcB = rpcClient.proxy()
rpcB.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Instigator(stateAndRef, upgrade) },
rpcB.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow(stateAndRef, upgrade) },
<<StateAndRef of the contract state>>,
DummyContractV2::class.java)

View File

@ -84,7 +84,7 @@ To change the notary for an input state, use the ``NotaryChangeFlow``. For examp
@Suspendable
fun changeNotary(originalState: StateAndRef<ContractState>,
newNotary: Party): StateAndRef<ContractState> {
val flow = NotaryChangeFlow.Instigator(originalState, newNotary)
val flow = NotaryChangeFlow(originalState, newNotary)
return subFlow(flow)
}

View File

@ -24,6 +24,7 @@ import net.corda.core.serialization.serialize
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.debug
import net.corda.flows.*
import net.corda.node.services.*
import net.corda.node.services.api.*
import net.corda.node.services.config.FullNodeConfiguration
import net.corda.node.services.config.NodeConfiguration
@ -92,7 +93,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
CashIssueFlow::class.java to setOf(Amount::class.java, OpaqueBytes::class.java, Party::class.java),
CashPaymentFlow::class.java to setOf(Amount::class.java, Party::class.java),
FinalityFlow::class.java to emptySet(),
ContractUpgradeFlow.Instigator::class.java to emptySet()
ContractUpgradeFlow::class.java to emptySet()
)
}
@ -270,8 +271,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
installCoreFlow(FetchTransactionsFlow::class) { otherParty, _ -> FetchTransactionsHandler(otherParty) }
installCoreFlow(FetchAttachmentsFlow::class) { otherParty, _ -> FetchAttachmentsHandler(otherParty) }
installCoreFlow(BroadcastTransactionFlow::class) { otherParty, _ -> NotifyTransactionHandler(otherParty) }
installCoreFlow(NotaryChangeFlow.Instigator::class) { otherParty, _ -> NotaryChangeFlow.Acceptor(otherParty) }
installCoreFlow(ContractUpgradeFlow.Instigator::class) { otherParty, _ -> ContractUpgradeFlow.Acceptor(otherParty) }
installCoreFlow(NotaryChangeFlow::class) { otherParty, _ -> NotaryChangeHandler(otherParty) }
installCoreFlow(ContractUpgradeFlow::class) { otherParty, _ -> ContractUpgradeHandler(otherParty) }
}
/**

View File

@ -0,0 +1,124 @@
package net.corda.node.services
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.TransactionType
import net.corda.core.contracts.UpgradedContract
import net.corda.core.contracts.requireThat
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.unwrap
import net.corda.flows.*
/**
* This class sets up network message handlers for requests from peers for data keyed by hash. It is a piece of simple
* glue that sits between the network layer and the database layer.
*
* Note that in our data model, to be able to name a thing by hash automatically gives the power to request it. There
* are no access control lists. If you want to keep some data private, then you must be careful who you give its name
* to, and trust that they will not pass the name onwards. If someone suspects some data might exist but does not have
* its name, then the 256-bit search space they'd have to cover makes it physically impossible to enumerate, and as
* such the hash of a piece of data can be seen as a type of password allowing access to it.
*
* Additionally, because nodes do not store invalid transactions, requesting such a transaction will always yield null.
*/
class FetchTransactionsHandler(otherParty: Party) : FetchDataHandler<SignedTransaction>(otherParty) {
override fun getData(id: SecureHash): SignedTransaction? {
return serviceHub.storageService.validatedTransactions.getTransaction(id)
}
}
// TODO: Use Artemis message streaming support here, called "large messages". This avoids the need to buffer.
class FetchAttachmentsHandler(otherParty: Party) : FetchDataHandler<ByteArray>(otherParty) {
override fun getData(id: SecureHash): ByteArray? {
return serviceHub.storageService.attachments.openAttachment(id)?.open()?.readBytes()
}
}
abstract class FetchDataHandler<out T>(val otherParty: Party) : FlowLogic<Unit>() {
@Suspendable
@Throws(FetchDataFlow.HashNotFound::class)
override fun call() {
val request = receive<FetchDataFlow.Request>(otherParty).unwrap {
if (it.hashes.isEmpty()) throw FlowException("Empty hash list")
it
}
val response = request.hashes.map {
getData(it) ?: throw FetchDataFlow.HashNotFound(it)
}
send(otherParty, response)
}
protected abstract fun getData(id: SecureHash): T?
}
// TODO: We should have a whitelist of contracts we're willing to accept at all, and reject if the transaction
// includes us in any outside that list. Potentially just if it includes any outside that list at all.
// TODO: Do we want to be able to reject specific transactions on more complex rules, for example reject incoming
// cash without from unknown parties?
class NotifyTransactionHandler(val otherParty: Party) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val request = receive<BroadcastTransactionFlow.NotifyTxRequest>(otherParty).unwrap { it }
subFlow(ResolveTransactionsFlow(request.tx, otherParty), shareParentSessions = true)
serviceHub.recordTransactions(request.tx)
}
}
class NotaryChangeHandler(otherSide: Party) : AbstractStateReplacementFlow.Acceptor<Party>(otherSide) {
/**
* Check the notary change proposal.
*
* 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
*/
override fun verifyProposal(proposal: AbstractStateReplacementFlow.Proposal<Party>): Unit {
val state = proposal.stateRef
val proposedTx = proposal.stx.tx
if (proposedTx.type !is TransactionType.NotaryChange) {
throw StateReplacementException("The proposed transaction is not a notary change transaction.")
}
val newNotary = proposal.modification
val isNotary = serviceHub.networkMapCache.notaryNodes.any { it.notaryIdentity == newNotary }
if (!isNotary) {
throw StateReplacementException("The proposed node $newNotary does not run a Notary service")
}
if (state !in proposedTx.inputs) {
throw StateReplacementException("The proposed state $state is not in the proposed transaction inputs")
}
// // An example requirement
// val blacklist = listOf("Evil Notary")
// checkProposal(newNotary.name !in blacklist) {
// "The proposed new notary $newNotary is not trusted by the party"
// }
}
}
class ContractUpgradeHandler(otherSide: Party) : AbstractStateReplacementFlow.Acceptor<Class<out UpgradedContract<ContractState, *>>>(otherSide) {
@Suspendable
@Throws(StateReplacementException::class)
override fun verifyProposal(proposal: AbstractStateReplacementFlow.Proposal<Class<out UpgradedContract<ContractState, *>>>) {
// Retrieve signed transaction from our side, we will apply the upgrade logic to the transaction on our side, and
// verify outputs matches the proposed upgrade.
val stx = subFlow(FetchTransactionsFlow(setOf(proposal.stateRef.txhash), otherSide)).fromDisk.singleOrNull()
requireNotNull(stx) { "We don't have a copy of the referenced state" }
val oldStateAndRef = stx!!.tx.outRef<ContractState>(proposal.stateRef.index)
val authorisedUpgrade = serviceHub.vaultService.getAuthorisedContractUpgrade(oldStateAndRef.ref) ?:
throw IllegalStateException("Contract state upgrade is unauthorised. State hash : ${oldStateAndRef.ref}")
val proposedTx = proposal.stx.tx
val expectedTx = ContractUpgradeFlow.assembleBareTx(oldStateAndRef, proposal.modification).toWireTransaction()
requireThat {
"The instigator is one of the participants" using (otherSide.owningKey in oldStateAndRef.state.data.participants)
"The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification == authorisedUpgrade)
"The proposed tx matches the expected tx for this upgrade" using (proposedTx == expectedTx)
}
ContractUpgradeFlow.verify(oldStateAndRef.state.data, expectedTx.outRef<ContractState>(0).state.data, expectedTx.commands.single())
}
}

View File

@ -1,65 +0,0 @@
package net.corda.node.services.persistence
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.unwrap
import net.corda.flows.*
/**
* This class sets up network message handlers for requests from peers for data keyed by hash. It is a piece of simple
* glue that sits between the network layer and the database layer.
*
* Note that in our data model, to be able to name a thing by hash automatically gives the power to request it. There
* are no access control lists. If you want to keep some data private, then you must be careful who you give its name
* to, and trust that they will not pass the name onwards. If someone suspects some data might exist but does not have
* its name, then the 256-bit search space they'd have to cover makes it physically impossible to enumerate, and as
* such the hash of a piece of data can be seen as a type of password allowing access to it.
*
* Additionally, because nodes do not store invalid transactions, requesting such a transaction will always yield null.
*/
class FetchTransactionsHandler(otherParty: Party) : FetchDataHandler<SignedTransaction>(otherParty) {
override fun getData(id: SecureHash): SignedTransaction? {
return serviceHub.storageService.validatedTransactions.getTransaction(id)
}
}
// TODO: Use Artemis message streaming support here, called "large messages". This avoids the need to buffer.
class FetchAttachmentsHandler(otherParty: Party) : FetchDataHandler<ByteArray>(otherParty) {
override fun getData(id: SecureHash): ByteArray? {
return serviceHub.storageService.attachments.openAttachment(id)?.open()?.readBytes()
}
}
abstract class FetchDataHandler<out T>(val otherParty: Party) : FlowLogic<Unit>() {
@Suspendable
@Throws(FetchDataFlow.HashNotFound::class)
override fun call() {
val request = receive<FetchDataFlow.Request>(otherParty).unwrap {
if (it.hashes.isEmpty()) throw FlowException("Empty hash list")
it
}
val response = request.hashes.map {
getData(it) ?: throw FetchDataFlow.HashNotFound(it)
}
send(otherParty, response)
}
protected abstract fun getData(id: SecureHash): T?
}
// TODO: We should have a whitelist of contracts we're willing to accept at all, and reject if the transaction
// includes us in any outside that list. Potentially just if it includes any outside that list at all.
// TODO: Do we want to be able to reject specific transactions on more complex rules, for example reject incoming
// cash without from unknown parties?
class NotifyTransactionHandler(val otherParty: Party) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val request = receive<BroadcastTransactionFlow.NotifyTxRequest>(otherParty).unwrap { it }
subFlow(ResolveTransactionsFlow(request.tx, otherParty), shareParentSessions = true)
serviceHub.recordTransactions(request.tx)
}
}

View File

@ -1,4 +1,4 @@
package net.corda.flows
package net.corda.node.services.transactions
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.Party
@ -6,6 +6,8 @@ import net.corda.core.node.services.TimestampChecker
import net.corda.core.node.services.UniquenessProvider
import net.corda.core.transactions.FilteredTransaction
import net.corda.core.utilities.unwrap
import net.corda.flows.NotaryFlow
import net.corda.flows.TransactionParts
class NonValidatingNotaryFlow(otherSide: Party,
timestampChecker: TimestampChecker,

View File

@ -3,7 +3,6 @@ package net.corda.node.services.transactions
import net.corda.core.crypto.Party
import net.corda.core.flows.FlowLogic
import net.corda.core.node.services.TimestampChecker
import net.corda.flows.NonValidatingNotaryFlow
/** A non-validating notary service operated by a group of mutually trusting parties, uses the Raft algorithm to achieve consensus. */
class RaftNonValidatingNotaryService(val timestampChecker: TimestampChecker,

View File

@ -3,7 +3,6 @@ package net.corda.node.services.transactions
import net.corda.core.crypto.Party
import net.corda.core.flows.FlowLogic
import net.corda.core.node.services.TimestampChecker
import net.corda.flows.ValidatingNotaryFlow
/** A validating notary service operated by a group of mutually trusting parties, uses the Raft algorithm to achieve consensus. */
class RaftValidatingNotaryService(val timestampChecker: TimestampChecker,

View File

@ -5,7 +5,6 @@ import net.corda.core.flows.FlowLogic
import net.corda.core.node.services.ServiceType
import net.corda.core.node.services.TimestampChecker
import net.corda.core.node.services.UniquenessProvider
import net.corda.flows.NonValidatingNotaryFlow
/** A simple Notary service that does not perform transaction validation */
class SimpleNotaryService(val timestampChecker: TimestampChecker,

View File

@ -1,4 +1,4 @@
package net.corda.flows
package net.corda.node.services.transactions
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.TransactionVerificationException
@ -8,6 +8,7 @@ import net.corda.core.node.services.UniquenessProvider
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.unwrap
import net.corda.flows.*
import java.security.SignatureException
/**

View File

@ -5,7 +5,6 @@ import net.corda.core.flows.FlowLogic
import net.corda.core.node.services.ServiceType
import net.corda.core.node.services.TimestampChecker
import net.corda.core.node.services.UniquenessProvider
import net.corda.flows.ValidatingNotaryFlow
/** A Notary service that validates the transaction chain of the submitted transaction before committing it */
class ValidatingNotaryService(val timestampChecker: TimestampChecker,

View File

@ -2,14 +2,13 @@ package net.corda.node.services
import net.corda.core.contracts.*
import net.corda.core.crypto.Party
import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.generateKeyPair
import net.corda.core.getOrThrow
import net.corda.core.node.services.ServiceInfo
import net.corda.core.seconds
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.flows.NotaryChangeFlow.Instigator
import net.corda.flows.NotaryChangeFlow
import net.corda.flows.StateReplacementException
import net.corda.node.internal.AbstractNode
import net.corda.node.services.network.NetworkMapService
@ -48,7 +47,7 @@ class NotaryChangeTests {
fun `should change notary for a state with single participant`() {
val state = issueState(clientNodeA, oldNotaryNode)
val newNotary = newNotaryNode.info.notaryIdentity
val flow = Instigator(state, newNotary)
val flow = NotaryChangeFlow(state, newNotary)
val future = clientNodeA.services.startFlow(flow)
net.runNetwork()
@ -61,7 +60,7 @@ class NotaryChangeTests {
fun `should change notary for a state with multiple participants`() {
val state = issueMultiPartyState(clientNodeA, clientNodeB, oldNotaryNode)
val newNotary = newNotaryNode.info.notaryIdentity
val flow = Instigator(state, newNotary)
val flow = NotaryChangeFlow(state, newNotary)
val future = clientNodeA.services.startFlow(flow)
net.runNetwork()
@ -77,7 +76,7 @@ class NotaryChangeTests {
fun `should throw when a participant refuses to change Notary`() {
val state = issueMultiPartyState(clientNodeA, clientNodeB, oldNotaryNode)
val newEvilNotary = Party(X500Name("CN=Evil Notary,O=Evil R3,OU=corda,L=London,C=UK"), generateKeyPair().public)
val flow = Instigator(state, newEvilNotary)
val flow = NotaryChangeFlow(state, newEvilNotary)
val future = clientNodeA.services.startFlow(flow)
net.runNetwork()
@ -93,7 +92,7 @@ class NotaryChangeTests {
val state = StateAndRef(issueTx.outputs.first(), StateRef(issueTx.id, 0))
val newNotary = newNotaryNode.info.notaryIdentity
val flow = Instigator(state, newNotary)
val flow = NotaryChangeFlow(state, newNotary)
val future = clientNodeA.services.startFlow(flow)
net.runNetwork()
val newState = future.resultFuture.getOrThrow()

View File

@ -12,6 +12,7 @@ import net.corda.core.node.services.unconsumedStates
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.flows.BroadcastTransactionFlow.NotifyTxRequest
import net.corda.node.services.NotifyTransactionHandler
import net.corda.node.utilities.transaction
import net.corda.testing.MEGA_CORP
import net.corda.testing.node.MockNetwork