mirror of
https://github.com/corda/corda.git
synced 2025-06-23 01:19:00 +00:00
Merge branch 'master' into shams-master-merge-291117
# Conflicts: # node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt # node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt # node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt # samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt # testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt # testing/node-driver/src/main/kotlin/net/corda/testing/internal/demorun/DemoRunner.kt # testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt
This commit is contained in:
@ -3,7 +3,6 @@
|
||||
|
||||
package net.corda.testing
|
||||
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
@ -23,11 +22,12 @@ import net.corda.nodeapi.internal.crypto.CertificateType
|
||||
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.serialization.amqp.AMQP_ENABLED
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.internal.stubbing.answers.ThrowsException
|
||||
import org.mockito.stubbing.Answer
|
||||
import java.nio.file.Files
|
||||
import java.security.KeyPair
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
/**
|
||||
@ -178,12 +178,13 @@ fun NodeInfo.singleIdentity(): Party = singleIdentityAndCert().party
|
||||
*/
|
||||
class UndefinedMockBehaviorException(message: String) : RuntimeException(message)
|
||||
|
||||
inline fun <reified T : Any> rigorousMock() = rigorousMock(T::class.java)
|
||||
/**
|
||||
* Create a Mockito mock that has [UndefinedMockBehaviorException] as the default behaviour of all methods.
|
||||
* @param T the type to mock. Note if you want to use [com.nhaarman.mockito_kotlin.doCallRealMethod] on a Kotlin interface,
|
||||
* it won't work unless you mock a (trivial) abstract implementation of that interface instead.
|
||||
*/
|
||||
inline fun <reified T : Any> rigorousMock() = mock<T>(Answer {
|
||||
fun <T> rigorousMock(clazz: Class<T>): T = mock(clazz) {
|
||||
// Use ThrowsException to hack the stack trace, and lazily so we can customise the message:
|
||||
ThrowsException(UndefinedMockBehaviorException("Please specify what should happen when '${it.method}' is called, or don't call it.")).answer(it)
|
||||
})
|
||||
ThrowsException(UndefinedMockBehaviorException("Please specify what should happen when '${it.method}' is called, or don't call it. Args: ${Arrays.toString(it.arguments)}")).answer(it)
|
||||
}
|
||||
|
@ -1,16 +0,0 @@
|
||||
package net.corda.testing
|
||||
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.isDirectory
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
object ProjectStructure {
|
||||
val projectRootDir: Path = run {
|
||||
var dir = Paths.get(javaClass.getResource("/").toURI())
|
||||
while (!(dir / ".git").isDirectory()) {
|
||||
dir = dir.parent
|
||||
}
|
||||
dir
|
||||
}
|
||||
}
|
@ -1,24 +1,51 @@
|
||||
package net.corda.testing
|
||||
|
||||
import com.nhaarman.mockito_kotlin.doNothing
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import com.nhaarman.mockito_kotlin.*
|
||||
import net.corda.client.rpc.internal.KryoClientSerializationScheme
|
||||
import net.corda.core.internal.staticField
|
||||
import net.corda.core.serialization.internal.*
|
||||
import net.corda.node.serialization.KryoServerSerializationScheme
|
||||
import net.corda.nodeapi.internal.serialization.*
|
||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme
|
||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
|
||||
import net.corda.testing.common.internal.asContextEnv
|
||||
import net.corda.testing.internal.testThreadFactory
|
||||
import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnector
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.Description
|
||||
import org.junit.runners.model.Statement
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
private val inVMExecutors = ConcurrentHashMap<SerializationEnvironment, ExecutorService>()
|
||||
|
||||
/** @param inheritable whether new threads inherit the environment, use sparingly. */
|
||||
class SerializationEnvironmentRule(private val inheritable: Boolean = false) : TestRule {
|
||||
val env: SerializationEnvironment = createTestSerializationEnv()
|
||||
override fun apply(base: Statement, description: Description?) = object : Statement() {
|
||||
override fun evaluate() = env.asContextEnv(inheritable) {
|
||||
base.evaluate()
|
||||
companion object {
|
||||
init {
|
||||
// Can't turn it off, and it creates threads that do serialization, so hack it:
|
||||
InVMConnector::class.staticField<ExecutorService>("threadPoolExecutor").value = rigorousMock<ExecutorService>().also {
|
||||
doAnswer {
|
||||
inVMExecutors.computeIfAbsent(effectiveSerializationEnv) {
|
||||
Executors.newCachedThreadPool(testThreadFactory(true)) // Close enough to what InVMConnector makes normally.
|
||||
}.execute(it.arguments[0] as Runnable)
|
||||
}.whenever(it).execute(any())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lateinit var env: SerializationEnvironment
|
||||
override fun apply(base: Statement, description: Description): Statement {
|
||||
env = createTestSerializationEnv(description.toString())
|
||||
return object : Statement() {
|
||||
override fun evaluate() {
|
||||
try {
|
||||
env.asContextEnv(inheritable) { base.evaluate() }
|
||||
} finally {
|
||||
inVMExecutors.remove(env)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -30,7 +57,7 @@ interface GlobalSerializationEnvironment : SerializationEnvironment {
|
||||
|
||||
/** @param inheritable whether new threads inherit the environment, use sparingly. */
|
||||
fun <T> withTestSerialization(inheritable: Boolean = false, callable: (SerializationEnvironment) -> T): T {
|
||||
return createTestSerializationEnv().asContextEnv(inheritable, callable)
|
||||
return createTestSerializationEnv("<context>").asContextEnv(inheritable, callable)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -53,9 +80,10 @@ fun <T> withoutTestSerialization(callable: () -> T): T {
|
||||
*/
|
||||
fun setGlobalSerialization(armed: Boolean): GlobalSerializationEnvironment {
|
||||
return if (armed) {
|
||||
object : GlobalSerializationEnvironment, SerializationEnvironment by createTestSerializationEnv() {
|
||||
object : GlobalSerializationEnvironment, SerializationEnvironment by createTestSerializationEnv("<global>") {
|
||||
override fun unset() {
|
||||
_globalSerializationEnv.set(null)
|
||||
inVMExecutors.remove(this)
|
||||
}
|
||||
}.also {
|
||||
_globalSerializationEnv.set(it)
|
||||
@ -67,7 +95,7 @@ fun setGlobalSerialization(armed: Boolean): GlobalSerializationEnvironment {
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTestSerializationEnv() = SerializationEnvironmentImpl(
|
||||
private fun createTestSerializationEnv(label: String) = object : SerializationEnvironmentImpl(
|
||||
SerializationFactoryImpl().apply {
|
||||
registerScheme(KryoClientSerializationScheme())
|
||||
registerScheme(KryoServerSerializationScheme())
|
||||
@ -78,7 +106,9 @@ private fun createTestSerializationEnv() = SerializationEnvironmentImpl(
|
||||
KRYO_RPC_SERVER_CONTEXT,
|
||||
KRYO_RPC_CLIENT_CONTEXT,
|
||||
if (isAmqpEnabled()) AMQP_STORAGE_CONTEXT else KRYO_STORAGE_CONTEXT,
|
||||
KRYO_CHECKPOINT_CONTEXT)
|
||||
KRYO_CHECKPOINT_CONTEXT) {
|
||||
override fun toString() = "testSerializationEnv($label)"
|
||||
}
|
||||
|
||||
private const val AMQP_ENABLE_PROP_NAME = "net.corda.testing.amqp.enable"
|
||||
|
||||
|
@ -8,276 +8,245 @@ import net.corda.core.crypto.SignatureMetadata
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.toFuture
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.finance.contracts.Commodity
|
||||
import net.corda.finance.contracts.DealState
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.finance.contracts.asset.CommodityContract
|
||||
import net.corda.finance.contracts.asset.DUMMY_CASH_ISSUER
|
||||
import net.corda.finance.contracts.asset.DUMMY_OBLIGATION_ISSUER
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.chooseIdentity
|
||||
import net.corda.testing.chooseIdentityAndCert
|
||||
import net.corda.testing.dummyCommand
|
||||
import net.corda.testing.singleIdentity
|
||||
import java.security.KeyPair
|
||||
import java.security.PublicKey
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.Instant.now
|
||||
import java.util.*
|
||||
|
||||
@JvmOverloads
|
||||
fun ServiceHub.fillWithSomeTestDeals(dealIds: List<String>,
|
||||
issuerServices: ServiceHub = this,
|
||||
/**
|
||||
* The service hub should provide at least a key management service and a storage service.
|
||||
* @param defaultNotary used in [fillWithSomeTestDeals] and [fillWithSomeTestLinearStates].
|
||||
* @param altNotary used in [fillWithSomeTestCash], [fillWithSomeTestCommodity] and consume/evolve methods. If not specified, same as [defaultNotary].
|
||||
* @param rngFactory used by [fillWithSomeTestCash] if no custom [Random] provided.
|
||||
*/
|
||||
class VaultFiller @JvmOverloads constructor(
|
||||
private val services: ServiceHub,
|
||||
private val defaultNotary: Party,
|
||||
private val defaultNotaryKeyPair: KeyPair,
|
||||
private val altNotary: Party = defaultNotary,
|
||||
private val rngFactory: () -> Random = { Random(0L) }) {
|
||||
companion object {
|
||||
fun calculateRandomlySizedAmounts(howMuch: Amount<Currency>, min: Int, max: Int, rng: Random): LongArray {
|
||||
val numSlots = min + Math.floor(rng.nextDouble() * (max - min)).toInt()
|
||||
val baseSize = howMuch.quantity / numSlots
|
||||
check(baseSize > 0) { baseSize }
|
||||
|
||||
val amounts = LongArray(numSlots) { baseSize }
|
||||
var distanceFromGoal = 0L
|
||||
// If we want 10 slots then max adjust is 0.1, so even if all random numbers come out to the largest downward
|
||||
// adjustment possible, the last slot ends at zero. With 20 slots, max adjust is 0.05 etc.
|
||||
val maxAdjust = 1.0 / numSlots
|
||||
for (i in amounts.indices) {
|
||||
if (i != amounts.lastIndex) {
|
||||
val adjustBy = rng.nextDouble() * maxAdjust - (maxAdjust / 2)
|
||||
val adjustment = (1 + adjustBy)
|
||||
val adjustTo = (amounts[i] * adjustment).toLong()
|
||||
amounts[i] = adjustTo
|
||||
distanceFromGoal += baseSize - adjustTo
|
||||
} else {
|
||||
amounts[i] += distanceFromGoal
|
||||
}
|
||||
}
|
||||
|
||||
// The desired amount may not have divided equally to start with, so adjust the first value to make up.
|
||||
amounts[0] += howMuch.quantity - amounts.sum()
|
||||
|
||||
return amounts
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
require(defaultNotary.owningKey == defaultNotaryKeyPair.public) { "Default notary public keys must match." }
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun fillWithSomeTestDeals(dealIds: List<String>,
|
||||
issuerServices: ServiceHub = services,
|
||||
participants: List<AbstractParty> = emptyList()): Vault<DealState> {
|
||||
val myKey: PublicKey = services.myInfo.chooseIdentity().owningKey
|
||||
val me = AnonymousParty(myKey)
|
||||
|
||||
val transactions: List<SignedTransaction> = dealIds.map {
|
||||
// Issue a deal state
|
||||
val dummyIssue = TransactionBuilder(notary = defaultNotary).apply {
|
||||
addOutputState(DummyDealContract.State(ref = it, participants = participants.plus(me)), DUMMY_DEAL_PROGRAM_ID)
|
||||
addCommand(dummyCommand())
|
||||
}
|
||||
val stx = issuerServices.signInitialTransaction(dummyIssue)
|
||||
return@map services.addSignature(stx, defaultNotaryKeyPair.public)
|
||||
}
|
||||
services.recordTransactions(transactions)
|
||||
// Get all the StateAndRefs of all the generated transactions.
|
||||
val states = transactions.flatMap { stx ->
|
||||
stx.tx.outputs.indices.map { i -> stx.tx.outRef<DealState>(i) }
|
||||
}
|
||||
|
||||
return Vault(states)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun fillWithSomeTestLinearStates(numberToCreate: Int,
|
||||
externalId: String? = null,
|
||||
participants: List<AbstractParty> = emptyList(),
|
||||
notary: Party = DUMMY_NOTARY): Vault<DealState> {
|
||||
val myKey: PublicKey = myInfo.chooseIdentity().owningKey
|
||||
val me = AnonymousParty(myKey)
|
||||
|
||||
val transactions: List<SignedTransaction> = dealIds.map {
|
||||
// Issue a deal state
|
||||
val dummyIssue = TransactionBuilder(notary = notary).apply {
|
||||
addOutputState(DummyDealContract.State(ref = it, participants = participants.plus(me)), DUMMY_DEAL_PROGRAM_ID)
|
||||
addCommand(dummyCommand())
|
||||
linearString: String = "",
|
||||
linearNumber: Long = 0L,
|
||||
linearBoolean: Boolean = false,
|
||||
linearTimestamp: Instant = now()): Vault<LinearState> {
|
||||
val myKey: PublicKey = services.myInfo.chooseIdentity().owningKey
|
||||
val me = AnonymousParty(myKey)
|
||||
val issuerKey = defaultNotaryKeyPair
|
||||
val signatureMetadata = SignatureMetadata(services.myInfo.platformVersion, Crypto.findSignatureScheme(issuerKey.public).schemeNumberID)
|
||||
val transactions: List<SignedTransaction> = (1..numberToCreate).map {
|
||||
// Issue a Linear state
|
||||
val dummyIssue = TransactionBuilder(notary = defaultNotary).apply {
|
||||
addOutputState(DummyLinearContract.State(
|
||||
linearId = UniqueIdentifier(externalId),
|
||||
participants = participants.plus(me),
|
||||
linearString = linearString,
|
||||
linearNumber = linearNumber,
|
||||
linearBoolean = linearBoolean,
|
||||
linearTimestamp = linearTimestamp), DUMMY_LINEAR_CONTRACT_PROGRAM_ID)
|
||||
addCommand(dummyCommand())
|
||||
}
|
||||
return@map services.signInitialTransaction(dummyIssue).withAdditionalSignature(issuerKey, signatureMetadata)
|
||||
}
|
||||
val stx = issuerServices.signInitialTransaction(dummyIssue)
|
||||
return@map addSignature(stx, notary.owningKey)
|
||||
}
|
||||
|
||||
recordTransactions(transactions)
|
||||
|
||||
// Get all the StateAndRefs of all the generated transactions.
|
||||
val states = transactions.flatMap { stx ->
|
||||
stx.tx.outputs.indices.map { i -> stx.tx.outRef<DealState>(i) }
|
||||
}
|
||||
|
||||
return Vault(states)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun ServiceHub.fillWithSomeTestLinearStates(numberToCreate: Int,
|
||||
externalId: String? = null,
|
||||
participants: List<AbstractParty> = emptyList(),
|
||||
linearString: String = "",
|
||||
linearNumber: Long = 0L,
|
||||
linearBoolean: Boolean = false,
|
||||
linearTimestamp: Instant = now()): Vault<LinearState> {
|
||||
val myKey: PublicKey = myInfo.chooseIdentity().owningKey
|
||||
val me = AnonymousParty(myKey)
|
||||
val issuerKey = DUMMY_NOTARY_KEY
|
||||
val signatureMetadata = SignatureMetadata(myInfo.platformVersion, Crypto.findSignatureScheme(issuerKey.public).schemeNumberID)
|
||||
|
||||
val transactions: List<SignedTransaction> = (1..numberToCreate).map {
|
||||
// Issue a Linear state
|
||||
val dummyIssue = TransactionBuilder(notary = DUMMY_NOTARY).apply {
|
||||
addOutputState(DummyLinearContract.State(
|
||||
linearId = UniqueIdentifier(externalId),
|
||||
participants = participants.plus(me),
|
||||
linearString = linearString,
|
||||
linearNumber = linearNumber,
|
||||
linearBoolean = linearBoolean,
|
||||
linearTimestamp = linearTimestamp), DUMMY_LINEAR_CONTRACT_PROGRAM_ID)
|
||||
addCommand(dummyCommand())
|
||||
services.recordTransactions(transactions)
|
||||
// Get all the StateAndRefs of all the generated transactions.
|
||||
val states = transactions.flatMap { stx ->
|
||||
stx.tx.outputs.indices.map { i -> stx.tx.outRef<LinearState>(i) }
|
||||
}
|
||||
|
||||
return@map signInitialTransaction(dummyIssue).withAdditionalSignature(issuerKey, signatureMetadata)
|
||||
return Vault(states)
|
||||
}
|
||||
|
||||
recordTransactions(transactions)
|
||||
@JvmOverloads
|
||||
fun fillWithSomeTestCash(howMuch: Amount<Currency>,
|
||||
issuerServices: ServiceHub,
|
||||
thisManyStates: Int,
|
||||
issuedBy: PartyAndReference,
|
||||
owner: AbstractParty? = null,
|
||||
rng: Random? = null) = fillWithSomeTestCash(howMuch, issuerServices, thisManyStates, thisManyStates, issuedBy, owner, rng)
|
||||
|
||||
// Get all the StateAndRefs of all the generated transactions.
|
||||
val states = transactions.flatMap { stx ->
|
||||
stx.tx.outputs.indices.map { i -> stx.tx.outRef<LinearState>(i) }
|
||||
/**
|
||||
* Creates a random set of between (by default) 3 and 10 cash states that add up to the given amount and adds them
|
||||
* to the vault. This is intended for unit tests. By default the cash is owned by the legal
|
||||
* identity key from the storage service.
|
||||
*
|
||||
* @param issuerServices service hub of the issuer node, which will be used to sign the transaction.
|
||||
* @return a vault object that represents the generated states (it will NOT be the full vault from the service hub!).
|
||||
*/
|
||||
fun fillWithSomeTestCash(howMuch: Amount<Currency>,
|
||||
issuerServices: ServiceHub,
|
||||
atLeastThisManyStates: Int,
|
||||
atMostThisManyStates: Int,
|
||||
issuedBy: PartyAndReference,
|
||||
owner: AbstractParty? = null,
|
||||
rng: Random? = null): Vault<Cash.State> {
|
||||
val amounts = calculateRandomlySizedAmounts(howMuch, atLeastThisManyStates, atMostThisManyStates, rng ?: rngFactory())
|
||||
// We will allocate one state to one transaction, for simplicities sake.
|
||||
val cash = Cash()
|
||||
val transactions: List<SignedTransaction> = amounts.map { pennies ->
|
||||
val issuance = TransactionBuilder(null as Party?)
|
||||
cash.generateIssue(issuance, Amount(pennies, Issued(issuedBy, howMuch.token)), owner ?: services.myInfo.singleIdentity(), altNotary)
|
||||
return@map issuerServices.signInitialTransaction(issuance, issuedBy.party.owningKey)
|
||||
}
|
||||
services.recordTransactions(transactions)
|
||||
// Get all the StateRefs of all the generated transactions.
|
||||
val states = transactions.flatMap { stx ->
|
||||
stx.tx.outputs.indices.map { i -> stx.tx.outRef<Cash.State>(i) }
|
||||
}
|
||||
|
||||
return Vault(states)
|
||||
}
|
||||
|
||||
return Vault(states)
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param issuerServices service hub of the issuer node, which will be used to sign the transaction.
|
||||
* @return a vault object that represents the generated states (it will NOT be the full vault from the service hub!).
|
||||
*/
|
||||
// TODO: need to make all FungibleAsset commands (issue, move, exit) generic
|
||||
fun fillWithSomeTestCommodity(amount: Amount<Commodity>, issuerServices: ServiceHub, issuedBy: PartyAndReference): Vault<CommodityContract.State> {
|
||||
val myKey: PublicKey = services.myInfo.chooseIdentity().owningKey
|
||||
val me = AnonymousParty(myKey)
|
||||
|
||||
/**
|
||||
* Creates a random set of cash states that add up to the given amount and adds them to the vault. This is intended for
|
||||
* unit tests. The cash is owned by the legal identity key from the storage service.
|
||||
*
|
||||
* The service hub needs to provide at least a key management service and a storage service.
|
||||
*
|
||||
* @param issuerServices service hub of the issuer node, which will be used to sign the transaction.
|
||||
* @param outputNotary the notary to use for output states. The transaction is NOT signed by this notary.
|
||||
* @return a vault object that represents the generated states (it will NOT be the full vault from the service hub!).
|
||||
*/
|
||||
fun ServiceHub.fillWithSomeTestCash(howMuch: Amount<Currency>,
|
||||
issuerServices: ServiceHub,
|
||||
outputNotary: Party,
|
||||
states: Int,
|
||||
issuedBy: PartyAndReference): Vault<Cash.State>
|
||||
= fillWithSomeTestCash(howMuch, issuerServices, outputNotary, states, states, issuedBy = issuedBy)
|
||||
|
||||
/**
|
||||
* Creates a random set of between (by default) 3 and 10 cash states that add up to the given amount and adds them
|
||||
* to the vault. This is intended for unit tests. By default the cash is issued by [DUMMY_CASH_ISSUER] and owned by the legal
|
||||
* identity key from the storage service.
|
||||
*
|
||||
* The service hub needs to provide at least a key management service and a storage service.
|
||||
*
|
||||
* @param issuerServices service hub of the issuer node, which will be used to sign the transaction.
|
||||
* @param outputNotary the notary to use for output states. The transaction is NOT signed by this notary.
|
||||
* @return a vault object that represents the generated states (it will NOT be the full vault from the service hub!).
|
||||
*/
|
||||
fun ServiceHub.fillWithSomeTestCash(howMuch: Amount<Currency>,
|
||||
issuerServices: ServiceHub = this,
|
||||
outputNotary: Party = DUMMY_NOTARY,
|
||||
atLeastThisManyStates: Int = 3,
|
||||
atMostThisManyStates: Int = 10,
|
||||
rng: Random = Random(),
|
||||
owner: AbstractParty? = null,
|
||||
issuedBy: PartyAndReference = DUMMY_CASH_ISSUER): Vault<Cash.State> {
|
||||
val amounts = calculateRandomlySizedAmounts(howMuch, atLeastThisManyStates, atMostThisManyStates, rng)
|
||||
|
||||
// We will allocate one state to one transaction, for simplicities sake.
|
||||
val cash = Cash()
|
||||
val transactions: List<SignedTransaction> = amounts.map { pennies ->
|
||||
val commodity = CommodityContract()
|
||||
val issuance = TransactionBuilder(null as Party?)
|
||||
cash.generateIssue(issuance, Amount(pennies, Issued(issuedBy, howMuch.token)),owner ?: myInfo.singleIdentity(), outputNotary)
|
||||
|
||||
return@map issuerServices.signInitialTransaction(issuance, issuedBy.party.owningKey)
|
||||
commodity.generateIssue(issuance, Amount(amount.quantity, Issued(issuedBy, amount.token)), me, altNotary)
|
||||
val transaction = issuerServices.signInitialTransaction(issuance, issuedBy.party.owningKey)
|
||||
services.recordTransactions(transaction)
|
||||
return Vault(setOf(transaction.tx.outRef(0)))
|
||||
}
|
||||
|
||||
recordTransactions(transactions)
|
||||
|
||||
// Get all the StateRefs of all the generated transactions.
|
||||
val states = transactions.flatMap { stx ->
|
||||
stx.tx.outputs.indices.map { i -> stx.tx.outRef<Cash.State>(i) }
|
||||
}
|
||||
|
||||
return Vault(states)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param issuerServices service hub of the issuer node, which will be used to sign the transaction.
|
||||
* @param outputNotary the notary to use for output states. The transaction is NOT signed by this notary.
|
||||
* @return a vault object that represents the generated states (it will NOT be the full vault from the service hub!).
|
||||
*/
|
||||
// TODO: need to make all FungibleAsset commands (issue, move, exit) generic
|
||||
fun ServiceHub.fillWithSomeTestCommodity(amount: Amount<Commodity>,
|
||||
issuerServices: ServiceHub = this,
|
||||
outputNotary: Party = DUMMY_NOTARY,
|
||||
ref: OpaqueBytes = OpaqueBytes(ByteArray(1, { 1 })),
|
||||
ownedBy: AbstractParty? = null,
|
||||
issuedBy: PartyAndReference = DUMMY_OBLIGATION_ISSUER.ref(1)): Vault<CommodityContract.State> {
|
||||
val myKey: PublicKey = ownedBy?.owningKey ?: myInfo.chooseIdentity().owningKey
|
||||
val me = AnonymousParty(myKey)
|
||||
|
||||
val commodity = CommodityContract()
|
||||
val issuance = TransactionBuilder(null as Party?)
|
||||
commodity.generateIssue(issuance, Amount(amount.quantity, Issued(issuedBy.copy(reference = ref), amount.token)), me, outputNotary)
|
||||
val transaction = issuerServices.signInitialTransaction(issuance, issuedBy.party.owningKey)
|
||||
|
||||
recordTransactions(transaction)
|
||||
|
||||
return Vault(setOf(transaction.tx.outRef<CommodityContract.State>(0)))
|
||||
}
|
||||
|
||||
fun calculateRandomlySizedAmounts(howMuch: Amount<Currency>, min: Int, max: Int, rng: Random): LongArray {
|
||||
val numSlots = min + Math.floor(rng.nextDouble() * (max - min)).toInt()
|
||||
val baseSize = howMuch.quantity / numSlots
|
||||
check(baseSize > 0) { baseSize }
|
||||
|
||||
val amounts = LongArray(numSlots) { baseSize }
|
||||
var distanceFromGoal = 0L
|
||||
// If we want 10 slots then max adjust is 0.1, so even if all random numbers come out to the largest downward
|
||||
// adjustment possible, the last slot ends at zero. With 20 slots, max adjust is 0.05 etc.
|
||||
val maxAdjust = 1.0 / numSlots
|
||||
for (i in amounts.indices) {
|
||||
if (i != amounts.lastIndex) {
|
||||
val adjustBy = rng.nextDouble() * maxAdjust - (maxAdjust / 2)
|
||||
val adjustment = (1 + adjustBy)
|
||||
val adjustTo = (amounts[i] * adjustment).toLong()
|
||||
amounts[i] = adjustTo
|
||||
distanceFromGoal += baseSize - adjustTo
|
||||
} else {
|
||||
amounts[i] += distanceFromGoal
|
||||
private fun <T : LinearState> consume(states: List<StateAndRef<T>>) {
|
||||
// Create a txn consuming different contract types
|
||||
states.forEach {
|
||||
val builder = TransactionBuilder(notary = altNotary).apply {
|
||||
addInputState(it)
|
||||
addCommand(dummyCommand(altNotary.owningKey))
|
||||
}
|
||||
val consumedTx = services.signInitialTransaction(builder, altNotary.owningKey)
|
||||
services.recordTransactions(consumedTx)
|
||||
}
|
||||
}
|
||||
|
||||
// The desired amount may not have divided equally to start with, so adjust the first value to make up.
|
||||
amounts[0] += howMuch.quantity - amounts.sum()
|
||||
|
||||
return amounts
|
||||
}
|
||||
|
||||
fun <T : LinearState> ServiceHub.consume(states: List<StateAndRef<T>>, notary: Party) {
|
||||
// Create a txn consuming different contract types
|
||||
states.forEach {
|
||||
val builder = TransactionBuilder(notary = notary).apply {
|
||||
addInputState(it)
|
||||
addCommand(dummyCommand(notary.owningKey))
|
||||
private fun <T : LinearState> consumeAndProduce(stateAndRef: StateAndRef<T>): StateAndRef<T> {
|
||||
// Create a txn consuming different contract types
|
||||
var builder = TransactionBuilder(notary = altNotary).apply {
|
||||
addInputState(stateAndRef)
|
||||
addCommand(dummyCommand(altNotary.owningKey))
|
||||
}
|
||||
val consumedTx = signInitialTransaction(builder, notary.owningKey)
|
||||
val consumedTx = services.signInitialTransaction(builder, altNotary.owningKey)
|
||||
services.recordTransactions(consumedTx)
|
||||
// Create a txn consuming different contract types
|
||||
builder = TransactionBuilder(notary = altNotary).apply {
|
||||
addOutputState(DummyLinearContract.State(linearId = stateAndRef.state.data.linearId,
|
||||
participants = stateAndRef.state.data.participants), DUMMY_LINEAR_CONTRACT_PROGRAM_ID)
|
||||
addCommand(dummyCommand(altNotary.owningKey))
|
||||
}
|
||||
val producedTx = services.signInitialTransaction(builder, altNotary.owningKey)
|
||||
services.recordTransactions(producedTx)
|
||||
return producedTx.tx.outRef(0)
|
||||
}
|
||||
|
||||
recordTransactions(consumedTx)
|
||||
private fun <T : LinearState> consumeAndProduce(states: List<StateAndRef<T>>) {
|
||||
states.forEach {
|
||||
consumeAndProduce(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun consumeDeals(dealStates: List<StateAndRef<DealState>>) = consume(dealStates)
|
||||
fun consumeLinearStates(linearStates: List<StateAndRef<LinearState>>) = consume(linearStates)
|
||||
fun evolveLinearStates(linearStates: List<StateAndRef<LinearState>>) = consumeAndProduce(linearStates)
|
||||
fun evolveLinearState(linearState: StateAndRef<LinearState>): StateAndRef<LinearState> = consumeAndProduce(linearState)
|
||||
/**
|
||||
* Consume cash, sending any change to the default identity for this node. Only suitable for use in test scenarios,
|
||||
* where nodes have a default identity.
|
||||
*/
|
||||
fun consumeCash(amount: Amount<Currency>, to: AbstractParty): Vault.Update<ContractState> {
|
||||
val ourIdentity = services.myInfo.chooseIdentityAndCert()
|
||||
val update = services.vaultService.rawUpdates.toFuture()
|
||||
// A tx that spends our money.
|
||||
val builder = TransactionBuilder(altNotary).apply {
|
||||
Cash.generateSpend(services, this, amount, ourIdentity, to)
|
||||
}
|
||||
val spendTx = services.signInitialTransaction(builder, altNotary.owningKey)
|
||||
services.recordTransactions(spendTx)
|
||||
return update.getOrThrow(Duration.ofSeconds(3))
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : LinearState> ServiceHub.consumeAndProduce(stateAndRef: StateAndRef<T>, notary: Party): StateAndRef<T> {
|
||||
// Create a txn consuming different contract types
|
||||
var builder = TransactionBuilder(notary = notary).apply {
|
||||
addInputState(stateAndRef)
|
||||
addCommand(dummyCommand(notary.owningKey))
|
||||
}
|
||||
val consumedTx = signInitialTransaction(builder, notary.owningKey)
|
||||
|
||||
recordTransactions(consumedTx)
|
||||
|
||||
// Create a txn consuming different contract types
|
||||
builder = TransactionBuilder(notary = notary).apply {
|
||||
addOutputState(DummyLinearContract.State(linearId = stateAndRef.state.data.linearId,
|
||||
participants = stateAndRef.state.data.participants), DUMMY_LINEAR_CONTRACT_PROGRAM_ID)
|
||||
addCommand(dummyCommand(notary.owningKey))
|
||||
}
|
||||
val producedTx = signInitialTransaction(builder, notary.owningKey)
|
||||
|
||||
recordTransactions(producedTx)
|
||||
|
||||
return producedTx.tx.outRef<T>(0)
|
||||
}
|
||||
|
||||
fun <T : LinearState> ServiceHub.consumeAndProduce(states: List<StateAndRef<T>>, notary: Party) {
|
||||
states.forEach {
|
||||
consumeAndProduce(it, notary)
|
||||
}
|
||||
}
|
||||
|
||||
fun ServiceHub.consumeDeals(dealStates: List<StateAndRef<DealState>>, notary: Party) = consume(dealStates, notary)
|
||||
fun ServiceHub.consumeLinearStates(linearStates: List<StateAndRef<LinearState>>, notary: Party) = consume(linearStates, notary)
|
||||
fun ServiceHub.evolveLinearStates(linearStates: List<StateAndRef<LinearState>>, notary: Party) = consumeAndProduce(linearStates, notary)
|
||||
fun ServiceHub.evolveLinearState(linearState: StateAndRef<LinearState>, notary: Party): StateAndRef<LinearState> = consumeAndProduce(linearState, notary)
|
||||
|
||||
/**
|
||||
* Consume cash, sending any change to the default identity for this node. Only suitable for use in test scenarios,
|
||||
* where nodes have a default identity.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun ServiceHub.consumeCash(amount: Amount<Currency>, to: Party = CHARLIE, notary: Party): Vault.Update<ContractState> {
|
||||
return consumeCash(amount, myInfo.chooseIdentityAndCert(), to, notary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume cash, sending any change to the specified identity.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun ServiceHub.consumeCash(amount: Amount<Currency>, ourIdentity: PartyAndCertificate, to: Party = CHARLIE, notary: Party): Vault.Update<ContractState> {
|
||||
val update = vaultService.rawUpdates.toFuture()
|
||||
val services = this
|
||||
|
||||
// A tx that spends our money.
|
||||
val builder = TransactionBuilder(notary).apply {
|
||||
Cash.generateSpend(services, this, amount, ourIdentity, to)
|
||||
}
|
||||
val spendTx = signInitialTransaction(builder, notary.owningKey)
|
||||
|
||||
recordTransactions(spendTx)
|
||||
|
||||
return update.getOrThrow(Duration.ofSeconds(3))
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@ -14,11 +15,13 @@ import java.util.concurrent.TimeUnit
|
||||
*/
|
||||
object HttpUtils {
|
||||
private val logger = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
private val client by lazy {
|
||||
OkHttpClient.Builder()
|
||||
.connectTimeout(5, TimeUnit.SECONDS)
|
||||
.readTimeout(60, TimeUnit.SECONDS).build()
|
||||
}
|
||||
|
||||
val defaultMapper: ObjectMapper by lazy {
|
||||
net.corda.client.jackson.JacksonSupport.createNonRpcMapper()
|
||||
}
|
||||
@ -28,9 +31,9 @@ object HttpUtils {
|
||||
return makeRequest(Request.Builder().url(url).header("Content-Type", "application/json").put(body).build())
|
||||
}
|
||||
|
||||
fun postJson(url: URL, data: String): Boolean {
|
||||
fun postJson(url: URL, data: String) {
|
||||
val body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), data)
|
||||
return makeRequest(Request.Builder().url(url).header("Content-Type", "application/json").post(body).build())
|
||||
makeExceptionalRequest(Request.Builder().url(url).header("Content-Type", "application/json").post(body).build())
|
||||
}
|
||||
|
||||
fun postPlain(url: URL, data: String): Boolean {
|
||||
@ -44,6 +47,14 @@ object HttpUtils {
|
||||
return mapper.readValue(parameterisedUrl, T::class.java)
|
||||
}
|
||||
|
||||
// TODO Move everything to use this instead of makeRequest
|
||||
private fun makeExceptionalRequest(request: Request) {
|
||||
val response = client.newCall(request).execute()
|
||||
if (!response.isSuccessful) {
|
||||
throw IOException("${request.method()} to ${request.url()} returned a ${response.code()}: ${response.body().string()}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeRequest(request: Request): Boolean {
|
||||
val response = client.newCall(request).execute()
|
||||
|
||||
|
@ -0,0 +1,15 @@
|
||||
package net.corda.testing.internal
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ThreadFactory
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
private val familyToNextPoolNumber = ConcurrentHashMap<String, AtomicInteger>()
|
||||
fun Any.testThreadFactory(useEnclosingClassName: Boolean = false): ThreadFactory {
|
||||
val poolFamily = javaClass.let { (if (useEnclosingClassName) it.enclosingClass else it).simpleName }
|
||||
val poolNumber = familyToNextPoolNumber.computeIfAbsent(poolFamily) { AtomicInteger(1) }.getAndIncrement()
|
||||
val nextThreadNumber = AtomicInteger(1)
|
||||
return ThreadFactory { task ->
|
||||
Thread(task, "$poolFamily-$poolNumber-${nextThreadNumber.getAndIncrement()}")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user