mirror of
https://github.com/corda/corda.git
synced 2024-12-27 00:21: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.OnLedgerAsset
|
||||||
import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PartyAndAmount
|
import com.r3.corda.enterprise.perftestcordapp.contracts.asset.PartyAndAmount
|
||||||
import net.corda.confidential.SwapIdentitiesFlow
|
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.flows.StartableByRPC
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.AnonymousParty
|
import net.corda.core.identity.AnonymousParty
|
||||||
@ -16,11 +18,7 @@ import net.corda.core.utilities.ProgressTracker
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiates a flow that self-issues cash (which should then be sent to recipient(s) using a payment transaction).
|
* Initiates a flow that self-issues cash and then is immediately sent to another party, without coin selection.
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
*
|
||||||
* @param amount the amount of currency to issue.
|
* @param amount the amount of currency to issue.
|
||||||
* @param issueRef a reference to put on the issued currency.
|
* @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