mirror of
https://github.com/corda/corda.git
synced 2025-06-01 07:00:54 +00:00
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:
parent
e22ad19fcd
commit
eba753ddfe
@ -1,84 +1,62 @@
|
|||||||
package net.corda.flows
|
package net.corda.flows
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.Party
|
|
||||||
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 net.corda.flows.AbstractStateReplacementFlow.Proposal
|
|
||||||
import net.corda.flows.ContractUpgradeFlow.Acceptor
|
|
||||||
import net.corda.flows.ContractUpgradeFlow.Instigator
|
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A flow to be used for upgrading state objects of an old contract to a new contract.
|
* 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
|
* This 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.
|
* 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
|
* 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.
|
* use the new updated state for future transactions.
|
||||||
*/
|
*/
|
||||||
object ContractUpgradeFlow {
|
class ContractUpgradeFlow<OldState : ContractState, out NewState : ContractState>(
|
||||||
@JvmStatic
|
originalState: StateAndRef<OldState>,
|
||||||
fun verify(tx: TransactionForContract) {
|
newContractClass: Class<out UpgradedContract<OldState, NewState>>
|
||||||
// Contract Upgrade transaction should have 1 input, 1 output and 1 command.
|
) : AbstractStateReplacementFlow.Instigator<OldState, NewState, Class<out UpgradedContract<OldState, NewState>>>(originalState, newContractClass) {
|
||||||
verify(tx.inputs.single(), tx.outputs.single(), tx.commands.map { Command(it.value, it.signers) }.single())
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
companion object {
|
||||||
fun verify(input: ContractState, output: ContractState, commandData: Command) {
|
@JvmStatic
|
||||||
val command = commandData.value as UpgradeCommand
|
fun verify(tx: TransactionForContract) {
|
||||||
val participants: Set<PublicKey> = input.participants.toSet()
|
// Contract Upgrade transaction should have 1 input, 1 output and 1 command.
|
||||||
val keysThatSigned: Set<PublicKey> = commandData.signers.toSet()
|
verify(tx.inputs.single(), tx.outputs.single(), tx.commands.map { Command(it.value, it.signers) }.single())
|
||||||
@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))
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun <OldState : ContractState, NewState : ContractState> assembleBareTx(
|
@JvmStatic
|
||||||
stateRef: StateAndRef<OldState>,
|
fun verify(input: ContractState, output: ContractState, commandData: Command) {
|
||||||
upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>
|
val command = commandData.value as UpgradeCommand
|
||||||
): TransactionBuilder {
|
val participants: Set<PublicKey> = input.participants.toSet()
|
||||||
val contractUpgrade = upgradedContractClass.newInstance()
|
val keysThatSigned: Set<PublicKey> = commandData.signers.toSet()
|
||||||
return TransactionType.General.Builder(stateRef.state.notary)
|
@Suppress("UNCHECKED_CAST")
|
||||||
.withItems(stateRef, contractUpgrade.upgrade(stateRef.state.data), Command(UpgradeCommand(upgradedContractClass), stateRef.state.data.participants))
|
val upgradedContract = command.upgradedContractClass.newInstance() as UpgradedContract<ContractState, *>
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
requireThat {
|
requireThat {
|
||||||
"The instigator is one of the participants" using oldStateAndRef.state.data.participants.contains(otherSide.owningKey)
|
"The signing keys include all participant keys" using keysThatSigned.containsAll(participants)
|
||||||
"The proposed upgrade ${proposal.modification.javaClass} is a trusted upgrade path" using (proposal.modification == authorisedUpgrade)
|
"Inputs state reference the legacy contract" using (input.contract.javaClass == upgradedContract.legacyContract)
|
||||||
"The proposed tx matches the expected tx for this upgrade" using (proposedTx == expectedTx)
|
"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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,119 +5,81 @@ import net.corda.core.crypto.Party
|
|||||||
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 net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.flows.NotaryChangeFlow.Acceptor
|
|
||||||
import net.corda.flows.NotaryChangeFlow.Instigator
|
|
||||||
import java.security.PublicKey
|
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
|
* 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.
|
* must point to the same notary.
|
||||||
*
|
*
|
||||||
* The [Instigator] assembles the transaction for notary replacement and sends out change proposals to all participants
|
* This 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.
|
* 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
|
* 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.
|
* 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>(
|
override fun assembleTx(): Pair<SignedTransaction, Iterable<PublicKey>> {
|
||||||
originalState: StateAndRef<T>,
|
val state = originalState.state
|
||||||
newNotary: Party,
|
val tx = TransactionType.NotaryChange.Builder(originalState.state.notary)
|
||||||
progressTracker: ProgressTracker = tracker()) : AbstractStateReplacementFlow.Instigator<T, T, Party>(originalState, newNotary, progressTracker) {
|
|
||||||
|
|
||||||
override fun assembleTx(): Pair<SignedTransaction, Iterable<PublicKey>> {
|
val participants: Iterable<PublicKey>
|
||||||
val state = originalState.state
|
|
||||||
val tx = TransactionType.NotaryChange.Builder(originalState.state.notary)
|
|
||||||
|
|
||||||
val participants: Iterable<PublicKey>
|
if (state.encumbrance == null) {
|
||||||
|
val modifiedState = TransactionState(state.data, modification)
|
||||||
if (state.encumbrance == null) {
|
tx.addInputState(originalState)
|
||||||
val modifiedState = TransactionState(state.data, modification)
|
tx.addOutputState(modifiedState)
|
||||||
tx.addInputState(originalState)
|
participants = state.data.participants
|
||||||
tx.addOutputState(modifiedState)
|
} else {
|
||||||
participants = state.data.participants
|
participants = resolveEncumbrances(tx)
|
||||||
} else {
|
|
||||||
participants = resolveEncumbrances(tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
val myKey = serviceHub.legalIdentityKey
|
|
||||||
tx.signWith(myKey)
|
|
||||||
|
|
||||||
val stx = tx.toSignedTransaction(false)
|
|
||||||
|
|
||||||
return Pair(stx, participants)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
val myKey = serviceHub.legalIdentityKey
|
||||||
* Adds the notary change state transitions to the [tx] builder for the [originalState] and its encumbrance
|
tx.signWith(myKey)
|
||||||
* 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 participants = mutableSetOf<PublicKey>()
|
val stx = tx.toSignedTransaction(false)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return Pair(stx, participants)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Acceptor(otherSide: Party) : AbstractStateReplacementFlow.Acceptor<Party>(otherSide) {
|
/**
|
||||||
/**
|
* Adds the notary change state transitions to the [tx] builder for the [originalState] and its encumbrance
|
||||||
* Check the notary change proposal.
|
* state chain (encumbrance states might be themselves encumbered by other states).
|
||||||
*
|
*
|
||||||
* For example, if the proposed new notary has the same behaviour (e.g. both are non-validating)
|
* @return union of all added states' participants
|
||||||
* 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 resolveEncumbrances(tx: TransactionBuilder): Iterable<PublicKey> {
|
||||||
*/
|
val stateRef = originalState.ref
|
||||||
override fun verifyProposal(proposal: AbstractStateReplacementFlow.Proposal<Party>): Unit {
|
val txId = stateRef.txhash
|
||||||
val state = proposal.stateRef
|
val issuingTx = serviceHub.storageService.validatedTransactions.getTransaction(txId)
|
||||||
val proposedTx = proposal.stx.tx
|
?: throw StateReplacementException("Transaction $txId not found")
|
||||||
|
val outputs = issuingTx.tx.outputs
|
||||||
|
|
||||||
if (proposedTx.type !is TransactionType.NotaryChange) {
|
val participants = mutableSetOf<PublicKey>()
|
||||||
throw StateReplacementException("The proposed transaction is not a notary change transaction.")
|
|
||||||
|
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
|
newOutputPosition++
|
||||||
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"
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return participants
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,12 +21,12 @@ import net.corda.testing.node.MockNetwork
|
|||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.security.PublicKey
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
import java.security.*
|
|
||||||
|
|
||||||
class ContractUpgradeFlowTest {
|
class ContractUpgradeFlowTest {
|
||||||
lateinit var mockNet: MockNetwork
|
lateinit var mockNet: MockNetwork
|
||||||
@ -66,7 +66,7 @@ class ContractUpgradeFlowTest {
|
|||||||
requireNotNull(btx)
|
requireNotNull(btx)
|
||||||
|
|
||||||
// The request is expected to be rejected because party B haven't authorise the upgrade yet.
|
// 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()
|
mockNet.runNetwork()
|
||||||
assertFailsWith(ExecutionException::class) { rejectedFuture.get() }
|
assertFailsWith(ExecutionException::class) { rejectedFuture.get() }
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ class ContractUpgradeFlowTest {
|
|||||||
b.services.vaultService.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
|
b.services.vaultService.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
|
||||||
|
|
||||||
// Party A initiates contract upgrade flow, expected to succeed this time.
|
// 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()
|
mockNet.runNetwork()
|
||||||
|
|
||||||
val result = resultFuture.get()
|
val result = resultFuture.get()
|
||||||
@ -121,10 +121,10 @@ class ContractUpgradeFlowTest {
|
|||||||
val rpcB = CordaRPCOpsImpl(b.services, b.smm, b.database)
|
val rpcB = CordaRPCOpsImpl(b.services, b.smm, b.database)
|
||||||
|
|
||||||
CURRENT_RPC_USER.set(User("user", "pwd", permissions = setOf(
|
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),
|
atx!!.tx.outRef<DummyContract.State>(0),
|
||||||
DummyContractV2::class.java).returnValue
|
DummyContractV2::class.java).returnValue
|
||||||
|
|
||||||
@ -135,7 +135,7 @@ class ContractUpgradeFlowTest {
|
|||||||
rpcB.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
|
rpcB.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
|
||||||
|
|
||||||
// Party A initiates contract upgrade flow, expected to succeed this time.
|
// 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),
|
atx.tx.outRef<DummyContract.State>(0),
|
||||||
DummyContractV2::class.java).returnValue
|
DummyContractV2::class.java).returnValue
|
||||||
|
|
||||||
@ -165,7 +165,7 @@ class ContractUpgradeFlowTest {
|
|||||||
assertTrue(baseState.state.data is Cash.State, "Contract state is old version.")
|
assertTrue(baseState.state.data is Cash.State, "Contract state is old version.")
|
||||||
val stateAndRef = result.getOrThrow().tx.outRef<Cash.State>(0)
|
val stateAndRef = result.getOrThrow().tx.outRef<Cash.State>(0)
|
||||||
// Starts contract upgrade flow.
|
// Starts contract upgrade flow.
|
||||||
a.services.startFlow(ContractUpgradeFlow.Instigator(stateAndRef, CashV2::class.java))
|
a.services.startFlow(ContractUpgradeFlow(stateAndRef, CashV2::class.java))
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
// Get contract state from the vault.
|
// Get contract state from the vault.
|
||||||
val firstState = a.database.transaction { a.vault.unconsumedStates<ContractState>().single() }
|
val firstState = a.database.transaction { a.vault.unconsumedStates<ContractState>().single() }
|
||||||
|
@ -13,6 +13,10 @@ UNRELEASED
|
|||||||
* ``FlowLogic.getCounterpartyMarker`` is no longer used and been deprecated for removal. If you were using this to
|
* ``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.
|
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
|
Milestone 11.0
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
@ -56,9 +56,9 @@ Currently the vault service is used to manage the authorisation records. The adm
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Authorise a contract state upgrade.
|
* 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].
|
* 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<*, *>>)
|
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 rpcClient : CordaRPCClient = << Bank B's Corda RPC Client >>
|
||||||
val rpcB = rpcClient.proxy()
|
val rpcB = rpcClient.proxy()
|
||||||
rpcB.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Instigator(stateAndRef, upgrade) },
|
rpcB.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow(stateAndRef, upgrade) },
|
||||||
<<StateAndRef of the contract state>>,
|
<<StateAndRef of the contract state>>,
|
||||||
DummyContractV2::class.java)
|
DummyContractV2::class.java)
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ To change the notary for an input state, use the ``NotaryChangeFlow``. For examp
|
|||||||
@Suspendable
|
@Suspendable
|
||||||
fun changeNotary(originalState: StateAndRef<ContractState>,
|
fun changeNotary(originalState: StateAndRef<ContractState>,
|
||||||
newNotary: Party): StateAndRef<ContractState> {
|
newNotary: Party): StateAndRef<ContractState> {
|
||||||
val flow = NotaryChangeFlow.Instigator(originalState, newNotary)
|
val flow = NotaryChangeFlow(originalState, newNotary)
|
||||||
return subFlow(flow)
|
return subFlow(flow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ import net.corda.core.serialization.serialize
|
|||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
import net.corda.flows.*
|
import net.corda.flows.*
|
||||||
|
import net.corda.node.services.*
|
||||||
import net.corda.node.services.api.*
|
import net.corda.node.services.api.*
|
||||||
import net.corda.node.services.config.FullNodeConfiguration
|
import net.corda.node.services.config.FullNodeConfiguration
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
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),
|
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),
|
CashPaymentFlow::class.java to setOf(Amount::class.java, Party::class.java),
|
||||||
FinalityFlow::class.java to emptySet(),
|
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(FetchTransactionsFlow::class) { otherParty, _ -> FetchTransactionsHandler(otherParty) }
|
||||||
installCoreFlow(FetchAttachmentsFlow::class) { otherParty, _ -> FetchAttachmentsHandler(otherParty) }
|
installCoreFlow(FetchAttachmentsFlow::class) { otherParty, _ -> FetchAttachmentsHandler(otherParty) }
|
||||||
installCoreFlow(BroadcastTransactionFlow::class) { otherParty, _ -> NotifyTransactionHandler(otherParty) }
|
installCoreFlow(BroadcastTransactionFlow::class) { otherParty, _ -> NotifyTransactionHandler(otherParty) }
|
||||||
installCoreFlow(NotaryChangeFlow.Instigator::class) { otherParty, _ -> NotaryChangeFlow.Acceptor(otherParty) }
|
installCoreFlow(NotaryChangeFlow::class) { otherParty, _ -> NotaryChangeHandler(otherParty) }
|
||||||
installCoreFlow(ContractUpgradeFlow.Instigator::class) { otherParty, _ -> ContractUpgradeFlow.Acceptor(otherParty) }
|
installCoreFlow(ContractUpgradeFlow::class) { otherParty, _ -> ContractUpgradeHandler(otherParty) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
124
node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt
Normal file
124
node/src/main/kotlin/net/corda/node/services/CoreFlowHandlers.kt
Normal 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())
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package net.corda.flows
|
package net.corda.node.services.transactions
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.crypto.Party
|
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.node.services.UniquenessProvider
|
||||||
import net.corda.core.transactions.FilteredTransaction
|
import net.corda.core.transactions.FilteredTransaction
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
|
import net.corda.flows.NotaryFlow
|
||||||
|
import net.corda.flows.TransactionParts
|
||||||
|
|
||||||
class NonValidatingNotaryFlow(otherSide: Party,
|
class NonValidatingNotaryFlow(otherSide: Party,
|
||||||
timestampChecker: TimestampChecker,
|
timestampChecker: TimestampChecker,
|
@ -3,7 +3,6 @@ package net.corda.node.services.transactions
|
|||||||
import net.corda.core.crypto.Party
|
import net.corda.core.crypto.Party
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.node.services.TimestampChecker
|
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. */
|
/** 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,
|
class RaftNonValidatingNotaryService(val timestampChecker: TimestampChecker,
|
||||||
|
@ -3,7 +3,6 @@ package net.corda.node.services.transactions
|
|||||||
import net.corda.core.crypto.Party
|
import net.corda.core.crypto.Party
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.node.services.TimestampChecker
|
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. */
|
/** A validating notary service operated by a group of mutually trusting parties, uses the Raft algorithm to achieve consensus. */
|
||||||
class RaftValidatingNotaryService(val timestampChecker: TimestampChecker,
|
class RaftValidatingNotaryService(val timestampChecker: TimestampChecker,
|
||||||
|
@ -5,7 +5,6 @@ import net.corda.core.flows.FlowLogic
|
|||||||
import net.corda.core.node.services.ServiceType
|
import net.corda.core.node.services.ServiceType
|
||||||
import net.corda.core.node.services.TimestampChecker
|
import net.corda.core.node.services.TimestampChecker
|
||||||
import net.corda.core.node.services.UniquenessProvider
|
import net.corda.core.node.services.UniquenessProvider
|
||||||
import net.corda.flows.NonValidatingNotaryFlow
|
|
||||||
|
|
||||||
/** A simple Notary service that does not perform transaction validation */
|
/** A simple Notary service that does not perform transaction validation */
|
||||||
class SimpleNotaryService(val timestampChecker: TimestampChecker,
|
class SimpleNotaryService(val timestampChecker: TimestampChecker,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package net.corda.flows
|
package net.corda.node.services.transactions
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.TransactionVerificationException
|
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.SignedTransaction
|
||||||
import net.corda.core.transactions.WireTransaction
|
import net.corda.core.transactions.WireTransaction
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
|
import net.corda.flows.*
|
||||||
import java.security.SignatureException
|
import java.security.SignatureException
|
||||||
|
|
||||||
/**
|
/**
|
@ -5,7 +5,6 @@ import net.corda.core.flows.FlowLogic
|
|||||||
import net.corda.core.node.services.ServiceType
|
import net.corda.core.node.services.ServiceType
|
||||||
import net.corda.core.node.services.TimestampChecker
|
import net.corda.core.node.services.TimestampChecker
|
||||||
import net.corda.core.node.services.UniquenessProvider
|
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 */
|
/** A Notary service that validates the transaction chain of the submitted transaction before committing it */
|
||||||
class ValidatingNotaryService(val timestampChecker: TimestampChecker,
|
class ValidatingNotaryService(val timestampChecker: TimestampChecker,
|
||||||
|
@ -2,14 +2,13 @@ package net.corda.node.services
|
|||||||
|
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.Party
|
import net.corda.core.crypto.Party
|
||||||
import net.corda.core.crypto.X509Utilities
|
|
||||||
import net.corda.core.crypto.generateKeyPair
|
import net.corda.core.crypto.generateKeyPair
|
||||||
import net.corda.core.getOrThrow
|
import net.corda.core.getOrThrow
|
||||||
import net.corda.core.node.services.ServiceInfo
|
import net.corda.core.node.services.ServiceInfo
|
||||||
import net.corda.core.seconds
|
import net.corda.core.seconds
|
||||||
import net.corda.core.transactions.WireTransaction
|
import net.corda.core.transactions.WireTransaction
|
||||||
import net.corda.core.utilities.DUMMY_NOTARY
|
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.flows.StateReplacementException
|
||||||
import net.corda.node.internal.AbstractNode
|
import net.corda.node.internal.AbstractNode
|
||||||
import net.corda.node.services.network.NetworkMapService
|
import net.corda.node.services.network.NetworkMapService
|
||||||
@ -48,7 +47,7 @@ class NotaryChangeTests {
|
|||||||
fun `should change notary for a state with single participant`() {
|
fun `should change notary for a state with single participant`() {
|
||||||
val state = issueState(clientNodeA, oldNotaryNode)
|
val state = issueState(clientNodeA, oldNotaryNode)
|
||||||
val newNotary = newNotaryNode.info.notaryIdentity
|
val newNotary = newNotaryNode.info.notaryIdentity
|
||||||
val flow = Instigator(state, newNotary)
|
val flow = NotaryChangeFlow(state, newNotary)
|
||||||
val future = clientNodeA.services.startFlow(flow)
|
val future = clientNodeA.services.startFlow(flow)
|
||||||
|
|
||||||
net.runNetwork()
|
net.runNetwork()
|
||||||
@ -61,7 +60,7 @@ class NotaryChangeTests {
|
|||||||
fun `should change notary for a state with multiple participants`() {
|
fun `should change notary for a state with multiple participants`() {
|
||||||
val state = issueMultiPartyState(clientNodeA, clientNodeB, oldNotaryNode)
|
val state = issueMultiPartyState(clientNodeA, clientNodeB, oldNotaryNode)
|
||||||
val newNotary = newNotaryNode.info.notaryIdentity
|
val newNotary = newNotaryNode.info.notaryIdentity
|
||||||
val flow = Instigator(state, newNotary)
|
val flow = NotaryChangeFlow(state, newNotary)
|
||||||
val future = clientNodeA.services.startFlow(flow)
|
val future = clientNodeA.services.startFlow(flow)
|
||||||
|
|
||||||
net.runNetwork()
|
net.runNetwork()
|
||||||
@ -77,7 +76,7 @@ class NotaryChangeTests {
|
|||||||
fun `should throw when a participant refuses to change Notary`() {
|
fun `should throw when a participant refuses to change Notary`() {
|
||||||
val state = issueMultiPartyState(clientNodeA, clientNodeB, oldNotaryNode)
|
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 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)
|
val future = clientNodeA.services.startFlow(flow)
|
||||||
|
|
||||||
net.runNetwork()
|
net.runNetwork()
|
||||||
@ -93,7 +92,7 @@ class NotaryChangeTests {
|
|||||||
|
|
||||||
val state = StateAndRef(issueTx.outputs.first(), StateRef(issueTx.id, 0))
|
val state = StateAndRef(issueTx.outputs.first(), StateRef(issueTx.id, 0))
|
||||||
val newNotary = newNotaryNode.info.notaryIdentity
|
val newNotary = newNotaryNode.info.notaryIdentity
|
||||||
val flow = Instigator(state, newNotary)
|
val flow = NotaryChangeFlow(state, newNotary)
|
||||||
val future = clientNodeA.services.startFlow(flow)
|
val future = clientNodeA.services.startFlow(flow)
|
||||||
net.runNetwork()
|
net.runNetwork()
|
||||||
val newState = future.resultFuture.getOrThrow()
|
val newState = future.resultFuture.getOrThrow()
|
||||||
|
@ -12,6 +12,7 @@ import net.corda.core.node.services.unconsumedStates
|
|||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.DUMMY_NOTARY
|
import net.corda.core.utilities.DUMMY_NOTARY
|
||||||
import net.corda.flows.BroadcastTransactionFlow.NotifyTxRequest
|
import net.corda.flows.BroadcastTransactionFlow.NotifyTxRequest
|
||||||
|
import net.corda.node.services.NotifyTransactionHandler
|
||||||
import net.corda.node.utilities.transaction
|
import net.corda.node.utilities.transaction
|
||||||
import net.corda.testing.MEGA_CORP
|
import net.corda.testing.MEGA_CORP
|
||||||
import net.corda.testing.node.MockNetwork
|
import net.corda.testing.node.MockNetwork
|
||||||
|
Loading…
x
Reference in New Issue
Block a user