mirror of
https://github.com/corda/corda.git
synced 2024-12-26 16:11:12 +00:00
First working notary functional tests for R3Net. (#200)
This commit is contained in:
parent
037673abe4
commit
af596cfdde
@ -0,0 +1,93 @@
|
||||
package com.r3.corda.enterprise.perftestcordapp.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash
|
||||
import com.r3.corda.enterprise.perftestcordapp.contracts.asset.OnLedgerAsset
|
||||
import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PartyAndAmount
|
||||
import net.corda.confidential.SwapIdentitiesFlow
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.Issued
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.flows.FlowException
|
||||
import net.corda.core.flows.NotaryError
|
||||
import net.corda.core.flows.NotaryException
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Initiates a flow that self-issues cash. We then try and send it to another party twice. The flow only succeeds if
|
||||
* the second payment is rejected by the notary as a double spend.
|
||||
*
|
||||
* @param amount the amount of currency to issue.
|
||||
* @param issueRef a reference to put on the issued currency.
|
||||
* @param recipient payee Party
|
||||
* @param anonymous whether to anonymise before the transaction
|
||||
* @param notary the notary to set on the output states.
|
||||
*/
|
||||
@StartableByRPC
|
||||
class CashIssueAndDoublePayment(val amount: Amount<Currency>,
|
||||
val issueRef: OpaqueBytes,
|
||||
val recipient: Party,
|
||||
val anonymous: Boolean,
|
||||
val notary: Party,
|
||||
progressTracker: ProgressTracker) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) {
|
||||
constructor(request: CashIssueAndPaymentFlow.IssueAndPaymentRequest) : this(request.amount, request.issueRef, request.recipient, request.anonymous, request.notary, tracker())
|
||||
constructor(amount: Amount<Currency>, issueRef: OpaqueBytes, payTo: Party, anonymous: Boolean, notary: Party) : this(amount, issueRef, payTo, anonymous, notary, tracker())
|
||||
|
||||
@Suspendable
|
||||
override fun call(): AbstractCashFlow.Result {
|
||||
fun deriveState(txState: TransactionState<Cash.State>, amt: Amount<Issued<Currency>>, owner: AbstractParty)
|
||||
= txState.copy(data = txState.data.copy(amount = amt, owner = owner))
|
||||
|
||||
val issueResult = subFlow(CashIssueFlow(amount, issueRef, notary))
|
||||
val cashStateAndRef = issueResult.stx.tx.outRef<Cash.State>(0)
|
||||
|
||||
progressTracker.currentStep = GENERATING_ID
|
||||
val txIdentities = if (anonymous) {
|
||||
subFlow(SwapIdentitiesFlow(recipient))
|
||||
} else {
|
||||
emptyMap<Party, AnonymousParty>()
|
||||
}
|
||||
val anonymousRecipient = txIdentities[recipient] ?: recipient
|
||||
|
||||
val changeIdentity = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, false)
|
||||
|
||||
progressTracker.currentStep = GENERATING_TX
|
||||
val builder1 = TransactionBuilder(notary)
|
||||
val (spendTx1, keysForSigning1) = OnLedgerAsset.generateSpend(builder1, listOf(PartyAndAmount(anonymousRecipient, amount)), listOf(cashStateAndRef),
|
||||
changeIdentity.party.anonymise(),
|
||||
{ state, quantity, owner -> deriveState(state, quantity, owner) },
|
||||
{ Cash().generateMoveCommand() })
|
||||
|
||||
val builder2 = TransactionBuilder(notary)
|
||||
val (spendTx2, keysForSigning2) = OnLedgerAsset.generateSpend(builder2, listOf(PartyAndAmount(anonymousRecipient, amount)), listOf(cashStateAndRef),
|
||||
changeIdentity.party.anonymise(),
|
||||
{ state, quantity, owner -> deriveState(state, quantity, owner) },
|
||||
{ Cash().generateMoveCommand() })
|
||||
|
||||
progressTracker.currentStep = SIGNING_TX
|
||||
val tx1 = serviceHub.signInitialTransaction(spendTx1, keysForSigning1)
|
||||
val tx2 = serviceHub.signInitialTransaction(spendTx2, keysForSigning2)
|
||||
|
||||
progressTracker.currentStep = FINALISING_TX
|
||||
val notarised1 = finaliseTx(tx1, setOf(recipient), "Unable to notarise spend first time")
|
||||
try {
|
||||
val notarised2 = finaliseTx(tx2, setOf(recipient), "Unable to notarise spend second time")
|
||||
} catch (expected: CashException) {
|
||||
val cause = expected.cause
|
||||
if (cause is NotaryException) {
|
||||
if (cause.error is NotaryError.Conflict) {
|
||||
return Result(notarised1, recipient)
|
||||
}
|
||||
throw expected // Wasn't actually expected!
|
||||
}
|
||||
}
|
||||
throw FlowException("Managed to do double spend. Should have thrown NotaryError.Conflict.")
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package com.r3.corda.enterprise.perftestcordapp.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash
|
||||
import com.r3.corda.enterprise.perftestcordapp.contracts.asset.OnLedgerAsset
|
||||
import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PartyAndAmount
|
||||
import net.corda.confidential.SwapIdentitiesFlow
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.Issued
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Initiates a flow that self-issues cash and then immediately spends it without coin selection. It also then attempts
|
||||
* to notarise exactly the same transaction again, which should succeed since it is exactly the same notarisation request.
|
||||
*
|
||||
* @param amount the amount of currency to issue.
|
||||
* @param issueRef a reference to put on the issued currency.
|
||||
* @param recipient payee Party
|
||||
* @param anonymous whether to anonymise before the transaction
|
||||
* @param notary the notary to set on the output states.
|
||||
*/
|
||||
@StartableByRPC
|
||||
class CashIssueAndDuplicatePayment(val amount: Amount<Currency>,
|
||||
val issueRef: OpaqueBytes,
|
||||
val recipient: Party,
|
||||
val anonymous: Boolean,
|
||||
val notary: Party,
|
||||
progressTracker: ProgressTracker) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) {
|
||||
constructor(request: CashIssueAndPaymentFlow.IssueAndPaymentRequest) : this(request.amount, request.issueRef, request.recipient, request.anonymous, request.notary, tracker())
|
||||
constructor(amount: Amount<Currency>, issueRef: OpaqueBytes, payTo: Party, anonymous: Boolean, notary: Party) : this(amount, issueRef, payTo, anonymous, notary, tracker())
|
||||
|
||||
@Suspendable
|
||||
override fun call(): Result {
|
||||
fun deriveState(txState: TransactionState<Cash.State>, amt: Amount<Issued<Currency>>, owner: AbstractParty)
|
||||
= txState.copy(data = txState.data.copy(amount = amt, owner = owner))
|
||||
|
||||
val issueResult = subFlow(CashIssueFlow(amount, issueRef, notary))
|
||||
val cashStateAndRef = issueResult.stx.tx.outRef<Cash.State>(0)
|
||||
|
||||
progressTracker.currentStep = GENERATING_ID
|
||||
val txIdentities = if (anonymous) {
|
||||
subFlow(SwapIdentitiesFlow(recipient))
|
||||
} else {
|
||||
emptyMap<Party, AnonymousParty>()
|
||||
}
|
||||
val anonymousRecipient = txIdentities[recipient] ?: recipient
|
||||
|
||||
val changeIdentity = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, false)
|
||||
|
||||
progressTracker.currentStep = GENERATING_TX
|
||||
val builder = TransactionBuilder(notary)
|
||||
val (spendTx, keysForSigning) = OnLedgerAsset.generateSpend(builder, listOf(PartyAndAmount(anonymousRecipient, amount)), listOf(cashStateAndRef),
|
||||
changeIdentity.party.anonymise(),
|
||||
{ state, quantity, owner -> deriveState(state, quantity, owner) },
|
||||
{ Cash().generateMoveCommand() })
|
||||
|
||||
progressTracker.currentStep = SIGNING_TX
|
||||
val tx = serviceHub.signInitialTransaction(spendTx, keysForSigning)
|
||||
|
||||
progressTracker.currentStep = FINALISING_TX
|
||||
val notarised1 = finaliseTx(tx, setOf(recipient), "Unable to notarise spend first time")
|
||||
val notarised2 = finaliseTx(tx, setOf(recipient), "Unable to notarise spend second time")
|
||||
|
||||
return Result(notarised2, recipient)
|
||||
}
|
||||
}
|
@ -5,7 +5,9 @@ import com.r3.corda.enterprise.perftestcordapp.contracts.asset.Cash
|
||||
import com.r3.corda.enterprise.perftestcordapp.contracts.asset.OnLedgerAsset
|
||||
import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PartyAndAmount
|
||||
import net.corda.confidential.SwapIdentitiesFlow
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.Issued
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
@ -16,11 +18,7 @@ import net.corda.core.utilities.ProgressTracker
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Initiates a flow that self-issues cash (which should then be sent to recipient(s) using a payment transaction).
|
||||
*
|
||||
* We issue cash only to ourselves so that all KYC/AML checks on payments are enforced consistently, rather than risk
|
||||
* checks for issuance and payments differing. Outside of test scenarios it would be extremely unusual to issue cash
|
||||
* and immediately transfer it, so impact of this limitation is considered minimal.
|
||||
* Initiates a flow that self-issues cash and then is immediately sent to another party, without coin selection.
|
||||
*
|
||||
* @param amount the amount of currency to issue.
|
||||
* @param issueRef a reference to put on the issued currency.
|
||||
|
@ -0,0 +1,72 @@
|
||||
package com.r3.corda.jmeter
|
||||
|
||||
import com.r3.corda.enterprise.perftestcordapp.POUNDS
|
||||
import com.r3.corda.enterprise.perftestcordapp.flows.CashIssueAndDoublePayment
|
||||
import com.r3.corda.enterprise.perftestcordapp.flows.CashIssueAndDuplicatePayment
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import org.apache.jmeter.config.Argument
|
||||
import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext
|
||||
|
||||
|
||||
/**
|
||||
* A sampler to just issue cash and pay another party, but the flow attempts to spend the cash twice (double spend) and
|
||||
* only succeeds if that is correctly rejected for the second spend.
|
||||
*
|
||||
* Use with 1 iteration and 1 thread in JMeter as a functional test.
|
||||
*/
|
||||
class NotariseDoubleSpendSampler : AbstractSampler() {
|
||||
companion object JMeterProperties {
|
||||
val otherParty = Argument("otherPartyName", "", "<meta>", "The X500 name of the payee.")
|
||||
}
|
||||
|
||||
lateinit var counterParty: Party
|
||||
|
||||
override fun setupTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) {
|
||||
getNotaryIdentity(rpcProxy, testContext)
|
||||
counterParty = getIdentity(rpcProxy, testContext, otherParty)
|
||||
}
|
||||
|
||||
override fun createFlowInvoke(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext): FlowInvoke<*> {
|
||||
val amount = 2_000_000.POUNDS
|
||||
return FlowInvoke<CashIssueAndDoublePayment>(CashIssueAndDoublePayment::class.java, arrayOf(amount, OpaqueBytes.of(1), counterParty, false, notaryIdentity))
|
||||
}
|
||||
|
||||
override fun teardownTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) {
|
||||
}
|
||||
|
||||
override val additionalArgs: Set<Argument>
|
||||
get() = setOf(notary, otherParty)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A sampler to just issue cash and pay another party. The flow actually submits the same transaction twice to the notary
|
||||
* which should succeed.
|
||||
*
|
||||
* Use with 1 iteration and 1 thread in JMeter as a functional test.
|
||||
*/
|
||||
class NotariseDuplicateTransactionSampler : AbstractSampler() {
|
||||
companion object JMeterProperties {
|
||||
val otherParty = Argument("otherPartyName", "", "<meta>", "The X500 name of the payee.")
|
||||
}
|
||||
|
||||
lateinit var counterParty: Party
|
||||
|
||||
override fun setupTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) {
|
||||
getNotaryIdentity(rpcProxy, testContext)
|
||||
counterParty = getIdentity(rpcProxy, testContext, otherParty)
|
||||
}
|
||||
|
||||
override fun createFlowInvoke(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext): FlowInvoke<*> {
|
||||
val amount = 2_000_000.POUNDS
|
||||
return FlowInvoke<CashIssueAndDuplicatePayment>(CashIssueAndDuplicatePayment::class.java, arrayOf(amount, OpaqueBytes.of(1), counterParty, false, notaryIdentity))
|
||||
}
|
||||
|
||||
override fun teardownTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) {
|
||||
}
|
||||
|
||||
override val additionalArgs: Set<Argument>
|
||||
get() = setOf(notary, otherParty)
|
||||
}
|
Loading…
Reference in New Issue
Block a user