mirror of
https://github.com/corda/corda.git
synced 2025-02-05 02:29:20 +00:00
Add test cases for more non-determinism in Contract.verify().
This commit is contained in:
parent
739ffda6c7
commit
01e235f9d3
@ -1,20 +0,0 @@
|
||||
package net.corda.contracts.djvm
|
||||
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.TypeOnlyCommandData
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import java.time.Instant
|
||||
|
||||
class NonDeterministicContract : Contract {
|
||||
override fun verify(tx: LedgerTransaction) {
|
||||
Instant.now().toString()
|
||||
}
|
||||
|
||||
class State : ContractState {
|
||||
override val participants: List<AbstractParty> get() = emptyList()
|
||||
}
|
||||
|
||||
object Cmd : TypeOnlyCommandData()
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package net.corda.contracts.djvm.broken
|
||||
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.TypeOnlyCommandData
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
class NonDeterministicContract : Contract {
|
||||
override fun verify(tx: LedgerTransaction) {
|
||||
when {
|
||||
tx.commandsOfType(InstantNow::class.java).isNotEmpty() -> verifyInstantNow()
|
||||
tx.commandsOfType(CurrentTimeMillis::class.java).isNotEmpty() -> verifyCurrentTimeMillis()
|
||||
tx.commandsOfType(NanoTime::class.java).isNotEmpty() -> verifyNanoTime()
|
||||
tx.commandsOfType(RandomUUID::class.java).isNotEmpty() -> UUID.randomUUID()
|
||||
tx.commandsOfType(WithReflection::class.java).isNotEmpty() -> verifyNoReflection()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun verifyInstantNow() {
|
||||
Instant.now()
|
||||
}
|
||||
|
||||
private fun verifyCurrentTimeMillis() {
|
||||
System.currentTimeMillis()
|
||||
}
|
||||
|
||||
private fun verifyNanoTime() {
|
||||
System.nanoTime()
|
||||
}
|
||||
|
||||
private fun verifyNoReflection() {
|
||||
Date::class.java.getDeclaredConstructor().newInstance()
|
||||
}
|
||||
|
||||
@Suppress("CanBeParameter", "MemberVisibilityCanBePrivate")
|
||||
class State(val issuer: AbstractParty) : ContractState {
|
||||
override val participants: List<AbstractParty> = listOf(issuer)
|
||||
}
|
||||
|
||||
class InstantNow : TypeOnlyCommandData()
|
||||
class CurrentTimeMillis : TypeOnlyCommandData()
|
||||
class NanoTime : TypeOnlyCommandData()
|
||||
class RandomUUID : TypeOnlyCommandData()
|
||||
class WithReflection : TypeOnlyCommandData()
|
||||
class NoOperation : TypeOnlyCommandData()
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package net.corda.flows.djvm
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.djvm.NonDeterministicContract
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.flows.FinalityFlow
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.unwrap
|
||||
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
class NonDeterministicFlow(private val otherSide: Party) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
val notary = serviceHub.networkMapCache.notaryIdentities[0]
|
||||
val stx = serviceHub.signInitialTransaction(
|
||||
TransactionBuilder(notary)
|
||||
.addOutputState(NonDeterministicContract.State())
|
||||
.addCommand(Command(NonDeterministicContract.Cmd, ourIdentity.owningKey))
|
||||
)
|
||||
stx.verify(serviceHub, checkSufficientSignatures = false)
|
||||
val session = initiateFlow(otherSide)
|
||||
subFlow(FinalityFlow(stx, session))
|
||||
// It's important we wait on this dummy receive, as otherwise it's possible we miss any errors the other side throws
|
||||
session.receive<String>().unwrap { require(it == "OK") { "Not OK: $it"} }
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package net.corda.flows.djvm.broken
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.djvm.broken.NonDeterministicContract
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.contracts.TypeOnlyCommandData
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
class NonDeterministicFlow(private val trouble: TypeOnlyCommandData) : FlowLogic<SecureHash>() {
|
||||
@Suspendable
|
||||
override fun call(): SecureHash {
|
||||
val notary = serviceHub.networkMapCache.notaryIdentities[0]
|
||||
val stx = serviceHub.signInitialTransaction(
|
||||
TransactionBuilder(notary)
|
||||
.addOutputState(NonDeterministicContract.State(ourIdentity))
|
||||
.addCommand(Command(trouble, ourIdentity.owningKey))
|
||||
)
|
||||
stx.verify(serviceHub, checkSufficientSignatures = false)
|
||||
return stx.id
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
package net.corda.node.services
|
||||
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.flows.djvm.NonDeterministicFlow
|
||||
import net.corda.node.DeterministicSourcesRule
|
||||
import net.corda.node.internal.djvm.DeterministicVerificationException
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.driver.internal.incrementalPortAllocation
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import net.corda.testing.node.internal.cordappsForPackages
|
||||
import org.assertj.core.api.AssertionsForInterfaceTypes.assertThat
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
|
||||
class DeterministicContractVerifyTest {
|
||||
companion object {
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val djvmSources = DeterministicSourcesRule()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test DJVM rejects non-deterministic contract`() {
|
||||
driver(DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess = false,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
|
||||
cordappsForAllNodes = cordappsForPackages(
|
||||
"net.corda.contracts.djvm",
|
||||
"net.corda.flows.djvm"
|
||||
),
|
||||
djvmBootstrapSource = djvmSources.bootstrap,
|
||||
djvmCordaSource = djvmSources.corda
|
||||
)) {
|
||||
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
|
||||
val ex = assertThrows<DeterministicVerificationException> {
|
||||
alice.rpc.startFlow(::NonDeterministicFlow, alice.nodeInfo.singleIdentity()).returnValue.getOrThrow()
|
||||
}
|
||||
assertThat(ex)
|
||||
.hasMessageStartingWith("NoSuchMethodError: ")
|
||||
.hasMessageContaining(" sandbox.java.time.Instant.now()")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
package net.corda.node.services
|
||||
|
||||
import net.corda.contracts.djvm.broken.NonDeterministicContract.*
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.flows.djvm.broken.NonDeterministicFlow
|
||||
import net.corda.node.DeterministicSourcesRule
|
||||
import net.corda.node.internal.djvm.DeterministicVerificationException
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.driver.DriverParameters
|
||||
import net.corda.testing.driver.driver
|
||||
import net.corda.testing.driver.internal.incrementalPortAllocation
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import net.corda.testing.node.internal.cordappsForPackages
|
||||
import org.assertj.core.api.AssertionsForInterfaceTypes.assertThat
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.assertDoesNotThrow
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
|
||||
class NonDeterministicContractVerifyTest {
|
||||
companion object {
|
||||
val logger = loggerFor<NonDeterministicContractVerifyTest>()
|
||||
|
||||
@ClassRule
|
||||
@JvmField
|
||||
val djvmSources = DeterministicSourcesRule()
|
||||
|
||||
fun parametersFor(djvmSources: DeterministicSourcesRule): DriverParameters {
|
||||
return DriverParameters(
|
||||
portAllocation = incrementalPortAllocation(),
|
||||
startNodesInProcess =false,
|
||||
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)),
|
||||
cordappsForAllNodes = cordappsForPackages(
|
||||
"net.corda.contracts.djvm.broken",
|
||||
"net.corda.flows.djvm.broken"
|
||||
),
|
||||
djvmBootstrapSource = djvmSources.bootstrap,
|
||||
djvmCordaSource = djvmSources.corda
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test DJVM rejects contract that uses Instant now`() {
|
||||
driver(parametersFor(djvmSources)) {
|
||||
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
|
||||
val ex = assertThrows<DeterministicVerificationException> {
|
||||
alice.rpc.startFlow(::NonDeterministicFlow, InstantNow())
|
||||
.returnValue.getOrThrow()
|
||||
}
|
||||
assertThat(ex)
|
||||
.hasMessageStartingWith("NoSuchMethodError: sandbox.java.time.Instant.now()Lsandbox/java/time/Instant;, ")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test DJVM rejects contract that uses System currentTimeMillis`() {
|
||||
driver(parametersFor(djvmSources)) {
|
||||
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
|
||||
val ex = assertThrows<DeterministicVerificationException> {
|
||||
alice.rpc.startFlow(::NonDeterministicFlow, CurrentTimeMillis())
|
||||
.returnValue.getOrThrow()
|
||||
}
|
||||
assertThat(ex)
|
||||
.hasMessageStartingWith("NoSuchMethodError: sandbox.java.lang.System.currentTimeMillis()J, ")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test DJVM rejects contract that uses System nanoTime`() {
|
||||
driver(parametersFor(djvmSources)) {
|
||||
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
|
||||
val ex = assertThrows<DeterministicVerificationException> {
|
||||
alice.rpc.startFlow(::NonDeterministicFlow, NanoTime())
|
||||
.returnValue.getOrThrow()
|
||||
}
|
||||
assertThat(ex)
|
||||
.hasMessageStartingWith("NoSuchMethodError: sandbox.java.lang.System.nanoTime()J, ")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test DJVM rejects contract that uses UUID randomUUID`() {
|
||||
driver(parametersFor(djvmSources)) {
|
||||
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
|
||||
val ex = assertThrows<DeterministicVerificationException> {
|
||||
alice.rpc.startFlow(::NonDeterministicFlow, RandomUUID())
|
||||
.returnValue.getOrThrow()
|
||||
}
|
||||
assertThat(ex)
|
||||
.hasMessageStartingWith("NoSuchMethodError: sandbox.java.util.UUID.randomUUID()Lsandbox/java/util/UUID;, ")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test DJVM rejects contract that uses reflection`() {
|
||||
driver(parametersFor(djvmSources)) {
|
||||
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
|
||||
val ex = assertThrows<DeterministicVerificationException> {
|
||||
alice.rpc.startFlow(::NonDeterministicFlow, WithReflection())
|
||||
.returnValue.getOrThrow()
|
||||
}
|
||||
assertThat(ex)
|
||||
.hasMessageStartingWith("RuleViolationError: Disallowed reference to API; java.lang.Class.getDeclaredConstructor(Class[]), ")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test DJVM can succeed`() {
|
||||
driver(parametersFor(djvmSources)) {
|
||||
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
|
||||
val txId = assertDoesNotThrow {
|
||||
alice.rpc.startFlow(::NonDeterministicFlow, NoOperation())
|
||||
.returnValue.getOrThrow()
|
||||
}
|
||||
logger.info("TX-ID: {}", txId)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user