mirror of
https://github.com/corda/corda.git
synced 2025-06-19 07:38:22 +00:00
Change CashIssueFlow to use anonymous identity
* Add functions for: * Retrieving nodes via their legal identity * Filtering a set of public keys down to those the node has corresponding private keys for * Modify contract upgrade flows to handle identifying participants after an anomymisation step * Correct terminology: "party who" -> "party which" * Modify CashIssueFlow and CashPaymentFlow to optionally use an anonymous identity for the recipient.
This commit is contained in:
@ -113,11 +113,13 @@ class NodeMonitorModelTest : DriverBasedTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `cash issue works end to end`() {
|
fun `cash issue works end to end`() {
|
||||||
|
val anonymous = false
|
||||||
rpc.startFlow(::CashIssueFlow,
|
rpc.startFlow(::CashIssueFlow,
|
||||||
Amount(100, USD),
|
Amount(100, USD),
|
||||||
OpaqueBytes(ByteArray(1, { 1 })),
|
OpaqueBytes(ByteArray(1, { 1 })),
|
||||||
aliceNode.legalIdentity,
|
aliceNode.legalIdentity,
|
||||||
notaryNode.notaryIdentity
|
notaryNode.notaryIdentity,
|
||||||
|
anonymous
|
||||||
)
|
)
|
||||||
|
|
||||||
vaultUpdates.expectEvents(isStrict = false) {
|
vaultUpdates.expectEvents(isStrict = false) {
|
||||||
@ -138,8 +140,9 @@ class NodeMonitorModelTest : DriverBasedTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `cash issue and move`() {
|
fun `cash issue and move`() {
|
||||||
rpc.startFlow(::CashIssueFlow, 100.DOLLARS, OpaqueBytes.of(1), aliceNode.legalIdentity, notaryNode.notaryIdentity).returnValue.getOrThrow()
|
val anonymous = false
|
||||||
rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, bobNode.legalIdentity).returnValue.getOrThrow()
|
rpc.startFlow(::CashIssueFlow, 100.DOLLARS, OpaqueBytes.of(1), aliceNode.legalIdentity, notaryNode.notaryIdentity, anonymous).returnValue.getOrThrow()
|
||||||
|
rpc.startFlow(::CashPaymentFlow, 100.DOLLARS, bobNode.legalIdentity, anonymous).returnValue.getOrThrow()
|
||||||
|
|
||||||
var issueSmId: StateMachineRunId? = null
|
var issueSmId: StateMachineRunId? = null
|
||||||
var moveSmId: StateMachineRunId? = null
|
var moveSmId: StateMachineRunId? = null
|
||||||
|
@ -26,7 +26,7 @@ open class EventGenerator(val parties: List<Party>, val currencies: List<Currenc
|
|||||||
|
|
||||||
protected val issueCashGenerator = amountGenerator.combine(partyGenerator, issueRefGenerator, currencyGenerator) { amount, to, issueRef, ccy ->
|
protected val issueCashGenerator = amountGenerator.combine(partyGenerator, issueRefGenerator, currencyGenerator) { amount, to, issueRef, ccy ->
|
||||||
addToMap(ccy, amount)
|
addToMap(ccy, amount)
|
||||||
CashFlowCommand.IssueCash(Amount(amount, ccy), issueRef, to, notary)
|
CashFlowCommand.IssueCash(Amount(amount, ccy), issueRef, to, notary, anonymous = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected val exitCashGenerator = amountGenerator.combine(issueRefGenerator, currencyGenerator) { amount, issueRef, ccy ->
|
protected val exitCashGenerator = amountGenerator.combine(issueRefGenerator, currencyGenerator) { amount, issueRef, ccy ->
|
||||||
@ -35,7 +35,7 @@ open class EventGenerator(val parties: List<Party>, val currencies: List<Currenc
|
|||||||
}
|
}
|
||||||
|
|
||||||
open val moveCashGenerator = amountGenerator.combine(partyGenerator, currencyGenerator) { amountIssued, recipient, currency ->
|
open val moveCashGenerator = amountGenerator.combine(partyGenerator, currencyGenerator) { amountIssued, recipient, currency ->
|
||||||
CashFlowCommand.PayCash(Amount(amountIssued, currency), recipient)
|
CashFlowCommand.PayCash(Amount(amountIssued, currency), recipient, anonymous = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
open val issuerGenerator = Generator.frequency(listOf(
|
open val issuerGenerator = Generator.frequency(listOf(
|
||||||
@ -71,11 +71,11 @@ class ErrorFlowsEventGenerator(parties: List<Party>, currencies: List<Currency>,
|
|||||||
}
|
}
|
||||||
|
|
||||||
val normalMoveGenerator = amountGenerator.combine(partyGenerator, currencyGenerator) { amountIssued, recipient, currency ->
|
val normalMoveGenerator = amountGenerator.combine(partyGenerator, currencyGenerator) { amountIssued, recipient, currency ->
|
||||||
CashFlowCommand.PayCash(Amount(amountIssued, currency), recipient)
|
CashFlowCommand.PayCash(Amount(amountIssued, currency), recipient, anonymous = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
val errorMoveGenerator = partyGenerator.combine(currencyGenerator) { recipient, currency ->
|
val errorMoveGenerator = partyGenerator.combine(currencyGenerator) { recipient, currency ->
|
||||||
CashFlowCommand.PayCash(Amount(currencyMap[currency]!! * 2, currency), recipient)
|
CashFlowCommand.PayCash(Amount(currencyMap[currency]!! * 2, currency), recipient, anonymous = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val moveCashGenerator = Generator.frequency(listOf(
|
override val moveCashGenerator = Generator.frequency(listOf(
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package net.corda.flows
|
||||||
|
|
||||||
|
import net.corda.core.identity.AnonymousParty
|
||||||
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import org.bouncycastle.cert.X509CertificateHolder
|
||||||
|
import java.security.PublicKey
|
||||||
|
import java.security.cert.CertPath
|
||||||
|
|
||||||
|
@CordaSerializable
|
||||||
|
data class AnonymisedIdentity(
|
||||||
|
val certPath: CertPath,
|
||||||
|
val certificate: X509CertificateHolder,
|
||||||
|
val identity: AnonymousParty) {
|
||||||
|
constructor(certPath: CertPath, certificate: X509CertificateHolder, identity: PublicKey)
|
||||||
|
: this(certPath, certificate, AnonymousParty(identity))
|
||||||
|
}
|
@ -350,6 +350,16 @@ inline fun <T : Any, A, B, C, D, reified R : FlowLogic<T>> CordaRPCOps.startFlow
|
|||||||
arg3: D
|
arg3: D
|
||||||
): FlowHandle<T> = startFlowDynamic(R::class.java, arg0, arg1, arg2, arg3)
|
): FlowHandle<T> = startFlowDynamic(R::class.java, arg0, arg1, arg2, arg3)
|
||||||
|
|
||||||
|
inline fun <T : Any, A, B, C, D, E, reified R : FlowLogic<T>> CordaRPCOps.startFlow(
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
flowConstructor: (A, B, C, D, E) -> R,
|
||||||
|
arg0: A,
|
||||||
|
arg1: B,
|
||||||
|
arg2: C,
|
||||||
|
arg3: D,
|
||||||
|
arg4: E
|
||||||
|
): FlowHandle<T> = startFlowDynamic(R::class.java, arg0, arg1, arg2, arg3, arg4)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same again, except this time with progress-tracking enabled.
|
* Same again, except this time with progress-tracking enabled.
|
||||||
*/
|
*/
|
||||||
|
@ -2,9 +2,11 @@ package net.corda.core.node.services
|
|||||||
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import net.corda.core.contracts.Contract
|
import net.corda.core.contracts.Contract
|
||||||
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.messaging.DataFeed
|
import net.corda.core.messaging.DataFeed
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.randomOrNull
|
import net.corda.core.randomOrNull
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
@ -63,6 +65,17 @@ interface NetworkMapCache {
|
|||||||
*/
|
*/
|
||||||
fun getRecommended(type: ServiceType, contract: Contract, vararg party: Party): NodeInfo? = getNodesWithService(type).firstOrNull()
|
fun getRecommended(type: ServiceType, contract: Contract, vararg party: Party): NodeInfo? = getNodesWithService(type).firstOrNull()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up the node info for a specific party. Will attempt to de-anonymise the party if applicable; if the party
|
||||||
|
* is anonymised and the well known party cannot be resolved, it is impossible ot identify the node and therefore this
|
||||||
|
* returns null.
|
||||||
|
*
|
||||||
|
* @param party party to retrieve node information for.
|
||||||
|
* @return the node for the identity, or null if the node could not be found. This does not necessarily mean there is
|
||||||
|
* no node for the party, only that this cache is unaware of it.
|
||||||
|
*/
|
||||||
|
fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo?
|
||||||
|
|
||||||
/** Look up the node info for a legal name. */
|
/** Look up the node info for a legal name. */
|
||||||
fun getNodeByLegalName(principal: X500Name): NodeInfo? = partyNodes.singleOrNull { it.legalIdentity.name == principal }
|
fun getNodeByLegalName(principal: X500Name): NodeInfo? = partyNodes.singleOrNull { it.legalIdentity.name == principal }
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import net.corda.core.toFuture
|
|||||||
import net.corda.core.transactions.LedgerTransaction
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import net.corda.core.transactions.WireTransaction
|
import net.corda.core.transactions.WireTransaction
|
||||||
|
import net.corda.flows.AnonymisedIdentity
|
||||||
import org.bouncycastle.cert.X509CertificateHolder
|
import org.bouncycastle.cert.X509CertificateHolder
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
@ -486,9 +487,17 @@ interface KeyManagementService {
|
|||||||
* @return X.509 certificate and path to the trust root.
|
* @return X.509 certificate and path to the trust root.
|
||||||
*/
|
*/
|
||||||
@Suspendable
|
@Suspendable
|
||||||
fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): Pair<X509CertificateHolder, CertPath>
|
fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymisedIdentity
|
||||||
|
|
||||||
/** Using the provided signing [PublicKey] internally looks up the matching [PrivateKey] and signs the data.
|
/**
|
||||||
|
* Filter some keys down to the set that this node owns (has private keys for).
|
||||||
|
*
|
||||||
|
* @param candidateKeys keys which this node may own.
|
||||||
|
*/
|
||||||
|
fun filterMyKeys(candidateKeys: Iterable<PublicKey>): Iterable<PublicKey>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Using the provided signing [PublicKey] internally looks up the matching [PrivateKey] and signs the data.
|
||||||
* @param bytes The data to sign over using the chosen key.
|
* @param bytes The data to sign over using the chosen key.
|
||||||
* @param publicKey The [PublicKey] partner to an internally held [PrivateKey], either derived from the node's primary identity,
|
* @param publicKey The [PublicKey] partner to an internally held [PrivateKey], either derived from the node's primary identity,
|
||||||
* or previously generated via the [freshKey] method.
|
* or previously generated via the [freshKey] method.
|
||||||
|
@ -73,7 +73,7 @@ object DefaultKryoCustomizer {
|
|||||||
|
|
||||||
noReferencesWithin<WireTransaction>()
|
noReferencesWithin<WireTransaction>()
|
||||||
|
|
||||||
register(sun.security.ec.ECPublicKeyImpl::class.java, PublicKeySerializer)
|
register(sun.security.ec.ECPublicKeyImpl::class.java, ECPublicKeyImplSerializer)
|
||||||
register(EdDSAPublicKey::class.java, Ed25519PublicKeySerializer)
|
register(EdDSAPublicKey::class.java, Ed25519PublicKeySerializer)
|
||||||
register(EdDSAPrivateKey::class.java, Ed25519PrivateKeySerializer)
|
register(EdDSAPrivateKey::class.java, Ed25519PrivateKeySerializer)
|
||||||
|
|
||||||
@ -113,6 +113,7 @@ object DefaultKryoCustomizer {
|
|||||||
register(BCRSAPublicKey::class.java, PublicKeySerializer)
|
register(BCRSAPublicKey::class.java, PublicKeySerializer)
|
||||||
register(BCSphincs256PrivateKey::class.java, PrivateKeySerializer)
|
register(BCSphincs256PrivateKey::class.java, PrivateKeySerializer)
|
||||||
register(BCSphincs256PublicKey::class.java, PublicKeySerializer)
|
register(BCSphincs256PublicKey::class.java, PublicKeySerializer)
|
||||||
|
register(sun.security.ec.ECPublicKeyImpl::class.java, PublicKeySerializer)
|
||||||
|
|
||||||
val customization = KryoSerializationCustomization(this)
|
val customization = KryoSerializationCustomization(this)
|
||||||
pluginRegistries.forEach { it.customizeSerialization(customization) }
|
pluginRegistries.forEach { it.customizeSerialization(customization) }
|
||||||
|
@ -387,6 +387,20 @@ object Ed25519PublicKeySerializer : Serializer<EdDSAPublicKey>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** For serialising an ed25519 public key */
|
||||||
|
@ThreadSafe
|
||||||
|
object ECPublicKeyImplSerializer : Serializer<sun.security.ec.ECPublicKeyImpl>() {
|
||||||
|
override fun write(kryo: Kryo, output: Output, obj: sun.security.ec.ECPublicKeyImpl) {
|
||||||
|
output.writeBytesWithLength(obj.encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(kryo: Kryo, input: Input, type: Class<sun.security.ec.ECPublicKeyImpl>): sun.security.ec.ECPublicKeyImpl {
|
||||||
|
val A = input.readBytesWithLength()
|
||||||
|
val der = sun.security.util.DerValue(A)
|
||||||
|
return sun.security.ec.ECPublicKeyImpl.parse(der) as sun.security.ec.ECPublicKeyImpl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO Implement standardized serialization of CompositeKeys. See JIRA issue: CORDA-249.
|
// TODO Implement standardized serialization of CompositeKeys. See JIRA issue: CORDA-249.
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
object CompositeKeySerializer : Serializer<CompositeKey>() {
|
object CompositeKeySerializer : Serializer<CompositeKey>() {
|
||||||
|
@ -9,6 +9,7 @@ import net.corda.core.crypto.isFulfilledBy
|
|||||||
import net.corda.core.flows.FlowException
|
import net.corda.core.flows.FlowException
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
|
import net.corda.core.identity.AnonymousParty
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
@ -33,6 +34,15 @@ abstract class AbstractStateReplacementFlow {
|
|||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class Proposal<out M>(val stateRef: StateRef, val modification: M, val stx: SignedTransaction)
|
data class Proposal<out M>(val stateRef: StateRef, val modification: M, val stx: SignedTransaction)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The assembled transaction for upgrading a contract.
|
||||||
|
*
|
||||||
|
* @param stx signed transaction to do the upgrade.
|
||||||
|
* @param participants the parties involved in the upgrade transaction.
|
||||||
|
* @param myKey key
|
||||||
|
*/
|
||||||
|
data class UpgradeTx(val stx: SignedTransaction, val participants: Iterable<PublicKey>, val myKey: PublicKey)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [Instigator] assembles the transaction for state replacement and sends out change proposals to all participants
|
* The [Instigator] assembles the transaction for state 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.
|
* ([Acceptor]) of that state. If participants agree to the proposed change, they each sign the transaction.
|
||||||
@ -57,17 +67,14 @@ abstract class AbstractStateReplacementFlow {
|
|||||||
@Suspendable
|
@Suspendable
|
||||||
@Throws(StateReplacementException::class)
|
@Throws(StateReplacementException::class)
|
||||||
override fun call(): StateAndRef<T> {
|
override fun call(): StateAndRef<T> {
|
||||||
val (stx, participants) = assembleTx()
|
val (stx, participantKeys, myKey) = assembleTx()
|
||||||
|
|
||||||
progressTracker.currentStep = SIGNING
|
progressTracker.currentStep = SIGNING
|
||||||
|
|
||||||
val myKey = serviceHub.myInfo.legalIdentity
|
val signatures = if (participantKeys.singleOrNull() == myKey) {
|
||||||
val me = listOf(myKey)
|
|
||||||
|
|
||||||
val signatures = if (participants == me) {
|
|
||||||
getNotarySignatures(stx)
|
getNotarySignatures(stx)
|
||||||
} else {
|
} else {
|
||||||
collectSignatures((participants - me).map { it.owningKey }, stx)
|
collectSignatures(participantKeys - myKey, stx)
|
||||||
}
|
}
|
||||||
|
|
||||||
val finalTx = stx + signatures
|
val finalTx = stx + signatures
|
||||||
@ -75,7 +82,13 @@ abstract class AbstractStateReplacementFlow {
|
|||||||
return finalTx.tx.outRef(0)
|
return finalTx.tx.outRef(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract protected fun assembleTx(): Pair<SignedTransaction, Iterable<AbstractParty>>
|
/**
|
||||||
|
* Build the upgrade transaction.
|
||||||
|
*
|
||||||
|
* @return a triple of the transaction, the public keys of all participants, and the participating public key of
|
||||||
|
* this node.
|
||||||
|
*/
|
||||||
|
abstract protected fun assembleTx(): UpgradeTx
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun collectSignatures(participants: Iterable<PublicKey>, stx: SignedTransaction): List<DigitalSignature.WithKey> {
|
private fun collectSignatures(participants: Iterable<PublicKey>, stx: SignedTransaction): List<DigitalSignature.WithKey> {
|
||||||
|
@ -58,9 +58,12 @@ class ContractUpgradeFlow<OldState : ContractState, out NewState : ContractState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun assembleTx(): Pair<SignedTransaction, Iterable<AbstractParty>> {
|
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
|
||||||
val baseTx = assembleBareTx(originalState, modification)
|
val baseTx = assembleBareTx(originalState, modification)
|
||||||
val stx = serviceHub.signInitialTransaction(baseTx)
|
val participantKeys = originalState.state.data.participants.map { it.owningKey }.toSet()
|
||||||
return stx to originalState.state.data.participants
|
// TODO: We need a much faster way of finding our key in the transaction
|
||||||
|
val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
|
||||||
|
val stx = serviceHub.signInitialTransaction(baseTx, myKey)
|
||||||
|
return AbstractStateReplacementFlow.UpgradeTx(stx, participantKeys, myKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import net.corda.core.identity.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 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
|
||||||
@ -24,24 +25,25 @@ class NotaryChangeFlow<out T : ContractState>(
|
|||||||
progressTracker: ProgressTracker = tracker())
|
progressTracker: ProgressTracker = tracker())
|
||||||
: AbstractStateReplacementFlow.Instigator<T, T, Party>(originalState, newNotary, progressTracker) {
|
: AbstractStateReplacementFlow.Instigator<T, T, Party>(originalState, newNotary, progressTracker) {
|
||||||
|
|
||||||
override fun assembleTx(): Pair<SignedTransaction, Iterable<AbstractParty>> {
|
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
|
||||||
val state = originalState.state
|
val state = originalState.state
|
||||||
val tx = TransactionType.NotaryChange.Builder(originalState.state.notary)
|
val tx = TransactionType.NotaryChange.Builder(originalState.state.notary)
|
||||||
|
|
||||||
val participants: Iterable<AbstractParty>
|
val participants: Iterable<AbstractParty> = if (state.encumbrance == null) {
|
||||||
|
|
||||||
if (state.encumbrance == null) {
|
|
||||||
val modifiedState = TransactionState(state.data, modification)
|
val modifiedState = TransactionState(state.data, modification)
|
||||||
tx.addInputState(originalState)
|
tx.addInputState(originalState)
|
||||||
tx.addOutputState(modifiedState)
|
tx.addOutputState(modifiedState)
|
||||||
participants = state.data.participants
|
state.data.participants
|
||||||
} else {
|
} else {
|
||||||
participants = resolveEncumbrances(tx)
|
resolveEncumbrances(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
val stx = serviceHub.signInitialTransaction(tx)
|
val stx = serviceHub.signInitialTransaction(tx)
|
||||||
|
val participantKeys = participants.map { it.owningKey }
|
||||||
|
// TODO: We need a much faster way of finding our key in the transaction
|
||||||
|
val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
|
||||||
|
|
||||||
return Pair(stx, participants)
|
return AbstractStateReplacementFlow.UpgradeTx(stx, participantKeys, myKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -5,13 +5,10 @@ import net.corda.core.flows.FlowLogic
|
|||||||
import net.corda.core.flows.InitiatedBy
|
import net.corda.core.flows.InitiatedBy
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
import net.corda.core.flows.StartableByRPC
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.identity.AnonymousParty
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
import org.bouncycastle.cert.X509CertificateHolder
|
|
||||||
import java.security.cert.CertPath
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Very basic flow which exchanges transaction key and certificate paths between two parties in a transaction.
|
* Very basic flow which exchanges transaction key and certificate paths between two parties in a transaction.
|
||||||
@ -19,11 +16,11 @@ import java.security.cert.CertPath
|
|||||||
*/
|
*/
|
||||||
object TxKeyFlow {
|
object TxKeyFlow {
|
||||||
abstract class AbstractIdentityFlow<out T>(val otherSide: Party, val revocationEnabled: Boolean): FlowLogic<T>() {
|
abstract class AbstractIdentityFlow<out T>(val otherSide: Party, val revocationEnabled: Boolean): FlowLogic<T>() {
|
||||||
fun validateIdentity(untrustedIdentity: AnonymousIdentity): AnonymousIdentity {
|
fun validateIdentity(untrustedIdentity: AnonymisedIdentity): AnonymisedIdentity {
|
||||||
val (certPath, theirCert, txIdentity) = untrustedIdentity
|
val (certPath, theirCert, txIdentity) = untrustedIdentity
|
||||||
if (theirCert.subject == otherSide.name) {
|
if (theirCert.subject == otherSide.name) {
|
||||||
serviceHub.identityService.registerAnonymousIdentity(txIdentity, otherSide, certPath)
|
serviceHub.identityService.registerAnonymousIdentity(txIdentity, otherSide, certPath)
|
||||||
return AnonymousIdentity(certPath, theirCert, txIdentity)
|
return AnonymisedIdentity(certPath, theirCert, txIdentity)
|
||||||
} else
|
} else
|
||||||
throw IllegalStateException("Expected certificate subject to be ${otherSide.name} but found ${theirCert.subject}")
|
throw IllegalStateException("Expected certificate subject to be ${otherSide.name} but found ${theirCert.subject}")
|
||||||
}
|
}
|
||||||
@ -32,7 +29,7 @@ object TxKeyFlow {
|
|||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
class Requester(otherSide: Party,
|
class Requester(otherSide: Party,
|
||||||
override val progressTracker: ProgressTracker) : AbstractIdentityFlow<Map<Party, AnonymousIdentity>>(otherSide, false) {
|
override val progressTracker: ProgressTracker) : AbstractIdentityFlow<TxIdentities>(otherSide, false) {
|
||||||
constructor(otherSide: Party) : this(otherSide, tracker())
|
constructor(otherSide: Party) : this(otherSide, tracker())
|
||||||
companion object {
|
companion object {
|
||||||
object AWAITING_KEY : ProgressTracker.Step("Awaiting key")
|
object AWAITING_KEY : ProgressTracker.Step("Awaiting key")
|
||||||
@ -41,14 +38,20 @@ object TxKeyFlow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): Map<Party, AnonymousIdentity> {
|
override fun call(): TxIdentities {
|
||||||
progressTracker.currentStep = AWAITING_KEY
|
progressTracker.currentStep = AWAITING_KEY
|
||||||
val myIdentityFragment = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, revocationEnabled)
|
val myIdentity = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, revocationEnabled)
|
||||||
val myIdentity = AnonymousIdentity(myIdentityFragment)
|
serviceHub.identityService.registerAnonymousIdentity(myIdentity.identity, serviceHub.myInfo.legalIdentity, myIdentity.certPath)
|
||||||
val theirIdentity = receive<AnonymousIdentity>(otherSide).unwrap { validateIdentity(it) }
|
|
||||||
send(otherSide, myIdentity)
|
// Special case that if we're both parties, a single identity is generated
|
||||||
return mapOf(Pair(otherSide, myIdentity),
|
return if (otherSide == serviceHub.myInfo.legalIdentity) {
|
||||||
Pair(serviceHub.myInfo.legalIdentity, theirIdentity))
|
TxIdentities(Pair(otherSide, myIdentity))
|
||||||
|
} else {
|
||||||
|
val theirIdentity = receive<AnonymisedIdentity>(otherSide).unwrap { validateIdentity(it) }
|
||||||
|
send(otherSide, myIdentity)
|
||||||
|
TxIdentities(Pair(otherSide, myIdentity),
|
||||||
|
Pair(serviceHub.myInfo.legalIdentity, theirIdentity))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +60,7 @@ object TxKeyFlow {
|
|||||||
* counterparty and as the result from the flow.
|
* counterparty and as the result from the flow.
|
||||||
*/
|
*/
|
||||||
@InitiatedBy(Requester::class)
|
@InitiatedBy(Requester::class)
|
||||||
class Provider(otherSide: Party) : AbstractIdentityFlow<Map<Party, AnonymousIdentity>>(otherSide, false) {
|
class Provider(otherSide: Party) : AbstractIdentityFlow<TxIdentities>(otherSide, false) {
|
||||||
companion object {
|
companion object {
|
||||||
object SENDING_KEY : ProgressTracker.Step("Sending key")
|
object SENDING_KEY : ProgressTracker.Step("Sending key")
|
||||||
}
|
}
|
||||||
@ -65,25 +68,24 @@ object TxKeyFlow {
|
|||||||
override val progressTracker: ProgressTracker = ProgressTracker(SENDING_KEY)
|
override val progressTracker: ProgressTracker = ProgressTracker(SENDING_KEY)
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): Map<Party, AnonymousIdentity> {
|
override fun call(): TxIdentities {
|
||||||
val revocationEnabled = false
|
val revocationEnabled = false
|
||||||
progressTracker.currentStep = SENDING_KEY
|
progressTracker.currentStep = SENDING_KEY
|
||||||
val myIdentityFragment = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, revocationEnabled)
|
val myIdentity = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentityAndCert, revocationEnabled)
|
||||||
val myIdentity = AnonymousIdentity(myIdentityFragment)
|
|
||||||
send(otherSide, myIdentity)
|
send(otherSide, myIdentity)
|
||||||
val theirIdentity = receive<AnonymousIdentity>(otherSide).unwrap { validateIdentity(it) }
|
val theirIdentity = receive<AnonymisedIdentity>(otherSide).unwrap { validateIdentity(it) }
|
||||||
return mapOf(Pair(otherSide, myIdentity),
|
return TxIdentities(Pair(otherSide, myIdentity),
|
||||||
Pair(serviceHub.myInfo.legalIdentity, theirIdentity))
|
Pair(serviceHub.myInfo.legalIdentity, theirIdentity))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class AnonymousIdentity(
|
data class TxIdentities(val identities: List<Pair<Party, AnonymisedIdentity>>) {
|
||||||
val certPath: CertPath,
|
constructor(vararg identities: Pair<Party, AnonymisedIdentity>) : this(identities.toList())
|
||||||
val certificate: X509CertificateHolder,
|
init {
|
||||||
val identity: AnonymousParty) {
|
require(identities.size == identities.map { it.first }.toSet().size) { "Identities must be unique: ${identities.map { it.first }}" }
|
||||||
constructor(myIdentity: Pair<X509CertificateHolder, CertPath>) : this(myIdentity.second,
|
}
|
||||||
myIdentity.first,
|
fun forParty(party: Party): AnonymisedIdentity = identities.single { it.first == party }.second
|
||||||
AnonymousParty(myIdentity.second.certificates.first().publicKey))
|
fun toMap(): Map<Party, AnonymisedIdentity> = this.identities.toMap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,9 +173,11 @@ class ContractUpgradeFlowTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `upgrade Cash to v2`() {
|
fun `upgrade Cash to v2`() {
|
||||||
// Create some cash.
|
// Create some cash.
|
||||||
val result = a.services.startFlow(CashIssueFlow(Amount(1000, USD), OpaqueBytes.of(1), a.info.legalIdentity, notary)).resultFuture
|
val anonymous = false
|
||||||
|
val result = a.services.startFlow(CashIssueFlow(Amount(1000, USD), OpaqueBytes.of(1), a.info.legalIdentity, notary, anonymous)).resultFuture
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
val stateAndRef = result.getOrThrow().tx.outRef<Cash.State>(0)
|
val stx = result.getOrThrow().stx
|
||||||
|
val stateAndRef = stx.tx.outRef<Cash.State>(0)
|
||||||
val baseState = a.database.transaction { a.vault.unconsumedStates<ContractState>().single() }
|
val baseState = a.database.transaction { a.vault.unconsumedStates<ContractState>().single() }
|
||||||
assertTrue(baseState.state.data is Cash.State, "Contract state is old version.")
|
assertTrue(baseState.state.data is Cash.State, "Contract state is old version.")
|
||||||
// Starts contract upgrade flow.
|
// Starts contract upgrade flow.
|
||||||
|
@ -40,7 +40,7 @@ class TxKeyFlowTests {
|
|||||||
val requesterFlow = aliceNode.services.startFlow(TxKeyFlow.Requester(bob))
|
val requesterFlow = aliceNode.services.startFlow(TxKeyFlow.Requester(bob))
|
||||||
|
|
||||||
// Get the results
|
// Get the results
|
||||||
val actual: Map<Party, TxKeyFlow.AnonymousIdentity> = requesterFlow.resultFuture.getOrThrow()
|
val actual: Map<Party, AnonymisedIdentity> = requesterFlow.resultFuture.getOrThrow().toMap()
|
||||||
assertEquals(2, actual.size)
|
assertEquals(2, actual.size)
|
||||||
// Verify that the generated anonymous identities do not match the well known identities
|
// Verify that the generated anonymous identities do not match the well known identities
|
||||||
val aliceAnonymousIdentity = actual[alice] ?: throw IllegalStateException()
|
val aliceAnonymousIdentity = actual[alice] ?: throw IllegalStateException()
|
||||||
|
@ -67,7 +67,8 @@ class IntegrationTestingTutorial {
|
|||||||
i.DOLLARS,
|
i.DOLLARS,
|
||||||
issueRef,
|
issueRef,
|
||||||
bob.nodeInfo.legalIdentity,
|
bob.nodeInfo.legalIdentity,
|
||||||
notary.nodeInfo.notaryIdentity
|
notary.nodeInfo.notaryIdentity,
|
||||||
|
false // Not anonymised
|
||||||
).returnValue)
|
).returnValue)
|
||||||
}
|
}
|
||||||
}.forEach(Thread::join) // Ensure the stack of futures is populated.
|
}.forEach(Thread::join) // Ensure the stack of futures is populated.
|
||||||
@ -90,7 +91,7 @@ class IntegrationTestingTutorial {
|
|||||||
|
|
||||||
// START 5
|
// START 5
|
||||||
for (i in 1..10) {
|
for (i in 1..10) {
|
||||||
bobProxy.startFlow(::CashPaymentFlow, i.DOLLARS, alice.nodeInfo.legalIdentity).returnValue.getOrThrow()
|
bobProxy.startFlow(::CashPaymentFlow, i.DOLLARS, alice.nodeInfo.legalIdentity, false).returnValue.getOrThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
aliceVaultUpdates.expectEvents {
|
aliceVaultUpdates.expectEvents {
|
||||||
|
@ -13,7 +13,10 @@ import net.corda.core.flows.InitiatedBy
|
|||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
|
import net.corda.core.node.services.Vault
|
||||||
|
import net.corda.core.node.services.queryBy
|
||||||
import net.corda.core.node.services.unconsumedStates
|
import net.corda.core.node.services.unconsumedStates
|
||||||
|
import net.corda.core.node.services.vault.QueryCriteria
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
@ -39,14 +42,15 @@ private fun gatherOurInputs(serviceHub: ServiceHub,
|
|||||||
amountRequired: Amount<Issued<Currency>>,
|
amountRequired: Amount<Issued<Currency>>,
|
||||||
notary: Party?): Pair<List<StateAndRef<Cash.State>>, Long> {
|
notary: Party?): Pair<List<StateAndRef<Cash.State>>, Long> {
|
||||||
// Collect cash type inputs
|
// Collect cash type inputs
|
||||||
val cashStates = serviceHub.vaultService.unconsumedStates<Cash.State>()
|
val queryCriteria = QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED, setOf(Cash.State::class.java))
|
||||||
|
val cashStates = serviceHub.vaultQueryService.queryBy<Cash.State>(queryCriteria).states
|
||||||
// extract our identity for convenience
|
// extract our identity for convenience
|
||||||
val ourIdentity = serviceHub.myInfo.legalIdentity
|
val ourKeys = serviceHub.keyManagementService.keys
|
||||||
// Filter down to our own cash states with right currency and issuer
|
// Filter down to our own cash states with right currency and issuer
|
||||||
val suitableCashStates = cashStates.filter {
|
val suitableCashStates = cashStates.filter {
|
||||||
val state = it.state.data
|
val state = it.state.data
|
||||||
(state.owner == ourIdentity)
|
// TODO: We may want to have the list of our states pre-cached somewhere for performance
|
||||||
&& (state.amount.token == amountRequired.token)
|
(state.owner.owningKey in ourKeys) && (state.amount.token == amountRequired.token)
|
||||||
}
|
}
|
||||||
require(!suitableCashStates.isEmpty()) { "Insufficient funds" }
|
require(!suitableCashStates.isEmpty()) { "Insufficient funds" }
|
||||||
var remaining = amountRequired.quantity
|
var remaining = amountRequired.quantity
|
||||||
@ -132,9 +136,6 @@ class ForeignExchangeFlow(val tradeId: String,
|
|||||||
require(it.inputs.all { it.state.notary == notary }) {
|
require(it.inputs.all { it.state.notary == notary }) {
|
||||||
"notary of remote states must be same as for our states"
|
"notary of remote states must be same as for our states"
|
||||||
}
|
}
|
||||||
require(it.inputs.all { it.state.data.owner == remoteRequestWithNotary.owner }) {
|
|
||||||
"The inputs are not owned by the correct counterparty"
|
|
||||||
}
|
|
||||||
require(it.inputs.all { it.state.data.amount.token == remoteRequestWithNotary.amount.token }) {
|
require(it.inputs.all { it.state.data.amount.token == remoteRequestWithNotary.amount.token }) {
|
||||||
"Inputs not of the correct currency"
|
"Inputs not of the correct currency"
|
||||||
}
|
}
|
||||||
@ -200,7 +201,7 @@ class ForeignExchangeFlow(val tradeId: String,
|
|||||||
// We have already validated their response and trust our own data
|
// We have already validated their response and trust our own data
|
||||||
// so we can sign. Note the returned SignedTransaction is still not fully signed
|
// so we can sign. Note the returned SignedTransaction is still not fully signed
|
||||||
// and would not pass full verification yet.
|
// and would not pass full verification yet.
|
||||||
return serviceHub.signInitialTransaction(builder)
|
return serviceHub.signInitialTransaction(builder, ourSigners.single())
|
||||||
}
|
}
|
||||||
// DOCEND 3
|
// DOCEND 3
|
||||||
}
|
}
|
||||||
@ -234,10 +235,11 @@ class ForeignExchangeRemoteFlow(val source: Party) : FlowLogic<Unit>() {
|
|||||||
val ourResponse = prepareOurInputsAndOutputs(serviceHub, request)
|
val ourResponse = prepareOurInputsAndOutputs(serviceHub, request)
|
||||||
|
|
||||||
// Send back our proposed states and await the full transaction to verify
|
// Send back our proposed states and await the full transaction to verify
|
||||||
|
val ourKey = serviceHub.keyManagementService.filterMyKeys(ourResponse.inputs.flatMap { it.state.data.participants }.map { it.owningKey }).single()
|
||||||
val proposedTrade = sendAndReceive<SignedTransaction>(source, ourResponse).unwrap {
|
val proposedTrade = sendAndReceive<SignedTransaction>(source, ourResponse).unwrap {
|
||||||
val wtx = it.tx
|
val wtx = it.tx
|
||||||
// check all signatures are present except our own and the notary
|
// check all signatures are present except our own and the notary
|
||||||
it.verifySignatures(serviceHub.myInfo.legalIdentity.owningKey, wtx.notary!!.owningKey)
|
it.verifySignatures(ourKey, wtx.notary!!.owningKey)
|
||||||
|
|
||||||
// We need to fetch their complete input states and dependencies so that verify can operate
|
// We need to fetch their complete input states and dependencies so that verify can operate
|
||||||
checkDependencies(it)
|
checkDependencies(it)
|
||||||
@ -251,7 +253,7 @@ class ForeignExchangeRemoteFlow(val source: Party) : FlowLogic<Unit>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// assuming we have completed state and business level validation we can sign the trade
|
// assuming we have completed state and business level validation we can sign the trade
|
||||||
val ourSignature = serviceHub.createSignature(proposedTrade)
|
val ourSignature = serviceHub.createSignature(proposedTrade, ourKey)
|
||||||
|
|
||||||
// send the other side our signature.
|
// send the other side our signature.
|
||||||
send(source, ourSignature)
|
send(source, ourSignature)
|
||||||
|
@ -48,7 +48,8 @@ class FxTransactionBuildTutorialTest {
|
|||||||
val flowHandle1 = nodeA.services.startFlow(CashIssueFlow(DOLLARS(1000),
|
val flowHandle1 = nodeA.services.startFlow(CashIssueFlow(DOLLARS(1000),
|
||||||
OpaqueBytes.of(0x01),
|
OpaqueBytes.of(0x01),
|
||||||
nodeA.info.legalIdentity,
|
nodeA.info.legalIdentity,
|
||||||
notaryNode.info.notaryIdentity))
|
notaryNode.info.notaryIdentity,
|
||||||
|
false))
|
||||||
// Wait for the flow to stop and print
|
// Wait for the flow to stop and print
|
||||||
flowHandle1.resultFuture.getOrThrow()
|
flowHandle1.resultFuture.getOrThrow()
|
||||||
printBalances()
|
printBalances()
|
||||||
@ -57,7 +58,8 @@ class FxTransactionBuildTutorialTest {
|
|||||||
val flowHandle2 = nodeB.services.startFlow(CashIssueFlow(POUNDS(1000),
|
val flowHandle2 = nodeB.services.startFlow(CashIssueFlow(POUNDS(1000),
|
||||||
OpaqueBytes.of(0x01),
|
OpaqueBytes.of(0x01),
|
||||||
nodeB.info.legalIdentity,
|
nodeB.info.legalIdentity,
|
||||||
notaryNode.info.notaryIdentity))
|
notaryNode.info.notaryIdentity,
|
||||||
|
false))
|
||||||
// Wait for flow to come to an end and print
|
// Wait for flow to come to an end and print
|
||||||
flowHandle2.resultFuture.getOrThrow()
|
flowHandle2.resultFuture.getOrThrow()
|
||||||
printBalances()
|
printBalances()
|
||||||
|
@ -429,7 +429,7 @@ class Obligation<P : Any> : Contract {
|
|||||||
/**
|
/**
|
||||||
* Generate a transaction performing close-out netting of two or more states.
|
* Generate a transaction performing close-out netting of two or more states.
|
||||||
*
|
*
|
||||||
* @param signer the party who will sign the transaction. Must be one of the obligor or beneficiary.
|
* @param signer the party which will sign the transaction. Must be one of the obligor or beneficiary.
|
||||||
* @param states two or more states, which must be compatible for bilateral netting (same issuance definitions,
|
* @param states two or more states, which must be compatible for bilateral netting (same issuance definitions,
|
||||||
* and same parties involved).
|
* and same parties involved).
|
||||||
*/
|
*/
|
||||||
@ -458,7 +458,7 @@ class Obligation<P : Any> : Contract {
|
|||||||
* @param amountIssued the amount to be exited, represented as a quantity of issued currency.
|
* @param amountIssued the amount to be exited, represented as a quantity of issued currency.
|
||||||
* @param assetStates the asset states to take funds from. No checks are done about ownership of these states, it is
|
* @param assetStates the asset states to take funds from. No checks are done about ownership of these states, it is
|
||||||
* the responsibility of the caller to check that they do not exit funds held by others.
|
* the responsibility of the caller to check that they do not exit funds held by others.
|
||||||
* @return the public keys who must sign the transaction for it to be valid.
|
* @return the public keys which must sign the transaction for it to be valid.
|
||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun generateExit(tx: TransactionBuilder, amountIssued: Amount<Issued<Terms<P>>>,
|
fun generateExit(tx: TransactionBuilder, amountIssued: Amount<Issued<Terms<P>>>,
|
||||||
|
@ -207,13 +207,15 @@ abstract class OnLedgerAsset<T : Any, C : CommandData, S : FungibleAsset<T>> : C
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun <S : FungibleAsset<T>, T: Any> generateIssue(tx: TransactionBuilder,
|
fun <S : FungibleAsset<T>, T: Any> generateIssue(tx: TransactionBuilder,
|
||||||
transactionState: TransactionState<S>,
|
transactionState: TransactionState<S>,
|
||||||
issueCommand: CommandData) {
|
issueCommand: CommandData): Set<PublicKey> {
|
||||||
check(tx.inputStates().isEmpty())
|
check(tx.inputStates().isEmpty())
|
||||||
check(tx.outputStates().map { it.data }.filterIsInstance(transactionState.javaClass).isEmpty())
|
check(tx.outputStates().map { it.data }.filterIsInstance(transactionState.javaClass).isEmpty())
|
||||||
require(transactionState.data.amount.quantity > 0)
|
require(transactionState.data.amount.quantity > 0)
|
||||||
val at = transactionState.data.amount.token.issuer
|
val at = transactionState.data.amount.token.issuer
|
||||||
|
val commandSigner = at.party.owningKey
|
||||||
tx.addOutputState(transactionState)
|
tx.addOutputState(transactionState)
|
||||||
tx.addCommand(issueCommand, at.party.owningKey)
|
tx.addCommand(issueCommand, commandSigner)
|
||||||
|
return setOf(commandSigner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,30 +3,44 @@ package net.corda.flows
|
|||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.flows.FlowException
|
import net.corda.core.flows.FlowException
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.identity.AnonymousParty
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.identity.PartyAndCertificate
|
||||||
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiates a flow that produces an Issue/Move or Exit Cash transaction.
|
* Initiates a flow that produces an Issue/Move or Exit Cash transaction.
|
||||||
*/
|
*/
|
||||||
abstract class AbstractCashFlow(override val progressTracker: ProgressTracker) : FlowLogic<SignedTransaction>() {
|
abstract class AbstractCashFlow<T>(override val progressTracker: ProgressTracker) : FlowLogic<T>() {
|
||||||
companion object {
|
companion object {
|
||||||
|
object GENERATING_ID : ProgressTracker.Step("Generating anonymous identities")
|
||||||
object GENERATING_TX : ProgressTracker.Step("Generating transaction")
|
object GENERATING_TX : ProgressTracker.Step("Generating transaction")
|
||||||
object SIGNING_TX : ProgressTracker.Step("Signing transaction")
|
object SIGNING_TX : ProgressTracker.Step("Signing transaction")
|
||||||
object FINALISING_TX : ProgressTracker.Step("Finalising transaction")
|
object FINALISING_TX : ProgressTracker.Step("Finalising transaction")
|
||||||
|
|
||||||
fun tracker() = ProgressTracker(GENERATING_TX, SIGNING_TX, FINALISING_TX)
|
fun tracker() = ProgressTracker(GENERATING_ID, GENERATING_TX, SIGNING_TX, FINALISING_TX)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
internal fun finaliseTx(participants: Set<Party>, tx: SignedTransaction, message: String) {
|
protected fun finaliseTx(participants: Set<Party>, tx: SignedTransaction, message: String) {
|
||||||
try {
|
try {
|
||||||
subFlow(FinalityFlow(tx, participants))
|
subFlow(FinalityFlow(tx, participants))
|
||||||
} catch (e: NotaryException) {
|
} catch (e: NotaryException) {
|
||||||
throw CashException(message, e)
|
throw CashException(message, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combined signed transaction and identity lookup map, which is the resulting data from regular cash flows.
|
||||||
|
* Specialised flows for unit tests differ from this.
|
||||||
|
*
|
||||||
|
* @param stx the signed transaction.
|
||||||
|
* @param identities a mapping from the original identities of the parties to the anonymised equivalents.
|
||||||
|
*/
|
||||||
|
@CordaSerializable
|
||||||
|
data class Result(val stx: SignedTransaction, val identities: TxKeyFlow.TxIdentities)
|
||||||
}
|
}
|
||||||
|
|
||||||
class CashException(message: String, cause: Throwable) : FlowException(message, cause)
|
class CashException(message: String, cause: Throwable) : FlowException(message, cause)
|
@ -9,7 +9,6 @@ import net.corda.core.contracts.issuedBy
|
|||||||
import net.corda.core.flows.StartableByRPC
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.serialization.OpaqueBytes
|
import net.corda.core.serialization.OpaqueBytes
|
||||||
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 java.util.*
|
import java.util.*
|
||||||
@ -22,16 +21,20 @@ import java.util.*
|
|||||||
* issuer.
|
* issuer.
|
||||||
*/
|
*/
|
||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
class CashExitFlow(val amount: Amount<Currency>, val issueRef: OpaqueBytes, progressTracker: ProgressTracker) : AbstractCashFlow(progressTracker) {
|
class CashExitFlow(val amount: Amount<Currency>, val issueRef: OpaqueBytes, progressTracker: ProgressTracker) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) {
|
||||||
constructor(amount: Amount<Currency>, issueRef: OpaqueBytes) : this(amount, issueRef, tracker())
|
constructor(amount: Amount<Currency>, issueRef: OpaqueBytes) : this(amount, issueRef, tracker())
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun tracker() = ProgressTracker(GENERATING_TX, SIGNING_TX, FINALISING_TX)
|
fun tracker() = ProgressTracker(GENERATING_TX, SIGNING_TX, FINALISING_TX)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the signed transaction, and a mapping of parties to new anonymous identities generated
|
||||||
|
* (for this flow this map is always empty).
|
||||||
|
*/
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@Throws(CashException::class)
|
@Throws(CashException::class)
|
||||||
override fun call(): SignedTransaction {
|
override fun call(): AbstractCashFlow.Result {
|
||||||
progressTracker.currentStep = GENERATING_TX
|
progressTracker.currentStep = GENERATING_TX
|
||||||
val builder: TransactionBuilder = TransactionType.General.Builder(notary = null as Party?)
|
val builder: TransactionBuilder = TransactionType.General.Builder(notary = null as Party?)
|
||||||
val issuer = serviceHub.myInfo.legalIdentity.ref(issueRef)
|
val issuer = serviceHub.myInfo.legalIdentity.ref(issueRef)
|
||||||
@ -67,6 +70,6 @@ class CashExitFlow(val amount: Amount<Currency>, val issueRef: OpaqueBytes, prog
|
|||||||
// Commit the transaction
|
// Commit the transaction
|
||||||
progressTracker.currentStep = FINALISING_TX
|
progressTracker.currentStep = FINALISING_TX
|
||||||
finaliseTx(participants, tx, "Unable to notarise exit")
|
finaliseTx(participants, tx, "Unable to notarise exit")
|
||||||
return tx
|
return Result(tx, TxKeyFlow.TxIdentities())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,13 @@ import net.corda.core.messaging.CordaRPCOps
|
|||||||
import net.corda.core.messaging.FlowHandle
|
import net.corda.core.messaging.FlowHandle
|
||||||
import net.corda.core.messaging.startFlow
|
import net.corda.core.messaging.startFlow
|
||||||
import net.corda.core.serialization.OpaqueBytes
|
import net.corda.core.serialization.OpaqueBytes
|
||||||
import net.corda.core.transactions.SignedTransaction
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A command to initiate the cash flow with.
|
* A command to initiate the cash flow with.
|
||||||
*/
|
*/
|
||||||
sealed class CashFlowCommand {
|
sealed class CashFlowCommand {
|
||||||
abstract fun startFlow(proxy: CordaRPCOps): FlowHandle<SignedTransaction>
|
abstract fun startFlow(proxy: CordaRPCOps): FlowHandle<AbstractCashFlow.Result>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A command to initiate the Cash flow with.
|
* A command to initiate the Cash flow with.
|
||||||
@ -21,8 +20,9 @@ sealed class CashFlowCommand {
|
|||||||
data class IssueCash(val amount: Amount<Currency>,
|
data class IssueCash(val amount: Amount<Currency>,
|
||||||
val issueRef: OpaqueBytes,
|
val issueRef: OpaqueBytes,
|
||||||
val recipient: Party,
|
val recipient: Party,
|
||||||
val notary: Party) : CashFlowCommand() {
|
val notary: Party,
|
||||||
override fun startFlow(proxy: CordaRPCOps) = proxy.startFlow(::CashIssueFlow, amount, issueRef, recipient, notary)
|
val anonymous: Boolean) : CashFlowCommand() {
|
||||||
|
override fun startFlow(proxy: CordaRPCOps) = proxy.startFlow(::CashIssueFlow, amount, issueRef, recipient, notary, anonymous)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,8 +31,9 @@ sealed class CashFlowCommand {
|
|||||||
* @param amount the amount of currency to issue on to the ledger.
|
* @param amount the amount of currency to issue on to the ledger.
|
||||||
* @param recipient the party to issue the cash to.
|
* @param recipient the party to issue the cash to.
|
||||||
*/
|
*/
|
||||||
data class PayCash(val amount: Amount<Currency>, val recipient: Party, val issuerConstraint: Party? = null) : CashFlowCommand() {
|
data class PayCash(val amount: Amount<Currency>, val recipient: Party, val issuerConstraint: Party? = null,
|
||||||
override fun startFlow(proxy: CordaRPCOps) = proxy.startFlow(::CashPaymentFlow, amount, recipient)
|
val anonymous: Boolean) : CashFlowCommand() {
|
||||||
|
override fun startFlow(proxy: CordaRPCOps) = proxy.startFlow(::CashPaymentFlow, amount, recipient, anonymous)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -5,10 +5,9 @@ import net.corda.contracts.asset.Cash
|
|||||||
import net.corda.core.contracts.Amount
|
import net.corda.core.contracts.Amount
|
||||||
import net.corda.core.contracts.TransactionType
|
import net.corda.core.contracts.TransactionType
|
||||||
import net.corda.core.contracts.issuedBy
|
import net.corda.core.contracts.issuedBy
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.flows.StartableByRPC
|
import net.corda.core.flows.StartableByRPC
|
||||||
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.serialization.OpaqueBytes
|
import net.corda.core.serialization.OpaqueBytes
|
||||||
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 java.util.*
|
import java.util.*
|
||||||
@ -26,23 +25,39 @@ class CashIssueFlow(val amount: Amount<Currency>,
|
|||||||
val issueRef: OpaqueBytes,
|
val issueRef: OpaqueBytes,
|
||||||
val recipient: Party,
|
val recipient: Party,
|
||||||
val notary: Party,
|
val notary: Party,
|
||||||
progressTracker: ProgressTracker) : AbstractCashFlow(progressTracker) {
|
val anonymous: Boolean,
|
||||||
|
progressTracker: ProgressTracker) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) {
|
||||||
constructor(amount: Amount<Currency>,
|
constructor(amount: Amount<Currency>,
|
||||||
issueRef: OpaqueBytes,
|
issueRef: OpaqueBytes,
|
||||||
recipient: Party,
|
recipient: Party,
|
||||||
notary: Party) : this(amount, issueRef, recipient, notary, tracker())
|
notary: Party) : this(amount, issueRef, recipient, notary, true, tracker())
|
||||||
|
constructor(amount: Amount<Currency>,
|
||||||
|
issueRef: OpaqueBytes,
|
||||||
|
recipient: Party,
|
||||||
|
notary: Party,
|
||||||
|
anonymous: Boolean) : this(amount, issueRef, recipient, notary, anonymous, tracker())
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): SignedTransaction {
|
override fun call(): AbstractCashFlow.Result {
|
||||||
|
progressTracker.currentStep = GENERATING_ID
|
||||||
|
val txIdentities = if (anonymous) {
|
||||||
|
subFlow(TxKeyFlow.Requester(recipient))
|
||||||
|
} else {
|
||||||
|
TxKeyFlow.TxIdentities(emptyList())
|
||||||
|
}
|
||||||
|
val anonymousRecipient = if (anonymous) {
|
||||||
|
txIdentities.forParty(recipient).identity
|
||||||
|
} else {
|
||||||
|
recipient
|
||||||
|
}
|
||||||
progressTracker.currentStep = GENERATING_TX
|
progressTracker.currentStep = GENERATING_TX
|
||||||
val builder: TransactionBuilder = TransactionType.General.Builder(notary = notary)
|
val builder: TransactionBuilder = TransactionType.General.Builder(notary = notary)
|
||||||
val issuer = serviceHub.myInfo.legalIdentity.ref(issueRef)
|
val issuer = serviceHub.myInfo.legalIdentity.ref(issueRef)
|
||||||
// TODO: Get a transaction key, don't just re-use the owning key
|
val signers = Cash().generateIssue(builder, amount.issuedBy(issuer), anonymousRecipient, notary)
|
||||||
Cash().generateIssue(builder, amount.issuedBy(issuer), recipient, notary)
|
|
||||||
progressTracker.currentStep = SIGNING_TX
|
progressTracker.currentStep = SIGNING_TX
|
||||||
val tx = serviceHub.signInitialTransaction(builder)
|
val tx = serviceHub.signInitialTransaction(builder, signers)
|
||||||
progressTracker.currentStep = FINALISING_TX
|
progressTracker.currentStep = FINALISING_TX
|
||||||
subFlow(FinalityFlow(tx))
|
subFlow(FinalityFlow(tx))
|
||||||
return tx
|
return Result(tx, txIdentities)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import net.corda.core.contracts.InsufficientBalanceException
|
|||||||
import net.corda.core.contracts.TransactionType
|
import net.corda.core.contracts.TransactionType
|
||||||
import net.corda.core.flows.StartableByRPC
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
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 java.util.*
|
import java.util.*
|
||||||
@ -17,18 +16,34 @@ import java.util.*
|
|||||||
* @param amount the amount of a currency to pay to the recipient.
|
* @param amount the amount of a currency to pay to the recipient.
|
||||||
* @param recipient the party to pay the currency to.
|
* @param recipient the party to pay the currency to.
|
||||||
* @param issuerConstraint if specified, the payment will be made using only cash issued by the given parties.
|
* @param issuerConstraint if specified, the payment will be made using only cash issued by the given parties.
|
||||||
|
* @param anonymous whether to anonymous the recipient party. Should be true for normal usage, but may be false
|
||||||
|
* for testing purposes.
|
||||||
*/
|
*/
|
||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
open class CashPaymentFlow(
|
open class CashPaymentFlow(
|
||||||
val amount: Amount<Currency>,
|
val amount: Amount<Currency>,
|
||||||
val recipient: Party,
|
val recipient: Party,
|
||||||
|
val anonymous: Boolean,
|
||||||
progressTracker: ProgressTracker,
|
progressTracker: ProgressTracker,
|
||||||
val issuerConstraint: Set<Party>? = null) : AbstractCashFlow(progressTracker) {
|
val issuerConstraint: Set<Party>? = null) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) {
|
||||||
/** A straightforward constructor that constructs spends using cash states of any issuer. */
|
/** A straightforward constructor that constructs spends using cash states of any issuer. */
|
||||||
constructor(amount: Amount<Currency>, recipient: Party) : this(amount, recipient, tracker())
|
constructor(amount: Amount<Currency>, recipient: Party) : this(amount, recipient, true, tracker())
|
||||||
|
/** A straightforward constructor that constructs spends using cash states of any issuer. */
|
||||||
|
constructor(amount: Amount<Currency>, recipient: Party, anonymous: Boolean) : this(amount, recipient, anonymous, tracker())
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun call(): SignedTransaction {
|
override fun call(): AbstractCashFlow.Result {
|
||||||
|
progressTracker.currentStep = GENERATING_ID
|
||||||
|
val txIdentities = if (anonymous) {
|
||||||
|
subFlow(TxKeyFlow.Requester(recipient))
|
||||||
|
} else {
|
||||||
|
TxKeyFlow.TxIdentities(emptyList())
|
||||||
|
}
|
||||||
|
val anonymousRecipient = if (anonymous) {
|
||||||
|
txIdentities.forParty(recipient).identity
|
||||||
|
} else {
|
||||||
|
recipient
|
||||||
|
}
|
||||||
progressTracker.currentStep = GENERATING_TX
|
progressTracker.currentStep = GENERATING_TX
|
||||||
val builder: TransactionBuilder = TransactionType.General.Builder(null as Party?)
|
val builder: TransactionBuilder = TransactionType.General.Builder(null as Party?)
|
||||||
// TODO: Have some way of restricting this to states the caller controls
|
// TODO: Have some way of restricting this to states the caller controls
|
||||||
@ -36,8 +51,7 @@ open class CashPaymentFlow(
|
|||||||
serviceHub.vaultService.generateSpend(
|
serviceHub.vaultService.generateSpend(
|
||||||
builder,
|
builder,
|
||||||
amount,
|
amount,
|
||||||
// TODO: Get a transaction key, don't just re-use the owning key
|
anonymousRecipient,
|
||||||
recipient,
|
|
||||||
issuerConstraint)
|
issuerConstraint)
|
||||||
} catch (e: InsufficientBalanceException) {
|
} catch (e: InsufficientBalanceException) {
|
||||||
throw CashException("Insufficient cash for spend: ${e.message}", e)
|
throw CashException("Insufficient cash for spend: ${e.message}", e)
|
||||||
@ -48,6 +62,6 @@ open class CashPaymentFlow(
|
|||||||
|
|
||||||
progressTracker.currentStep = FINALISING_TX
|
progressTracker.currentStep = FINALISING_TX
|
||||||
finaliseTx(setOf(recipient), tx, "Unable to notarise spend")
|
finaliseTx(setOf(recipient), tx, "Unable to notarise spend")
|
||||||
return tx
|
return Result(tx, txIdentities)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,8 +1,10 @@
|
|||||||
package net.corda.flows
|
package net.corda.flows
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
|
import net.corda.contracts.asset.Cash
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.*
|
||||||
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.serialization.OpaqueBytes
|
import net.corda.core.serialization.OpaqueBytes
|
||||||
@ -20,21 +22,45 @@ import java.util.*
|
|||||||
*/
|
*/
|
||||||
object IssuerFlow {
|
object IssuerFlow {
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class IssuanceRequestState(val amount: Amount<Currency>, val issueToParty: Party, val issuerPartyRef: OpaqueBytes)
|
data class IssuanceRequestState(val amount: Amount<Currency>,
|
||||||
|
val issueToParty: Party,
|
||||||
|
val issuerPartyRef: OpaqueBytes,
|
||||||
|
val anonymous: Boolean)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IssuanceRequester should be used by a client to ask a remote node to issue some [FungibleAsset] with the given details.
|
* IssuanceRequester should be used by a client to ask a remote node to issue some [FungibleAsset] with the given details.
|
||||||
* Returns the transaction created by the Issuer to move the cash to the Requester.
|
* Returns the transaction created by the Issuer to move the cash to the Requester.
|
||||||
|
*
|
||||||
|
* @param anonymous true if the issued asset should be sent to a new confidential identity, false to send it to the
|
||||||
|
* well known identity (generally this is only used in testing).
|
||||||
*/
|
*/
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
class IssuanceRequester(val amount: Amount<Currency>, val issueToParty: Party, val issueToPartyRef: OpaqueBytes,
|
class IssuanceRequester(val amount: Amount<Currency>,
|
||||||
val issuerBankParty: Party) : FlowLogic<SignedTransaction>() {
|
val issueToParty: Party,
|
||||||
|
val issueToPartyRef: OpaqueBytes,
|
||||||
|
val issuerBankParty: Party,
|
||||||
|
val anonymous: Boolean) : FlowLogic<AbstractCashFlow.Result>() {
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@Throws(CashException::class)
|
@Throws(CashException::class)
|
||||||
override fun call(): SignedTransaction {
|
override fun call(): AbstractCashFlow.Result {
|
||||||
val issueRequest = IssuanceRequestState(amount, issueToParty, issueToPartyRef)
|
val issueRequest = IssuanceRequestState(amount, issueToParty, issueToPartyRef, anonymous)
|
||||||
return sendAndReceive<SignedTransaction>(issuerBankParty, issueRequest).unwrap { it }
|
return sendAndReceive<AbstractCashFlow.Result>(issuerBankParty, issueRequest).unwrap { res ->
|
||||||
|
val tx = res.stx.tx
|
||||||
|
val recipient = if (anonymous) {
|
||||||
|
res.identities.forParty(issueToParty).identity
|
||||||
|
} else {
|
||||||
|
issueToParty
|
||||||
|
}
|
||||||
|
val expectedAmount = Amount(amount.quantity, Issued(issuerBankParty.ref(issueToPartyRef), amount.token))
|
||||||
|
val cashOutputs = tx.outputs
|
||||||
|
.map { it.data}
|
||||||
|
.filterIsInstance<Cash.State>()
|
||||||
|
.filter { state -> state.owner == recipient }
|
||||||
|
require(cashOutputs.size == 1) { "Require a single cash output paying $recipient, found ${tx.outputs}" }
|
||||||
|
require(cashOutputs.single().amount == expectedAmount) { "Require payment of $expectedAmount"}
|
||||||
|
res
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,22 +92,23 @@ object IssuerFlow {
|
|||||||
it
|
it
|
||||||
}
|
}
|
||||||
// TODO: parse request to determine Asset to issue
|
// TODO: parse request to determine Asset to issue
|
||||||
val txn = issueCashTo(issueRequest.amount, issueRequest.issueToParty, issueRequest.issuerPartyRef)
|
val txn = issueCashTo(issueRequest.amount, issueRequest.issueToParty, issueRequest.issuerPartyRef, issueRequest.anonymous)
|
||||||
progressTracker.currentStep = SENDING_CONFIRM
|
progressTracker.currentStep = SENDING_CONFIRM
|
||||||
send(otherParty, txn)
|
send(otherParty, txn)
|
||||||
return txn
|
return txn.stx
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun issueCashTo(amount: Amount<Currency>,
|
private fun issueCashTo(amount: Amount<Currency>,
|
||||||
issueTo: Party,
|
issueTo: Party,
|
||||||
issuerPartyRef: OpaqueBytes): SignedTransaction {
|
issuerPartyRef: OpaqueBytes,
|
||||||
|
anonymous: Boolean): AbstractCashFlow.Result {
|
||||||
// TODO: pass notary in as request parameter
|
// TODO: pass notary in as request parameter
|
||||||
val notaryParty = serviceHub.networkMapCache.notaryNodes[0].notaryIdentity
|
val notaryParty = serviceHub.networkMapCache.notaryNodes[0].notaryIdentity
|
||||||
// invoke Cash subflow to issue Asset
|
// invoke Cash subflow to issue Asset
|
||||||
progressTracker.currentStep = ISSUING
|
progressTracker.currentStep = ISSUING
|
||||||
val bankOfCordaParty = serviceHub.myInfo.legalIdentity
|
val issueRecipient = serviceHub.myInfo.legalIdentity
|
||||||
val issueCashFlow = CashIssueFlow(amount, issuerPartyRef, bankOfCordaParty, notaryParty)
|
val issueCashFlow = CashIssueFlow(amount, issuerPartyRef, issueRecipient, notaryParty, anonymous = false)
|
||||||
val issueTx = subFlow(issueCashFlow)
|
val issueTx = subFlow(issueCashFlow)
|
||||||
// NOTE: issueCashFlow performs a Broadcast (which stores a local copy of the txn to the ledger)
|
// NOTE: issueCashFlow performs a Broadcast (which stores a local copy of the txn to the ledger)
|
||||||
// short-circuit when issuing to self
|
// short-circuit when issuing to self
|
||||||
@ -89,7 +116,7 @@ object IssuerFlow {
|
|||||||
return issueTx
|
return issueTx
|
||||||
// now invoke Cash subflow to Move issued assetType to issue requester
|
// now invoke Cash subflow to Move issued assetType to issue requester
|
||||||
progressTracker.currentStep = TRANSFERRING
|
progressTracker.currentStep = TRANSFERRING
|
||||||
val moveCashFlow = CashPaymentFlow(amount, issueTo)
|
val moveCashFlow = CashPaymentFlow(amount, issueTo, anonymous)
|
||||||
val moveTx = subFlow(moveCashFlow)
|
val moveTx = subFlow(moveCashFlow)
|
||||||
// NOTE: CashFlow PayCash calls FinalityFlow which performs a Broadcast (which stores a local copy of the txn to the ledger)
|
// NOTE: CashFlow PayCash calls FinalityFlow which performs a Broadcast (which stores a local copy of the txn to the ledger)
|
||||||
return moveTx
|
return moveTx
|
||||||
|
@ -3,8 +3,8 @@ package net.corda.flows
|
|||||||
import net.corda.contracts.asset.Cash
|
import net.corda.contracts.asset.Cash
|
||||||
import net.corda.core.contracts.DOLLARS
|
import net.corda.core.contracts.DOLLARS
|
||||||
import net.corda.core.contracts.`issued by`
|
import net.corda.core.contracts.`issued by`
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.getOrThrow
|
import net.corda.core.getOrThrow
|
||||||
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.serialization.OpaqueBytes
|
import net.corda.core.serialization.OpaqueBytes
|
||||||
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
|
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
|
||||||
import net.corda.testing.node.MockNetwork
|
import net.corda.testing.node.MockNetwork
|
||||||
@ -51,7 +51,7 @@ class CashExitFlowTests {
|
|||||||
val future = bankOfCordaNode.services.startFlow(CashExitFlow(exitAmount,
|
val future = bankOfCordaNode.services.startFlow(CashExitFlow(exitAmount,
|
||||||
ref)).resultFuture
|
ref)).resultFuture
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
val exitTx = future.getOrThrow().tx
|
val exitTx = future.getOrThrow().stx.tx
|
||||||
val expected = (initialBalance - exitAmount).`issued by`(bankOfCorda.ref(ref))
|
val expected = (initialBalance - exitAmount).`issued by`(bankOfCorda.ref(ref))
|
||||||
assertEquals(1, exitTx.inputs.size)
|
assertEquals(1, exitTx.inputs.size)
|
||||||
assertEquals(1, exitTx.outputs.size)
|
assertEquals(1, exitTx.outputs.size)
|
||||||
|
@ -3,8 +3,8 @@ package net.corda.flows
|
|||||||
import net.corda.contracts.asset.Cash
|
import net.corda.contracts.asset.Cash
|
||||||
import net.corda.core.contracts.DOLLARS
|
import net.corda.core.contracts.DOLLARS
|
||||||
import net.corda.core.contracts.`issued by`
|
import net.corda.core.contracts.`issued by`
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.getOrThrow
|
import net.corda.core.getOrThrow
|
||||||
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.serialization.OpaqueBytes
|
import net.corda.core.serialization.OpaqueBytes
|
||||||
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
|
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
|
||||||
import net.corda.testing.node.MockNetwork
|
import net.corda.testing.node.MockNetwork
|
||||||
@ -46,7 +46,7 @@ class CashIssueFlowTests {
|
|||||||
bankOfCorda,
|
bankOfCorda,
|
||||||
notary)).resultFuture
|
notary)).resultFuture
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
val issueTx = future.getOrThrow()
|
val issueTx = future.getOrThrow().stx
|
||||||
val output = issueTx.tx.outputs.single().data as Cash.State
|
val output = issueTx.tx.outputs.single().data as Cash.State
|
||||||
assertEquals(expected.`issued by`(bankOfCorda.ref(ref)), output.amount)
|
assertEquals(expected.`issued by`(bankOfCorda.ref(ref)), output.amount)
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,9 @@ class CashPaymentFlowTests {
|
|||||||
notary = notaryNode.info.notaryIdentity
|
notary = notaryNode.info.notaryIdentity
|
||||||
bankOfCorda = bankOfCordaNode.info.legalIdentity
|
bankOfCorda = bankOfCordaNode.info.legalIdentity
|
||||||
|
|
||||||
mockNet.runNetwork()
|
notaryNode.registerInitiatedFlow(TxKeyFlow.Provider::class.java)
|
||||||
|
notaryNode.identity.registerIdentity(bankOfCordaNode.info.legalIdentityAndCert)
|
||||||
|
bankOfCordaNode.identity.registerIdentity(notaryNode.info.legalIdentityAndCert)
|
||||||
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref,
|
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref,
|
||||||
bankOfCorda,
|
bankOfCorda,
|
||||||
notary)).resultFuture
|
notary)).resultFuture
|
||||||
@ -53,11 +55,11 @@ class CashPaymentFlowTests {
|
|||||||
val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expectedPayment,
|
val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expectedPayment,
|
||||||
payTo)).resultFuture
|
payTo)).resultFuture
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
val paymentTx = future.getOrThrow()
|
val (paymentTx, identities) = future.getOrThrow()
|
||||||
val states = paymentTx.tx.outputs.map { it.data }.filterIsInstance<Cash.State>()
|
val states = paymentTx.tx.outputs.map { it.data }.filterIsInstance<Cash.State>()
|
||||||
val ourState = states.single { it.owner.owningKey != payTo.owningKey }
|
val paymentState: Cash.State = states.single { it.owner == identities.forParty(payTo).identity }
|
||||||
val paymentState = states.single { it.owner.owningKey == payTo.owningKey }
|
val changeState: Cash.State = states.single { it != paymentState }
|
||||||
assertEquals(expectedChange.`issued by`(bankOfCorda.ref(ref)), ourState.amount)
|
assertEquals(expectedChange.`issued by`(bankOfCorda.ref(ref)), changeState.amount)
|
||||||
assertEquals(expectedPayment.`issued by`(bankOfCorda.ref(ref)), paymentState.amount)
|
assertEquals(expectedPayment.`issued by`(bankOfCorda.ref(ref)), paymentState.amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +39,12 @@ class IssuerFlowTest {
|
|||||||
notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name)
|
notaryNode = mockNet.createNotaryNode(null, DUMMY_NOTARY.name)
|
||||||
bankOfCordaNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOC.name)
|
bankOfCordaNode = mockNet.createPartyNode(notaryNode.network.myAddress, BOC.name)
|
||||||
bankClientNode = mockNet.createPartyNode(notaryNode.network.myAddress, MEGA_CORP.name)
|
bankClientNode = mockNet.createPartyNode(notaryNode.network.myAddress, MEGA_CORP.name)
|
||||||
|
val nodes = listOf(notaryNode, bankOfCordaNode, bankClientNode)
|
||||||
|
|
||||||
|
nodes.forEach { node ->
|
||||||
|
nodes.map { it.info.legalIdentityAndCert }.forEach(node.services.identityService::registerIdentity)
|
||||||
|
node.registerInitiatedFlow(TxKeyFlow.Provider::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -51,7 +57,7 @@ class IssuerFlowTest {
|
|||||||
// using default IssueTo Party Reference
|
// using default IssueTo Party Reference
|
||||||
val (issuer, issuerResult) = runIssuerAndIssueRequester(bankOfCordaNode, bankClientNode, 1000000.DOLLARS,
|
val (issuer, issuerResult) = runIssuerAndIssueRequester(bankOfCordaNode, bankClientNode, 1000000.DOLLARS,
|
||||||
bankClientNode.info.legalIdentity, OpaqueBytes.of(123))
|
bankClientNode.info.legalIdentity, OpaqueBytes.of(123))
|
||||||
assertEquals(issuerResult.get(), issuer.get().resultFuture.get())
|
assertEquals(issuerResult.get().stx, issuer.get().resultFuture.get())
|
||||||
|
|
||||||
// try to issue an amount of a restricted currency
|
// try to issue an amount of a restricted currency
|
||||||
assertFailsWith<FlowException> {
|
assertFailsWith<FlowException> {
|
||||||
@ -65,7 +71,7 @@ class IssuerFlowTest {
|
|||||||
// using default IssueTo Party Reference
|
// using default IssueTo Party Reference
|
||||||
val (issuer, issuerResult) = runIssuerAndIssueRequester(bankOfCordaNode, bankOfCordaNode, 1000000.DOLLARS,
|
val (issuer, issuerResult) = runIssuerAndIssueRequester(bankOfCordaNode, bankOfCordaNode, 1000000.DOLLARS,
|
||||||
bankOfCordaNode.info.legalIdentity, OpaqueBytes.of(123))
|
bankOfCordaNode.info.legalIdentity, OpaqueBytes.of(123))
|
||||||
assertEquals(issuerResult.get(), issuer.get().resultFuture.get())
|
assertEquals(issuerResult.get().stx, issuer.get().resultFuture.get())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -78,7 +84,7 @@ class IssuerFlowTest {
|
|||||||
bankClientNode.info.legalIdentity, OpaqueBytes.of(123))
|
bankClientNode.info.legalIdentity, OpaqueBytes.of(123))
|
||||||
}
|
}
|
||||||
handles.forEach {
|
handles.forEach {
|
||||||
require(it.issueRequestResult.get() is SignedTransaction)
|
require(it.issueRequestResult.get().stx is SignedTransaction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +97,8 @@ class IssuerFlowTest {
|
|||||||
val issuerFlows: Observable<IssuerFlow.Issuer> = issuerNode.registerInitiatedFlow(IssuerFlow.Issuer::class.java)
|
val issuerFlows: Observable<IssuerFlow.Issuer> = issuerNode.registerInitiatedFlow(IssuerFlow.Issuer::class.java)
|
||||||
val firstIssuerFiber = issuerFlows.toFuture().map { it.stateMachine }
|
val firstIssuerFiber = issuerFlows.toFuture().map { it.stateMachine }
|
||||||
|
|
||||||
val issueRequest = IssuanceRequester(amount, party, issueToPartyAndRef.reference, issuerNode.info.legalIdentity)
|
val issueRequest = IssuanceRequester(amount, party, issueToPartyAndRef.reference, issuerNode.info.legalIdentity,
|
||||||
|
anonymous = false)
|
||||||
val issueRequestResultFuture = issueToNode.services.startFlow(issueRequest).resultFuture
|
val issueRequestResultFuture = issueToNode.services.startFlow(issueRequest).resultFuture
|
||||||
|
|
||||||
return IssuerFlowTest.RunResult(firstIssuerFiber, issueRequestResultFuture)
|
return IssuerFlowTest.RunResult(firstIssuerFiber, issueRequestResultFuture)
|
||||||
@ -99,6 +106,6 @@ class IssuerFlowTest {
|
|||||||
|
|
||||||
private data class RunResult(
|
private data class RunResult(
|
||||||
val issuer: ListenableFuture<FlowStateMachine<*>>,
|
val issuer: ListenableFuture<FlowStateMachine<*>>,
|
||||||
val issueRequestResult: ListenableFuture<SignedTransaction>
|
val issueRequestResult: ListenableFuture<AbstractCashFlow.Result>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -260,6 +260,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
rpcFlows = emptyList()
|
rpcFlows = emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Investigate having class path scanning find this flow
|
||||||
|
registerInitiatedFlow(TxKeyFlow.Provider::class.java)
|
||||||
// TODO Remove this once the cash stuff is in its own CorDapp
|
// TODO Remove this once the cash stuff is in its own CorDapp
|
||||||
registerInitiatedFlow(IssuerFlow.Issuer::class.java)
|
registerInitiatedFlow(IssuerFlow.Issuer::class.java)
|
||||||
|
|
||||||
@ -459,7 +461,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
val storageServices = initialiseStorageService(configuration.baseDirectory)
|
val storageServices = initialiseStorageService(configuration.baseDirectory)
|
||||||
storage = storageServices.first
|
storage = storageServices.first
|
||||||
checkpointStorage = storageServices.second
|
checkpointStorage = storageServices.second
|
||||||
netMapCache = InMemoryNetworkMapCache()
|
netMapCache = InMemoryNetworkMapCache(services)
|
||||||
network = makeMessagingService()
|
network = makeMessagingService()
|
||||||
schemas = makeSchemaService()
|
schemas = makeSchemaService()
|
||||||
vault = makeVaultService(configuration.dataSourceProperties)
|
vault = makeVaultService(configuration.dataSourceProperties)
|
||||||
|
@ -5,11 +5,11 @@ import net.corda.core.crypto.DigitalSignature
|
|||||||
import net.corda.core.crypto.generateKeyPair
|
import net.corda.core.crypto.generateKeyPair
|
||||||
import net.corda.core.crypto.keys
|
import net.corda.core.crypto.keys
|
||||||
import net.corda.core.crypto.sign
|
import net.corda.core.crypto.sign
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.identity.PartyAndCertificate
|
import net.corda.core.identity.PartyAndCertificate
|
||||||
import net.corda.core.node.services.IdentityService
|
import net.corda.core.node.services.IdentityService
|
||||||
import net.corda.core.node.services.KeyManagementService
|
import net.corda.core.node.services.KeyManagementService
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
|
import net.corda.flows.AnonymisedIdentity
|
||||||
import org.bouncycastle.cert.X509CertificateHolder
|
import org.bouncycastle.cert.X509CertificateHolder
|
||||||
import org.bouncycastle.operator.ContentSigner
|
import org.bouncycastle.operator.ContentSigner
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
@ -58,7 +58,7 @@ class E2ETestKeyManagementService(val identityService: IdentityService,
|
|||||||
return keyPair.public
|
return keyPair.public
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): Pair<X509CertificateHolder, CertPath> {
|
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymisedIdentity {
|
||||||
return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled)
|
return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,6 +71,10 @@ class E2ETestKeyManagementService(val identityService: IdentityService,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun filterMyKeys(candidateKeys: Iterable<PublicKey>): Iterable<PublicKey> {
|
||||||
|
return mutex.locked { candidateKeys.filter { it in this.keys } }
|
||||||
|
}
|
||||||
|
|
||||||
override fun sign(bytes: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey {
|
override fun sign(bytes: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey {
|
||||||
val keyPair = getSigningKeyPair(publicKey)
|
val keyPair = getSigningKeyPair(publicKey)
|
||||||
val signature = keyPair.sign(bytes)
|
val signature = keyPair.sign(bytes)
|
||||||
|
@ -4,6 +4,7 @@ import net.corda.core.crypto.*
|
|||||||
import net.corda.core.identity.AnonymousParty
|
import net.corda.core.identity.AnonymousParty
|
||||||
import net.corda.core.identity.PartyAndCertificate
|
import net.corda.core.identity.PartyAndCertificate
|
||||||
import net.corda.core.node.services.IdentityService
|
import net.corda.core.node.services.IdentityService
|
||||||
|
import net.corda.flows.AnonymisedIdentity
|
||||||
import org.bouncycastle.cert.X509CertificateHolder
|
import org.bouncycastle.cert.X509CertificateHolder
|
||||||
import org.bouncycastle.operator.ContentSigner
|
import org.bouncycastle.operator.ContentSigner
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
@ -30,7 +31,7 @@ fun freshCertificate(identityService: IdentityService,
|
|||||||
subjectPublicKey: PublicKey,
|
subjectPublicKey: PublicKey,
|
||||||
issuer: PartyAndCertificate,
|
issuer: PartyAndCertificate,
|
||||||
issuerSigner: ContentSigner,
|
issuerSigner: ContentSigner,
|
||||||
revocationEnabled: Boolean = false): Pair<X509CertificateHolder, CertPath> {
|
revocationEnabled: Boolean = false): AnonymisedIdentity {
|
||||||
val issuerCertificate = issuer.certificate
|
val issuerCertificate = issuer.certificate
|
||||||
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, Duration.ofDays(10 * 365), issuerCertificate)
|
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, Duration.ofDays(10 * 365), issuerCertificate)
|
||||||
val ourCertificate = Crypto.createCertificate(CertificateType.IDENTITY, issuerCertificate.subject, issuerSigner, issuer.name, subjectPublicKey, window)
|
val ourCertificate = Crypto.createCertificate(CertificateType.IDENTITY, issuerCertificate.subject, issuerSigner, issuer.name, subjectPublicKey, window)
|
||||||
@ -39,7 +40,7 @@ fun freshCertificate(identityService: IdentityService,
|
|||||||
identityService.registerAnonymousIdentity(AnonymousParty(subjectPublicKey),
|
identityService.registerAnonymousIdentity(AnonymousParty(subjectPublicKey),
|
||||||
issuer.party,
|
issuer.party,
|
||||||
ourCertPath)
|
ourCertPath)
|
||||||
return Pair(issuerCertificate, ourCertPath)
|
return AnonymisedIdentity(ourCertPath, issuerCertificate, subjectPublicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSigner(issuerKeyPair: KeyPair): ContentSigner {
|
fun getSigner(issuerKeyPair: KeyPair): ContentSigner {
|
||||||
|
@ -9,6 +9,7 @@ import net.corda.core.identity.PartyAndCertificate
|
|||||||
import net.corda.core.node.services.IdentityService
|
import net.corda.core.node.services.IdentityService
|
||||||
import net.corda.core.node.services.KeyManagementService
|
import net.corda.core.node.services.KeyManagementService
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
|
import net.corda.flows.AnonymisedIdentity
|
||||||
import net.corda.node.utilities.*
|
import net.corda.node.utilities.*
|
||||||
import org.bouncycastle.cert.X509CertificateHolder
|
import org.bouncycastle.cert.X509CertificateHolder
|
||||||
import org.bouncycastle.operator.ContentSigner
|
import org.bouncycastle.operator.ContentSigner
|
||||||
@ -60,6 +61,10 @@ class PersistentKeyManagementService(val identityService: IdentityService,
|
|||||||
|
|
||||||
override val keys: Set<PublicKey> get() = mutex.locked { keys.keys }
|
override val keys: Set<PublicKey> get() = mutex.locked { keys.keys }
|
||||||
|
|
||||||
|
override fun filterMyKeys(candidateKeys: Iterable<PublicKey>): Iterable<PublicKey> {
|
||||||
|
return mutex.locked { candidateKeys.filter { it in this.keys } }
|
||||||
|
}
|
||||||
|
|
||||||
override fun freshKey(): PublicKey {
|
override fun freshKey(): PublicKey {
|
||||||
val keyPair = generateKeyPair()
|
val keyPair = generateKeyPair()
|
||||||
mutex.locked {
|
mutex.locked {
|
||||||
@ -68,7 +73,7 @@ class PersistentKeyManagementService(val identityService: IdentityService,
|
|||||||
return keyPair.public
|
return keyPair.public
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): Pair<X509CertificateHolder, CertPath> {
|
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymisedIdentity {
|
||||||
return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled)
|
return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,12 +4,15 @@ import com.google.common.annotations.VisibleForTesting
|
|||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.google.common.util.concurrent.SettableFuture
|
import com.google.common.util.concurrent.SettableFuture
|
||||||
import net.corda.core.bufferUntilSubscribed
|
import net.corda.core.bufferUntilSubscribed
|
||||||
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.map
|
import net.corda.core.map
|
||||||
import net.corda.core.messaging.DataFeed
|
import net.corda.core.messaging.DataFeed
|
||||||
import net.corda.core.messaging.SingleMessageRecipient
|
import net.corda.core.messaging.SingleMessageRecipient
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.services.DEFAULT_SESSION_ID
|
import net.corda.core.node.services.DEFAULT_SESSION_ID
|
||||||
|
import net.corda.core.node.services.IdentityService
|
||||||
import net.corda.core.node.services.NetworkMapCache.MapChange
|
import net.corda.core.node.services.NetworkMapCache.MapChange
|
||||||
import net.corda.core.node.services.PartyInfo
|
import net.corda.core.node.services.PartyInfo
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
@ -35,9 +38,13 @@ import javax.annotation.concurrent.ThreadSafe
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Extremely simple in-memory cache of the network map.
|
* Extremely simple in-memory cache of the network map.
|
||||||
|
*
|
||||||
|
* @param serviceHub an optional service hub from which we'll take the identity service. We take a service hub rather
|
||||||
|
* than the identity service directly, as this avoids problems with service start sequence (network map cache
|
||||||
|
* and identity services depend on each other). Should always be provided except for unit test cases.
|
||||||
*/
|
*/
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
open class InMemoryNetworkMapCache : SingletonSerializeAsToken(), NetworkMapCacheInternal {
|
open class InMemoryNetworkMapCache(private val serviceHub: ServiceHub?) : SingletonSerializeAsToken(), NetworkMapCacheInternal {
|
||||||
companion object {
|
companion object {
|
||||||
val logger = loggerFor<InMemoryNetworkMapCache>()
|
val logger = loggerFor<InMemoryNetworkMapCache>()
|
||||||
}
|
}
|
||||||
@ -71,6 +78,17 @@ open class InMemoryNetworkMapCache : SingletonSerializeAsToken(), NetworkMapCach
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getNodeByLegalIdentityKey(identityKey: PublicKey): NodeInfo? = registeredNodes[identityKey]
|
override fun getNodeByLegalIdentityKey(identityKey: PublicKey): NodeInfo? = registeredNodes[identityKey]
|
||||||
|
override fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? {
|
||||||
|
val wellKnownParty = if (serviceHub != null) {
|
||||||
|
serviceHub.identityService.partyFromAnonymous(party)
|
||||||
|
} else {
|
||||||
|
party
|
||||||
|
}
|
||||||
|
|
||||||
|
return wellKnownParty?.let {
|
||||||
|
getNodeByLegalIdentityKey(it.owningKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun track(): DataFeed<List<NodeInfo>, MapChange> {
|
override fun track(): DataFeed<List<NodeInfo>, MapChange> {
|
||||||
synchronized(_changed) {
|
synchronized(_changed) {
|
||||||
|
@ -7,6 +7,7 @@ import net.corda.core.crypto.isFulfilledBy
|
|||||||
import net.corda.core.crypto.keys
|
import net.corda.core.crypto.keys
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.StateMachineRunId
|
import net.corda.core.flows.StateMachineRunId
|
||||||
|
import net.corda.core.getOrThrow
|
||||||
import net.corda.core.messaging.*
|
import net.corda.core.messaging.*
|
||||||
import net.corda.core.node.services.ServiceInfo
|
import net.corda.core.node.services.ServiceInfo
|
||||||
import net.corda.core.node.services.Vault
|
import net.corda.core.node.services.Vault
|
||||||
@ -86,18 +87,11 @@ class CordaRPCOpsImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tell the monitoring service node to issue some cash
|
// Tell the monitoring service node to issue some cash
|
||||||
|
val anonymous = false
|
||||||
val recipient = aliceNode.info.legalIdentity
|
val recipient = aliceNode.info.legalIdentity
|
||||||
rpc.startFlow(::CashIssueFlow, Amount(quantity, GBP), ref, recipient, notaryNode.info.notaryIdentity)
|
val result = rpc.startFlow(::CashIssueFlow, Amount(quantity, GBP), ref, recipient, notaryNode.info.notaryIdentity, anonymous)
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
|
|
||||||
val expectedState = Cash.State(Amount(quantity,
|
|
||||||
Issued(aliceNode.info.legalIdentity.ref(ref), GBP)),
|
|
||||||
recipient)
|
|
||||||
|
|
||||||
// Query vault via RPC
|
|
||||||
val cash = rpc.vaultQueryBy<Cash.State>()
|
|
||||||
assertEquals(expectedState, cash.states.first().state.data)
|
|
||||||
|
|
||||||
var issueSmId: StateMachineRunId? = null
|
var issueSmId: StateMachineRunId? = null
|
||||||
stateMachineUpdates.expectEvents {
|
stateMachineUpdates.expectEvents {
|
||||||
sequence(
|
sequence(
|
||||||
@ -111,11 +105,14 @@ class CordaRPCOpsImplTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
transactions.expectEvents {
|
val tx = result.returnValue.getOrThrow()
|
||||||
expect { tx ->
|
val expectedState = Cash.State(Amount(quantity,
|
||||||
assertEquals(expectedState, tx.tx.outputs.single().data)
|
Issued(aliceNode.info.legalIdentity.ref(ref), GBP)),
|
||||||
}
|
recipient)
|
||||||
}
|
|
||||||
|
// Query vault via RPC
|
||||||
|
val cash = rpc.vaultQueryBy<Cash.State>()
|
||||||
|
assertEquals(expectedState, cash.states.first().state.data)
|
||||||
|
|
||||||
// TODO: deprecated
|
// TODO: deprecated
|
||||||
vaultUpdates.expectEvents {
|
vaultUpdates.expectEvents {
|
||||||
@ -135,22 +132,24 @@ class CordaRPCOpsImplTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `issue and move`() {
|
fun `issue and move`() {
|
||||||
rpc.startFlow(::CashIssueFlow,
|
val anonymous = false
|
||||||
|
val result = rpc.startFlow(::CashIssueFlow,
|
||||||
Amount(100, USD),
|
Amount(100, USD),
|
||||||
OpaqueBytes(ByteArray(1, { 1 })),
|
OpaqueBytes(ByteArray(1, { 1 })),
|
||||||
aliceNode.info.legalIdentity,
|
aliceNode.info.legalIdentity,
|
||||||
notaryNode.info.notaryIdentity
|
notaryNode.info.notaryIdentity,
|
||||||
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
|
|
||||||
rpc.startFlow(::CashPaymentFlow, Amount(100, USD), aliceNode.info.legalIdentity)
|
rpc.startFlow(::CashPaymentFlow, Amount(100, USD), aliceNode.info.legalIdentity, anonymous)
|
||||||
|
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
|
|
||||||
var issueSmId: StateMachineRunId? = null
|
var issueSmId: StateMachineRunId? = null
|
||||||
var moveSmId: StateMachineRunId? = null
|
var moveSmId: StateMachineRunId? = null
|
||||||
stateMachineUpdates.expectEvents {
|
stateMachineUpdates.expectEvents() {
|
||||||
sequence(
|
sequence(
|
||||||
// ISSUE
|
// ISSUE
|
||||||
expect { add: StateMachineUpdate.Added ->
|
expect { add: StateMachineUpdate.Added ->
|
||||||
@ -169,6 +168,7 @@ class CordaRPCOpsImplTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val tx = result.returnValue.getOrThrow()
|
||||||
transactions.expectEvents {
|
transactions.expectEvents {
|
||||||
sequence(
|
sequence(
|
||||||
// ISSUE
|
// ISSUE
|
||||||
@ -233,7 +233,8 @@ class CordaRPCOpsImplTest {
|
|||||||
Amount(100, USD),
|
Amount(100, USD),
|
||||||
OpaqueBytes(ByteArray(1, { 1 })),
|
OpaqueBytes(ByteArray(1, { 1 })),
|
||||||
aliceNode.info.legalIdentity,
|
aliceNode.info.legalIdentity,
|
||||||
notaryNode.info.notaryIdentity
|
notaryNode.info.notaryIdentity,
|
||||||
|
false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ open class MockServiceHubInternal(
|
|||||||
val network: MessagingService? = null,
|
val network: MessagingService? = null,
|
||||||
val identity: IdentityService? = MOCK_IDENTITY_SERVICE,
|
val identity: IdentityService? = MOCK_IDENTITY_SERVICE,
|
||||||
val storage: TxWritableStorageService? = MockStorageService(),
|
val storage: TxWritableStorageService? = MockStorageService(),
|
||||||
val mapCache: NetworkMapCacheInternal? = MockNetworkMapCache(),
|
val mapCache: NetworkMapCacheInternal? = null,
|
||||||
val scheduler: SchedulerService? = null,
|
val scheduler: SchedulerService? = null,
|
||||||
val overrideClock: Clock? = NodeClock(),
|
val overrideClock: Clock? = NodeClock(),
|
||||||
val schemas: SchemaService? = NodeSchemaService(),
|
val schemas: SchemaService? = NodeSchemaService(),
|
||||||
@ -46,7 +46,7 @@ open class MockServiceHubInternal(
|
|||||||
override val networkService: MessagingService
|
override val networkService: MessagingService
|
||||||
get() = network ?: throw UnsupportedOperationException()
|
get() = network ?: throw UnsupportedOperationException()
|
||||||
override val networkMapCache: NetworkMapCacheInternal
|
override val networkMapCache: NetworkMapCacheInternal
|
||||||
get() = mapCache ?: throw UnsupportedOperationException()
|
get() = mapCache ?: MockNetworkMapCache(this)
|
||||||
override val storageService: StorageService
|
override val storageService: StorageService
|
||||||
get() = storage ?: throw UnsupportedOperationException()
|
get() = storage ?: throw UnsupportedOperationException()
|
||||||
override val schedulerService: SchedulerService
|
override val schedulerService: SchedulerService
|
||||||
|
@ -60,7 +60,8 @@ class ArtemisMessagingTests {
|
|||||||
var messagingClient: NodeMessagingClient? = null
|
var messagingClient: NodeMessagingClient? = null
|
||||||
var messagingServer: ArtemisMessagingServer? = null
|
var messagingServer: ArtemisMessagingServer? = null
|
||||||
|
|
||||||
val networkMapCache = InMemoryNetworkMapCache()
|
// TODO: We should have a dummy service hub rather than change behaviour in tests
|
||||||
|
val networkMapCache = InMemoryNetworkMapCache(serviceHub = null)
|
||||||
|
|
||||||
val rpcOps = object : RPCOps {
|
val rpcOps = object : RPCOps {
|
||||||
override val protocolVersion: Int get() = throw UnsupportedOperationException()
|
override val protocolVersion: Int get() = throw UnsupportedOperationException()
|
||||||
|
@ -6,6 +6,7 @@ import net.corda.core.identity.Party
|
|||||||
import net.corda.core.identity.PartyAndCertificate
|
import net.corda.core.identity.PartyAndCertificate
|
||||||
import net.corda.core.node.services.IdentityService
|
import net.corda.core.node.services.IdentityService
|
||||||
import net.corda.core.utilities.*
|
import net.corda.core.utilities.*
|
||||||
|
import net.corda.flows.AnonymisedIdentity
|
||||||
import net.corda.flows.TxKeyFlow
|
import net.corda.flows.TxKeyFlow
|
||||||
import net.corda.node.services.identity.InMemoryIdentityService
|
import net.corda.node.services.identity.InMemoryIdentityService
|
||||||
import net.corda.testing.ALICE_PUBKEY
|
import net.corda.testing.ALICE_PUBKEY
|
||||||
@ -136,14 +137,14 @@ class InMemoryIdentityServiceTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createParty(x500Name: X500Name, ca: CertificateAndKeyPair): Pair<PartyAndCertificate, TxKeyFlow.AnonymousIdentity> {
|
private fun createParty(x500Name: X500Name, ca: CertificateAndKeyPair): Pair<PartyAndCertificate, AnonymisedIdentity> {
|
||||||
val certFactory = CertificateFactory.getInstance("X509")
|
val certFactory = CertificateFactory.getInstance("X509")
|
||||||
val issuerKeyPair = generateKeyPair()
|
val issuerKeyPair = generateKeyPair()
|
||||||
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public, ca)
|
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public, ca)
|
||||||
val txKey = Crypto.generateKeyPair()
|
val txKey = Crypto.generateKeyPair()
|
||||||
val txCert = X509Utilities.createCertificate(CertificateType.IDENTITY, issuer.certificate, issuerKeyPair, x500Name, txKey.public)
|
val txCert = X509Utilities.createCertificate(CertificateType.IDENTITY, issuer.certificate, issuerKeyPair, x500Name, txKey.public)
|
||||||
val txCertPath = certFactory.generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
|
val txCertPath = certFactory.generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
|
||||||
return Pair(issuer, TxKeyFlow.AnonymousIdentity(txCertPath, txCert, AnonymousParty(txKey.public)))
|
return Pair(issuer, AnonymisedIdentity(txCertPath, txCert, AnonymousParty(txKey.public)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package net.corda.node.services.network
|
package net.corda.node.services.network
|
||||||
|
|
||||||
import net.corda.core.getOrThrow
|
import net.corda.core.getOrThrow
|
||||||
|
import net.corda.core.node.services.NetworkMapCache
|
||||||
import net.corda.core.node.services.ServiceInfo
|
import net.corda.core.node.services.ServiceInfo
|
||||||
import net.corda.core.utilities.ALICE
|
import net.corda.core.utilities.ALICE
|
||||||
import net.corda.core.utilities.BOB
|
import net.corda.core.utilities.BOB
|
||||||
import net.corda.node.utilities.transaction
|
import net.corda.node.utilities.transaction
|
||||||
import net.corda.testing.node.MockNetwork
|
import net.corda.testing.node.MockNetwork
|
||||||
|
import org.junit.After
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@ -13,6 +15,11 @@ import kotlin.test.assertEquals
|
|||||||
class InMemoryNetworkMapCacheTest {
|
class InMemoryNetworkMapCacheTest {
|
||||||
private val mockNet = MockNetwork()
|
private val mockNet = MockNetwork()
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun teardown() {
|
||||||
|
mockNet.stopNodes()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun registerWithNetwork() {
|
fun registerWithNetwork() {
|
||||||
val (n0, n1) = mockNet.createTwoNodes()
|
val (n0, n1) = mockNet.createTwoNodes()
|
||||||
@ -28,6 +35,8 @@ class InMemoryNetworkMapCacheTest {
|
|||||||
val nodeB = mockNet.createNode(null, -1, MockNetwork.DefaultFactory, true, BOB.name, null, entropy, ServiceInfo(NetworkMapService.type))
|
val nodeB = mockNet.createNode(null, -1, MockNetwork.DefaultFactory, true, BOB.name, null, entropy, ServiceInfo(NetworkMapService.type))
|
||||||
assertEquals(nodeA.info.legalIdentity, nodeB.info.legalIdentity)
|
assertEquals(nodeA.info.legalIdentity, nodeB.info.legalIdentity)
|
||||||
|
|
||||||
|
mockNet.runNetwork()
|
||||||
|
|
||||||
// Node A currently knows only about itself, so this returns node A
|
// Node A currently knows only about itself, so this returns node A
|
||||||
assertEquals(nodeA.netMapCache.getNodeByLegalIdentityKey(nodeA.info.legalIdentity.owningKey), nodeA.info)
|
assertEquals(nodeA.netMapCache.getNodeByLegalIdentityKey(nodeA.info.legalIdentity.owningKey), nodeA.info)
|
||||||
|
|
||||||
@ -37,4 +46,17 @@ class InMemoryNetworkMapCacheTest {
|
|||||||
// The details of node B write over those for node A
|
// The details of node B write over those for node A
|
||||||
assertEquals(nodeA.netMapCache.getNodeByLegalIdentityKey(nodeA.info.legalIdentity.owningKey), nodeB.info)
|
assertEquals(nodeA.netMapCache.getNodeByLegalIdentityKey(nodeA.info.legalIdentity.owningKey), nodeB.info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getNodeByLegalIdentity`() {
|
||||||
|
val (n0, n1) = mockNet.createTwoNodes()
|
||||||
|
val node0Cache: NetworkMapCache = n0.services.networkMapCache
|
||||||
|
val expected = n1.info
|
||||||
|
|
||||||
|
mockNet.runNetwork()
|
||||||
|
val actual = node0Cache.getNodeByLegalIdentity(n1.info.legalIdentity)
|
||||||
|
assertEquals(expected, actual)
|
||||||
|
|
||||||
|
// TODO: Should have a test case with anonymous lookup
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -328,10 +328,11 @@ class FlowFrameworkTests {
|
|||||||
2000.DOLLARS,
|
2000.DOLLARS,
|
||||||
OpaqueBytes.of(0x01),
|
OpaqueBytes.of(0x01),
|
||||||
node1.info.legalIdentity,
|
node1.info.legalIdentity,
|
||||||
notary1.info.notaryIdentity))
|
notary1.info.notaryIdentity,
|
||||||
|
anonymous = false))
|
||||||
// We pay a couple of times, the notary picking should go round robin
|
// We pay a couple of times, the notary picking should go round robin
|
||||||
for (i in 1..3) {
|
for (i in 1..3) {
|
||||||
node1.services.startFlow(CashPaymentFlow(500.DOLLARS, node2.info.legalIdentity))
|
node1.services.startFlow(CashPaymentFlow(500.DOLLARS, node2.info.legalIdentity, anonymous = false))
|
||||||
mockNet.runNetwork()
|
mockNet.runNetwork()
|
||||||
}
|
}
|
||||||
val endpoint = mockNet.messagingNetwork.endpoint(notary1.network.myAddress as InMemoryMessagingNetwork.PeerHandle)!!
|
val endpoint = mockNet.messagingNetwork.endpoint(notary1.network.myAddress as InMemoryMessagingNetwork.PeerHandle)!!
|
||||||
|
@ -19,8 +19,9 @@ class BankOfCordaHttpAPITest {
|
|||||||
startNode(BOC.name, setOf(ServiceInfo(SimpleNotaryService.type))),
|
startNode(BOC.name, setOf(ServiceInfo(SimpleNotaryService.type))),
|
||||||
startNode(BIGCORP_LEGAL_NAME)
|
startNode(BIGCORP_LEGAL_NAME)
|
||||||
).getOrThrow()
|
).getOrThrow()
|
||||||
|
val anonymous = true
|
||||||
val nodeBankOfCordaApiAddr = startWebserver(nodeBankOfCorda).getOrThrow().listenAddress
|
val nodeBankOfCordaApiAddr = startWebserver(nodeBankOfCorda).getOrThrow().listenAddress
|
||||||
assertTrue(BankOfCordaClientApi(nodeBankOfCordaApiAddr).requestWebIssue(IssueRequestParams(1000, "USD", BIGCORP_LEGAL_NAME, "1", BOC.name)))
|
assertTrue(BankOfCordaClientApi(nodeBankOfCordaApiAddr).requestWebIssue(IssueRequestParams(1000, "USD", BIGCORP_LEGAL_NAME, "1", BOC.name, anonymous)))
|
||||||
}, isDebug = true)
|
}, isDebug = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,25 +39,28 @@ class BankOfCordaRPCClientTest {
|
|||||||
val vaultUpdatesBigCorp = bigCorpProxy.vaultAndUpdates().second
|
val vaultUpdatesBigCorp = bigCorpProxy.vaultAndUpdates().second
|
||||||
|
|
||||||
// Kick-off actual Issuer Flow
|
// Kick-off actual Issuer Flow
|
||||||
|
// TODO: Update checks below to reflect states consumed/produced under anonymisation
|
||||||
|
val anonymous = false
|
||||||
bocProxy.startFlow(
|
bocProxy.startFlow(
|
||||||
::IssuanceRequester,
|
::IssuanceRequester,
|
||||||
1000.DOLLARS,
|
1000.DOLLARS,
|
||||||
nodeBigCorporation.nodeInfo.legalIdentity,
|
nodeBigCorporation.nodeInfo.legalIdentity,
|
||||||
BIG_CORP_PARTY_REF,
|
BIG_CORP_PARTY_REF,
|
||||||
nodeBankOfCorda.nodeInfo.legalIdentity).returnValue.getOrThrow()
|
nodeBankOfCorda.nodeInfo.legalIdentity,
|
||||||
|
anonymous).returnValue.getOrThrow()
|
||||||
|
|
||||||
// Check Bank of Corda Vault Updates
|
// Check Bank of Corda Vault Updates
|
||||||
vaultUpdatesBoc.expectEvents {
|
vaultUpdatesBoc.expectEvents {
|
||||||
sequence(
|
sequence(
|
||||||
// ISSUE
|
// ISSUE
|
||||||
expect { update ->
|
expect { update ->
|
||||||
require(update.consumed.isEmpty()) { update.consumed.size }
|
require(update.consumed.isEmpty()) { "Expected 0 consumed states, actual: $update" }
|
||||||
require(update.produced.size == 1) { update.produced.size }
|
require(update.produced.size == 1) { "Expected 1 produced states, actual: $update" }
|
||||||
},
|
},
|
||||||
// MOVE
|
// MOVE
|
||||||
expect { update ->
|
expect { update ->
|
||||||
require(update.consumed.size == 1) { update.consumed.size }
|
require(update.consumed.size == 1) { "Expected 1 consumed states, actual: $update" }
|
||||||
require(update.produced.isEmpty()) { update.produced.size }
|
require(update.produced.isEmpty()) { "Expected 0 produced states, actual: $update" }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,8 @@ private class BankOfCordaDriver {
|
|||||||
}, isDebug = true)
|
}, isDebug = true)
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
val requestParams = IssueRequestParams(options.valueOf(quantity), options.valueOf(currency), BIGCORP_LEGAL_NAME, "1", BOC.name)
|
val anonymous = true
|
||||||
|
val requestParams = IssueRequestParams(options.valueOf(quantity), options.valueOf(currency), BIGCORP_LEGAL_NAME, "1", BOC.name, anonymous)
|
||||||
when (role) {
|
when (role) {
|
||||||
Role.ISSUE_CASH_RPC -> {
|
Role.ISSUE_CASH_RPC -> {
|
||||||
println("Requesting Cash via RPC ...")
|
println("Requesting Cash via RPC ...")
|
||||||
|
@ -44,7 +44,7 @@ class BankOfCordaClientApi(val hostAndPort: HostAndPort) {
|
|||||||
val amount = Amount(params.amount, currency(params.currency))
|
val amount = Amount(params.amount, currency(params.currency))
|
||||||
val issuerToPartyRef = OpaqueBytes.of(params.issueToPartyRefAsString.toByte())
|
val issuerToPartyRef = OpaqueBytes.of(params.issueToPartyRefAsString.toByte())
|
||||||
|
|
||||||
return proxy.startFlow(::IssuanceRequester, amount, issueToParty, issuerToPartyRef, issuerBankParty).returnValue.getOrThrow()
|
return proxy.startFlow(::IssuanceRequester, amount, issueToParty, issuerToPartyRef, issuerBankParty, params.anonymous).returnValue.getOrThrow().stx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,8 @@ import javax.ws.rs.core.Response
|
|||||||
class BankOfCordaWebApi(val rpc: CordaRPCOps) {
|
class BankOfCordaWebApi(val rpc: CordaRPCOps) {
|
||||||
data class IssueRequestParams(val amount: Long, val currency: String,
|
data class IssueRequestParams(val amount: Long, val currency: String,
|
||||||
val issueToPartyName: X500Name, val issueToPartyRefAsString: String,
|
val issueToPartyName: X500Name, val issueToPartyRefAsString: String,
|
||||||
val issuerBankName: X500Name)
|
val issuerBankName: X500Name,
|
||||||
|
val anonymous: Boolean)
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
val logger = loggerFor<BankOfCordaWebApi>()
|
val logger = loggerFor<BankOfCordaWebApi>()
|
||||||
@ -48,11 +49,12 @@ class BankOfCordaWebApi(val rpc: CordaRPCOps) {
|
|||||||
|
|
||||||
val amount = Amount(params.amount, currency(params.currency))
|
val amount = Amount(params.amount, currency(params.currency))
|
||||||
val issuerToPartyRef = OpaqueBytes.of(params.issueToPartyRefAsString.toByte())
|
val issuerToPartyRef = OpaqueBytes.of(params.issueToPartyRefAsString.toByte())
|
||||||
|
val anonymous = params.anonymous
|
||||||
|
|
||||||
// invoke client side of Issuer Flow: IssuanceRequester
|
// invoke client side of Issuer Flow: IssuanceRequester
|
||||||
// The line below blocks and waits for the future to resolve.
|
// The line below blocks and waits for the future to resolve.
|
||||||
val status = try {
|
val status = try {
|
||||||
rpc.startFlow(::IssuanceRequester, amount, issueToParty, issuerToPartyRef, issuerBankParty).returnValue.getOrThrow()
|
rpc.startFlow(::IssuanceRequester, amount, issueToParty, issuerToPartyRef, issuerBankParty, anonymous).returnValue.getOrThrow()
|
||||||
logger.info("Issue request completed successfully: $params")
|
logger.info("Issue request completed successfully: $params")
|
||||||
Response.Status.CREATED
|
Response.Status.CREATED
|
||||||
} catch (e: FlowException) {
|
} catch (e: FlowException) {
|
||||||
|
@ -8,6 +8,7 @@ import net.corda.core.transactions.SignedTransaction
|
|||||||
import net.corda.flows.AbstractStateReplacementFlow
|
import net.corda.flows.AbstractStateReplacementFlow
|
||||||
import net.corda.flows.StateReplacementException
|
import net.corda.flows.StateReplacementException
|
||||||
import net.corda.vega.contracts.RevisionedState
|
import net.corda.vega.contracts.RevisionedState
|
||||||
|
import java.security.PublicKey
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flow that generates an update on a mutable deal state and commits the resulting transaction reaching consensus
|
* Flow that generates an update on a mutable deal state and commits the resulting transaction reaching consensus
|
||||||
@ -16,13 +17,16 @@ import net.corda.vega.contracts.RevisionedState
|
|||||||
object StateRevisionFlow {
|
object StateRevisionFlow {
|
||||||
class Requester<T>(curStateRef: StateAndRef<RevisionedState<T>>,
|
class Requester<T>(curStateRef: StateAndRef<RevisionedState<T>>,
|
||||||
updatedData: T) : AbstractStateReplacementFlow.Instigator<RevisionedState<T>, RevisionedState<T>, T>(curStateRef, updatedData) {
|
updatedData: T) : AbstractStateReplacementFlow.Instigator<RevisionedState<T>, RevisionedState<T>, T>(curStateRef, updatedData) {
|
||||||
override fun assembleTx(): Pair<SignedTransaction, List<AbstractParty>> {
|
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
|
||||||
val state = originalState.state.data
|
val state = originalState.state.data
|
||||||
val tx = state.generateRevision(originalState.state.notary, originalState, modification)
|
val tx = state.generateRevision(originalState.state.notary, originalState, modification)
|
||||||
tx.addTimeWindow(serviceHub.clock.instant(), 30.seconds)
|
tx.addTimeWindow(serviceHub.clock.instant(), 30.seconds)
|
||||||
|
|
||||||
val stx = serviceHub.signInitialTransaction(tx)
|
val stx = serviceHub.signInitialTransaction(tx)
|
||||||
return Pair(stx, state.participants)
|
val participantKeys = state.participants.map { it.owningKey }
|
||||||
|
// TODO: We need a much faster way of finding our key in the transaction
|
||||||
|
val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
|
||||||
|
return AbstractStateReplacementFlow.UpgradeTx(stx, participantKeys, myKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,8 @@ class TraderDemoTest : NodeBasedTest() {
|
|||||||
val expectedBCash = clientB.cashCount + 1
|
val expectedBCash = clientB.cashCount + 1
|
||||||
val expectedPaper = listOf(clientA.commercialPaperCount + 1, clientB.commercialPaperCount)
|
val expectedPaper = listOf(clientA.commercialPaperCount + 1, clientB.commercialPaperCount)
|
||||||
|
|
||||||
clientA.runBuyer(amount = 100.DOLLARS)
|
// TODO: Enable anonymisation
|
||||||
|
clientA.runBuyer(amount = 100.DOLLARS, anonymous = false)
|
||||||
clientB.runSeller(counterparty = nodeA.info.legalIdentity.name, amount = 5.DOLLARS)
|
clientB.runSeller(counterparty = nodeA.info.legalIdentity.name, amount = 5.DOLLARS)
|
||||||
|
|
||||||
assertThat(clientA.cashCount).isGreaterThan(originalACash)
|
assertThat(clientA.cashCount).isGreaterThan(originalACash)
|
||||||
|
@ -43,14 +43,14 @@ class TraderDemoClientApi(val rpc: CordaRPCOps) {
|
|||||||
return vault.filterStatesOfType<CommercialPaper.State>().size
|
return vault.filterStatesOfType<CommercialPaper.State>().size
|
||||||
}
|
}
|
||||||
|
|
||||||
fun runBuyer(amount: Amount<Currency> = 30000.DOLLARS) {
|
fun runBuyer(amount: Amount<Currency> = 30000.DOLLARS, anonymous: Boolean = true) {
|
||||||
val bankOfCordaParty = rpc.partyFromX500Name(BOC.name)
|
val bankOfCordaParty = rpc.partyFromX500Name(BOC.name)
|
||||||
?: throw Exception("Unable to locate ${BOC.name} in Network Map Service")
|
?: throw Exception("Unable to locate ${BOC.name} in Network Map Service")
|
||||||
val me = rpc.nodeIdentity()
|
val me = rpc.nodeIdentity()
|
||||||
val amounts = calculateRandomlySizedAmounts(amount, 3, 10, Random())
|
val amounts = calculateRandomlySizedAmounts(amount, 3, 10, Random())
|
||||||
// issuer random amounts of currency totaling 30000.DOLLARS in parallel
|
// issuer random amounts of currency totaling 30000.DOLLARS in parallel
|
||||||
val resultFutures = amounts.map { pennies ->
|
val resultFutures = amounts.map { pennies ->
|
||||||
rpc.startFlow(::IssuanceRequester, Amount(pennies, amount.token), me.legalIdentity, OpaqueBytes.of(1), bankOfCordaParty).returnValue
|
rpc.startFlow(::IssuanceRequester, Amount(pennies, amount.token), me.legalIdentity, OpaqueBytes.of(1), bankOfCordaParty, anonymous).returnValue
|
||||||
}
|
}
|
||||||
|
|
||||||
Futures.allAsList(resultFutures).getOrThrow()
|
Futures.allAsList(resultFutures).getOrThrow()
|
||||||
|
@ -5,6 +5,7 @@ import com.google.common.net.HostAndPort
|
|||||||
import net.corda.core.crypto.entropyToKeyPair
|
import net.corda.core.crypto.entropyToKeyPair
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.services.NetworkMapCache
|
import net.corda.core.node.services.NetworkMapCache
|
||||||
import net.corda.core.utilities.getTestPartyAndCertificate
|
import net.corda.core.utilities.getTestPartyAndCertificate
|
||||||
import net.corda.node.services.network.InMemoryNetworkMapCache
|
import net.corda.node.services.network.InMemoryNetworkMapCache
|
||||||
@ -17,7 +18,7 @@ import java.math.BigInteger
|
|||||||
/**
|
/**
|
||||||
* Network map cache with no backing map service.
|
* Network map cache with no backing map service.
|
||||||
*/
|
*/
|
||||||
class MockNetworkMapCache : InMemoryNetworkMapCache() {
|
class MockNetworkMapCache(serviceHub: ServiceHub) : InMemoryNetworkMapCache(serviceHub) {
|
||||||
private companion object {
|
private companion object {
|
||||||
val BANK_C = getTestPartyAndCertificate(getTestX509Name("Bank C"), entropyToKeyPair(BigInteger.valueOf(1000)).public)
|
val BANK_C = getTestPartyAndCertificate(getTestX509Name("Bank C"), entropyToKeyPair(BigInteger.valueOf(1000)).public)
|
||||||
val BANK_D = getTestPartyAndCertificate(getTestX509Name("Bank D"), entropyToKeyPair(BigInteger.valueOf(2000)).public)
|
val BANK_D = getTestPartyAndCertificate(getTestX509Name("Bank D"), entropyToKeyPair(BigInteger.valueOf(2000)).public)
|
||||||
|
@ -15,6 +15,7 @@ import net.corda.core.serialization.SingletonSerializeAsToken
|
|||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.DUMMY_CA
|
import net.corda.core.utilities.DUMMY_CA
|
||||||
import net.corda.core.utilities.getTestPartyAndCertificate
|
import net.corda.core.utilities.getTestPartyAndCertificate
|
||||||
|
import net.corda.flows.AnonymisedIdentity
|
||||||
import net.corda.node.services.database.HibernateConfiguration
|
import net.corda.node.services.database.HibernateConfiguration
|
||||||
import net.corda.node.services.identity.InMemoryIdentityService
|
import net.corda.node.services.identity.InMemoryIdentityService
|
||||||
import net.corda.node.services.keys.freshCertificate
|
import net.corda.node.services.keys.freshCertificate
|
||||||
@ -104,7 +105,9 @@ class MockKeyManagementService(val identityService: IdentityService,
|
|||||||
return k.public
|
return k.public
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): Pair<X509CertificateHolder, CertPath> {
|
override fun filterMyKeys(candidateKeys: Iterable<PublicKey>): Iterable<PublicKey> = candidateKeys.filter { it in this.keys }
|
||||||
|
|
||||||
|
override fun freshKeyAndCert(identity: PartyAndCertificate, revocationEnabled: Boolean): AnonymisedIdentity {
|
||||||
return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled)
|
return freshCertificate(identityService, freshKey(), identity, getSigner(identity.owningKey), revocationEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,8 @@ class SimpleNode(val config: NodeConfiguration, val address: HostAndPort = freeL
|
|||||||
val identityService: IdentityService = InMemoryIdentityService(trustRoot = trustRoot)
|
val identityService: IdentityService = InMemoryIdentityService(trustRoot = trustRoot)
|
||||||
val keyService: KeyManagementService = E2ETestKeyManagementService(identityService, setOf(identity))
|
val keyService: KeyManagementService = E2ETestKeyManagementService(identityService, setOf(identity))
|
||||||
val executor = ServiceAffinityExecutor(config.myLegalName.commonName, 1)
|
val executor = ServiceAffinityExecutor(config.myLegalName.commonName, 1)
|
||||||
val broker = ArtemisMessagingServer(config, address.port, rpcAddress.port, InMemoryNetworkMapCache(), userService)
|
// TODO: We should have a dummy service hub rather than change behaviour in tests
|
||||||
|
val broker = ArtemisMessagingServer(config, address.port, rpcAddress.port, InMemoryNetworkMapCache(serviceHub = null), userService)
|
||||||
val networkMapRegistrationFuture: SettableFuture<Unit> = SettableFuture.create<Unit>()
|
val networkMapRegistrationFuture: SettableFuture<Unit> = SettableFuture.create<Unit>()
|
||||||
val network = database.transaction {
|
val network = database.transaction {
|
||||||
NodeMessagingClient(
|
NodeMessagingClient(
|
||||||
|
@ -22,11 +22,7 @@ import net.corda.core.transactions.SignedTransaction
|
|||||||
import net.corda.core.utilities.ALICE
|
import net.corda.core.utilities.ALICE
|
||||||
import net.corda.core.utilities.BOB
|
import net.corda.core.utilities.BOB
|
||||||
import net.corda.core.utilities.DUMMY_NOTARY
|
import net.corda.core.utilities.DUMMY_NOTARY
|
||||||
import net.corda.flows.CashExitFlow
|
import net.corda.flows.*
|
||||||
import net.corda.flows.CashFlowCommand
|
|
||||||
import net.corda.flows.CashIssueFlow
|
|
||||||
import net.corda.flows.CashPaymentFlow
|
|
||||||
import net.corda.flows.IssuerFlow
|
|
||||||
import net.corda.testing.driver.NodeHandle
|
import net.corda.testing.driver.NodeHandle
|
||||||
import net.corda.testing.driver.PortAllocation
|
import net.corda.testing.driver.PortAllocation
|
||||||
import net.corda.testing.driver.driver
|
import net.corda.testing.driver.driver
|
||||||
@ -136,10 +132,11 @@ class ExplorerSimulation(val options: OptionSet) {
|
|||||||
|
|
||||||
private fun startSimulation(eventGenerator: EventGenerator, maxIterations: Int) {
|
private fun startSimulation(eventGenerator: EventGenerator, maxIterations: Int) {
|
||||||
// Log to logger when flow finish.
|
// Log to logger when flow finish.
|
||||||
fun FlowHandle<SignedTransaction>.log(seq: Int, name: String) {
|
fun FlowHandle<AbstractCashFlow.Result>.log(seq: Int, name: String) {
|
||||||
val out = "[$seq] $name $id :"
|
val out = "[$seq] $name $id :"
|
||||||
returnValue.success {
|
returnValue.success {
|
||||||
Main.log.info("$out ${it.id} ${(it.tx.outputs.first().data as Cash.State).amount}")
|
val (stx, idenities) = it
|
||||||
|
Main.log.info("$out ${stx.id} ${(stx.tx.outputs.first().data as Cash.State).amount}")
|
||||||
}.failure {
|
}.failure {
|
||||||
Main.log.info("$out ${it.message}")
|
Main.log.info("$out ${it.message}")
|
||||||
}
|
}
|
||||||
@ -179,11 +176,12 @@ class ExplorerSimulation(val options: OptionSet) {
|
|||||||
currencies = listOf(GBP, USD)
|
currencies = listOf(GBP, USD)
|
||||||
)
|
)
|
||||||
val maxIterations = 100_000
|
val maxIterations = 100_000
|
||||||
|
val anonymous = true
|
||||||
// Pre allocate some money to each party.
|
// Pre allocate some money to each party.
|
||||||
eventGenerator.parties.forEach {
|
eventGenerator.parties.forEach {
|
||||||
for (ref in 0..1) {
|
for (ref in 0..1) {
|
||||||
for ((currency, issuer) in issuers) {
|
for ((currency, issuer) in issuers) {
|
||||||
CashFlowCommand.IssueCash(Amount(1_000_000, currency), OpaqueBytes(ByteArray(1, { ref.toByte() })), it, notaryNode.nodeInfo.notaryIdentity).startFlow(issuer)
|
CashFlowCommand.IssueCash(Amount(1_000_000, currency), OpaqueBytes(ByteArray(1, { ref.toByte() })), it, notaryNode.nodeInfo.notaryIdentity, anonymous).startFlow(issuer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,14 +20,16 @@ import net.corda.client.jfx.utils.unique
|
|||||||
import net.corda.core.contracts.Amount
|
import net.corda.core.contracts.Amount
|
||||||
import net.corda.core.contracts.sumOrNull
|
import net.corda.core.contracts.sumOrNull
|
||||||
import net.corda.core.contracts.withoutIssuer
|
import net.corda.core.contracts.withoutIssuer
|
||||||
import net.corda.core.identity.AbstractParty
|
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.flows.FlowException
|
import net.corda.core.flows.FlowException
|
||||||
import net.corda.core.getOrThrow
|
import net.corda.core.getOrThrow
|
||||||
|
import net.corda.core.identity.AbstractParty
|
||||||
|
import net.corda.core.identity.Party
|
||||||
|
import net.corda.core.messaging.FlowHandle
|
||||||
import net.corda.core.messaging.startFlow
|
import net.corda.core.messaging.startFlow
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.serialization.OpaqueBytes
|
import net.corda.core.serialization.OpaqueBytes
|
||||||
import net.corda.core.then
|
import net.corda.core.then
|
||||||
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.explorer.formatters.PartyNameFormatter
|
import net.corda.explorer.formatters.PartyNameFormatter
|
||||||
import net.corda.explorer.model.CashTransaction
|
import net.corda.explorer.model.CashTransaction
|
||||||
import net.corda.explorer.model.IssuerModel
|
import net.corda.explorer.model.IssuerModel
|
||||||
@ -35,6 +37,7 @@ import net.corda.explorer.model.ReportingCurrencyModel
|
|||||||
import net.corda.explorer.views.bigDecimalFormatter
|
import net.corda.explorer.views.bigDecimalFormatter
|
||||||
import net.corda.explorer.views.byteFormatter
|
import net.corda.explorer.views.byteFormatter
|
||||||
import net.corda.explorer.views.stringConverter
|
import net.corda.explorer.views.stringConverter
|
||||||
|
import net.corda.flows.AbstractCashFlow
|
||||||
import net.corda.flows.CashFlowCommand
|
import net.corda.flows.CashFlowCommand
|
||||||
import net.corda.flows.IssuerFlow.IssuanceRequester
|
import net.corda.flows.IssuerFlow.IssuanceRequester
|
||||||
import org.controlsfx.dialog.ExceptionDialog
|
import org.controlsfx.dialog.ExceptionDialog
|
||||||
@ -92,18 +95,20 @@ class NewTransaction : Fragment() {
|
|||||||
initOwner(window)
|
initOwner(window)
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
val handle = if (command is CashFlowCommand.IssueCash) {
|
val handle: FlowHandle<AbstractCashFlow.Result> = if (command is CashFlowCommand.IssueCash) {
|
||||||
rpcProxy.value!!.startFlow(::IssuanceRequester,
|
rpcProxy.value!!.startFlow(::IssuanceRequester,
|
||||||
command.amount,
|
command.amount,
|
||||||
command.recipient,
|
command.recipient,
|
||||||
command.issueRef,
|
command.issueRef,
|
||||||
myIdentity.value!!.legalIdentity)
|
myIdentity.value!!.legalIdentity,
|
||||||
|
command.anonymous)
|
||||||
} else {
|
} else {
|
||||||
command.startFlow(rpcProxy.value!!)
|
command.startFlow(rpcProxy.value!!)
|
||||||
}
|
}
|
||||||
runAsync {
|
runAsync {
|
||||||
handle.returnValue.then { dialog.dialogPane.isDisable = false }.getOrThrow()
|
handle.returnValue.then { dialog.dialogPane.isDisable = false }.getOrThrow()
|
||||||
}.ui {
|
}.ui { it ->
|
||||||
|
val stx: SignedTransaction = it.stx
|
||||||
val type = when (command) {
|
val type = when (command) {
|
||||||
is CashFlowCommand.IssueCash -> "Cash Issued"
|
is CashFlowCommand.IssueCash -> "Cash Issued"
|
||||||
is CashFlowCommand.ExitCash -> "Cash Exited"
|
is CashFlowCommand.ExitCash -> "Cash Exited"
|
||||||
@ -117,7 +122,7 @@ class NewTransaction : Fragment() {
|
|||||||
row { label(type) { font = Font.font(font.family, FontWeight.EXTRA_BOLD, font.size + 2) } }
|
row { label(type) { font = Font.font(font.family, FontWeight.EXTRA_BOLD, font.size + 2) } }
|
||||||
row {
|
row {
|
||||||
label("Transaction ID :") { GridPane.setValignment(this, VPos.TOP) }
|
label("Transaction ID :") { GridPane.setValignment(this, VPos.TOP) }
|
||||||
label { text = Splitter.fixedLength(16).split("${it.id}").joinToString("\n") }
|
label { text = Splitter.fixedLength(16).split("${stx.id}").joinToString("\n") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dialog.dialogPane.scene.window.sizeToScene()
|
dialog.dialogPane.scene.window.sizeToScene()
|
||||||
@ -141,14 +146,16 @@ class NewTransaction : Fragment() {
|
|||||||
dialogPane = root
|
dialogPane = root
|
||||||
initOwner(window)
|
initOwner(window)
|
||||||
setResultConverter {
|
setResultConverter {
|
||||||
|
// TODO: Enable confidential identities
|
||||||
|
val anonymous = false
|
||||||
val defaultRef = OpaqueBytes.of(1)
|
val defaultRef = OpaqueBytes.of(1)
|
||||||
val issueRef = if (issueRef.value != null) OpaqueBytes.of(issueRef.value) else defaultRef
|
val issueRef = if (issueRef.value != null) OpaqueBytes.of(issueRef.value) else defaultRef
|
||||||
when (it) {
|
when (it) {
|
||||||
executeButton -> when (transactionTypeCB.value) {
|
executeButton -> when (transactionTypeCB.value) {
|
||||||
CashTransaction.Issue -> {
|
CashTransaction.Issue -> {
|
||||||
CashFlowCommand.IssueCash(Amount.fromDecimal(amount.value, currencyChoiceBox.value), issueRef, partyBChoiceBox.value.legalIdentity, notaries.first().notaryIdentity)
|
CashFlowCommand.IssueCash(Amount.fromDecimal(amount.value, currencyChoiceBox.value), issueRef, partyBChoiceBox.value.legalIdentity, notaries.first().notaryIdentity, anonymous)
|
||||||
}
|
}
|
||||||
CashTransaction.Pay -> CashFlowCommand.PayCash(Amount.fromDecimal(amount.value, currencyChoiceBox.value), partyBChoiceBox.value.legalIdentity)
|
CashTransaction.Pay -> CashFlowCommand.PayCash(Amount.fromDecimal(amount.value, currencyChoiceBox.value), partyBChoiceBox.value.legalIdentity, anonymous = anonymous)
|
||||||
CashTransaction.Exit -> CashFlowCommand.ExitCash(Amount.fromDecimal(amount.value, currencyChoiceBox.value), issueRef)
|
CashTransaction.Exit -> CashFlowCommand.ExitCash(Amount.fromDecimal(amount.value, currencyChoiceBox.value), issueRef)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
@ -117,13 +117,14 @@ val crossCashTest = LoadTest<CrossCashCommand, CrossCashState>(
|
|||||||
|
|
||||||
generate = { (nodeVaults), parallelism ->
|
generate = { (nodeVaults), parallelism ->
|
||||||
val nodeMap = simpleNodes.associateBy { it.info.legalIdentity }
|
val nodeMap = simpleNodes.associateBy { it.info.legalIdentity }
|
||||||
|
val anonymous = true
|
||||||
Generator.pickN(parallelism, simpleNodes).bind { nodes ->
|
Generator.pickN(parallelism, simpleNodes).bind { nodes ->
|
||||||
Generator.sequence(
|
Generator.sequence(
|
||||||
nodes.map { node ->
|
nodes.map { node ->
|
||||||
val quantities = nodeVaults[node.info.legalIdentity] ?: mapOf()
|
val quantities = nodeVaults[node.info.legalIdentity] ?: mapOf()
|
||||||
val possibleRecipients = nodeMap.keys.toList()
|
val possibleRecipients = nodeMap.keys.toList()
|
||||||
val moves = quantities.map {
|
val moves = quantities.map {
|
||||||
it.value.toDouble() / 1000 to generateMove(it.value, USD, node.info.legalIdentity, possibleRecipients)
|
it.value.toDouble() / 1000 to generateMove(it.value, USD, node.info.legalIdentity, possibleRecipients, anonymous)
|
||||||
}
|
}
|
||||||
val exits = quantities.mapNotNull {
|
val exits = quantities.mapNotNull {
|
||||||
if (it.key == node.info.legalIdentity) {
|
if (it.key == node.info.legalIdentity) {
|
||||||
@ -133,7 +134,7 @@ val crossCashTest = LoadTest<CrossCashCommand, CrossCashState>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val command = Generator.frequency(
|
val command = Generator.frequency(
|
||||||
listOf(1.0 to generateIssue(10000, USD, notary.info.notaryIdentity, possibleRecipients)) + moves + exits
|
listOf(1.0 to generateIssue(10000, USD, notary.info.notaryIdentity, possibleRecipients, anonymous)) + moves + exits
|
||||||
)
|
)
|
||||||
command.map { CrossCashCommand(it, nodeMap[node.info.legalIdentity]!!) }
|
command.map { CrossCashCommand(it, nodeMap[node.info.legalIdentity]!!) }
|
||||||
}
|
}
|
||||||
|
@ -15,13 +15,14 @@ fun generateIssue(
|
|||||||
max: Long,
|
max: Long,
|
||||||
currency: Currency,
|
currency: Currency,
|
||||||
notary: Party,
|
notary: Party,
|
||||||
possibleRecipients: List<Party>
|
possibleRecipients: List<Party>,
|
||||||
|
anonymous: Boolean
|
||||||
): Generator<CashFlowCommand.IssueCash> {
|
): Generator<CashFlowCommand.IssueCash> {
|
||||||
return generateAmount(1, max, Generator.pure(currency)).combine(
|
return generateAmount(1, max, Generator.pure(currency)).combine(
|
||||||
Generator.pure(OpaqueBytes.of(0)),
|
Generator.pure(OpaqueBytes.of(0)),
|
||||||
Generator.pickOne(possibleRecipients)
|
Generator.pickOne(possibleRecipients)
|
||||||
) { amount, ref, recipient ->
|
) { amount, ref, recipient ->
|
||||||
CashFlowCommand.IssueCash(amount, ref, recipient, notary)
|
CashFlowCommand.IssueCash(amount, ref, recipient, notary, anonymous)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,12 +30,13 @@ fun generateMove(
|
|||||||
max: Long,
|
max: Long,
|
||||||
currency: Currency,
|
currency: Currency,
|
||||||
issuer: Party,
|
issuer: Party,
|
||||||
possibleRecipients: List<Party>
|
possibleRecipients: List<Party>,
|
||||||
|
anonymous: Boolean
|
||||||
): Generator<CashFlowCommand.PayCash> {
|
): Generator<CashFlowCommand.PayCash> {
|
||||||
return generateAmount(1, max, Generator.pure(Issued(PartyAndReference(issuer, OpaqueBytes.of(0)), currency))).combine(
|
return generateAmount(1, max, Generator.pure(Issued(PartyAndReference(issuer, OpaqueBytes.of(0)), currency))).combine(
|
||||||
Generator.pickOne(possibleRecipients)
|
Generator.pickOne(possibleRecipients)
|
||||||
) { amount, recipient ->
|
) { amount, recipient ->
|
||||||
CashFlowCommand.PayCash(amount.withoutIssuer(), recipient, issuer)
|
CashFlowCommand.PayCash(amount.withoutIssuer(), recipient, issuer, anonymous)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ val selfIssueTest = LoadTest<SelfIssueCommand, SelfIssueState>(
|
|||||||
|
|
||||||
generate = { _, parallelism ->
|
generate = { _, parallelism ->
|
||||||
val generateIssue = Generator.pickOne(simpleNodes).bind { node ->
|
val generateIssue = Generator.pickOne(simpleNodes).bind { node ->
|
||||||
generateIssue(1000, USD, notary.info.notaryIdentity, listOf(node.info.legalIdentity)).map {
|
generateIssue(1000, USD, notary.info.notaryIdentity, listOf(node.info.legalIdentity), anonymous = true).map {
|
||||||
SelfIssueCommand(it, node)
|
SelfIssueCommand(it, node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ object StabilityTest {
|
|||||||
val nodeMap = simpleNodes.associateBy { it.info.legalIdentity }
|
val nodeMap = simpleNodes.associateBy { it.info.legalIdentity }
|
||||||
Generator.sequence(simpleNodes.map { node ->
|
Generator.sequence(simpleNodes.map { node ->
|
||||||
val possibleRecipients = nodeMap.keys.toList()
|
val possibleRecipients = nodeMap.keys.toList()
|
||||||
val moves = 0.5 to generateMove(1, USD, node.info.legalIdentity, possibleRecipients)
|
val moves = 0.5 to generateMove(1, USD, node.info.legalIdentity, possibleRecipients, anonymous = true)
|
||||||
val exits = 0.5 to generateExit(1, USD)
|
val exits = 0.5 to generateExit(1, USD)
|
||||||
val command = Generator.frequency(listOf(moves, exits))
|
val command = Generator.frequency(listOf(moves, exits))
|
||||||
command.map { CrossCashCommand(it, nodeMap[node.info.legalIdentity]!!) }
|
command.map { CrossCashCommand(it, nodeMap[node.info.legalIdentity]!!) }
|
||||||
@ -42,7 +42,7 @@ object StabilityTest {
|
|||||||
"Self issuing cash randomly",
|
"Self issuing cash randomly",
|
||||||
generate = { _, parallelism ->
|
generate = { _, parallelism ->
|
||||||
val generateIssue = Generator.pickOne(simpleNodes).bind { node ->
|
val generateIssue = Generator.pickOne(simpleNodes).bind { node ->
|
||||||
generateIssue(1000, USD, notary.info.notaryIdentity, listOf(node.info.legalIdentity)).map {
|
generateIssue(1000, USD, notary.info.notaryIdentity, listOf(node.info.legalIdentity), anonymous = true).map {
|
||||||
SelfIssueCommand(it, node)
|
SelfIssueCommand(it, node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user