First working notary functional tests for R3Net. (#200)

This commit is contained in:
Rick Parker 2017-12-15 17:11:51 +00:00 committed by GitHub
parent 037673abe4
commit af596cfdde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 243 additions and 6 deletions

View File

@ -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.")
}
}

View File

@ -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)
}
}

View File

@ -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.

View File

@ -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)
}