mirror of
https://github.com/corda/corda.git
synced 2025-06-05 17:01:45 +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